Spaces:
Sleeping
Sleeping
Update core/qualitative_engine.py
Browse files- core/qualitative_engine.py +19 -6
core/qualitative_engine.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
import json
|
| 3 |
import logging
|
|
|
|
|
|
|
| 4 |
|
| 5 |
class QualitativeEngine:
|
| 6 |
def __init__(self, agent):
|
|
@@ -9,24 +11,36 @@ class QualitativeEngine:
|
|
| 9 |
def resolve_qualitative_data(self, topic, keywords, raw_interviews=""):
|
| 10 |
"""
|
| 11 |
核心逻辑:解析真实访谈数据,或在无数据时模拟 ASQ 级别的定性数据结构表。
|
|
|
|
| 12 |
"""
|
| 13 |
if raw_interviews and len(raw_interviews) > 100:
|
| 14 |
logging.info("📝 检测到用户上传访谈记录,正在通过 DeepSeek-Reasoner 进行深度主题萃取...")
|
| 15 |
prompt = f"Topic: {topic}. Extract the 15 most powerful, representative quotes from the following interview/field text. Organize into a Gioia-style coding structure. Return ONLY a valid JSON array of objects with keys: 'theme' (2nd-order), 'concept' (1st-order), and 'quote' (exact words)."
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
else:
|
| 18 |
logging.info("⚙️ 未检测到真实访谈记录,正在通过蒙特卡洛引擎模拟高质量定性编码树防幻觉...")
|
| 19 |
prompt = f"Topic: {topic}. Keywords: {keywords}. As an ASQ researcher, simulate a rigorous set of qualitative data for this study. Provide 12 highly realistic, human-sounding representative interview quotes. Group them into emergent 1st-order concepts and 2nd-order themes. Return ONLY a valid JSON array of objects with keys: 'theme', 'concept', 'quote'."
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
data = self.agent._extract_json_from_text(response)
|
| 23 |
|
| 24 |
-
# 容错处理
|
| 25 |
if not data:
|
| 26 |
logging.error("❌ 定性数据 JSON 解析失败,回退至默认模拟数据。")
|
| 27 |
-
return "### Table: Qualitative Coding Structure\n| 2nd-order Theme | 1st-order Concept | Representative Quote |\n| :--- | :--- | :--- |\n| Sensemaking | Initial Confusion | \"We really didn't know how to adapt to the new framework.\" [QUOTE-01] |"
|
| 28 |
|
| 29 |
-
# 兼容 JSON 格式 (字典或列表)
|
| 30 |
if isinstance(data, dict):
|
| 31 |
items = data.get("data", data.get("quotes", []))
|
| 32 |
elif isinstance(data, list):
|
|
@@ -34,7 +48,6 @@ class QualitativeEngine:
|
|
| 34 |
else:
|
| 35 |
items = []
|
| 36 |
|
| 37 |
-
# 转换为 Markdown 表格,供流水线作为事实锚点注入
|
| 38 |
md = "### Table: Qualitative Coding Structure (Data Evidence)\n| 2nd-order Theme | 1st-order Concept | Representative Quote |\n| :--- | :--- | :--- |\n"
|
| 39 |
for i, item in enumerate(items[:15]):
|
| 40 |
q = str(item.get("quote", "")).replace('"', "'").replace("\n", " ")
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
import json
|
| 3 |
import logging
|
| 4 |
+
import re
|
| 5 |
+
import streamlit as st
|
| 6 |
|
| 7 |
class QualitativeEngine:
|
| 8 |
def __init__(self, agent):
|
|
|
|
| 11 |
def resolve_qualitative_data(self, topic, keywords, raw_interviews=""):
|
| 12 |
"""
|
| 13 |
核心逻辑:解析真实访谈数据,或在无数据时模拟 ASQ 级别的定性数据结构表。
|
| 14 |
+
引入流式状态机,防止 WebSocket 假死超时。
|
| 15 |
"""
|
| 16 |
if raw_interviews and len(raw_interviews) > 100:
|
| 17 |
logging.info("📝 检测到用户上传访谈记录,正在通过 DeepSeek-Reasoner 进行深度主题萃取...")
|
| 18 |
prompt = f"Topic: {topic}. Extract the 15 most powerful, representative quotes from the following interview/field text. Organize into a Gioia-style coding structure. Return ONLY a valid JSON array of objects with keys: 'theme' (2nd-order), 'concept' (1st-order), and 'quote' (exact words)."
|
| 19 |
+
|
| 20 |
+
# [修复点 1] 改用流式渲染,保活网关连接
|
| 21 |
+
with st.status("🧠 正在使用 R1 模型深度萃取访谈语录 (耗时较长,请观察下方思考流以防断连)...", expanded=True) as status:
|
| 22 |
+
ph = st.empty()
|
| 23 |
+
response = ph.write_stream(self.agent.stream_deepseek("You are an expert qualitative analyst.", f"DATA:\n{raw_interviews[:30000]}", model_name="deepseek-reasoner"))
|
| 24 |
+
status.update(label="✅ 访谈主题萃取完成!", state="complete", expanded=False)
|
| 25 |
+
|
| 26 |
+
# 清理流式输出中带有的 think 标签
|
| 27 |
+
response = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL).strip()
|
| 28 |
else:
|
| 29 |
logging.info("⚙️ 未检测到真实访谈记录,正在通过蒙特卡洛引擎模拟高质量定性编码树防幻觉...")
|
| 30 |
prompt = f"Topic: {topic}. Keywords: {keywords}. As an ASQ researcher, simulate a rigorous set of qualitative data for this study. Provide 12 highly realistic, human-sounding representative interview quotes. Group them into emergent 1st-order concepts and 2nd-order themes. Return ONLY a valid JSON array of objects with keys: 'theme', 'concept', 'quote'."
|
| 31 |
+
|
| 32 |
+
# [修复点 2] 模拟过程同样流式化
|
| 33 |
+
with st.status("⚙️ 正在通过 V3 模型极速模拟定性编码树...", expanded=True) as status:
|
| 34 |
+
ph = st.empty()
|
| 35 |
+
response = ph.write_stream(self.agent.stream_deepseek("You are an expert qualitative analyst.", prompt, model_name="deepseek-chat"))
|
| 36 |
+
status.update(label="✅ 模拟定性编码树构建完成!", state="complete", expanded=False)
|
| 37 |
|
| 38 |
data = self.agent._extract_json_from_text(response)
|
| 39 |
|
|
|
|
| 40 |
if not data:
|
| 41 |
logging.error("❌ 定性数据 JSON 解析失败,回退至默认模拟数据。")
|
| 42 |
+
return "### Table: Qualitative Coding Structure\n| 2nd-order Theme | 1st-order Concept | Representative Quote |\n| :--- | :--- | :--- |\n| Sensemaking | Initial Confusion | \"We really didn't know how to adapt to the new framework.\" [QUOTE-01] |", "⚠️ 数据解析出现偏差,已启用降级保护预案。"
|
| 43 |
|
|
|
|
| 44 |
if isinstance(data, dict):
|
| 45 |
items = data.get("data", data.get("quotes", []))
|
| 46 |
elif isinstance(data, list):
|
|
|
|
| 48 |
else:
|
| 49 |
items = []
|
| 50 |
|
|
|
|
| 51 |
md = "### Table: Qualitative Coding Structure (Data Evidence)\n| 2nd-order Theme | 1st-order Concept | Representative Quote |\n| :--- | :--- | :--- |\n"
|
| 52 |
for i, item in enumerate(items[:15]):
|
| 53 |
q = str(item.get("quote", "")).replace('"', "'").replace("\n", " ")
|