7.1 User特质的周期提取与自进化(智能体工程)

《智能体工程驱动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 核心循环

流程:

  1. 调用 LLM

  2. 判断是否有工具调用

  3. 执行工具

  4. 将结果回传 LLM

  5. 重复直到无工具调用

"""

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:

从输入的提示词和回答看到,此时模型已经知晓了最新的用户特质。在后续的使用过程中,读者可以继续自行验证。

相关推荐
FII工业富联科技服务2 分钟前
Omniverse + Isaac Teleop + 合成数据:工业富联机器人大脑训练+执行落地闭环拆解
大数据·人工智能·深度学习·机器学习·机器人·制造·具身智能
东风破_17 分钟前
大模型格式化输出
人工智能
Shockang23 分钟前
AI 研究偏好模型
人工智能
ZGIAI23 分钟前
律师最贵的不是知识,是时间:哪些工作真的可以先交给 Agent?
人工智能·架构
东风破_26 分钟前
讲透 SSE:从流式响应到 LangChain model.stream()
人工智能
β添砖java2 小时前
深度学习31注意力机制、注意力分数、使用注意力机制的seq2seq、自注意力
人工智能·深度学习
ZGIAI2 小时前
销售团队最缺的不是另一个 AI,而是有人把跟进这件事一直做下去
人工智能·架构
隔窗听雨眠2 小时前
MCP会成为Agentic AI的标准吗?技术演进、生态博弈与标准之路的深度分析
人工智能
2601_967659882 小时前
2026年多个网页快速提取重点、自动汇总、对比并整理成表格的AI工具清单
人工智能
IT古董2 小时前
AI资讯日报|2026年9月5日:GPT-6 Astra全量推送却遭“翻车“,奥特曼紧急致歉一天12条大新闻:OpenAI翻车、英伟达收购、最狠AI法案出台
人工智能