王晓华《智能体工程驱动AI Agent开发》1~10章试读-CSDN博客
智能体作为llmAgentBook项目复杂任务分解与执行的核心载体,其运行逻辑围绕任务全生命周期展开,核心流程可概括为:用户输入→plan_task分解任务→create_from_plan初始化待办→主循环迭代执行。主循环中,通过固定步骤保障任务有序推进、闭环落地,具体流程如下:
用户输入 → plan_task分解任务 → create_from_plan初始化待办 → 主循环中:
├── 注入todo上下文到system prompt
├── 调用LLM
├── 执行工具
├── tick_round() 推进计数
└── 判断是否需要mark_current_done()
在这里,接收用户输入后先拆解任务并初始化待办,随后进入循环,不断把待办注入提示词、调用大模型、执行工具、推进轮次,并判断是否标记当前任务完成,循环直至结束。
10.2.1 主智能体中的子任务推进
通过单独实现的任务管理工具,我们现在结束任务规划阶段,接下来需要完成清单初始化,第一个任务自动变成in_progress,并根据项目推进分解的任务清单依次执行。具体设计说明如下。
第一步:注入todo上下文
在每轮循环的开头,我们需要注入任务。
# 每轮循环开头
if not self.todo_manager.is_empty():
todo_context = self.todo_manager.render() # 渲染成文本
if self.todo_manager.should_nag(): # 超过3轮没进展?
todo_context += "\n\n" + self.todo_manager.get_nag_message()
full_system_with_todo = full_system + todo_context # 拼到system prompt
render() 生成的文本会追加到system prompt末尾,LLM每一轮都能看到
第二步:调LLM + 执行工具
根据当前任务的要求,调用对应的工具对任务进行执行,工具执行完毕后,使用should_nag()增加对任务状况的判断。如果连续 3 轮都在调工具但没完成当前子任务,should_nag()返回True,下一轮system prompt里会多出一段提醒。例如:
[系统提醒] 你已经 3 轮没有更新任务状态了。
当前任务: 定义函数
已完成: 0/3, 剩余: 2 个任务待处理。
请集中精力完成当前任务。
从而协助和提醒模型完成任务。
第三步:子任务完成→推进到下一个
当LLM不再调用工具(纯文本回复),意味着当前子任务做完了:
# 无工具调用则判断任务进度
if not tool_calls:
if not self.todo_manager.is_empty():
self.todo_manager.mark_current_done()
if self.todo_manager.all_done():
logger.info("[任务计划] 全部完成")
return full_text or "[任务完成]"
# 还有未完成的任务,注入继续指令,继续循环
logger.info("[任务计划] 当前子任务完成,继续下一个")
continue
logger.info(f"[Step {step}] 完成")
return full_text or "[任务完成]"
最后判断:
if self.todo_manager.all_done():
return full_text # 全部完成,返回结果
logger.info("当前子任务完成,继续下一个")
continue # 还有任务,回到循环顶部
此时重新执行
continue回到循环顶部 → 重新渲染todo(task2 现在是in_progress)→ LLM看到task2 → 开始处理task2
如此循环直到全部完成。
一句话总结,即:
plan_task负责拆,create_from_plan负责存,主循环里的render负责让LLM看到,mark_current_done负责推进,continue负责驱动下一个子任务的执行

