《智能体工程驱动AI Agent开发》1~6章试读-持续更新-CSDN博客
在前面的章节中。我们实现了基于单轮对话的用户特质实时抽取,完成了用户偏好、行为习惯、核心诉求的基础画像构建。但该逐轮抽取模式存在不可忽视的核心缺陷:持续的特质抽取指令与重复的特征校验,会带来大量非必要的词元开销,长期对话中会持续挤占宝贵的上下文窗口资源,同时还存在特质碎片化、跨轮一致性不足、冗余信息堆积等问题,反而会影响Agent交互的精准度与响应效率。
基于此,本章将提出用户特质的周期提取与自进化机制,通过对话周期切片、增量特征提取、冲突自动消解、画像迭代更新的全链路设计,在保障特质提取精准度的前提下,大幅压缩词元消耗;同时建立用户画像的动态自进化体系,为Agent的长期个性化交互提供稳定、高效的底层支撑。
7.1.1 User特质的提取:周期对话的深度挖掘
上一章中我们采用的逐轮对话实时提取用户特质的方式,虽然能做到响应及时,但也存在明显的短板:就像每一次交互都要重新调取一次用户偏好档案,不仅会造成不必要的算力与词元消耗,还容易让提取到的用户特质变得碎片化、连贯性不足。因此,本节将提出一套更高效的周期化用户特质提取方案。
方案的第一步是确定提取的周期规则,我们既支持固定间隔循环触发,也可设置每日固定时间点启动。这里我们优先选用"每日定点触发"的模式,每天到了预设的固定时间,就会对当日产生的全量对话数据进行统一的深度挖掘,集中完成用户特质的提取与更新,在保障提取精准度的同时,大幅降低了资源消耗,也让用户画像的连贯性和完整性得到了明显提升。
接下来,我们需要完成对周期对话的深度挖掘。在contextbuild.py中,我们根据现在的要求,重构了记忆提取和载入类,同样与前面动态提取相似,我们需要一个子智能体作为特质提取,提取前5日的信息,代码如下:
class MemoryExtractor:
"""记忆提取器 --- 根据 MD 标题结构,从最近 N 日对话中批量提取。"""
_SKIP_SECTIONS = frozenset({"说明"})
def init(self, model_name: str = "glm", md_path: str = "",
conversation_log_path: str = ""):
self.model_name = model_name
self.md_path = md_path
self.sections: Liststr = self._parse_sections(md_path)
self.conversation_log_path = conversation_log_path
if "qwen" in model_name:
from llm_moudle import qwen_moudle
self.client = qwen_moudle.QwenClient(model_name=model_name)
else:
from llm_moudle import glm_moudle
self.client = glm_moudle.GLMClient(model_name=model_name)
def _parse_sections(self, md_path: str) -> Liststr:
raw = _load_md(md_path)
if not raw:
return \[\]
return [
s for s in re.findall(r"^#{2,3}\s+(.+)$", raw, re.MULTILINE)
if s.strip() not in self._SKIP_SECTIONS
]
def _get_recent_conversations(self, days: int = 5) -> str:
if not self.conversation_log_path:
return ""
abs_path = os.path.abspath(self.conversation_log_path)
if not os.path.exists(abs_path):
abs_path = os.path.join(_PROJECT_ROOT, self.conversation_log_path)
if not os.path.exists(abs_path):
return ""
try:
with open(abs_path, "r", encoding="utf-8") as f:
data = json.load(f)
except (json.JSONDecodeError, ValueError):
return ""
today = datetime.now()
date_keys = (today - timedelta(days=i)).strftime("%Y-%m-%d") for i in range(days)
parts = \[\]
for date_key in date_keys:
for conv in data.get(date_key, \[\]):
parts.append("" + date_key + " 用户: " + conv.get("query", "") +
"\n助手: " + conv.get("response", ""))
return "\n\n".join(parts)
async def extract(self, days: int = 5) -> Dictstr, Any:
text = self._get_recent_conversations(days)
if not text.strip() or not self.sections:
return {}
prompt = (
"从以下近" + str(days) + "日对话记录中提取信息。只提取这些字段:" + ", ".join(self.sections) + "\n"
"规则:只提取明确提到的,不推测。没有新发现就输出空 JSON: {}\n"
"输出纯 JSON:{\"字段名\": \"值或列表\", ...}"
)
try:
response = await self.client.chat(messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": text},
])
raw = "".join(b.get("text", "") for b in response.get("content", \[\])
if b.get("type") == "text")
return _extract_json(raw)
except Exception as e:
logger.warning(f"MemoryExtractor 提取失败: {e}")
return {}
而在具体使用上,我们则需要首先完成系列日期的载入,之后再调用用于记忆提取的子智能体完成特质抽取,代码如下:
class MemoryExtractor:
"""记忆提取器 --- 根据 MD 标题结构,从最近 N 日对话中批量提取。"""
_SKIP_SECTIONS = frozenset({"说明"})
def init(self, model_name: str = "glm", md_path: str = "",
conversation_log_path: str = ""):
self.model_name = model_name
self.md_path = md_path
self.sections: Liststr = self._parse_sections(md_path)
self.conversation_log_path = conversation_log_path
if "qwen" in model_name:
from llm_moudle import qwen_moudle
self.client = qwen_moudle.QwenClient(model_name=model_name)
else:
from llm_moudle import glm_moudle
self.client = glm_moudle.GLMClient(model_name=model_name)
def _parse_sections(self, md_path: str) -> Liststr:
raw = _load_md(md_path)
if not raw:
return \[\]
return [
s for s in re.findall(r"^#{2,3}\s+(.+)$", raw, re.MULTILINE)
if s.strip() not in self._SKIP_SECTIONS
]
def _get_recent_conversations(self, days: int = 5) -> str:
if not self.conversation_log_path:
return ""
abs_path = os.path.abspath(self.conversation_log_path)
if not os.path.exists(abs_path):
abs_path = os.path.join(_PROJECT_ROOT, self.conversation_log_path)
if not os.path.exists(abs_path):
return ""
try:
with open(abs_path, "r", encoding="utf-8") as f:
data = json.load(f)
except (json.JSONDecodeError, ValueError):
return ""
today = datetime.now()
date_keys = (today - timedelta(days=i)).strftime("%Y-%m-%d") for i in range(days)
parts = \[\]
for date_key in date_keys:
for conv in data.get(date_key, \[\]):
parts.append("" + date_key + " 用户: " + conv.get("query", "") +
"\n助手: " + conv.get("response", ""))
return "\n\n".join(parts)
async def extract(self, days: int = 5) -> Dictstr, Any:
text = self._get_recent_conversations(days)
if not text.strip() or not self.sections:
return {}
prompt = (
"从以下近" + str(days) + "日对话记录中提取信息。只提取这些字段:" + ", ".join(self.sections) + "\n"
"规则:只提取明确提到的,不推测。没有新发现就输出空 JSON: {}\n"
"输出纯 JSON:{\"字段名\": \"值或列表\", ...}"
)
try:
response = await self.client.chat(messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": text},
])
raw = "".join(b.get("text", "") for b in response.get("content", \[\])
if b.get("type") == "text")
return _extract_json(raw)
except Exception as e:
logger.warning(f"MemoryExtractor 提取失败: {e}")
return {}
下面测试打印提取的内容,
if name == "main":
print("=" * 60)
print("7.1.1 周期对话深度挖掘(MemoryExtractor + UseMemory)")
print("=" * 60)
clog = ConversationLogger("initspace/memorys/conversation_log.json")
clog.log(query="我叫小明,住海淀", response="小明您好!", model="glm")
clog.log(query="我喜欢逛胡同、吃卤煮", response="胡同文化源远流长...", model="glm")
print("已写入 2 条对话到 conversation_log.json")
import shutil
demo_md = "_ch7_user_demo.md"
shutil.copy("initspace/brain/USER.md", demo_md)
async def run():
mem = UseMemory(demo_md, "initspace/memorys/conversation_log.json")
print("USER.md 提取字段:", mem.sections)
result = await mem.update()
print("周期提取结果:", result)
asyncio.run(run())
os.remove(demo_md)
结果如下:
...
{'用户画像': 'xiaohua', '已确认需求': '搜一下北京的小吃有哪些', '你有什么建议么', '已确认决策': \[\], '偏好记录': '北京小吃', '北京文旅', '任务进度': '已提供北京小吃搜索结果', '已整理顺路吃遍老北京的路线信息', '敏感约束': \[\], '历史摘要': '用户xiaohua询问了北京文旅建议及小吃信息,助手提供了搜索结果并为用户整理了一条顺路吃遍老北京的路线'}
=== USER.md ===
标题: '用户画像', '已确认需求', '已确认决策', '偏好记录', '任务进度', '敏感约束', '历史摘要'
提取: None
读者可以自行测试。

