feat:完成重构和测试

This commit is contained in:
2026-10-02 08:47:24 -07:00
parent 5b1e6c5654
commit 62a5b2a8f4
15 changed files with 138 additions and 43 deletions
+39
View File
@@ -0,0 +1,39 @@
"""会话历史:数据库保存完整对话,每次请求按 token 预算只带最近的完整轮次
裁剪在服务层组装请求时做一次,意图判断、问答和 screen agent 看到的是同一段历史
"""
from langchain_core.messages import (
AIMessage,
BaseMessage,
HumanMessage,
trim_messages,
)
from langchain_core.messages.utils import count_tokens_approximately
from app.schemas.base import JsonObject
def history_messages(history: list[JsonObject]) -> list[BaseMessage]:
"""每轮转成一条用户消息和一条助手消息,按时间从早到晚排列"""
messages: list[BaseMessage] = []
for turn in history:
messages.append(HumanMessage(content=turn["user"]))
messages.append(AIMessage(content=turn["assistant"]))
return messages
def trim_history(history: list[JsonObject], max_tokens: int) -> list[JsonObject]:
"""从最近一轮往前保留,总量不超过 max_tokens;只保留完整的轮次,不从一段回复中间截断
单独一轮就超过预算时这一轮也不保留
"""
kept = trim_messages(
history_messages(history),
max_tokens=max_tokens,
token_counter=count_tokens_approximately,
strategy="last",
# 从用户消息开始,助手回复放不下时连同它的用户消息一起去掉
start_on="human",
)
return history[len(history) - len(kept) // 2 :]
+3 -1
View File
@@ -14,8 +14,10 @@ class IntentNode(BaseNode):
async def process(self, state: QueryState) -> QueryState:
updated_state: QueryState = state.copy()
context = state["input_context"]
request = DecisionRequest(
state=request_summary(state["input_context"]),
# Jev 只接收 JSON,会话历史作为 history 字段和本次请求放在一起
state={"history": context.history, **request_summary(context)},
decisions={"intent": build_intent_question()},
)
response = await get_decision_engine().run(request)
+7 -1
View File
@@ -6,6 +6,7 @@ from langgraph.config import get_stream_writer
from app.clients.llm import get_llm
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
@@ -24,11 +25,16 @@ class QANode(BaseNode):
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=state["input_context"].model_dump_json(exclude_unset=True)
content=context.model_dump_json(
exclude_unset=True, exclude={"history"}
)
),
]
):
+4 -1
View File
@@ -10,6 +10,7 @@ from langchain_core.messages import HumanMessage
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.summary import request_summary
from app.resources.component import get_catalog_cache
@@ -55,10 +56,12 @@ class ScreenAgentNode(BaseNode):
agent = await get_screen_agent()
result = await agent.ainvoke(
{
# 会话历史作为此前的对话消息,本次请求是最后一条用户消息
"messages": [
*history_messages(context.history),
HumanMessage(
content=json.dumps(request_summary(context), ensure_ascii=False)
)
),
]
},
context=ScreenAgentContext(screen=context.screen, catalog=catalog),
+5 -3
View File
@@ -1,11 +1,14 @@
"""请求摘要:意图判断和 screen agent 共用,只给组件的识别信息和布局,完整配置按需读取"""
"""请求摘要:意图判断和 screen agent 共用,只给组件的识别信息和布局,完整配置按需读取
只描述本次请求;会话历史由各使用方另行传入,见 history.py
"""
from app.schemas.base import JsonObject
from app.schemas.input_context import InputContext
def request_summary(context: InputContext) -> JsonObject:
"""用户输入、会话历史、澄清记录、编辑上下文、附件和大屏摘要"""
"""用户输入、澄清记录、编辑上下文、附件和大屏摘要"""
nodes: list[JsonObject] = []
for node in context.screen.nodes:
# 图表标题帮助判断用户指的是哪个组件;没有 option 或标题格式不符时按无标题处理
@@ -28,7 +31,6 @@ def request_summary(context: InputContext) -> JsonObject:
return {
"prompt": context.prompt,
"history": context.history,
"clarification_history": context.clarification_history,
"context": context.context.model_dump(mode="json", exclude_unset=True),
"screen_summary": {