feat:LLM 分析文件并生成结构化建议

This commit is contained in:
2026-10-10 06:07:56 -07:00
parent b90842237f
commit e3699d1a30
22 changed files with 457 additions and 327 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ from app.processor.nodes.intent_node import IntentNode
from app.processor.nodes.qa_node import QANode
from app.processor.nodes.screen_agent_node import ScreenAgentNode
from app.processor.nodes.wait_for_user_node import WaitForUserNode
from app.processor.state import QueryState
from app.processor.state import QueryState, RunContext
def build_checkpoint_serde() -> JsonPlusSerializer:
@@ -47,7 +47,7 @@ def route_after_wait(state: QueryState) -> str:
def build_graph(checkpointer=None):
workflow = StateGraph(QueryState)
workflow = StateGraph(QueryState, context_schema=RunContext)
nodes = {
"input_node": InputNode(),
"intent_node": IntentNode(),
+2 -3
View File
@@ -10,11 +10,10 @@ from langchain_core.messages import AIMessageChunk, HumanMessage
from langchain_core.output_parsers import StrOutputParser
from langgraph.config import get_stream_writer
from app.agent.data_tools import DataContext
from app.agent.qa_agent import QAContext, get_qa_agent
from app.processor.base import BaseNode
from app.processor.history import history_messages
from app.processor.state import QueryState
from app.processor.state import QueryState, run_data
from app.processor.summary import data_file_summary
# 模型在一次回答里分几次输出文字(例如查询前后各说一段)时,段落之间空一行
@@ -56,7 +55,7 @@ class QANode(BaseNode):
HumanMessage(content=json.dumps(payload, ensure_ascii=False)),
]
},
context=QAContext(data=DataContext(tuple(context.data_files))),
context=QAContext(data=run_data()),
stream_mode="messages",
):
# 工具结果、工具调用和中间件插入的消息都不推送
+2 -3
View File
@@ -7,12 +7,11 @@ import json
from langchain_core.messages import HumanMessage
from app.agent.data_tools import DataContext
from app.agent.screen_agent import AgentReply, get_screen_agent
from app.agent.tools import ScreenAgentContext
from app.processor.base import BaseNode
from app.processor.history import history_messages
from app.processor.state import QueryState
from app.processor.state import QueryState, run_data
from app.processor.summary import request_summary
from app.resources.component import get_catalog_cache
@@ -68,7 +67,7 @@ class ScreenAgentNode(BaseNode):
context=ScreenAgentContext(
screen=context.screen,
catalog=catalog,
data=DataContext(tuple(context.data_files)),
data=run_data(),
),
)
if result.get("structured_response") is None:
+22
View File
@@ -1,5 +1,9 @@
from dataclasses import dataclass, field
from typing import Literal, NotRequired, TypedDict
from langgraph.runtime import get_runtime
from app.agent.data_tools import DataContext
from app.schemas.actions import Action
from app.schemas.base import JsonObject
from app.schemas.clarification import Clarification
@@ -25,3 +29,21 @@ class QueryState(TypedDict):
status: NotRequired[
Literal["running", "waiting_for_input", "completed", "cancelled", "failed"]
] # 后端处理、等待补充、处理完成、用户取消或失败,完成不代表前端已执行
@dataclass(frozen=True)
class RunContext:
"""图的运行上下文:本次运行要用、但不进检查点的依赖,每次启动和恢复时由调用方传入"""
# 本会话的数据文件;状态里的 input_context.data_files 只有摘要,列概况和路径在这里
data: DataContext = field(default_factory=DataContext)
def run_data() -> DataContext:
"""当前运行可用的数据表;单独调用节点(不在图里执行)或调用方没传 context 时为空"""
try:
runtime = get_runtime(RunContext)
except RuntimeError:
return DataContext()
context = runtime.context if runtime is not None else None
return context.data if context is not None else DataContext()