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:

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

相关推荐
阿里云大数据AI技术1 小时前
免费领票!9月22日-24日,2026云栖大会杭州见
大数据·人工智能·agent
circuitsosk1 小时前
AI输出的“质检员”:构建智能体质量评估、异常检测与人工兜底的三层防线
人工智能·python·microsoft·正则表达式·langchain
IvanCodes1 小时前
我做了一个软著 Skill,可以一键生成申请材料
人工智能·agent
狂奔蜗牛(bradley)1 小时前
RKNN‑Toolkit2 模型转换全流程
人工智能
双星系统1 小时前
双臂机器人迎来广阔应用风口!既是工业柔性主力,也是人形机器人优质上肢配件
人工智能·机器人
羚羊角uou1 小时前
【AI agent】RAG 全链路深度详解及RAG调优策略
人工智能
甲维斯1 小时前
国内外模型3D台球大赛,DS垫底,Claude最强!
人工智能·游戏开发
CTA终结者1 小时前
先用小策略练清条件和动作
人工智能·python
hhzz1 小时前
《深度学习框架PyTorch入门与实践》系列:11-实战猫狗大战之可复用的PyTorch项目架构
人工智能·pytorch·深度学习