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

102 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""调用 screen agent,按它的回复和成功提交的动作写回状态
kind 由模型决定;代码只在"回复 done 但最后一次提交失败"时不采纳:最后想做的修改没有写入草稿,
模型却说已完成。这是保守做法,last_write_failed 只看工具结果,任务是否完成仍以模型的结论和评测为准
agent 正常结束却没有结论时(调用次数用尽、只回了文本或空回复)补一次只读收尾;取消和异常照常抛出,不收尾
"""
import json
from langchain_core.messages import HumanMessage
from app.actions.batch import mark_sample_data
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
from app.schemas.component_resource import ComponentCatalog
# 评测按这句话识别"草稿因最后一次提交失败没有交付",单独核对草稿本身是否满足要求
DRAFT_REJECTED = "修改没有通过校验,这次没有生效,可以换个说法再试"
def reply_update(
result: dict, *, limit_hit: bool = False, catalog: ComponentCatalog | None = None
) -> QueryState:
"""把 agent 的运行结果转换成 QueryState 的更新
交付的新增组件按交付后的大屏定 sample_data:留着物料示例数据的也标为示例数据(mark_sample_data)
"""
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 不交付,下次请求从原始大屏重新执行
actions = result.get("actions") or []
if actions:
actions = mark_sample_data(actions, result["working_screen"], catalog)
return {"status": "completed", "actions": actions, "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, catalog=catalog))
return updated_state