diff --git a/graph_chat/gen_data_report_agent.py b/graph_chat/gen_data_report_agent.py index 25b782c..1db2899 100644 --- a/graph_chat/gen_data_report_agent.py +++ b/graph_chat/gen_data_report_agent.py @@ -11,7 +11,7 @@ from langgraph.graph import StateGraph, END, START from graph_chat.gen_sql_chart_agent import gen_history_llm from util.utils import extract_nested_json -from service.conversation_service import update_conversation +from service.conversation_service import update_conversation,get_latest_question logger = logging.getLogger(__name__) from template.template import get_base_template from decouple import config @@ -28,6 +28,7 @@ class DateReportAgentState(TypedDict): question:str retry_sql:str sql_correct:bool + history:Optional[str] @@ -70,7 +71,8 @@ def _feedback_qa(state: DateReportAgentState) -> dict: logger.info(f"feedback_temp is {feedback_temp}") ddl_list = vn.get_related_ddl(user_question) qa_list = vn.get_similar_question_sql(user_question) - sys_promot = feedback_temp['system'].format(question=user_question, sql=sql, sql_result=sql_result, + history = state.get('history', []) + sys_promot = feedback_temp['system'].format(question=user_question, sql=sql, sql_result=sql_result,history=history, current_time=datetime.now(),ddl_list=ddl_list,qa_list=qa_list) logger.info(f"system_temp is {sys_promot}") result = gen_history_llm.invoke(sys_promot).text() @@ -81,8 +83,6 @@ def _feedback_qa(state: DateReportAgentState) -> dict: logger.info(f"result is {result} type:{type(result)}") if not result["is_result_correct"] and (result["suggested_sql"]): logger.info("开始替换") - logger.info(f"suggested_sql is ".format(result["suggested_sql"])) - logger.info(f"current state: {state}") return {"retry_sql": result["suggested_sql"]} if result["is_result_correct"]: return {"sql_correct": True,"run_sql_error":""} @@ -110,27 +110,41 @@ def handle_no_feedback(state: DateReportAgentState) -> str: def handle_with_feedback(state: DateReportAgentState) -> str: logger.info(f"user:{state.get('user_id', '1')} ---------------进入 handle_with_feedback 节点 ---------------") sql_error = state.get('run_sql_error', '') - # data = state.get('data', {}) - # sql_correct = state.get('sql_correct', False) + sql_correct = state.get('sql_correct', False) + force_check = config('FORCE_CHECK', False) + sql_retry_count = state.get('retry_count', 0) - logger.info(f"handle with feedback sql_retry_count is {sql_retry_count} sql error is {sql_error}") - if sql_retry_count < 3: - if sql_error and len(sql_error) > 0: - return '_feedback_qa' + logger.info(f"handle with feedback sql_retry_count is {sql_retry_count} " + f"sql error is {sql_error} sql correct is {sql_correct}") + if force_check: + if sql_retry_count < 3: + if sql_correct: + return '_gen_report' + else: + return '_feedback_qa' else: - return '_gen_report' - # if sql_correct: - # return '_gen_report' - # else: - # if sql_error and len(sql_error) > 0: - # return '_feedback_qa' - # else: - # return END + if sql_correct: + return '_gen_report' + else: + return END else: - if sql_error and len(sql_error)>0: - return END + if sql_retry_count < 3: + if sql_error and len(sql_error) > 0: + return '_feedback_qa' + else: + return '_gen_report' + # if sql_correct: + # return '_gen_report' + # else: + # if sql_error and len(sql_error) > 0: + # return '_feedback_qa' + # else: + # return END else: - return '_gen_report' + if sql_error and len(sql_error) > 0: + return END + else: + return '_gen_report' def run_sql_hande(state: DateReportAgentState) -> str: logger.info(f"user:{state.get('user_id', '1')} ---------------进入 _run_sql_hande 节点 ---------------") diff --git a/graph_chat/gen_sql_chart_agent.py b/graph_chat/gen_sql_chart_agent.py index 8d012fb..fc2222b 100644 --- a/graph_chat/gen_sql_chart_agent.py +++ b/graph_chat/gen_sql_chart_agent.py @@ -81,6 +81,7 @@ def _gen_sql(state: SqlAgentState) -> dict: question_sql_list = service.get_similar_question_sql(question) if question_sql_list and len(question_sql_list) > 3: question_sql_list = question_sql_list[:3] + logger.info(f"similar question:{question_sql_list}") ddl_list = service.get_related_ddl(question) template = get_base_template() sql_temp = template['template']['sql'] diff --git a/main_service.py b/main_service.py index 8336704..aa29f79 100644 --- a/main_service.py +++ b/main_service.py @@ -382,6 +382,7 @@ def run_sql_3(): logger.info("Start to run sql in main") try: user_id = request.args.get("user_id") + cvs_id = request.args.get("cvs_id") if not vn.run_sql_is_set: return jsonify( { @@ -389,12 +390,22 @@ def run_sql_3(): "error": "Please connect to a database using vn.connect_to_... in order to run SQL queries.", } ) + question_context = get_latest_question(cvs_id, user_id, limit_count=2) + history = [] + i = 0 + for q in question_context: + is_latest=False + if i==0: + is_latest=True + history.append({"role": "user", 'content': q, 'order': i,'is_latest':is_latest}) + i +=1 initial_state: DateReportAgentState = { "id": id, "user_id": user_id, "sql": sql, "question": question, "retry_count": 0, + "history": history, } config = {"configurable": {"thread_id": str(uuid.uuid4())}} rr = result_report_agent.invoke(initial_state, config) diff --git a/template.yaml b/template.yaml index f330174..59ea539 100644 --- a/template.yaml +++ b/template.yaml @@ -525,54 +525,98 @@ template: rewrite_question: system: | # 角色 - 你是一个专业的智能数据分析助手的对话上下文理解模块。 - # 任务 - 你的核心任务是判断用户的当前问题是否依赖于之前的对话历史,并据此生成一个独立的、可供数据分析系统直接执行的问题。 - # 核心规则 - 1. **识别关联性**: - * 分析当前问题是否包含指代词(如“它”、“这个”、“那个”)、简称(如“去年同期”、“此产品”)、或省略的主语/宾语。 - * 如果存在上述特征,且这些信息能在历史对话中找到明确的对应项,则判定为**关联**。 - * 如果当前问题是一个全新的、独立的查询,与历史话题无关,则判定为**不关联**。 - 2. **生成最终问题**: - * **如果判定为【关联】**:你必须将历史对话中的相关上下文信息**融合**到当前问题中,形成一个**完整清晰、无任何指代或歧义的新问题**。 - * **如果判定为【不关联】**:你只需**原样输出**当前问题。 - 3. 短语指标库替换,如果问题出现短语指标,则替换为指标库中的指标。 - - 3.1 :数信部 -> 数字信息部 (必须替换!) - 3.2 :安质部 -> 安全质量部 (必须替换!) - 3.3 :数信中心 -> 数信中心 - 例如:用户:查询数信部 → SQL: LIKE '%数字信息部%' - 用户:查询安质部 → SQL: LIKE '%安全质量部%' - - # 输出格式要求 - - **你的唯一输出必须是一个JSON对象**。 - - **严禁**在JSON前后添加任何解释性文字、代码块标记(如 ```json)或任何其他内容。 - - JSON的键固定为 `question`,值为处理后的最终问题字符串。 - **最终输出格式示例**: - {{"question": "A产品上个月的成本是多少?"}} - - ## 示例1 - **对话历史:** - Q: B产品上月的销量是? A: 200件 - **当前用户问题:** - 那利润率呢? - **输出:** - {{"question": "B产品上月的利润率是多少?"}} - ## 示例2 - **对话历史:** - Q: 列出所有华东区的销售员 A: 张三、李四、王五 - **当前用户问题:** - 帮我预定一张明天去上海的机票 - **输出:** - {{"question": "帮我预定一张明天去上海的机票"}} - - #### - # 上下文信息 - 注意:[历史对话中order越小,代表消息越新,优先基于最新消息进行关联性分析] - **对话历史:** - {history} - **当前用户问题:** - {current_question} + 你是一个专业的智能数据分析助手的对话上下文理解与问题规范化模块。 + + # 核心任务 + 你的任务分为两个顺序执行的核心步骤:首先是**上下文消歧**,然后是**术语标准化**。最终生成一个独立、无歧义、术语规范的、可供数据分析系统直接执行的问题。 + + # 执行步骤 + + ## 步骤一:上下文消歧 (Context Disambiguation) + 1. **识别关联性**: + * 分析当前问题是否包含指代词(如“它”、“这个”、“那个”)、简称(如“去年同期”、“此产品”)、或省略的主语/宾语。 + * 若存在,且能在历史对话中找到明确对应项,则判定为**关联**。 + * 若当前问题是全新的、独立的查询,则判定为**不关联**。 + + 2. **生成中间问题**: + * **如果判定为【关联】**:将历史对话中的相关上下文信息**融合**到当前问题中,形成一个**完整清晰、无任何指代或歧义的中间问题**。 + * **如果判定为【不关联】**:中间问题即为**原样**的当前问题。 + + ## 步骤二:术语标准化 (Terminology Standardization) + 1. **识别与替换**:在步骤一生成的“中间问题”基础上,扫描并替换下表中定义的术语。 + * **规则**:**严格**、**精确**地匹配下表中的“源短语”,并将其替换为“目标短语”。 + * **重要**:如果“中间问题”中的词语是全称或已经是正确的形式(例如,已经是“数字信息部”),**绝对禁止任何形式替换**。此步骤只处理从简称变为全称的场景。 + * **重要** :如果匹配到 ‘数信中心’ 不需要替换,不需要替换,不需要替换 + 2. **术语替换映射表** + | 源短语 | 目标短语 | 指令 | + |---|---|---| + | 数信部 | 数字信息部 | 如果匹配,则替换 | + | 安质部 | 安全质量部 | 如果匹配,则替换 | + + + # 输出格式要求 + - 你的**唯一**输出必须是一个JSON对象。 + - **严禁**在JSON前后添加任何解释性文字、代码块标记(如 ```json)或任何其他内容。 + - JSON的键固定为 `question`,值为经过上述两个步骤处理后的最终问题字符串。 + **最终输出格式示例**: + {{"question": "销售部上个月的A产品销量是多少?"}} + + --- + ## 示例 + + ### 示例1 (上下文关联 + 术语替换) + **对话历史:** + Q: 数信部上月的A产品成本是多少? A: 5000元 + **当前用户问题:** + 那销售额呢? + **你的思考过程:** + 1. **步骤一 (消歧)**: 当前问题“那销售额呢?”依赖于历史对话。关联主体是“数信部”和“A产品”,时间是“上月”。生成中间问题:“数信部上月的A产品销售额是多少?”。 + 2. **步骤二 (标准化)**: 中间问题中包含源短语“数信部”,根据映射表替换为“数字信息部”。最终问题为:“数字信息部上月的A产品销售额是多少?”。 + **输出:** + {{"question": "数字信息部上月的A产品销售额是多少?"}} + + ### 示例2 (上下文不关联 + 术语替换) + **对话历史:** + (无) + **当前用户问题:** + 查询安质部的项目数 + **你的思考过程:** + 1. **步骤一 (消歧)**: 当前问题是独立查询,与历史无关。中间问题为:“查询安质部的项目数”。 + 2. **步骤二 (标准化)**: 中间问题中包含源短语“安质部”,根据映射表替换为“安全质量部”。最终问题为:“查询安全质量部的项目数”。 + **输出:** + {{"question": "查询安全质量部的项目数"}} + + ### 示例3 (上下文关联 + 错误的术语/无需替换) + **对话历史:** + Q: 列出数字信息部的所有员工 A: 张三、李四 + **当前用户问题:** + 他们分别负责什么项目? + **你的思考过程:** + 1. **步骤一 (消歧)**: 当前问题“他们分别负责什么项目?”依赖于历史对话。“他们”指代“数字信息部”的员工。生成中间问题:“数字信息部的员工分别负责什么项目?”。 + 2. **步骤二 (标准化)**: 中间问题中包含“数字信息部”,这是目标短语,不是源短语。因此,**不进行任何替换**。 + **输出:** + {{"question": "数字信息部的员工分别负责什么项目?"}} + + ### 示例4 (上下文不关联 + 无需替换) + **对话历史:** + (无) + **当前用户问题:** + 帮我预定一张明天去上海的机票 + **你的思考过程:** + 1. **步骤一 (消歧)**: 独立查询,中间问题为:“帮我预定一张明天去上海的机票”。 + 2. **步骤二 (标准化)**: 中间问题不包含任何源短语,不执行替换。 + **输出:** + {{"question": "帮我预定一张明天去上海的机票"}} + --- + + # 上下文信息 + **注意**:[历史对话中order越小,代表消息越新,优先基于最新消息进行关联性分析] + + **对话历史:** + {history} + + **当前用户问题:** + {current_question} result_summary: system: | @@ -628,6 +672,7 @@ template: [生成的SQL]: {sql}。 [执行结果]: {sql_result}。 [当前时间]: {current_time} + [历史信息]: {history} [表结构信息]:{ddl_list} [问答参考]:{qa_list} # 核心规则