工程规范

代码风格

语言与版本

  • 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]

命名规范

元素

规范

示例

模块

snake_case.py

litellm_provider.py

PascalCase

AgentLoop, MessageBus

函数 / 方法

snake_case

build_system_prompt()

私有方法

_snake_case

_handle_message()

常量

UPPER_SNAKE_CASE

BUILTIN_SKILLS_DIR

配置字段(Python)

snake_case

api_key, allow_from

配置字段(JSON)

camelCase

apiKey, allowFrom

Dataclass 字段

snake_case

sender_id, chat_id

测试函数

test_descriptive_name

test_validate_params_missing_required

配置命名

Pydantic 的 Base model 使用了 alias_generator=to_camel,因此:

  • Python 代码中使用 snake_caseconfig.providers.openrouter.api_key

  • JSON 配置中两种写法都接受:"apiKey""api_key"

这种双重命名是有意为之的 — 既保持了 Python 代码中的惯用风格,又兼容了 Claude Desktop / Cursor 等使用 camelCase 的配置格式。

架构模式

抽象基类

核心抽象使用 Python ABC:

  • LLMProvider — 抽象方法 chat()get_default_model()

  • BaseChannel — 抽象方法 start()stop()send()

  • Tool — 抽象属性 namedescriptionparameters,抽象方法 execute()

注册表模式

Provider 系统使用声明式注册表(一个包含 ProviderSpec 条目的 tuple),而非 if-elif 链。添加新 provider 只需添加数据,无需修改控制流。

消息总线(发布/订阅)

Channel 和 agent loop 通过 asyncio.Queue 解耦。这意味着:

  • Channel 不知道 agent 的存在

  • Agent 不知道 channel 的存在

  • 所有协调都通过 InboundMessage / OutboundMessage dataclass 完成

延迟导入

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:使用 DummyProviderSampleTool 等轻量级测试替身,定义在测试文件中

  • 异步测试:使用 @pytest.mark.asyncio 装饰器,用 tmp_path fixture 处理临时文件

  • Fixturetmp_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