Onyx(原 Danswer)仓库 AI 智能体开发指南:环境搭建、测试体系与工程规范全解析
Onyx(曾用名 Danswer)是开源的 Gen-AI 与企业级搜索平台,将企业文档、应用与人连接,并支持连接任意 LLM 的智能对话。本文以仓库根目录的 CLAUDE.md 项目知识库为主线,完整讲解在该仓库中进行 AI 智能体(Agent)开发所需的全部工作流——从 uv 虚拟环境、测试密钥解析、四层测试体系,到 Celery 后台任务、错误处理、代码质量与写作规范,并结合源码逐项实证,帮助开发者快速上手并为仓库做出高质量贡献。
仓库定位与技术栈总览
CLAUDE.md 明确指出:Onyx(前身 Danswer)是一个开源 Gen-AI 与企业搜索平台,采用模块化架构,同时提供 MIT 许可的 Community Edition(社区版,即 backend/onyx/ 核心)与企业版(Enterprise Edition,即 backend/ee/onyx/)两种形态。
其技术栈全景如下:
| 层次 | 技术选型 |
|---|---|
| 后端 | Python 3.13、FastAPI、SQLAlchemy、Alembic、Celery |
| 前端 | Next.js 16、React 19、TypeScript、Tailwind CSS |
| 数据库 | PostgreSQL(关系库)+ Redis(缓存) |
| 搜索 | OpenSearch 支撑的关键词与向量文档索引 |
| 认证 | OAuth2、SAML,多提供商支持 |
| AI/ML | LangChain、LiteLLM、多种 embedding 模型 |
值得注意:虽然旧版本及部分迁移脚本中仍残留 "Vespa" 字样,但根据 backend/AGENTS.md 的说明,OpenSearch 才是当前文档索引后端,Vespa 相关内容仅是兼容性或迁移遗留物(除非活跃的 DocumentIndex 工厂/配置路径显式使用它们)。
开发环境准备:KEY NOTES 逐条详解
CLAUDE.md 的 Key Notes 部分定义了在该仓库工作的第一性原则,下面逐条展开并结合源码给出可执行命令。
1. Python 依赖与 uv 虚拟环境
Python 依赖全部托管在仓库根目录的 uv 管理的虚拟环境 .venv 中。如果 .venv 尚不存在,用以下命令创建并激活:
uv sync --frozen
source .venv/bin/activate
--frozen 表示严格按锁文件(uv.lock)安装,不更新依赖版本。根据 CONTRIBUTING.md 的补充,推荐显式指定 Python 3.13 创建环境:
uv venv .venv --python 3.13
版本前提:Python 3.13 是仓库基准版本;3.14 暂不支持(部分依赖如
onnxruntime与 CUDA 版torch尚未发布 3.14 wheel),其他版本可能需要代码或依赖改动。Windows 下激活命令为.venv\Scripts\activate(PowerShell 用Activate.ps1)。
运行 pytest 时可直接从仓库根目录通过 uv run 执行,无需手动激活环境——uv run 会自动使用锁文件锁定的环境,并在需要时创建/同步 .venv。
2. 测试密钥(Test Secrets)解析链
测试中用到的 API Key 等密钥由 backend/tests/utils/aws_secrets.py 统一解析,解析顺序严格为:
- 进程环境变量(已设置的环境变量优先);
.vscode/.env(被 gitignore,同时被backend/AGENTS.md中的 pytest 命令使用;创建方式见下文);- AWS Secrets Manager(需先执行
aws sso login)。
源码实现印证:aws_secrets.py 中的 _get_local_secrets() 先查 os.environ,再通过 dotenv_values 读取 .vscode/.env(路径由 _DOTENV_PATH 拼接得出);剩余未解析的密钥通过 _get_aws_secrets() 调用 batch_get_secret_value 批量拉取,单批最多 20 个密钥 ID(_AWS_BATCH_GET_MAX_IDS = 20)。
- AWS 区域通过环境变量
AWS_REGION配置,默认us-east-2; - 密钥名按枚举类型推导 AWS 前缀:
TestSecret对应test/,DeploySecret对应deploy/(见 secret_names.py); - 混合不同环境的枚举会在类型检查阶段被拒绝(
get_secrets的重载与运行时校验双保险)。
测试声明机制:测试通过 @pytest.mark.secrets(TestSecret.X) 声明所需密钥。pytest 插件 pytest_secrets.py 在 pytest_collection_modifyitems 阶段收集全部测试声明的密钥并集,然后由 session 级 fixture test_secrets 一次性批量解析;返回的 RedactedDict 重写了 __repr__ 为 <redacted>,防止密钥泄漏到测试输出中。
TestSecret 枚举覆盖范围很广:从 OPENAI_API_KEY、ANTHROPIC_API_KEY、COHERE_API_KEY、AZURE_API_KEY、LITELLM_API_KEY 等 LLM 提供商密钥,到 Confluence、Jira、Google Drive、Slack、Notion、SharePoint、Box 等数十个连接器的测试凭据,均可通过该机制声明。
关键约定:如果需要的密钥始终无法解析,应当询问用户而不是跳过测试——这是仓库对 Agent 的明确要求。
创建 .vscode/.env:复制 .vscode/env_template.txt 为 .vscode/.env 并填写 <REPLACE THIS> 占位值。模板中的核心变量包括:
| 变量 | 说明 | 示例/默认 |
|---|---|---|
AUTH_TYPE |
认证类型 | basic |
USER_AUTH_SECRET |
签名密码重置、邮箱验证、OAuth 登录态与验证码 Cookie;生产必填 | openssl rand -hex 32 生成 |
DEV_MODE |
开发模式 | true |
LOG_ONYX_MODEL_INTERACTIONS |
将模型 prompts、推理与回答打印到 stdout | False |
LOG_LEVEL |
日志级别 | debug |
GEN_AI_API_KEY / OPENAI_API_KEY |
LLM 密钥,避免每次在 UI 手动配置 | 必填 |
GEN_AI_MODEL_VERSION |
默认模型 | gpt-4o |
ENABLE_PAID_ENTERPRISE_EDITION_FEATURES |
企业版开关 | 无付费许可证必须为 False |
S3_ENDPOINT_URL 等 |
MinIO 文件存储配置 | http://localhost:9004、minioadmin |
OPENSEARCH_INITIAL_ADMIN_PASSWORD |
OpenSearch 初始管理员密码 | 本地开发任意值即可 |
3. Playwright 前端探索账号
使用 Playwright 探索前端时,登录凭据固定为:
- 用户名:
admin_user@example.com - 密码:
TestPassword123!
该账号由 Playwright global setup 自动创建(见 constants.ts 中的 TEST_ADMIN_CREDENTIALS)。如果账号尚不存在,可通过注册页注册——第一个注册的用户自动成为 admin。应用访问地址为 http://localhost:3000。
constants.ts 中还定义了 TEST_ADMIN2_CREDENTIALS 与 WORKER_USER_POOL_SIZE = 8:并行 worker 用户池,供多 worker 并发场景使用 workerIndex % WORKER_USER_POOL_SIZE 取模分配(重试会生成递增的 workerIndex,取模可避免越界)。
4. 服务与日志状态确认
开发时默认假设所有 Onyx 服务都在运行。验证方式是检查 backend/log 目录下是否持续产出对应服务的日志——所有 Onyx 服务(api_server、web_server、celery_X)都会把日志 tail 到该目录。
5. 连接 Postgres 数据库
主机 checkout 与 devcontainer 内均可使用:
PGPASSWORD="${POSTGRES_PASSWORD:-password}" psql -h "${POSTGRES_HOST:-localhost}" -U postgres -c "<SQL>"
若本机没有 psql 客户端,回退到 Docker 容器内执行(注意不加 -it,因为 Agent shell 没有 TTY):
docker exec onyx-relational_db-1 psql -U postgres -c "<SQL>"
6. 后端调用必须走前端
调用后端 API 时始终经由前端代理,例如访问 http://localhost:3000/api/persona 而不是 http://localhost:8080/api/persona。这一约定保证了认证 Cookie、CSRF 与 Next.js 代理层的统一处理(前端读取与后端一致的 AUTH_COOKIE_NAME,见 constants.py 中 FASTAPI_USERS_AUTH_COOKIE_NAME 的定义)。
仓库布局与子项目规范
每个子项目都有独立的 agents 规范文件,进入相应目录前必须先阅读:
- backend/AGENTS.md — FastAPI 应用 + Celery workers。
onyx/是社区版核心,ee/镜像其目录结构承载企业功能,alembic/存放数据库迁移,tests/是测试套件。规范覆盖 Celery、迁移、测试、错误处理、LLM 追踪。 - web/AGENTS.md — Next.js 前端,其规范同样覆盖
desktop/(Tauri shell)。 - mobile/AGENTS.md — React Native + Expo 应用。移动端与 Web 差异显著(无 DOM、NativeWind、expo-router),不要想当然套用 Web 规则。
CLAUDE.md 还建议:探索目录树用 ls 而非依赖文档来获取完整包列表,避免文档与代码库脱节。
Celery 后台任务体系(源码级)
从 backend/AGENTS.md 与 celery/apps/ 目录可看到完整的 Celery worker 体系:
| Worker | 职责 |
|---|---|
primary |
协调核心后台任务:连接器管理/删除、文档索引同步、剪枝检查、LLM 模型更新、用户文件同步 |
docfetching |
从连接器拉取文档,派生 docprocessing 任务;对卡死连接器做 watchdog |
docprocessing |
索引流水线:upsert 文档到 Postgres、分块、经 model server 嵌入、写块到文档索引、更新元数据 |
light |
快速轻量操作:元数据同步、权限 upsert、检查点/索引尝试清理 |
heavy |
资源密集型操作:剪枝、文档权限同步、外部组同步、CSV 生成 |
monitoring |
系统健康监控与指标采集 |
user_file_processing |
用户上传文件的索引与项目同步 |
scheduled_tasks |
执行用户定时(Craft)任务 |
beat |
周期任务调度器,使用 DynamicTenantScheduler 支持多租户 |
关键事实(源码确认于 app_base.py):
- 所有 worker 使用线程池而非进程——因此 Celery 的 time limit 功能静默失效,超时逻辑必须在任务内部自行实现;
- 多租户:
DynamicTenantScheduler显式向每个 Beat 任务 kwargs 注入tenant_id;直接发送的任务需自行传播,TenantAwareTask在缺失时会静默回退到默认 schema(POSTGRES_DEFAULT_SCHEMA); - 队列与优先级:任务路由到命名队列并携带 High/Medium/Low 优先级(
OnyxCeleryQueues/OnyxCeleryPriority,定义于 constants.py)。队列划分精细,例如PORT(重索引队列,避免迁移饿死实时索引)、CHAT_TTL_DELETION(聊天 TTL 硬删除队列,避免清理饿死check_for_indexing)等; - 任务定义规则:一律用
@shared_task而非@celery_app;任务放在background/celery/tasks/或ee/background/celery/tasks;发送任务必须带expires=,防止队列无限增长。
代码质量:pre-commit 与严格类型
# 安装并运行 pre-commit 钩子
pre-commit install
pre-commit run --all-files
# 更快的做法:只对你改动的文件运行
pre-commit run --files <path> [<path> ...]
两条全局铁律:
- 一切必须严格类型化(Python 与 TypeScript 皆如此);
- 代码注释保持简短,只写长期有效、面向未来读者的信息。
从 backend/AGENTS.md 还能看到配套约束:禁用 getattr(会躲开类型检查器),需要真正动态查找时必须加 # ods: ignore[getattr] 注释并说明理由(由 ods check-getattr 检查)。前端的格式化为 oxfmt、lint 为 oxlint,均由 bun install 安装,pre-commit 会自动对改动文件运行。
技术写作规范:ASD-STE100 简化技术英语
所有 prose(文档、commit message、PR 描述、报告、回复)统一遵循 ASD-STE100 Simplified Technical English:
- 只用已批准的词汇,每个词只有一个含义;
- 一个概念用一个词表达,不用两个词指代同一事物;
- 短句写作,指令句不超过 20 词;
- 使用主动语态:写 "Turn the switch",而不是 "The switch must be turned";
- 段落短小,每段只讲一个主题;
- 代码注释聚焦长期相关或面向未来读者的信息。
这套规范保证了 AI Agent 与人类协作时沟通信息的一致性,也是该仓库"为 Agent 友好而设计"的体现。
四层测试体系:从单元到端到端
仓库共有 4 类测试,按范围递增:
| 类型 | 位置 | 前置条件 | 运行命令 |
|---|---|---|---|
| 单元测试 Unit | backend/tests/unit |
不假设任何 Onyx/外部服务可用,用 unittest.mock mock 外部交互 |
uv run pytest -xv backend/tests/unit |
| 外部依赖单元测试 External Dependency Unit | backend/tests/external_dependency_unit |
假设 Postgres、Redis、MinIO/S3、OpenSearch 均在运行,OpenAI 可调用、外网可访问;但 Onyx 容器不运行,直接调用被测函数 | uv run --env-file .vscode/.env pytest backend/tests/external_dependency_unit |
| 集成测试 Integration | backend/tests/integration |
完整的真实 Onyx 部署在运行,不可 mock 任何东西 | uv run --env-file .vscode/.env pytest backend/tests/integration |
| Playwright E2E | web/tests/e2e |
全部 Onyx 服务(含 Web Server)运行,TypeScript 编写 | cd web && bun run playwright <TEST_NAME> |
各层要点(依据 backend/tests/README.md):
- 优先级:能写集成测试就优先写集成测试(或需要 mock/内部验证时写外部依赖单元测试),单元测试只留给复杂、隔离的模块(如
citation_processing.py); - 示例:外部依赖单元测试的优秀范例是
backend/tests/external_dependency_unit/connectors/confluence/test_confluence_group_sync.py;集成测试的优秀范例是backend/tests/integration/tests/streaming_endpoints/test_chat_stream.py; - 并行:集成测试按目录级别并行;
- fixture 优先:写集成测试时优先使用 Manager 类工具(
backend/tests/integration/common_utils)与根conftest.py的 fixture(如用admin_userfixture 而非手动UserManager.create(name="admin_user")); - 真实 LLM 调用的模型选择:OpenAI 用
gpt-5-mini(绝不使用gpt-4o/gpt-4o-mini),Anthropic 用claude-haiku-4-5; - 重复运行找 flaky:可用
pytest-repeat,如pytest --count=50 -x backend/tests/unit/path/to/test.py::test_name; - EE 模式:使用
enable_eefixture 而非内联global_version.set_ee();注意conftest.py中的pytestmark不会作用到该目录下的测试,应使用 autouse fixture 包装模式。
Playwright 运行提示:文档明确要求使用 bun run playwright 脚本而非 bunx/npx——后者可能静默拉取未锁定版本的 Playwright。E2E 规格书写规则(Page Object Model、locator 优先级)见 web/tests/e2e/README.md。
日志机制
在(1)编写集成测试或(2)进行活体测试(如 curl / playwright)时,可通过 backend/log/<service_name>_debug.log 获取日志。所有 Onyx 服务(api_server、web_server、celery_X)都会把日志 tail 到这个文件。这为调试与验证提供了统一入口。
安全注意事项
- 绝不向仓库提交 API Key 或密钥;
- 连接器凭据必须使用加密凭据存储;
- 新功能遵循现有 RBAC 模式。
这些原则与 backend/AGENTS.md 的错误处理规范互为补充:业务代码一律抛出 OnyxError(来自 onyx.error_handling.exceptions)而非 HTTPException,由全局 FastAPI 异常处理器转换为标准 JSON 响应 {"error_code": "...", "detail": "..."}。错误码先定义在 error_codes.py,禁止临时发明。上游服务动态状态码场景使用 status_code_override 参数。其底层实现在 exceptions.py:
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
# ✅ 推荐
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Session not found")
# ✅ 无需额外消息
raise OnyxError(OnyxErrorCode.UNAUTHENTICATED)
# ✅ 上游服务动态状态码
raise OnyxError(OnyxErrorCode.BAD_GATEWAY, detail, status_code_override=upstream_status)
# ❌ 禁止:直接使用 HTTPException / starlette / fastapi.status 常量
另外,所有 LLM、embedding、rerank、图像生成、语音(STT/TTS)、意图分类调用都必须打开带 LLMFlow 标签的 generation span(注册表在 flows.py),确保每次 LLM 调用可追踪、可观测。
编写实现计划(Plan)
在 plans 目录(已 gitignore,不存在则创建)编写计划时,至少包含以下要素:
- Issues to Address:这次改动要解决什么问题;
- Important Notes:研究过程中对实现重要的发现;
- Implementation strategy:高层级的实现策略;
- Tests:计划编写哪些单元(少用)、外部依赖单元、集成与 Playwright 测试来验证正确行为。不要过度测试——通常一个改动只需一种类型的测试。
明确禁止在计划中出现的:Timeline(时间线)、Rollback plan(回滚计划)。这是最小清单,可自由补充。计划中不写代码,保持高层级,可以引用特定文件或函数。写计划前必须做研究、探索代码库相关部分。
工程最佳实践:与 CONTRIBUTING.md 的呼应
CLAUDE.md 指出,工程最佳实践详见 CONTRIBUTING.md 的 "Engineering Best Practices" 章节,核心要点包括:
原则与协作
- 1-way vs 2-way doors 决策模型:可逆决策快速迭代,不可逆决策深思熟虑;
- 一致性优先于"正确":保持代码库统一模式,真正糟糕的模式则全局修复;
- 修复你触及的代码(选择性):不引入新坏实践,顺手修复被改动代码暴露的问题;
- 不堆砌功能:新增功能时按需重构,避免接口混乱与技术债累积。
风格与可维护性
- 全仓库严格类型化,用
cast处理松散类型接口; - 偏好 Pydantic 而非 dataclass;偏好显式
None而非哨兵空字符串;用字符串枚举而非整数编码;杜绝 magic number 与 magic string; - 命名:优先长且描述性的函数名(便于代码搜索),同一对象在调用栈中保持一致命名;
- 组合与函数式风格优先于继承/OOP;状态对象应意图明确、显式、尽量不可变;
- 禁止死代码与注释掉的代码;禁止重复逻辑(LLM 常产生细微的重复逻辑,需仔细审查)。
性能与正确性
- 避免长期持有资源(DB session、锁/信号量);
- 连接器代码中任何可无界增长的内存结构必须周期性检查大小(连接器 OOM 常表现为 "missing celery tasks");
- 绝不新增 async/event loop Python 代码,并尽量将现有 async 代码同步化——除非完全理解且确有必要。
仓库约定
- Pydantic + 数据模型放
models.py;DB 接口函数放db/目录;LLM prompts 放prompts/目录;API 路由放server/目录; - SQLAlchemy 注意 lazy loading 在规模下的开销与"session 外访问属性失败"风险;
- Trunk-based development:PR 净改动不超过 500 行、频繁合并 main、用短生命周期 feature flag 增量上线、flag 加在 API/UI 入口层而非业务逻辑深处、同时测试 flag 开/关两种状态;
- 新增 TODO 必须附带负责人姓名或 issue 编号;
- 避免模块级 import 副作用;可执行脚本放
backend/scripts/并包含if __name__ == "__main__":块。
总结:一份可执行的 Agent 上手指南
综合 CLAUDE.md 与其引用的各子项目规范,在 Onyx 仓库中进行 AI Agent 开发的完整工作流可以浓缩为:
uv sync --frozen建立.venv,复制.vscode/env_template.txt为.vscode/.env并填写密钥;- 确认所有 Onyx 服务运行(检查
backend/log的日志输出),必要时用docker exec onyx-relational_db-1 psql连接 Postgres 验证数据; - 调用后端一律经
http://localhost:3000前端代理;用 Playwright 时使用admin_user@example.com账号; - 修改代码遵守严格类型、pre-commit(
ruff/oxfmt/oxlint)、ASD-STE100 写作规范; - 测试按"集成测试优先"原则选择层级,用
@pytest.mark.secrets(TestSecret.X)声明密钥、uv run执行 pytest、bun run playwright执行 E2E; - 后台任务改动需用户重启 Celery worker(无热重载机制),迁移用
uv run alembic upgrade head(多租户加-n schema_private); - 动手前先在
plans/写出包含 Issues/Notes/Strategy/Tests 四要素的简短计划,再进入实现。
掌握上述工作流后,你便可以在 Onyx 的社区版核心、企业版扩展、前端、移动端与测试体系中高效、规范地开展工作。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00