feat:完成数据文件上传解析及测试
This commit is contained in:
@@ -1,14 +1,24 @@
|
||||
"""基于当前大屏和会话历史流式回答问题"""
|
||||
"""基于当前大屏、上传的数据和会话历史流式回答问题
|
||||
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
由问答 agent 执行:会话里有数据文件时先查询再回答,否则只调用一次模型;只把模型输出的文字推送给前端
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from langchain.agents.middleware.model_call_limit import ModelCallLimitExceededError
|
||||
from langchain_core.messages import AIMessageChunk, HumanMessage
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langgraph.config import get_stream_writer
|
||||
|
||||
from app.clients.llm import get_llm
|
||||
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.prompt.qa import QA_SYSTEM_PROMPT
|
||||
from app.processor.summary import data_file_summary
|
||||
|
||||
# 模型在一次回答里分几次输出文字(例如查询前后各说一段)时,段落之间空一行
|
||||
PARAGRAPH_BREAK = "\n\n"
|
||||
|
||||
|
||||
class QANode(BaseNode):
|
||||
@@ -23,23 +33,42 @@ class QANode(BaseNode):
|
||||
except RuntimeError:
|
||||
writer = None
|
||||
|
||||
context = state["input_context"]
|
||||
payload = context.model_dump(
|
||||
mode="json", exclude_unset=True, exclude={"history", "data_files"}
|
||||
)
|
||||
if context.data_files:
|
||||
# 只给表名、行数和列名,文件路径和列概况不进提示词,需要时由 describe_data 查看
|
||||
payload["data_files"] = [
|
||||
data_file_summary(item) for item in context.data_files
|
||||
]
|
||||
agent = get_qa_agent(with_data=bool(context.data_files))
|
||||
|
||||
parser = StrOutputParser()
|
||||
parts: list[str] = []
|
||||
context = state["input_context"]
|
||||
async for chunk in get_llm().astream(
|
||||
[
|
||||
SystemMessage(content=QA_SYSTEM_PROMPT),
|
||||
# 会话历史作为此前的对话消息,本次请求是最后一条用户消息
|
||||
*history_messages(context.history),
|
||||
HumanMessage(
|
||||
content=context.model_dump_json(
|
||||
exclude_unset=True, exclude={"history"}
|
||||
)
|
||||
),
|
||||
]
|
||||
):
|
||||
# 工具调用等无文本片段会得到空串
|
||||
if delta := parser.invoke(chunk):
|
||||
last_message_id = None
|
||||
try:
|
||||
async for chunk, metadata in agent.astream(
|
||||
{
|
||||
"messages": [
|
||||
# 会话历史作为此前的对话消息,本次请求是最后一条用户消息
|
||||
*history_messages(context.history),
|
||||
HumanMessage(content=json.dumps(payload, ensure_ascii=False)),
|
||||
]
|
||||
},
|
||||
context=QAContext(data=DataContext(tuple(context.data_files))),
|
||||
stream_mode="messages",
|
||||
):
|
||||
# 工具结果、工具调用和中间件插入的消息都不推送
|
||||
if metadata.get("langgraph_node") != "model" or not isinstance(
|
||||
chunk, AIMessageChunk
|
||||
):
|
||||
continue
|
||||
if not (delta := parser.invoke(chunk)):
|
||||
continue
|
||||
if parts and chunk.id != last_message_id:
|
||||
delta = PARAGRAPH_BREAK + delta
|
||||
last_message_id = chunk.id
|
||||
parts.append(delta)
|
||||
if writer is not None:
|
||||
writer(
|
||||
@@ -49,6 +78,15 @@ class QANode(BaseNode):
|
||||
"delta": delta,
|
||||
}
|
||||
)
|
||||
except ModelCallLimitExceededError:
|
||||
self.logger.warning("问答 agent 超出模型调用上限(MAX_QA_STEPS)")
|
||||
updated_state["status"] = "failed"
|
||||
updated_state["answer"] = (
|
||||
"查询步骤超出上限,这次没有完成,可以缩小范围或换个问法再试"
|
||||
)
|
||||
return updated_state
|
||||
|
||||
# 推送了什么就保存什么,会话历史和前端看到的回答一致
|
||||
answer = "".join(parts).strip()
|
||||
# 没有有效文本时报错,避免将空回答标记为处理完成
|
||||
if not answer:
|
||||
|
||||
Reference in New Issue
Block a user