这是一个基于 Python 的 AI 智能体开发教学项目,包含多个实践练习,帮助学习者掌握 AI 智能体的开发技能。
code_AI_fin/
├── venv/ # Python 虚拟环境
├── .gitignore # Git 排除配置
├── .env # 环境变量配置(需自行创建)
├── env.example # 环境变量模板
├── README.md # 项目说明文档
├── practice01/ # 练习1:基础 LLM 调用
│ └── llm_client.py # 使用标准 HTTP 库调用 LLM
├── practice02/ # 练习2:工具调用
│ └── llm_tool_client.py # 支持文件操作和网络访问工具调用
└── practice03/ # 练习3:聊天记录管理
├── llm_summary_client.py # 新代码1:聊天记录智能压缩与总结
└── llm_knowledge_client.py # 新代码2:5W记忆提取 + 历史检索
└── practice04/ # 练习4:外部知识库集成
└── llm_knowledge_client.py # 集成AnythingLLM文档仓库查询
├── practice05/ # 练习5:技能系统(Skill System)
│ └── llm_knowledge_client.py # 技能列表读取 + 技能加载 + 按技能指令执行
├── practice06/ # 练习6:链式工具调用(Chained Tool Calls)
│ └── llm_knowledge_client.py # 链式调用上下文管理 + 多步自主决策执行
├── .agents/ # 技能定义目录
└── skills/ # 技能集合
└── notice/ # 通知撰写技能
└── SKILL.md # 技能定义文件(YAML front matter + 正文)
- Python 3.11+
- OpenAI 兼容的 LLM API(如 OpenAI、LM Studio、Ollama 等)
# Windows
venv\Scripts\activate
# 退出虚拟环境
deactivate复制环境变量模板并填写您的 LLM 配置:
copy env.example .env编辑 .env 文件:
LLM_BASE_URL=http://127.0.0.1:1234/v1
LLM_MODEL=qwen3.5-9b
LLM_API_KEY=your-api-key-here
MAX_TOKENS=2000
TEMPERATURE=0.7练习1:基础 LLM 调用
python practice01/llm_client.py功能:使用 Python 标准 HTTP 库调用 LLM,统计 Token 消耗、时间和速度。
练习2:工具调用客户端
python practice02/llm_tool_client.py功能:增加工具调用能力,支持 list_files / rename_file / delete_file / create_file / read_file / curl_url 六类工具。
练习3 新代码1:聊天记录自动总结
python practice03/llm_summary_client.py触发条件:对话轮数 > 5 轮,或上下文字符数 > 3000。
策略:前 70% 历史压缩为摘要,保留后 30% 原文。支持流式输出。
练习3 新代码2:5W记忆提取 + 历史检索
python practice03/llm_knowledge_client.py新增两大功能:
① 每 5 轮自动提取 5W 关键信息,追加写入 D:\chat-log\log.txt
② 检测到 /search 指令或搜索意图时,注入日志知识库进行检索问答
练习4:集成 AnythingLLM 文档仓库查询
python practice04/llm_knowledge_client.py在 practice03 基础上新增 AnythingLLM 文档仓库查询工具,用户提到"文档仓库"、"文件仓库"、"仓库"时自动触发。
练习5:技能系统(Skill System)
python practice05/llm_knowledge_client.py在 practice04 基础上新增动态技能系统。程序启动时自动扫描 .agents/skills/ 目录下所有技能,将技能列表注入 system prompt。当用户请求匹配某技能时,LLM 自动调用 load_skill_content 加载技能指令,然后严格按照指令执行。
内置技能:
| 技能名 | 说明 | 触发条件 |
|---|---|---|
notice |
部门通知撰写 | 用户要求写通知/发通知/润色通知 |
技能文件格式(.agents/skills/{skill_name}/SKILL.md):
---
name: skill_name
description: 技能描述,用于 LLM 判断是否需要加载此技能
---
### 技能正文(LLM 加载后按此指令执行)
...练习6:链式工具调用(Chained Tool Calls)
python practice06/llm_knowledge_client.py在 practice05 基础上新增链式工具调用能力。用户输入 /chain <请求> 进入链式调用模式,LLM 自主规划多步骤任务,前一步工具的输出作为后一步的输入,实现复杂的端到端自动化工作流。
链式调用模式使用方式:
You: /chain 查找 practice05 目录下所有包含'def'关键词的文件,并总结主要内容
[链式调用模式] 请求: 查找 practice05 目录下所有包含'def'关键词的文件...
──────────────────────────────────────────────────
[链式调用] 迭代 1/10
──────────────────────────────────────────────────
[决策] 调用工具: search_files_content
[参数] {"directory": "...", "keyword": "def"}
[结果] ...
──────────────────────────────────────────────────
[链式调用] 迭代 2/10
──────────────────────────────────────────────────
[决策] 调用工具: read_file
[参数] {"directory": "...", "file_name": "llm_knowledge_client.py"}
[结果] ...
──────────────────────────────────────────────────
[链式调用] 迭代 3/10
──────────────────────────────────────────────────
[完成] 任务结束
[最终回答] ...
(共 3 步, 耗时 12.34s)
[链式调用完成] 总步数: 3, 耗时: 12.34s
- 使用 Python 标准库
http.client调用 OpenAI 兼容 API - 读取
.env配置文件 - 统计 Token 消耗、响应时间、处理速度
- 在终端聊天客户端基础上增加工具调用能力
- 支持文件操作:列出文件、创建文件、读取文件、修改文件名、删除文件
- 支持网络访问:通过 HTTP/HTTPS 获取网页内容
- LLM 自动判断是否需要调用工具
核心教学目标:解决 LLM 上下文窗口有限的问题,通过智能压缩历史记录延长有效对话。
触发机制:
| 条件 | 阈值 | 说明 |
|---|---|---|
| 对话轮数 | > 5 轮 | user+assistant 算一轮 |
| 上下文长度 | > 3000 字符 | 仅统计非 system 消息 |
压缩策略:
历史消息(排除 system):
[msg1 msg2 msg3 ... msg7 msg8 msg9 msg10]
|_______前 70% 压缩为摘要___| |_后 30% 保留_|
重建后:
[system_原始, system_摘要, msg8, msg9, msg10]
关键函数:
| 函数 | 功能 |
|---|---|
should_compress(history) |
检测是否触发压缩,返回 (bool, 原因) |
compress_history(...) |
执行压缩:切分 → 调 LLM 总结 → 重建历史 |
_build_compress_prompt(msgs) |
构造总结提示词 |
call_llm_stream(...) |
流式调用 LLM 并实时打印 |
call_llm(...) |
非流式调用 LLM(用于总结/提取任务) |
核心教学目标:让 Agent 具备长期记忆能力——主动提取关键知识并在需要时精准检索。
每隔 5 轮对话,自动触发 LLM 按 5W 规则提取关键信息,追加写入本地日志文件。
5W 规则:
| 字段 | 含义 | 是否必填 |
|---|---|---|
| Who | 涉及的主体 | 必填 |
| What | 发生的事件/行动 | 必填 |
| When | 时间信息 | 可选 |
| Where | 地点信息 | 可选 |
| Why | 目的/原因 | 可选 |
日志格式(D:\chat-log\log.txt):
--------------------------------------------------
[2026-05-17 14:30:00] 关键信息提取
--------------------------------------------------
[1]
Who : 用户
What : 询问如何用Python读取文件
Why : 学习编程
[2]
Who : 助手
What : 介绍了 open() 函数的用法
When : 当前对话
5W 提示词工程要点:
- 要求 LLM 严格以 JSON 数组输出,温度设为 0.1 保证格式稳定
- 系统消息约束:「只能输出 JSON 数组,不得输出其他内容」
- 容错机制:提取响应中的
[...到]区间,防止 LLM 输出前缀文字
关键函数:
| 函数 | 功能 |
|---|---|
_build_5w_extract_prompt(msgs) |
构造 5W 提取提示词(要求 JSON 数组输出) |
extract_5w_info(history, ...) |
调用 LLM 提取信息,容错解析 JSON |
append_to_log(items, path) |
追加写入日志,自动创建目录/文件 |
maybe_extract_and_log(...) |
每 5 轮触发一次完整提取流程 |
当检测到搜索意图时,自动将 log.txt 作为知识库注入上下文,完成带记忆的 LLM 问答。
触发方式(优先级由高到低):
| 方式 | 示例 | Token 消耗 |
|---|---|---|
/search 前缀 |
/search 上次说的项目 |
零(关键词匹配) |
| 中文关键词 | 查找聊天历史 / 以前说过 | 零(关键词匹配) |
| LLM 意图识别 | 「你还记得我之前提到的事吗」 | 极少(YES/NO 约束) |
搜索意图识别提示词要点:
- 系统消息:「你是意图识别助手。你只能回答 YES 或 NO」
- 温度设为 0.0,max_tokens=5,强制最小化输出
- 关键词检测优先,LLM 识别作为兜底
检索增强提示词结构:
[system]
你是一个聊天历史检索助手。
以下是用户过往对话中提取的关键信息日志(5W格式):
<knowledge_base>
{log.txt 完整内容}
</knowledge_base>
请根据上述日志,准确回答用户的查询问题。
[user]
{用户的实际查询内容}
关键函数:
| 函数 | 功能 |
|---|---|
_detect_search_intent_by_keyword(input) |
基于关键词快速检测意图(零 token) |
_detect_search_intent_by_llm(input, ...) |
LLM 意图判断(YES/NO 强约束) |
read_log_file(path) |
读取日志,文件不存在时返回空串 |
handle_search_request(input, ...) |
注入知识库,执行检索流式问答 |
运行效果示例:
LLM Knowledge Client - 5W记忆提取 + 历史检索
=======================================================
5W提取 : 每 5 轮触发一次
日志路径 : D:\chat-log\log.txt
=======================================================
You: 帮我写个冒泡排序
Assistant: (流式回复...)
--- 状态统计 ---
累计轮数: 5 | 上下文长度: 1234 字符 | 已压缩: 0 次
──────────────────────────────────────────────────────
[5W提取 @第5轮] 触发关键信息提取...
[5W提取] 共提取 2 条关键信息:
[1] 用户 → 请求编写冒泡排序算法 | Why: 学习算法
[2] 助手 → 提供了冒泡排序Python实现
[5W提取] 已追加写入 D:\chat-log\log.txt
You: /search 我之前让你写过什么算法?
[搜索模式] 注入知识库(386 字符),正在检索...
Assistant: 根据历史记录,您曾请求编写冒泡排序算法...
- Python 3.11+ - 编程语言
- http.client - 标准 HTTP 客户端(无第三方依赖)
- json - JSON 数据处理
- OpenAI API 兼容协议 - LLM 接口标准
- Server-Sent Events (SSE) - 流式响应解析
| 常量 | 文件 | 默认值 | 说明 |
|---|---|---|---|
MAX_ROUNDS |
两个 py | 5 | 超过该轮数触发上下文压缩 |
MAX_CTX_CHARS |
两个 py | 3000 | 超过该字符数触发上下文压缩 |
KEEP_RATIO |
两个 py | 0.30 | 保留最近 30% 的对话原文 |
EXTRACT_EVERY |
knowledge | 5 | 每隔多少轮触发 5W 提取 |
LOG_FILE_PATH |
knowledge | D:\chat-log\log.txt | 关键信息持久化路径 |
MAX_CHAIN_ITERATIONS |
practice06 | 10 | 链式调用最大迭代次数 |
- 下载并安装 LM Studio
- 在 LM Studio 中下载您喜欢的模型
- 启动本地服务器(默认端口 1234)
- 配置
.env:LLM_BASE_URL=http://127.0.0.1:1234/v1 LLM_MODEL=您的模型名称 LLM_API_KEY=lm-studio MAX_TOKENS=2000 TEMPERATURE=0.7
{"tool": "list_files", "params": {"directory": "d:\\ziliao\\code_AI_fin"}}
{"tool": "create_file", "params": {"directory": "d:\\ziliao", "file_name": "test.txt", "content": "Hello"}}
{"tool": "read_file", "params": {"directory": "d:\\ziliao", "file_name": "test.txt"}}
{"tool": "rename_file", "params": {"directory": "d:\\ziliao", "old_name": "test.txt", "new_name": "new.txt"}}
{"tool": "delete_file", "params": {"directory": "d:\\ziliao", "file_name": "new.txt"}}{"tool": "curl_url", "params": {"url": "https://wttr.in/Chengdu"}}
{"tool": "curl_url", "params": {"url": "https://example.com"}}{"tool": "anythingllm_query", "params": {
"message": "项目使用了哪些技术栈?",
"api_key": "your-api-key",
"workspace_slug": "my-workspace"
}}核心教学目标:让 Agent 能够查询外部知识库(AnythingLLM 文档仓库),实现"文档问答"能力。
前置条件:
- 安装并运行 AnythingLLM(默认端口 3001)
- 在 AnythingLLM 中创建一个 Workspace,上传文档
- 在 AnythingLLM Settings > API Keys 中生成 API 密钥
- 在
.env中配置:ANYTHINGLLM_API_KEY=your-api-key ANYTHINGLLM_WORKSPACE_SLUG=your-workspace-slug
新增工具:
| 工具名 | 功能 | 触发条件 |
|---|---|---|
anythingllm_query |
查询 AnythingLLM 文档仓库 | 用户提到"文档仓库"、"文件仓库"、"仓库" |
技术实现要点:
| 要点 | 说明 |
|---|---|
| 通信方式 | subprocess 调用系统 curl 命令 |
| API 端点 | POST http://localhost:3001/api/v1/workspace/{slug}/chat |
| 认证方式 | Authorization: Bearer <API_KEY> |
| 请求体 | {"message": "...", "mode": "chat"} |
| 中文编码 | subprocess.run(..., encoding="utf-8", errors="replace") |
| 超时处理 | 120 秒超时保护 |
| 容错解析 | 支持 textResponse、response、content 多种响应键 |
运行效果示例:
LLM Knowledge Client + AnythingLLM
==================================================
Model: qwen3.5-9b
Base URL: http://127.0.0.1:1234/v1
5W Extract: every 5 rounds
AnythingLLM: my-docs @ http://localhost:3001
==================================================
You: 帮我查一下文档仓库里关于API认证的说明
[调用工具中...]
[工具执行结果]
[AnythingLLM 仓库回复]
根据文档,API认证采用Bearer Token方式...
Assistant: 根据文档仓库的信息,API认证采用Bearer Token...
核心教学目标:让 Agent 具备动态技能加载能力——通过读取外部技能定义文件,按需加载并严格遵循技能指令执行。
技能系统架构:
用户输入 → LLM 判断是否需要技能
↓ 是
调用 load_skill_content(skill_name)
↓
读取 .agents/skills/{name}/SKILL.md 正文
↓
注入 system prompt → LLM 按技能指令执行
核心函数:
| 函数 | 功能 |
|---|---|
list_available_skills() |
扫描 .agents/skills/ 子目录,解析 YAML front matter,返回技能列表 |
load_skill_content(skill_name) |
根据技能名加载 SKILL.md 正文(front matter 之后的部分) |
build_skills_prompt() |
构建技能列表的 system prompt 文本 |
_parse_front_matter(content) |
解析 YAML front matter,提取 name 和 description |
_extract_yaml_field(text, field) |
从 YAML 文本中提取指定字段值 |
技能文件规范:
| 要素 | 说明 |
|---|---|
| 目录结构 | .agents/skills/{skill_name}/SKILL.md |
| YAML front matter | --- 包裹,包含 name 和 description 字段 |
| 技能正文 | front matter 之后的内容,定义技能的执行规则 |
| 添加新技能 | 在 .agents/skills/ 下新建子目录,放入 SKILL.md 即可 |
新增工具:
| 工具名 | 功能 | 触发条件 |
|---|---|---|
load_skill_content |
加载指定技能的完整指令 | LLM 判断用户请求匹配某技能描述时调用 |
运行效果示例:
LLM Knowledge Client + Skills System
==================================================
Loaded skills: ['notice']
==================================================
You: 帮我写一个关于五一劳动节放假的通知
[调用工具中...]
[执行工具] load_skill_content
[参数] {"skill_name": "notice"}
[技能加载] notice
Assistant: **XX部通知**
为弘扬劳动精神,保障员工合法权益...
特此通知。
You: 我是销售部的,帮我写一个关于五一劳动节放假的通知
[调用工具中...]
[执行工具] load_skill_content
[参数] {"skill_name": "notice"}
[技能加载] notice
Assistant: **销售部通知**
为庆祝"五一"国际劳动节,保障员工合法权益...
特此通知。
核心教学目标:让 Agent 具备自主规划和执行多步骤任务的能力——前一个工具的输出作为后一个工具的输入,LLM 根据中间结果自主决定下一步操作。
链式调用架构:
用户请求: /chain 查找文件并总结
↓
┌─────────────────────────────────────┐
│ ChainedCallContext(上下文管理器) │
│ - 记录每步调用和结果 │
│ - 存储中间变量供后续步骤使用 │
│ - 设置最大迭代次数(默认 10) │
└──────────┬──────────────────────────┘
↓ 循环(最多 max_iterations 次)
┌─────────────────────────────────────┐
│ build_analysis_prompt() │
│ - 用户原始请求 │
│ - 已执行步骤历史 │
│ - 决策规则 + JSON 输出格式 │
└──────────┬──────────────────────────┘
↓
┌─────────────────────────────────────┐
│ LLM 决策 │
│ done=true → 返回最终回答 │
│ done=false → 调用工具,记录结果 │
└─────────────────────────────────────┘
核心类和函数:
| 类/函数 | 功能 |
|---|---|
ChainedCallContext |
链式调用上下文管理器,记录步骤、存储中间变量、限制最大迭代 |
ChainedCallContext.add_step() |
记录一步工具调用或最终回答 |
ChainedCallContext.get_history_text() |
获取已执行步骤的可读文本(供提示词使用) |
ChainedCallContext.is_max_reached() |
检查是否达到最大迭代次数 |
execute_chained_tool_call() |
执行链式调用的完整流程(循环 + LLM 决策 + 工具执行) |
build_analysis_prompt() |
构建分析提示词(含用户请求、步骤历史、决策规则、JSON 格式) |
execute_tool_by_name() |
根据工具名和参数执行对应工具函数 |
_extract_json_from_content() |
从 LLM 响应中提取 JSON(兼容纯 JSON、代码块、前缀文字) |
_parse_tool_calls_response() |
解析 OpenAI tool_calls 格式为统一决策格式 |
search_files_content() |
新增工具:搜索目录下包含关键词的文件及其上下文行 |
LLM 决策 JSON 格式:
| 场景 | 格式 |
|---|---|
| 任务完成 | {"done": true, "answer": "最终回答内容"} |
| 继续调用 | {"done": false, "tool_call": {"name": "工具名称", "arguments": {"参数名": "参数值"}}} |
新增工具:
| 工具名 | 功能 | 参数 |
|---|---|---|
search_files_content |
搜索目录下包含关键词的文件 | directory, keyword |
使用方式:
在交互模式中输入 /chain <请求> 进入链式调用模式:
You: /chain 查找 practice05 目录下所有包含'def'关键词的文件,并总结这些文件的主要内容
You: /chain 读取 D:\ziliao\1.txt 和 D:\ziliao\2.txt,把两个数相加写入 result.txt
You: /chain 访问 https://www.nsu.edu.cn/HTML/news/2024/06/article_3974.html 并总结页面内容,保存到 practice07/summary.txt
链式调用流程示例(文件搜索链):
[链式调用] 迭代 1/10
[决策] 调用工具: search_files_content
[参数] {"directory": "practice05", "keyword": "def"}
[结果] 文件: llm_knowledge_client.py (15处匹配)
[链式调用] 迭代 2/10
[决策] 调用工具: read_file
[参数] {"directory": "practice05", "file_name": "llm_knowledge_client.py"}
[结果] Content of 'llm_knowledge_client.py': ...
[链式调用] 迭代 3/10
[完成] 任务结束
[最终回答] practice05 目录下共找到 1 个文件包含 'def' 关键词...
(共 3 步, 耗时 15.23s)
错误处理与容错机制:
| 错误类型 | 处理方式 |
|---|---|
| LLM 响应为 None | 终止链式调用,返回错误信息 |
| JSON 解析失败(代码块/前缀文字) | _extract_json_from_content() 自动提取 {...} 块 |
| tool_calls 格式响应 | _parse_tool_calls_response() 转换为统一决策格式 |
| 工具执行异常 | try/except 捕获,返回错误信息给 LLM 继续决策 |
| 达到最大迭代次数 | _build_partial_answer() 根据已有步骤生成部分回答 |
| done=false 但无 tool_call | 强制结束,返回"任务中断"提示 |
MIT License
欢迎提交 Issue 和 Pull Request!