10.2 智能体计划清单处理的整合

王晓华《智能体工程驱动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回到循环顶部 重新渲染todotask2 现在是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分解任务并初始化待办列表;每轮循环都会重建包含用户记忆的系统提示词,若有待办任务,则将其上下文注入提示词,更新消息列表中的系统提示词并处理长度超限。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值