10.2.2 带有计划清单的智能体实现
下面是作者实现的、带有任务计划拆解清单的智能体。为了简化,在这里删除了定时任务的部分,代码如下:
import sys
import os
import json
import time
import asyncio
import logging
import threading
from datetime import datetime
from typing import Any, Dict, List, Optional
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from llm_moudle import glm_moudle
from initspace import todo_manager
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class AgentV1:
"""
统一多模型Agent
功能:
- 支持GLM/Qwen多模型切换
- 支持工具调用 (bash, read_file, write_file等)
- 支持定时任务和循环任务
使用:
agent = AgentV1(model_name="glm") # 或 "qwen"
result = agent.invoke("帮我查询天气")
"""
def __init__(
self,
model_name: str = "glm",
system_prompt: Optional[str] = None,
):
"""
初始化Agent
Args:
model_name: 模型名称 ("glm" 或 "qwen")
system_prompt: 自定义系统提示 (可选)
client: 外部注入的LLM客户端 (可选,用于复用连接池)
"""
self.model_name = model_name
self.messages: List[Dict[str, Any]] = []
self._used_tools: List[str] = []
self.max_steps = 20 # 增加到20步,支持复杂任务链
self.max_messages_length = 20
self.MAX_SYSTEM_LENGTH = 1024
# 创建或复用客户端
if model_name == "qwen":
self._client = qwen_moudle.QwenClient()
elif model_name == "glm":
self._client = glm_moudle.GLMClient()
else:
raise "大模型还没有定义!"
from anthropic_standard import basic_anthropic_tool
self._executor = basic_anthropic_tool.ToolExecutor()
self._register_tools()
# 动态构建system prompt(依赖 _executor中的工具schema)
from initspace.contextbuild import build_system_prompt
self.system_prompt = system_prompt or build_system_prompt(
tool_schemas=self._executor.get_all_schemas(),
brain_dir="initspace/brain")
logger.info(f"[Agent] system prompt已构建,内容: {(self.system_prompt)} ")
#建立和进行用户自进化-----------------------------------------
from initspace.contextbuild import UseMemory
self.user_memory = UseMemory("../initspace/brain/USER.md", conversation_log_path="../initspace/memorys/conversation_log.json")
#将所有的交互记录进行持久化-----------------------------------------
from initspace.memorybuild import ConversationLogger
self.conv_logger = ConversationLogger("../initspace/memorys/ conversation_log.json")
print("\033[0;31m%s\033[0m" % "关闭心跳管理器")
#内部任务管理器
self.todo_manager = todo_manager.TodoManager()
def _register_tools(self):
"""注册默认工具,并设置工具使用的模型名称"""
try:
from tool_moudle.bash_tool import (BashTool, ReadFileTool, WriteFileTool, EditFileTool )
tools = [
BashTool(), ReadFileTool(), WriteFileTool(), EditFileTool(),
]
for tool in tools:
# 为每个工具设置当前Agent的模型名称
tool.set_model_name(self.model_name)
self._executor.register(tool)
logger.info(f"[Agent] 已注册 {len(tools)} 个工具,模型: {self.model_name}")
except ImportError as e:
logger.warning(f"[Agent] 无法导入工具模块: {e}")
def register_tool(self, tool: "BaseTool"):
"""
后注册工具(初始化后手动添加新工具)
Args:
tool: BaseTool实例
Example:
from tool_moudle.search_tool import SearchTool
agent = AgentV0(model_name="glm")
agent.register_tool(SearchTool())
"""
tool.set_model_name(self.model_name)
self._executor.register(tool)
logger.info(f"[Agent] 后注册工具: {tool.name}")
def unregister_tool(self, tool_name: str) -> bool:
"""
注销工具
Args:
tool_name: 工具名称
Returns:
是否注销成功
"""
success = self._executor.unregister(tool_name)
if success:
logger.info(f"[Agent] 已注销工具: {tool_name}")
return success
def _get_tool_schemas(self) -> List[Dict]:
"""获取已注册工具的schema列表(复用已注册的工具实例)"""
return self._executor.get_all_schemas()
def add_message(self, role: str, content: str):
"""添加消息到历史"""
#首先进行硬截断
"""添加消息到历史"""
if len(self.messages) >= self.max_messages_length:
# 保留system prompt (index 0) + 最新N-1 条
self.messages = [self.messages[0]] + self.messages[-(self.max_messages_length - 1):]
self.messages.append({"role": role, "content": content})
def reset(self):
"""重置对话历史"""
self.messages = []
def invoke(self, user_input: str) -> str:
"""同步运行Agent"""
self._used_tools = []
start_time = time.time()
result = asyncio.run(self.run(user_input))
cost = round(time.time() - start_time, 2)
self.conv_logger.log(
query=user_input,
response=result,
model=self.model_name,
tool_calls=self._used_tools,
extra={
"execute_cost_time": cost,
"tool_call_count": len(self._used_tools),
}
)
return result
async def run(self, user_input: str) -> str:
"""
Agent核心循环
流程:
1. 调用LLM
2. 判断是否有工具调用
3. 执行工具
4. 将结果回传LLM
5. 重复直到无工具调用
"""
self.add_message("user", user_input)
# ===== 新增: 任务规划检测 =====
# 当检测到可能是多步骤任务时,让Agent先输出计划
plans = await todo_manager.plan_task(user_input, client=self._client)
render = self.todo_manager.create_from_plan(plans)
logger.info(f"任务分解为:\n{render}")
# 每轮重建system prompt(包含最新的用户记忆)
user_memory_context = self.user_memory.to_prompt()
full_system = self.system_prompt + "\n\n" + user_memory_context
for step in range(1 , self.max_steps + 1):
# 注入todo上下文到system prompt
if not self.todo_manager.is_empty():
todo_context = self.todo_manager.render()
current_task = self.todo_manager._get_in_progress()
if current_task:
logger.info(f"[任务计划] 当前子任务: [{current_task['id']}] {current_task['text']}")
if self.todo_manager.should_nag():
todo_context += "\n\n" + self.todo_manager.get_nag_message()
self.todo_manager._rounds_since_update = 0
full_system_with_todo = full_system + todo_context
else:
full_system_with_todo = full_system
# 每次更新的时候都进行full_system_prompt更新
if self.messages and self.messages[0]["role"] == "system":
self.messages[0]["content"] = full_system_with_todo
else:
self.messages.insert(0, {"role": "system", "content": full_system_with_todo})
if len(full_system_with_todo) > self.MAX_SYSTEM_LENGTH:
full_system_with_todo = full_system_with_todo[:self.MAX_SYSTEM_LENGTH] + "\n\n[系统提示已精简]"
logger.info(f"[Step {step}] 调用模型 ({self.model_name})...")
# 调用LLM
try:
response = await self._client.chat(
messages=self.messages,
tools=self._get_tool_schemas()
)
except Exception as e:
logger.error(f"[Step {step}] LLM调用失败: {e}")
self.messages = [self.messages[0]] + self.messages[-4:]
# 删掉截断后开头的孤立tool消息
while len(self.messages) > 1 and self.messages[1].get("role") == "tool":
self.messages.pop(1)
try:
response = await self._client.chat(
messages=self.messages,
tools=self._get_tool_schemas()
)
except Exception as e2:
logger.error(f"[Step {step}] 重试也失败: {e2}")
return full_text or "[请求失败,请重试]"
stop_reason = response.get("stop_reason", "stop")
content_blocks = response.get("content", [])
# 提取内容
tool_calls = [b for b in content_blocks if b.get("type") == "tool_use"]
texts = [b.get("text", "") for b in content_blocks if b.get("type") == "text"]
full_text = "\n".join(texts)
# 处理截断
if stop_reason in ["max_tokens", "length"]:
logger.warning(f"[Step {step}] 输出被截断")
if full_text:
self.add_message("assistant", full_text)
return full_text + "\n[输出被截断]"
# 记录文本回复
if full_text:
logger.info(f"[Step {step}] 助手: {full_text[:100]}...")
self.add_message("assistant", full_text)
# 无工具调用则判断任务进度
if not tool_calls:
if not self.todo_manager.is_empty():
self.todo_manager.mark_current_done()
if self.todo_manager.all_done():
logger.info("[任务计划] 全部完成")
return full_text or "[任务完成]"
# 还有未完成的任务,注入继续指令,继续循环
logger.info("[任务计划] 当前子任务完成,继续下一个")
continue
logger.info(f"[Step {step}] 完成")
return full_text or "[任务完成]"
# 执行工具
logger.info(f"[Step {step}] 执行 {len(tool_calls)} 个工具")
# 先添加assistant消息(包含tool_calls)
# 注意:arguments必须是JSON字符串
self.messages.append({
"role": "assistant",
"content": None,
"tool_calls": [
{
"id": tc["id"],
"type": "function",
"function": {
"name": tc["name"],
"arguments": json.dumps(tc.get("input", {}), ensure_ascii=False)
}
}
for tc in tool_calls
]
})
# 然后添加每个工具的tool结果消息
for tc in tool_calls:
tool_name = tc["name"]
tool_input = tc.get("input", {})
tool_id = tc["id"]
logger.info(f"[Step {step}] 工具: {tool_name}, 输入: {tool_input}")
try:
result = await self._executor.execute(tool_name, tool_input)
result_content = result or '(无输出)'
self._used_tools.append({"tool_name": tool_name, "args": tool_input})
except Exception as e:
result_content = f"执行失败: {e}"
if len(result_content) > 1024:
result_content = result_content[:1024] + "\n...(内容过长已截断)"
self.messages.append({
"role": "tool",
"tool_call_id": tool_id,
"name": tool_name,
"content": result_content
})
#for tc循环结束之后,添加tool结果后,检查总消息长度
if len(self.messages) > self.max_messages_length + 5:
self.messages = [self.messages[0]] + self.messages[-(self.max_messages_length - 1):]
logger.warning(f"[Step {step}] 消息过长,已截断")
#任务推进
self.todo_manager.tick_round()
return "[达到最大步数限制]"
在这里,我们将run方法作为核心循环,首先接收用户输入并添加到消息历史,随后调用TodoManager分解任务并初始化待办列表;每轮循环都会重建包含用户记忆的系统提示词,若有待办任务,则将其上下文注入提示词,更新消息列表中的系统提示词并处理长度超限。



1135

被折叠的 条评论
为什么被折叠?



