(三)基于事件与后台运行的Agent模块开发

1、多Agent规划步骤与记忆模型的设计与开发
1、记忆模型
app/domain/models/memory.py
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2025/05/17 14:56
@Author : thezehui@gmail.com
@File : memory.py
"""
import logging
from typing import List, Dict, Any, Optional
from pydantic import BaseModel, Field
logger = logging.getLogger(__name__)
class Memory(BaseModel):
"""记忆类,定义Agent的记忆基础信息"""
messages: List[Dict[str, Any]] = Field(default_factory=list)
@classmethod
def get_message_role(cls, message: Dict[str, Any]) -> str:
"""根据传递的消息来获取消息的角色信息"""
return message.get("role")
def add_message(self, message: Dict[str, Any]) -> None:
"""往记忆中添加一条消息"""
self.messages.append(message)
def add_messages(self, messages: List[Dict[str, Any]]) -> None:
"""往记忆中添加多条消息"""
self.messages.extend(messages)
def get_messages(self) -> List[Dict[str, Any]]:
"""获取记忆中的所有消息列表"""
return self.messages
def get_last_message(self) -> Optional[Dict[str, Any]]:
"""获取记忆中的最后一条消息,如果不存在则返回None"""
return self.messages[-1] if len(self.messages) > 0 else None
def roll_back(self) -> None:
"""回滚记忆,删除最后一条消息"""
self.messages = self.messages[:-1]
def compact(self) -> None:
"""记忆压缩,将记忆中已经执行的工具(搜索/网页源码获取/浏览器访问结果等)这类已经执行过的消息进行压缩检索"""
# 1.循环遍历所有的消息列表
for message in self.messages:
# 2.判断消息的角色是否为tool
if self.get_message_role(message) == "tool":
if message.get("function_name") in ["browser_view", "browser_navigate"]:
message["content"] = "(removed)"
logger.debug(f"从记忆中移除对应工具的结果: {message['function_name']}")
# 3.压缩记忆时reasoning_content内容可以去除压缩上下文
if "reasoning_content" in message:
logger.debug(f"从记忆中移除工具思考结果: {message['reasoning_content'][:50]}...")
del message["reasoning_content"]
@property
def empty(self) -> bool:
"""只读属性,检查记忆是否为空"""
return len(self.messages) == 0
2、创建任务
app/domain/models/plan.py
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2025/05/17 15:10
@Author : thezehui@gmail.com
@File : plan.py
"""
import uuid
from enum import Enum
from typing import List, Optional
from pydantic import BaseModel, Field
class ExecutionStatus(str, Enum):
"""规划/任务执行的状态"""
PENDING = "pending" # 空闲or等待中
RUNNING = "running" # 执行中
COMPLETED = "completed" # 执行完成
FAILED = "failed" # 失败
class Step(BaseModel):
"""计划中的每一个步骤/子任务"""
id: str = Field(default_factory=lambda: str(uuid.uuid4())) # 子任务id 每次创建一个新的 Step,如果用户没有提供 id,就重新调用这个函数生成一个值。
description: str = "" # 步骤的描述信息
status: ExecutionStatus = ExecutionStatus.PENDING # 子任务的执行状态
result: Optional[str] = None # 结果
error: Optional[str] = None # 错误信息
success: bool = False # 是否执行成功
attachments: List[str] = Field(default_factory=list) # 附件列表信息
@property
def done(self) -> bool:
"""只读属性,返回步骤是否结束"""
return self.status in [ExecutionStatus.COMPLETED, ExecutionStatus.FAILED]
class Plan(BaseModel):
"""规划Domain模型,用于存储用户传递消息拆分出来的子任务/子步骤"""
id: str = Field(default_factory=lambda: str(uuid.uuid4())) # 计划id
title: str = "" # 任务标题
goal: str = "" # 任务目标
language: str = "" # 工作语言
steps: List[Step] = Field(default_factory=list) # 步骤列表/子任务列表
message: str = "" # AI传递的消息
status: ExecutionStatus = ExecutionStatus.PENDING # 规划的状态
error: Optional[str] = None # 错误信息
@property
def done(self) -> bool:
"""只读属性,用于判断计划是否结束"""
return self.status in [ExecutionStatus.COMPLETED, ExecutionStatus.FAILED]
def get_next_step(self) -> Optional[Step]:
"""获取需要执行的下一个步骤"""
return next((step for step in self.steps if not step.done), None)
app/domain/models/event.py
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2025/05/18 0:54
@Author : thezehui@gmail.com
@File : event.py
"""
import uuid
from datetime import datetime
from enum import Enum
from typing import Literal, List, Union, Optional, Any, Dict, Annotated
from pydantic import BaseModel, Field
from .file import File
from .plan import Plan, Step
from .search import SearchResultItem
from .tool_result import ToolResult
class PlanEventStatus(str, Enum):
"""规划事件状态"""
CREATED = "created" # 已创建
UPDATED = "updated" # 已更新
COMPLETED = "completed" # 已完成
class StepEventStatus(str, Enum):
"""步骤事件状态"""
STARTED = "started" # 已开始
COMPLETED = "completed" # 已完成
FAILED = "failed" # 失败
class ToolEventStatus(str, Enum):
"""工具事件状态类型枚举"""
CALLING = "calling" # 调用中
CALLED = "called" # 调用完毕
class BaseEvent(BaseModel):
"""基础事件类型"""
id: str = Field(default_factory=lambda: str(uuid.uuid4())) # 事件id
type: Literal[""] = "" # 事件的类型
created_at: datetime = Field(default_factory=datetime.now) # 事件创建时间
class PlanEvent(BaseEvent):
"""规划事件类型"""
type: Literal["plan"] = "plan"
plan: Plan # 规划
status: PlanEventStatus = PlanEventStatus.CREATED # 规划事件状态
class TitleEvent(BaseEvent):
"""标题事件类型"""
type: Literal["title"] = "title"
title: str = "" # 标题
class StepEvent(BaseEvent):
"""子任务/步骤事件"""
type: Literal["step"] = "step"
step: Step # 步骤信息
status: StepEventStatus = StepEventStatus.STARTED
class MessageEvent(BaseEvent):
"""消息事件,包含人类消息和AI消息"""
type: Literal["message"] = "message"
role: Literal["user", "assistant"] = "assistant" # 消息角色
message: str = "" # 消息本身
attachments: List[File] = Field(default_factory=list) # 附件列表信息
class BrowserToolContent(BaseModel):
"""浏览器工具扩展内容"""
screenshot: str # 浏览器快照截图
class SearchToolContent(BaseModel):
"""搜索工具内容"""
results: List[SearchResultItem] # 搜索结果列表
class ShellToolContent(BaseModel):
"""Shell工具内容"""
console: Any # 控制台内容
class FileToolContent(BaseModel):
"""文件工具内容"""
content: str # 文件内容
class MCPToolContent(BaseModel):
"""MCP工具内容"""
result: Any # MCP工具结果
class A2AToolContent(BaseModel):
"""A2A智能体工具内容"""
a2a_result: Any # A2A智能体调用结果
ToolContent = Union[
BrowserToolContent,
SearchToolContent,
ShellToolContent,
FileToolContent,
MCPToolContent,
A2AToolContent,
]
class ToolEvent(BaseEvent):
"""工具事件"""
type: Literal["tool"] = "tool"
tool_call_id: str # 工具调用id
tool_name: str # 工具箱/工具集的名字
tool_content: Optional[ToolContent] = None # 工具扩展内容
function_name: str # LLM调用函数/工具名字
function_args: Dict[str, Any] # LLM生成的工具调用参数
function_result: Optional[ToolResult] = None # 工具调用结果
status: ToolEventStatus = ToolEventStatus.CALLING # 工具事件状态
class WaitEvent(BaseEvent):
"""等待事件,等待用户输入确认"""
type: Literal["wait"] = "wait"
class ErrorEvent(BaseEvent):
"""错误事件"""
type: Literal["error"] = "error"
error: str = "" # 错误信息
class DoneEvent(BaseEvent):
"""结束事件类型"""
type: Literal["done"] = "done"
# 定义应用事件类型声明
Event = Annotated[
Union[
PlanEvent,
TitleEvent,
StepEvent,
MessageEvent,
ToolEvent,
WaitEvent,
ErrorEvent,
DoneEvent,
],
Field(discriminator="type"),
]
2、LLM结构化输出缺陷与JSON修复解析器开发
python
https://github.com/mangiucugna/json_repair
安装:
python
pip install json-repair
用法:
python
import json_repair
decoded_object = json_repair.loads(json_string)
# 或者
decoded_object = json_repair.repair_json(json_string, return_objects=True)
app/domain/external/json_parser.py
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2025/05/18 1:44
@Author : thezehui@gmail.com
@File : json_parser.py
"""
from typing import Protocol, Optional, Any, Union, Dict, List
class JSONParser(Protocol):
"""JSON解析器,用于解析json字符串并修复"""
async def invoke(self, text: str, default_value: Optional[Any] = None) -> Union[Dict, List, Any]:
"""调用函数,用于将传递进来的文本进行解析并返回"""
...
app/infrastructure/external/json_parser/repair_json_parser.py
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2025/05/18 1:46
@Author : thezehui@gmail.com
@File : repair_json_parser.py
"""
import logging
from typing import Optional, Any, Union, Dict, List
import json_repair
from app.domain.external.json_parser import JSONParser
logger = logging.getLogger(__name__)
class RepairJSONParser(JSONParser):
"""基于修复逻辑的json解析器"""
async def invoke(self, text: str, default_value: Optional[Any] = None) -> Union[Dict, List, Any]:
"""传递文本,并使用json修复库进行修复"""
# 1.记录日志并判断text是否传递
logger.info(f"解析json文本: {text}")
if not text or not text.strip():
if default_value is not None:
return default_value
raise ValueError("json文本为空,且无默认值")
# 2.存在数值则使用json_repair库修复并解析
return json_repair.repair_json(text, ensure_ascii=False, return_objects=True)