7.1.2 User特质的自进化
在前一小节,我们通过子智能体完成了在每个周期中提取用户特质内容,并且也打印了所提取的特质内容。这些特质的作用主要是为了修正智能体,使得智能体在使用过程中更加贴合用户的需求。例如,在USER.md中我们记录了抽取出来的用户画像。
在具体实现上,我们可以通过修改updata函数,对用户的特征或者其他内容进行升级,从而实现智能体的自进化。
下面是特质自进化的升级代码:
if name == 'main':
print("=" * 60)
print("Agent V0 测试")
print("=" * 60)
选择模型
print("可用模型: glm, qwen")
model_name = "glm"
agent = AgentV0(model_name=model_name)
from tool_moudle.search_tool import SearchTool
agent.register_tool(SearchTool())
while 1:
time.sleep(1)
query = input("query:")
reply = agent.invoke(query)
print(reply)
print("=" * 60)
运行后,可以对内容进行修正,部分结果如下所示:
用户画像
-
称呼/昵称:(待识别)
-
所在城市:(待识别)
<!-- 动态更新:需求确认 -->
...
升级后如下:
用户画像
- xiaohua
已确认需求
-
搜索北京的小吃
-
询问北京游玩或饮食建议
...
基于USER.md更新后,新的用户特质的智能体代码如下:
class AgentV0:
"""
统一多模型 Agent
功能:
-
支持 GLM / Qwen 多模型切换
-
支持工具调用 (bash, read_file, write_file 等)
-
支持定时任务和循环任务
使用:
agent = AgentV0(model_name="glm") # 或 "qwen"
result = agent.invoke("帮我查询天气")
"""
def init(
self,
model_name: str = "glm",
system_prompt: Optionalstr = None,
):
"""
初始化 Agent
Args:
model_name: 模型名称 ("glm" 或 "qwen")
system_prompt: 自定义系统提示 (可选)
client: 外部注入的 LLM 客户端 (可选,用于复用连接池)
"""
self.model_name = model_name
self.messages: ListDict\[str, Any] = \[\]
self.max_steps = 20 # 增加到20步,支持复杂任务链
self.max_messages_length = 20
创建或复用客户端
if model_name == "qwen":
from llm_moudle import qwen_moudle
self._client = qwen_moudle.QwenClient()
elif model_name == "glm":
self._client = glm_moudle.GLMClient()
else:
raise ValueError(f"大模型 {model_name} 还没有定义!")
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.memorylib 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")
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.searchsearch_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) -> ListDict:
"""获取已注册工具的 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"""
result = asyncio.run(self.run(user_input))
self.conv_logger.log(query=user_input, response=result, model=self.model_name)
return result
async def run(self, user_input: str) -> str:
"""
Agent 核心循环
流程:
-
调用 LLM
-
判断是否有工具调用
-
执行工具
-
将结果回传 LLM
-
重复直到无工具调用
"""
self.add_message("user", user_input)
每轮重建 system prompt(包含最新的用户记忆)
user_memory_context = self.user_memory.to_prompt()
print(user_memory_context)
full_system = self.system_prompt + "\n\n" + user_memory_context
if self.messages and self.messages0"role" == "system":
self.messages0"content" = full_system
else:
self.messages.insert(0, {"role": "system", "content": full_system})
for step in range(1, self.max_steps + 1):
logger.info(f"Step {step} 调用模型 ({self.model_name})...")
调用 LLM
response = await self._client.chat(
messages=self.messages,
tools=self._get_tool_schemas()
)
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:
logger.info(f"Step {step} 完成")
return full_text or "任务完成"
执行工具
logger.info(f"Step {step} 执行 {len(tool_calls)} 个工具")
先添加 assistant 消息(包含 tool_calls)
注意:arguments 必须是 JSON 字符串
import 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 '(无输出)'
except Exception as e:
result_content = f"执行失败: {e}"
添加 tool 角色消息(必须包含 tool_call_id 和 name)
self.messages.append({
"role": "tool",
"tool_call_id": tool_id,
"name": tool_name,
"content": result_content
})
return "达到最大步数限制"
def close(self):
"""关闭客户端"""
if self._client:
self._client.close()
我们运行代码,打印出与智能体进行交互时载入的系统提示词,结果中也包含新的用户特质,具体如下所示:
query:hi
【USER.md记忆】
2026-04-04 06:39:10,585 - INFO - Step 1 调用模型 (glm)...
以下是提取到的信息:
【说明】
-
- 下方的结构化区块由Context Engineering在多轮对话中自动提取、存储并更新。
智能体在长对话中始终读取最新内容,保持记忆连贯、决策一致。
【用户画像】
- xiaohua
【已确认需求】
-
搜索北京的小吃
-
询问北京游玩或饮食建议
【已确认决策】
-
exit
-
quit
-
确认方案(回复:好的)
【偏好记录】
- 北京小吃
【任务进度】
- 已完成北京小吃搜索,已整理顺路吃遍老北京的路线
【历史摘要】
- 用户名为xiaohua,询问了北京相关建议,搜索了北京小吃,期间尝试退出,最后确认了助手整理的关于顺路吃遍老北京的路线。
2026-04-04 06:39:21,069 - INFO - HTTP Request: POST https://open.bigmodel.cn/api/coding/paas/v4/chat/completions "HTTP/1.1 200 OK"
2026-04-04 06:39:21,072 - INFO - Step 1 助手: 嘿,xiaohua!又见面啦,您今儿个有啥想法?
上次给您整的那个「顺路吃遍老北京」的路线还满意不?要是还想接着逛北京、吃北京,或者有啥新需求---不管是景点、美食、路线规划,您尽管开口,我随时给您安...
2026-04-04 06:39:21,072 - INFO - Step 1 完成
嘿,xiaohua!又见面啦,您今儿个有啥想法?
上次给您整的那个「顺路吃遍老北京」的路线还满意不?要是还想接着逛北京、吃北京,或者有啥新需求---不管是景点、美食、路线规划,您尽管开口,我随时给您安排!😊
============================================================
query:
从输入的提示词和回答看到,此时模型已经知晓了最新的用户特质。在后续的使用过程中,读者可以继续自行验证。


