Files
my-v0-server/app/processor/nodes/screen_agent_node.py
T

96 lines
4.2 KiB
Python

"""调用 screen agent,按它的回复和成功提交的动作写回状态
kind 由模型决定;代码只在"回复 done 但最后一次提交失败"时不采纳:最后想做的修改没有写入草稿,
模型却说已完成。这是保守做法,last_write_failed 只看工具结果,任务是否完成仍以模型的结论和评测为准
agent 正常结束却没有结论时(调用次数用尽、只回了文本或空回复)补一次只读收尾;取消和异常照常抛出,不收尾
"""
import json
from langchain_core.messages import HumanMessage
from app.agent.screen_agent import (
AgentReply,
finalize_reply,
get_screen_agent,
reached_call_limit,
)
from app.agent.tools import ScreenAgentContext, last_write_failed
from app.processor.base import BaseNode
from app.processor.history import history_messages
from app.processor.state import QueryState, run_data
from app.processor.summary import request_summary
from app.resources.component import get_catalog_cache
# 评测按这句话识别"草稿因最后一次提交失败没有交付",单独核对草稿本身是否满足要求
DRAFT_REJECTED = "修改没有通过校验,这次没有生效,可以换个说法再试"
def reply_update(result: dict, *, limit_hit: bool = False) -> QueryState:
"""把 agent 的运行结果转换成 QueryState 的更新"""
reply: AgentReply | None = result.get("structured_response")
if reply is None:
# 收尾也没有给出合法的结论;按真实原因说明,不再一律说超出上限
answer = (
"处理步骤超出上限,这次没有完成,修改没有生效,可以换个说法再试"
if limit_hit
else "这次没有得出结论,修改没有生效,可以换个说法再试"
)
return {"status": "failed", "answer": answer}
if reply.kind == "clarify":
return {"status": "waiting_for_input", "clarification": reply.clarification}
if reply.kind == "unable":
return {"status": "failed", "answer": reply.message}
if last_write_failed(result.get("messages", [])):
return {"status": "failed", "answer": DRAFT_REJECTED}
# 各次成功提交的动作按顺序拼接;clarify、unable 不交付,下次请求从原始大屏重新执行
return {
"status": "completed",
"actions": result.get("actions") or [],
"answer": reply.message,
}
class ScreenAgentNode(BaseNode):
name = "screen_agent_node"
async def process(self, state: QueryState) -> QueryState:
updated_state: QueryState = state.copy()
catalog = get_catalog_cache()
if catalog is None:
updated_state["status"] = "failed"
updated_state["answer"] = "物料目录未载入,暂时无法新建或修改大屏"
return updated_state
context = state["input_context"]
agent = await get_screen_agent(with_data=bool(context.data_files))
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,
data=run_data(),
),
)
limit_hit = False
if result.get("structured_response") is None:
limit_hit = reached_call_limit(result["messages"])
last = result["messages"][-1]
self.logger.warning(
f"agent 没有给出结论({'调用次数用尽' if limit_hit else '没有调用 AgentReply'}),收尾一次: "
f"{last.type} {str(last.content)[:500]} "
f"metadata={getattr(last, 'response_metadata', None)}"
)
reply = await finalize_reply(result, limit_hit=limit_hit)
result = {**result, "structured_response": reply}
updated_state.update(reply_update(result, limit_hit=limit_hit))
return updated_state