feat:完成重构和测试
This commit is contained in:
@@ -10,7 +10,7 @@ from typing import Literal, Self
|
||||
from langchain.agents import create_agent
|
||||
from langchain.agents.middleware import ModelCallLimitMiddleware, wrap_model_call
|
||||
from langchain.agents.structured_output import ToolStrategy
|
||||
from langchain_core.messages import AIMessage
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
@@ -62,21 +62,36 @@ class AgentReply(BaseModel):
|
||||
return self
|
||||
|
||||
|
||||
# 偶发的空回复最多重新调用几次
|
||||
EMPTY_REPLY_RETRIES = 2
|
||||
# 模型没有调用工具时最多纠正几次
|
||||
NO_TOOL_CALL_RETRIES = 2
|
||||
NO_TOOL_CALL_HINT = (
|
||||
"每一轮都要调用工具。要结束时调用 AgentReply,给用户的话写在 message 里"
|
||||
)
|
||||
|
||||
|
||||
@wrap_model_call
|
||||
async def retry_empty_reply(request, handler):
|
||||
"""每一轮都必须调用工具(最后一轮是 AgentReply);模型偶尔返回既没有文本也没有工具调用的空回复,
|
||||
这是调用失败而不是模型的决定,重新调用即可"""
|
||||
async def require_tool_call(request, handler):
|
||||
"""ToolStrategy 要求每一轮都调用工具,结束时调用 AgentReply;模型偶尔只回文本(想直接回复用户)或空回复
|
||||
|
||||
原样重试往往得到同样的结果,或者模型转而重复提交 apply_actions 直到调用次数用尽,
|
||||
所以把它的回复和纠正说明加在下一次调用里;只用于这一次调用,不写进 agent 的消息
|
||||
"""
|
||||
response = await handler(request)
|
||||
for _ in range(EMPTY_REPLY_RETRIES):
|
||||
for _ in range(NO_TOOL_CALL_RETRIES):
|
||||
message = response.result[0] if response.result else None
|
||||
if not isinstance(message, AIMessage) or message.tool_calls:
|
||||
break
|
||||
logger.warning(
|
||||
f"模型返回了没有工具调用的回复,重新调用: {message.response_metadata}"
|
||||
f"模型没有调用工具,纠正后重新调用: {str(message.content)[:200]}"
|
||||
)
|
||||
# 空回复不放回去,部分接口不接受内容为空的助手消息
|
||||
replied = [message] if message.content else []
|
||||
request = request.override(
|
||||
messages=[
|
||||
*request.messages,
|
||||
*replied,
|
||||
HumanMessage(content=NO_TOOL_CALL_HINT),
|
||||
]
|
||||
)
|
||||
response = await handler(request)
|
||||
return response
|
||||
@@ -93,12 +108,13 @@ async def get_screen_agent():
|
||||
model=get_llm(),
|
||||
tools=[get_nodes, apply_actions, *await get_mcp_tools()],
|
||||
system_prompt=SYSTEM_PROMPT,
|
||||
# 用工具调用给出结论:试过原生结构化输出(ProviderStrategy),歧义时模型更常直接执行而不追问,评测多删了组件
|
||||
response_format=ToolStrategy(AgentReply),
|
||||
state_schema=ScreenAgentState,
|
||||
context_schema=ScreenAgentContext,
|
||||
middleware=[
|
||||
*skill_middleware(),
|
||||
retry_empty_reply,
|
||||
require_tool_call,
|
||||
# 超过上限时插入说明并结束,没有结构化结果,由节点按失败处理
|
||||
ModelCallLimitMiddleware(
|
||||
run_limit=settings.max_agent_steps, exit_behavior="end"
|
||||
@@ -108,4 +124,5 @@ async def get_screen_agent():
|
||||
checkpointer=False,
|
||||
name="screen_agent",
|
||||
)
|
||||
|
||||
return _agent
|
||||
|
||||
Reference in New Issue
Block a user