从零手写 AI Agent:一个本地知识库助手的完整实现(Python + DeepSeek)
前言
最近 AI Agent 这个概念火得不行,各种框架(LangChain、CrewAI、AutoGen)层出不穷。但我发现很多新手一上来就啃框架源码,结果被各种抽象层绕晕了。
其实 Agent 的核心原理非常简单——就是一个 while 循环。你完全可以从零手写一个,不超过 200 行代码。
这篇文章带你从零实现一个本地知识库 AI Agent,它能自动搜索、阅读、整理你的 Markdown 笔记。全程 Python,调用 DeepSeek API,没有任何框架依赖。
一、什么是 Agent?
1.1 普通 LLM vs Agent
普通 ChatGPT 是一问一答:
你: "1+1等于几?" AI: "2" → 结束
Agent 是多轮思考:
你: "帮我找关于 Python 的笔记"
AI: [思考] 用户想找笔记 → 我先搜一下
→ 调用 search_notes("Python") ← 第一次 API 调用
→ 结果: python-basics.md
AI: [再思考] 搜到了,但用户想要内容 → 读一下
→ 调用 read_note("python-basics.md") ← 第二次 API 调用
→ 结果: 文件内容是...
AI: [最终] 拿到了,可以回答了
→ "你的笔记里讲了 Python 基础,包括..." ← 最终回复
Agent = LLM + 工具 + 循环。就这么简单。
1.2 为什么需要循环?
因为 LLM 不知道工具的执行结果。它只能说"我想用工具 X",然后停下来等你执行。你把结果喂回去,它才能继续思考。
二、项目结构
knowledge-agent/ ├── main.py # CLI 入口 ├── agent.py # ★ 核心:Agent 循环 ├── tools.py # 工具定义 + 执行 ├── config.py # 配置加载 ├── requirements.txt # 仅 2 个依赖 ├── .env # API Key └── knowledge_base/ # 你的笔记目录
技术选型
| 项目 | 选择 | 理由 |
|---|---|---|
| 语言 | Python 3.9+ | 生态最好 |
| LLM API | DeepSeek(deepseek-chat) | 便宜、支持 tool calling、OpenAI 兼容 |
| SDK | openai |
标准 SDK,DeepSeek 完全兼容 |
| 存储 | 本地 Markdown 文件 | 零依赖,即开即用 |
依赖
openai>=1.0.0 python-dotenv>=1.0.0
仅两个依赖,极简。
三、核心代码:Agent 循环
这是整个项目的心脏,agent.py 里的核心逻辑:
"""核心 Agent 循环 — 思考 → 行动 → 观察 → 再思考"""
import json
from openai import OpenAI
from config import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, MODEL, MAX_TOOL_ROUNDS
from tools import TOOL_SCHEMAS, execute_tool
client = OpenAI(api_key=DEEPSEEK_API_KEY, base_url=DEEPSEEK_BASE_URL)
SYSTEM_PROMPT = """你是一个知识库助手 Agent,帮助用户管理和检索笔记。
## 你的能力
- search_notes:搜索包含关键词的笔记
- list_notes:列出知识库中所有笔记
- read_note:读取一篇笔记的完整内容
- write_note:创建或更新一篇 Markdown 笔记
## 工作方式
1. 理解用户需求
2. 先搜索,再阅读 —— 不要猜测文件内容
3. 基于实际内容回答,不要编造
4. 用中文回复"""
def run_agent(user_query: str, on_tool_call=None) -> str:
"""执行一次 Agent 对话。"""
# messages 就是 Agent 的"记忆"
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_query},
]
# ★ Agent 循环
for round_num in range(1, MAX_TOOL_ROUNDS + 1):
# ① 调用 LLM
response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=TOOL_SCHEMAS,
temperature=0.1,
)
choice = response.choices[0]
msg = choice.message
# ② 判断:LLM 说完了还是想调工具?
if choice.finish_reason == "stop":
return msg.content or "(未返回回复)"
elif choice.finish_reason == "tool_calls":
# ③ 把 LLM 的回复加入记忆
messages.append(msg.to_dict())
# ④ 执行每个工具调用
for tc in msg.tool_calls:
tool_name = tc.function.name
tool_input = json.loads(tc.function.arguments)
if on_tool_call:
on_tool_call(tool_name, tool_input)
result = execute_tool(tool_name, tool_input)
# ⑤ 把工具结果加入记忆
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result,
})
# ⑥ 循环继续!LLM 看到结果后会再次思考
return "[WARNING] Agent 达到最大思考轮次。"
流程图
用户输入 │ ▼ ┌─────────────────────┐ │ LLM 思考 │ ← chat.completions.create() │ (带上全部历史消息) │ └──────┬──────────────┘ │ ├─ finish_reason="stop" ──→ 返回回复给用户 │ └─ finish_reason="tool_calls" │ ▼ ┌──────────────┐ │ 执行工具 │ ← execute_tool() └──────┬───────┘ │ ▼ ┌──────────────┐ │ 把结果加入 │ ← messages.append() │ messages │ └──────┬───────┘ │ └──→ 回到开头,继续循环
四、工具定义
工具是 Agent 的"手"。用 JSON Schema 定义,LLM 通过 description 字段理解每个工具该什么时候用。
# tools.py — 以 search_notes 为例
TOOL_SCHEMAS = [
{
"type": "function",
"function": {
"name": "search_notes",
"description": (
"在知识库中搜索包含指定关键词的笔记文件。"
"当用户问「有没有关于XX的笔记」「帮我找XX」时使用此工具。"
),
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词",
},
},
"required": ["query"],
},
},
},
# ... list_notes, read_note, write_note
]
关键点:description 非常非常重要! 它决定了 LLM 能不能在正确的时机调用工具。写得太笼统(如"用来搜索"),LLM 可能不知道该什么时候用。
工具执行是纯 Python 文件操作:
def _search_notes(query: str) -> str:
"""遍历知识库,搜索包含关键词的文件。"""
query_lower = query.lower()
results = []
for root, dirs, files in os.walk(KNOWLEDGE_BASE):
for fname in files:
if not fname.endswith((".md", ".txt")):
continue
content = Path(root, fname).read_text(encoding="utf-8")
if query_lower in content.lower():
# 提取匹配行作为摘要
matching_lines = [
line.strip() for line in content.split("\n")
if query_lower in line.lower()
]
results.append(f"[文件] {fname}\n {matching_lines[:5]}")
return f"找到 {len(results)} 篇:\n" + "\n\n".join(results) if results \
else f"未找到「{query}」相关笔记。"
五、安全设计
5.1 路径逃逸防护
LLM 是不可控的,万一它尝试 ../../etc/passwd 呢?
def _safe_path(relative_path: str) -> Path:
"""确保 LLM 无法访问知识库外的路径。"""
full = (KNOWLEDGE_BASE / relative_path).resolve()
if not str(full).startswith(str(KNOWLEDGE_BASE.resolve())):
raise ValueError(f"不允许访问知识库外的路径: {relative_path}")
return full
5.2 防死循环
MAX_TOOL_ROUNDS = 10,Agent 最多调用 10 轮工具后强制中断。
六、Anthropic vs DeepSeek 对比
我最初用的是 Claude API,后来换成 DeepSeek。Agent 循环逻辑完全没变,只是 API 格式不同:
| 项目 | Anthropic (Claude) | DeepSeek |
|---|---|---|
| SDK | import anthropic |
from openai import OpenAI |
| 调用方法 | client.messages.create() |
client.chat.completions.create() |
| 工具格式 | name + description + input_schema |
type:"function" + function:{...} |
| 结束标志 | stop_reason == "end_turn" |
finish_reason == "stop" |
| 工具调用标志 | stop_reason == "tool_use" |
finish_reason == "tool_calls" |
| 工具参数 | block.input(直接是 dict) |
json.loads(tc.function.arguments) |
| 结果回传 | role:"user" + type:"tool_result" |
role:"tool" |
| System Prompt | 独立参数 system=... |
messages[0], role:"system" |
核心收获
Agent 的灵魂是你写的循环逻辑,不是某个具体的 API。 换一家 LLM 只需改 API 调用格式,循环不动。
七、效果演示
$ python main.py ╔══════════════════════════════════════════╗ ║ 知识库 AI Agent ║ ╠══════════════════════════════════════════╣ ║ 模型: deepseek-chat ║ ║ 知识库: .../knowledge_base ║ ║ 工具: search_notes, list_notes, ... ║ ╚══════════════════════════════════════════╝ > 帮我找关于 Python 的笔记 Thinking... >> Tool: search_notes(query=Python) ← Agent 自动搜索 >> Tool: read_note(file_path=py.md) ← Agent 自动读取 你的知识库中有一篇关于 Python 的笔记,内容摘要如下: - Python 是一门解释型语言 - 语法简洁,适合初学者 - 广泛应用于数据科学、Web 开发、自动化脚本等领域 > 帮我写一篇 Git 常用命令的笔记 Thinking... >> Tool: write_note(file_path=git.md, content=...) ← Agent 自动创建 [OK] 笔记已保存: git-cheatsheet.md(856 字符)
八、如何扩展?
学完基础后,你可以:
-
加工具:网页搜索、天气查询、发邮件——在
tools.py里加定义 + 实现即可 -
加记忆:把对话历史存到 SQLite,下次启动恢复,实现跨会话记忆
-
加流式输出:
stream=True,让 LLM 回复逐字显示 -
换 MCP 协议:把工具标准化为 MCP Server,其他 Agent 也能用
-
做 Web UI:用 Streamlit 或 Gradio 包一层网页界面
-
加上 RAG:引入向量数据库,实现语义搜索
九、总结
AI Agent 开发不需要从啃框架开始。核心就三样东西:
Agent = LLM + 工具 + while 循环
理解了这三样,什么 LangChain/CrewAI/AutoGen,不过是在这个基础上加了更多抽象。
建议的学习路径:
-
先用起来:用 Claude Code、Cursor 等体验 Agent 能做什么
-
手写一个:像本文一样,50-200 行代码写一个最小 Agent
-
加复杂度:多工具、记忆、流式输出
-
回头看框架:这时候你才能真正理解框架的价值
纸上得来终觉浅,绝知此事要躬行。直接动手吧。
完整代码
项目地址:https://github.com/Hao-max1/knowledge-agent
直接把代码拷到本地,改 .env 里的 API Key 就能跑:
git clone https://github.com/Hao-max1/knowledge-agent.git cd knowledge-agent cp .env.example .env # 编辑 .env 填入你的 DeepSeek API Key pip install -r requirements.txt python main.py
如果这篇文章对你有帮助,欢迎点赞、收藏、关注。有问题可以在评论区留言交流。
更多推荐

所有评论(0)