工程规范
代码风格
语言与版本
Python ≥ 3.11 — 使用
X | Y联合类型语法、match语句、StrEnum等特性。行宽限制:100 字符(
pyproject.toml: [tool.ruff] line-length = 100)目标版本:py311
代码检查与格式化
nanobot 使用 ruff 进行代码检查,启用了以下规则集:
Code |
Rules |
|---|---|
E |
pycodestyle errors |
F |
Pyflakes |
I |
isort(import 排序) |
N |
pep8-naming |
W |
pycodestyle warnings |
已忽略:E501(行宽限制)— 100 字符的限制只是建议,不强制执行。
运行代码检查:
ruff check nanobot/
Import 组织
Import 遵循以下顺序(由 ruff 的 I 规则强制执行):
# 1. Future annotations(使用时始终放在最前面)
from __future__ import annotations
# 2. 标准库
import asyncio
import json
from pathlib import Path
from typing import Any, TYPE_CHECKING
# 3. 第三方库
from loguru import logger
from pydantic import BaseModel
# 4. 本地 import
from nanobot.bus.events import InboundMessage
from nanobot.agent.tools.base import Tool
TYPE_CHECKING 守卫:仅用于类型提示的重量级 import 应放在 if TYPE_CHECKING: 块中,以避免循环导入并加快模块加载速度。
类型注解
所有公开函数签名必须有类型注解
使用
str | None语法(而非Optional[str])使用
list[str](而非List[str])使用
dict[str, Any](而非Dict[str, Any])
命名规范
元素 |
规范 |
示例 |
|---|---|---|
模块 |
|
|
类 |
|
|
函数 / 方法 |
|
|
私有方法 |
|
|
常量 |
|
|
配置字段(Python) |
|
|
配置字段(JSON) |
|
|
Dataclass 字段 |
|
|
测试函数 |
|
|
配置命名
Pydantic 的 Base model 使用了 alias_generator=to_camel,因此:
Python 代码中使用
snake_case:config.providers.openrouter.api_keyJSON 配置中两种写法都接受:
"apiKey"或"api_key"
这种双重命名是有意为之的 — 既保持了 Python 代码中的惯用风格,又兼容了 Claude Desktop / Cursor 等使用 camelCase 的配置格式。
架构模式
抽象基类
核心抽象使用 Python ABC:
LLMProvider— 抽象方法chat()和get_default_model()BaseChannel— 抽象方法start()、stop()、send()Tool— 抽象属性name、description、parameters,抽象方法execute()
注册表模式
Provider 系统使用声明式注册表(一个包含 ProviderSpec 条目的 tuple),而非 if-elif 链。添加新 provider 只需添加数据,无需修改控制流。
消息总线(发布/订阅)
Channel 和 agent loop 通过 asyncio.Queue 解耦。这意味着:
Channel 不知道 agent 的存在
Agent 不知道 channel 的存在
所有协调都通过
InboundMessage/OutboundMessagedataclass 完成
延迟导入
Channel SDK 在 ChannelManager._init_channels() 内部延迟导入 — 只有当该 channel 被启用时才会加载。这样可以避免为用户未配置的 channel 加载重量级 SDK(telegram、discord、slack 等)。
仅追加会话
会话消息永远不会被修改或删除。Consolidation 会推进 last_consolidated 指针,但旧消息保持不变。这种设计:
保持了 LLM prompt cache 的效率(前缀不会变化)
简化了持久化(追加写入 JSONL)
避免了 consolidation 失败导致的数据丢失
错误处理
工具执行
ToolRegistry.execute() 方法将所有工具调用包裹在 try/except 中:
校验错误 → 返回错误字符串,附带
[Analyze the error above and try a different approach.]提示执行错误 → 返回错误字符串,附带同样的提示
LLM 会收到错误字符串作为工具结果,并可以自行纠正
Provider 错误
Provider 异常(API 错误、速率限制、超时)的处理方式:
通过
loguru记录日志向上传播到 agent loop
以错误消息的形式通知用户
Channel 错误
Channel 连接失败的处理方式:
记录为 warning 级别日志
将该 channel 标记为不可用
其他 channel 继续独立运行
日志
日志库:loguru(带颜色的结构化日志)
默认输出:stderr
日志格式:时间戳、级别、模块、消息
CLI 参数:
--logs在交互模式下同时显示日志和聊天输出最佳实践:关键事件用
logger.info(),可恢复的问题用logger.warning(),意外故障用logger.exception()
测试
框架
pytest + pytest-asyncio(mode:
auto)测试位于
tests/目录测试文件:
test_*.py
模式
单元测试:测试单个函数/方法,不依赖外部服务
Mock:使用
DummyProvider、SampleTool等轻量级测试替身,定义在测试文件中异步测试:使用
@pytest.mark.asyncio装饰器,用tmp_pathfixture 处理临时文件Fixture:
tmp_path(pytest 内置)用于隔离的文件系统测试
运行测试
# 运行所有测试
pytest -s tests/
# 运行指定测试文件
pytest -s tests/test_tool_validation.py
# 详细输出
pytest -v tests/
# 运行匹配特定模式的测试
pytest -k "test_heartbeat" tests/
Git 与贡献
分支策略
欢迎向 main 分支提交 PR
代码库刻意保持精简和可读
Commit 风格
使用描述性的 commit 消息
不强制要求特定的 commit 消息格式
代码规模预算
项目将核心 agent 代码的规模目标维持在 约 4,000 行。脚本 core_agent_lines.sh 用于验证这一点。
依赖管理
添加依赖
添加到
pyproject.toml的[project.dependencies]下锁定主版本范围:
"typer>=0.20.0,<1.0.0"可选依赖放在
[project.optional-dependencies]中
当前依赖分组
分组 |
用途 |
示例包 |
|---|---|---|
Core |
始终安装 |
typer, litellm, pydantic, httpx, loguru, rich |
Channels |
聊天平台 SDK |
python-telegram-bot, slack-sdk, qq-botpy |
Matrix |
可选的 Matrix 支持 |
matrix-nio, mistune, nh3 |
Dev |
开发工具 |
pytest, pytest-asyncio, ruff |
Skill 规范
目录结构
每个 skill 位于一个包含 SKILL.md 文件的目录中:
skills/
└── my-skill/
└── SKILL.md
Frontmatter
Skill 使用 YAML frontmatter 存放元数据:
---
name: weather
description: Query weather information for any location
metadata: '{"nanobot": {"requires": {"bins": ["curl"]}, "always": false}}'
---
优先级
工作区 skill(~/.nanobot/workspace/skills/)在名称冲突时会覆盖内置 skill(nanobot/skills/)。
相关文档
最后更新:2026-03-15 版本:1.0