一个基于 OpenAI 兼容 API 的智能对话 Agent,支持原生 Function Calling 工具调用循环、多轮会话记忆(含自动压缩)、RAG 个人知识库、可加载的技能指令包,以及全链路 Trace 追踪。
- 工具调用 Agent — 在 LLM 与工具之间循环推理,直到产出最终回答(基于原生 function calling,非 ReAct 文本解析)
- 多轮会话记忆 — SQLite 持久化,后台线程异步压缩旧对话为摘要,提取长期事实,按会话隔离
- RAG 知识库 — 混合检索(向量 + FTS 全文),支持导入
.md/.txt/.pdf/.docx,LLM 可通过kb_search工具自主检索 - 技能系统 — 可加载的指令包(Skill),扫描仓库与用户目录,按需注入 system prompt,内置
code-review技能 - 上下文预算管理 — CJK 感知的 token 计数,对系统消息、工具结果、调用轨迹分别预算截断
- Trace 追踪 — OTel 语义兼容的轻量追踪,LLM 请求/响应、工具调用全量落盘为 JSONL
- 事件流中间件 — 异步生成器中间件链,可插入日志、追踪等观察组件
- Python >= 3.11(需要 SQLite >= 3.45,用于知识库 FTS 与 sqlite-vec)
# 创建虚拟环境
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux/macOS
source .venv/bin/activate
# 安装依赖
pip install -r requirements.txt复制 .env.example 为 .env,填入你的 API 配置:
cp .env.example .envOPENAI_API_KEY=your_api_key_here
OPENAI_BASE_URL=https://api.openai.com/v1
MODEL_NAME=gpt-4o-mini支持任何 OpenAI 兼容的 API 服务(如智谱、DeepSeek 等),只需修改
OPENAI_BASE_URL和MODEL_NAME。
# 交互式会话(默认)
python main.py
# 指定会话 ID
python main.py --session alice
# 单次执行问题
python main.py --once "2+3*4等于多少"
# 列出所有会话
python main.py --list-sessions
# 批量导入文档到知识库
python main.py --kb-ingest docs/
# DEBUG 级别日志 + 写入文件
python main.py --verbose --log-file app.log| 命令 | 说明 |
|---|---|
/sessions |
列出所有会话 |
/switch ID |
切换会话 |
/clear |
清空当前会话 |
/facts |
显示长期事实 |
/summary |
显示历史摘要 |
/skills |
列出所有技能 |
/skill <名称> |
查看技能指令正文 |
/kb [查询] |
检索知识库 |
/kb-stats |
知识库文档/切片统计 |
chatbot/
├── core/ # Agent 核心
│ ├── agent.py # 事件流 Agent(工具调用循环)
│ ├── events.py # 事件定义(RunStarted/LLMChunk 等)
│ ├── middleware.py # 异步生成器中间件机制
│ ├── state.py # 运行状态
│ ├── checkpoint.py # HITL 检查点
│ ├── structured.py # 结构化输出
│ └── subagent.py # 子代理
├── llm_client.py # LLM 客户端:超时 + 指数退避重试
├── main.py # CLI 入口:交互式 / 单次 / 知识库导入
├── prompts.py # 系统提示词模板
├── tools/ # 工具模块
│ ├── base.py # Tool / ToolResult / @tool 装饰器
│ ├── registry.py # 工具注册表
│ ├── executor.py # 工具执行器
│ ├── calculator.py # 内置工具:计算器
│ └── datetime_tool.py # 内置工具:日期时间查询
├── memory/ # 记忆模块(SQLite 持久化)
│ ├── manager.py # 门面:存储 + 异步压缩
│ ├── compressor.py # 后台压缩线程
│ ├── session_store.py # 会话存储
│ ├── turn_store.py # 对话轮次存储
│ ├── summary_store.py # 摘要存储
│ ├── fact_store.py # 长期事实存储
│ └── llm.py # 压缩用 LLM 接口
├── context/ # 上下文管理
│ ├── manager.py # 预算分配 + 截断 + 多消息组装
│ ├── provider.py # Provider 接口(记忆/技能/知识库)
│ ├── fragment.py # 上下文片段
│ └── token_counter.py # CJK 感知 token 计数
├── knowledge/ # RAG 知识库
│ ├── ingest/ # 文档导入管道(加载/切分/嵌入)
│ ├── retriever.py # 混合检索(向量 + FTS)
│ ├── embedder.py # 嵌入模型封装
│ ├── search_tool.py # kb_search 工具
│ ├── db.py # 知识库表结构
│ └── schema.sql # SQL schema
├── skills/ # 技能模块
│ ├── manager.py # 技能管理器(扫描/加载)
│ ├── model.py # Skill 数据模型
│ ├── provider.py # 技能索引注入 Provider
│ ├── load_tool.py # load_skill 工具
│ └── code-review/ # 内置技能:代码审查
├── trace/ # Trace 追踪
│ ├── model.py # Span / SpanStatus 模型
│ ├── recorder.py # 记录器
│ └── exporter.py # JSONL 导出器
├── logger/ # 日志与事件流中间件
│ ├── setup.py # 日志配置
│ ├── logging_middleware.py # 日志中间件
│ └── trace_middleware.py # Trace 中间件
└── tests/ # 测试套件
| 变量 | 默认值 | 说明 |
|---|---|---|
OPENAI_API_KEY |
(必填) | API 密钥 |
OPENAI_BASE_URL |
https://api.openai.com/v1 |
API 地址 |
MODEL_NAME |
gpt-4o-mini |
模型名称 |
LLM_TIMEOUT |
60 |
请求超时(秒) |
LLM_MAX_ATTEMPTS |
3 |
最大尝试次数(含首次) |
CHATBOT_DB_PATH |
data/chatbot.db |
SQLite 数据库路径 |
CHATBOT_SKILLS_DIR |
skills |
仓库技能目录 |
CHATBOT_USER_SKILLS_DIR |
~/.chatbot/skills |
用户技能目录 |
CHATBOT_KB_ENABLED |
true |
是否启用知识库 |
EMBEDDING_MODEL |
text-embedding-3-small |
嵌入模型 |
EMBEDDING_DIM |
1536 |
嵌入维度 |
EMBEDDING_BASE_URL |
同 OPENAI_BASE_URL |
嵌入 API 地址 |
EMBEDDING_API_KEY |
同 OPENAI_API_KEY |
嵌入 API 密钥 |
CHATBOT_TRACE_DIR |
(空,关闭) | Trace 落盘目录 |
CHATBOT_TRACE_MAX_BYTES |
52428800 (50MB) |
Trace 文件轮转阈值 |
Agent 在 LLM 与工具之间循环:每轮将 system prompt + 记忆 + 当前问题 + 累积的工具调用轨迹发送给 LLM。若 LLM 返回 tool_calls,则逐个执行工具并将结果回传;若 LLM 返回纯文本,即为最终回答。最大迭代次数防止死循环。
- 每轮对话同步写入 SQLite(
turns表),保证不丢失 - 后台 daemon 线程串行执行压缩:将最旧的 N 条对话通过 LLM 压缩为摘要,提取长期事实
- 压缩异步化,用户无需等待 LLM 压缩调用即可看到回答
ContextManager 统一管理系统消息的组装与 token 预算:
- total_tokens — 基础消息预算(system + 历史 + 问题)
- observation_max_tokens — 单条工具结果截断上限
- trace_max_tokens — 工具调用轨迹截断上限(按消息组整组裁剪)
技能(Skill)是可加载的指令包,由 SKILL.md(frontmatter + 指令正文)定义:
- 启动时扫描仓库
skills/与用户~/.chatbot/skills/目录 - 技能索引注入 system prompt,LLM 可通过
load_skill工具按需加载完整指令 - 内置
code-review技能示例
- 导入:
IngestPipeline加载文档 → 切分 → 嵌入 → 存储(支持.md/.txt/.pdf/.docx) - 检索:
HybridRetriever结合向量相似度与 FTS 全文检索 - 工具化:封装为
kb_search工具,LLM 可在对话中自主检索知识库
# 运行全部测试
.venv\Scripts\python -m pytest
# 运行指定模块测试
.venv\Scripts\python -m pytest tests/test_memory/
# 显示详细输出
.venv\Scripts\python -m pytest -v测试覆盖所有核心模块:Agent、工具、记忆、上下文、知识库、技能、追踪、日志。
from pydantic import BaseModel, Field
from tools.base import ToolResult, tool
class MyArgs(BaseModel):
param: str = Field(description="参数说明")
@tool(MyArgs)
def my_tool(args: MyArgs) -> ToolResult:
"""工具描述(LLM 可见)"""
return ToolResult(ok=True, data="结果")然后在 tools/init.py 的 build_default_registry() 中注册:
registry.register(my_tool)在 skills/ 目录下创建子目录,添加 SKILL.md:
---
name: my-skill
description: 技能简要说明
---
## 角色
你是一名...
## 流程
1. ...
2. ...