mfanafuthimhlanga/wchats
GitHub: mfanafuthimhlanga/wchats
一个面向中小企业的多租户 AI 客服 agent 平台,通过四步流程和强制评估门禁确保每个 agent 在接触客户前都经过充分的文档支撑、质量评估和红队测试。
Stars: 0 | Forks: 0
# W Chats
**一个用于部署客服和交易型 AI agent 的多租户平台,在它们接触客户之前就具备强大的防御能力。**
大多数“AI agent”产品让你部署一个聊天机器人后就只能听天由命。这个产品拒绝这样做。一个 agent 在接触到客户之前,必须经过其业务自有文档的支撑、由自动化 eval suite 打分,并接受 red team 的攻击——而且所有这些结果都是必须通过的 gate,而不是仪表盘上的小部件。
目标用户是没有技术背景的企业主——比如美发店、维修店、电商店铺——他们需要在自己的网站上有一个支持 agent,但又不想雇开发人员。
有两种方式来驱动它,且无论是哪种方式,步骤都是相同的四步:
- **手动,在 console 中。** 企业主自己走一遍流程:命名 agent,起草它的核心设定,上传文档,运行 eval 和 red team 测试,通过 gate。
- **通过编程,从命令行。** 整个流程是一个在 `X-API-Key` 身份验证背后的 REST + SSE 接口,因此开发者的 coding agent 可以创建租户、流式传输配置进度、导入语料库、触发 eval 或 red team 运行,并轮询部署 gate——所有这些都无需浏览器。
这两条路径上的 gate 完全相同。console 是 API 的一个客户端,并不是具有特权的状态。
## 四个步骤
整个产品就是一个流程。console 围绕它构建,API 也逐步暴露了这个流程——所以下面的顺序既是企业主点击的顺序,同样也是 coding agent 编写脚本的顺序。
| | 步骤 | 发生了什么 |
|---|---|---|
| **1** | **创建** | 命名 agent 并设定其语气。为租户配置一个专属的 Neon Postgres 项目,以编程方式运行 migrations,进度通过 SSE 流式传回。 |
| **2** | **配置** | 起草 agent 的*soul*(语气、需做清单、禁做清单)并加载文档。Ingestion 会解析、分块、enrichment 并对它们进行 embed。 |
| **3** | **测试** | 运行 eval,然后是 red team。两者都会写入真实的数据行;两者都能造成阻塞。 |
| **4** | **部署** | 清除 pre-deployment gate,然后接待客户。一段可直接复制粘贴的 widget 代码片段会放到企业主自己的网站上。 |
在通过 gate 之前,任何内容都不会公开。
## 高级 RAG
检索是大多数系统将其视为 `embed → cosine similarity → top-k` 的部分。这个系统并非如此。
**Ingestion 是感知结构的。** 文档使用 [Docling](https://github.com/DS4SD/docling) 进行解析,因此标题、列表和表格能够作为结构保留下来,而不是被扁平化成纯文本——特别是表格,会通过专门的 table-aware 路径,以保留行/列关系。分块使用 [Chonkie](https://github.com/chonkie-inc/chonkie),因此边界会尊重文档结构,而不是任意的 token 计数。每个 chunk 都会获得一个确定性的 UUID(`document_id` + ordinal hash),因此整个 pipeline 在重试时是幂等的。
**每个 chunk 在被 embed 之前都会经过 enrichment** —— 生成的一段摘要、一个关键词列表,以及该 chunk 能够回答的一组假设性问题。这些 enrichment 本身就是可检索的表层信息。
**检索是真正的混合检索**,融合在一个单一的 SQL CTE 中:
```
query ─┬─► pgvector HNSW (vector_cosine_ops, 1024-dim) ─┐
│ ├─► Reciprocal Rank Fusion ─► rerank ─► top-k
└─► BM25 via native tsvector + ts_rank_cd ─┘
```
BM25 在原生的 Postgres `tsvector` + `ts_rank_cd` 上运行 —— 故意不使用 Neon 已经弃用的 `pg_search`/`pgbm25`。融合后的候选结果会经过 rerank(Voyage,以 Cohere 作为 fallback),并且在混合检索*之前*会查询 `verified_qa` 缓存,以便已知的优质答案能直接使整个路径短路。
**检索策略是按租户配置的**,而不是一个全局常量 —— `k` 值、rerank 阈值、是否开启 query expansion、元数据过滤器。从 M9 开始,这些策略会根据租户自身的语料信号自动合成,而不再需要手动调优。
**完整的检索 trace 是一个一阶响应字段**:哪条路径匹配到了、fusion 得分、rerank 差值。并且生产环境的检索是被埋点监控的 —— recall@k、nDCG@10、MRR、相比 BM25 基线的 reranker 提升、上下文窗口利用率、被引用 chunk 的排名,以及索引陈旧度,所有这些都会记录在一个你可以查询的 `retrieval_metrics` 表中。
## 验证链
四个独立的评判者,各自回答不同的问题。其中三个在响应流式传输给客户后异步运行;第四个则是*同步运行,且在任何变更操作之前*。
| 节点 | 提出的问题 | 裁决 |
|---|---|---|
| **Gatekeeper** | 这是否切中了实际被问到的问题? | `pass · fail · needs_clarification` |
| **Auditor** | 每一个事实性陈述都有检索到的上下文作为支撑吗? | `grounded · ungrounded · partial`(带有引用区间) |
| **Strategist** | 这是否连贯、符合品牌形象、与 agent 的角色定位一致? | `ship · revise · escalate` |
| **Actor** | 这个动作到底是否应该被允许执行? | `approve · block · require_human` |
针对某种检索模式持续出现的 `ungrounded` 裁决,会标记该 agent 以进行策略重构。每一个裁决都经过 Pydantic 验证,并记录到 Langfuse 中进行追踪。
## 托管部署:将 eval 和 red teaming 作为 gate
**Evals。** 一个 scenario-generator agent 会根据租户自身的业务领域构建一套测试 suite。运行结果会根据四个 Ragas 0.4.x 指标进行评分 —— Faithfulness、Answer Relevance、Context Precision、Context Recall —— 并且是在租户数据库的 *Neon branch* 上执行的,永远不会触及生产分支。Celery beat 每晚运行它们。被 Gatekeeper 或 Auditor 标记的生产环境对话会被挖掘并转化为新的 scenario,因此该 suite 会随着真实的失败案例不断增长。
**失败分类的飞轮效应。** 操作员将一条失败的生产环境 trace 标记为 `filed`,它就会被提升到 eval suite 中,标记为 `source='production'` 并保留其原始 trace id。一条已归档的 trace 不能被撤回。eval 账本会将源自生产的 scenario 与手动编写的 scenario 分开汇报。
**Red teaming。** 对抗性 agent 会跨多个类别攻击已部署的 agent,包括 prompt-injection(细分为 conversation-injection 和 content-injection 变体)、data-leakage 和 hallucination 类别,此外还有针对交易的特定探测 —— confused-deputy、value-bound evasion、identity-bypass。发现的结果会进行严重性分类,并作为一阶数据行与 strategy/probe/coverage 汇总信息一起存储。
**这些是必须通过的 gate,而不是报告。** 一个处于活跃状态的关键 red-team 发现会导致 pre-deployment checklist 变为 `recommendation='block'`,随后 `POST /approve-deployment` 将返回 **422**。企业主无法越过它进行部署。
## 交易能力
Agents 不仅仅是回答问题 —— 它们会采取行动。退款、下单、取消、订阅更改、预约。这使得每一个 prompt injection 都可能转化为潜在的财务损失,因此交易路径采用了多层防御:
- **强类型 tool 契约。** 六个 mutating tool 加上 `confirm_action`,全部都是强类型的 Pydantic 函数。没有字符串 blob,没有 SQL,也没有 URL 作为 tool 输入。
- **能力信封。** 基于技能的限制 —— 启用状态、频率限制、最大金额、是否需要确认、是否需要身份验证、Actor 模式 —— 在服务层强制执行 fail-closed(失败即关闭)策略,因此直接的 API 调用会被完全像 UI 那样拒绝。企业主可以收紧这些限制,但绝不能放宽。
- **带有原子性“预留后执行”的幂等性。** 重放某个 key 会返回原始结果,且不会重复执行。
- **Actor gate**,同步执行且发生在变更前,用于捕获对话看起来合法但提议的动作与客户意图不符的“从注入到动作”类攻击。
- **客户身份验证。** 通过 Email/SMS OTP 发放短期的已验证会话,基于技能需要,在服务端强制执行,绝不从 agent 的行文中推断。
- **服务端持有的凭证。** Provider 凭证使用 Fernet 加密,其密钥由基于 HKDF 派生的每租户密钥生成,仅能在 dispatcher 内部通过一个 redacting handle 解析。任何 agent 代码路径都永远无法看到原始凭证。支持的适配器:Stripe、Shopify、WooCommerce、Calendly。
- **全面的审计。** 每一次 mutating 调用都会写入一条 `tool_calls_audit` 数据行 —— 包括被拒绝的调用。
Provider SDK 刻意位于我们自身定义的狭窄 tool 背后,而不是向模型暴露一个 provider MCP server:provider 工具包将绕过能力信封、Actor gate 和审计追踪,并将 provider 的整个 API 表面倾倒进上下文中。
## 架构
```
Owner (Next.js console) Customer (Preact widget, 8 KB gzipped)
│ │
└──────────────┬───────────────────────────┘
▼
┌──────────────────────┐
│ FastAPI control │ never does work inline
│ plane · 18 routers │
└──────┬───────────────┘
│ dispatch ┌──────────────────┐
▼ │ Redis pub/sub │
┌───────────────────┐ publish │ job_events:{id} │──► SSE
│ Celery │──────────────────►└──────────────────┘
│ pipeline queue │ ingest · embed · staleness
│ runtime queue │ agent turns · evals · red team · validators
└─────────┬─────────┘
│
┌───────────────┴────────────────┐
▼ ▼
Control DB (Neon) Per-tenant Neon project
tenants · agents · jobs documents · chunks · embeddings (HNSW)
capability_envelopes conversations · messages · tool_calls
tool_calls_audit eval_runs · red_team_findings
prompt_versions turn_metrics · retrieval_metrics
```
**按租户划分的 Neon 项目,而不是按租户划分的 schema** —— 这是为了确保 eval 能够针对数据库 branch 运行所必需的。连接字符串永远不会作为 Celery task 的参数;task 会接收一个 `tenant_id` 并在 runtime 解密。每个 task 都是 `acks_late=True` *且*幂等的。
## 技术栈
| 层级 | 选择 |
|---|---|
| API | FastAPI · Pydantic · 基于 Redis pub/sub 的 SSE |
| Workers | Celery(两个队列:`pipeline`、`runtime`)· Redis |
| Data | Neon Postgres · pgvector (HNSW) · Alembic (19 个控制 + 12 个租户 migrations) |
| Agents | 面向客户 agent、strategist 和 red teamer 的 Claude Agent SDK;面向 judges 和 gates 的 Claude API 直接调用 |
| Ingestion | Docling (感知布局) · Chonkie (感知结构) |
| Embeddings | Amazon Bedrock Titan v2,Voyage 作为 fallback · Voyage/Cohere rerank |
| Evals | Ragas 0.4.x + 自定义的测试框架 |
| Observability | Langfuse v4 |
| Console | Next.js 16 · React 19 · 手工打造的 design system |
| Widget | Preact,gzip 后 8,087 字节 |
## 状态
分三个里程碑、在 22 个计划阶段内构建完成。约 29k 行 Python 代码,1,103 个通过的单元测试。
| | |
|---|---|
| **M1–M11** | ✅ 控制平面、ingestion、混合检索、推理引擎 + widget、验证链、evals、red team、pre-deployment checklist、检索策略自动合成、observability、管理后台 UI |
| **v1.1 — 交易能力** | ✅ 阶段 14–17(tool 契约、Actor 校验器、集成适配器、身份验证)。⏳ 阶段 18 进度 9/11(blast-radius gate、能力配置 UI、交易 red-team、PII 防火墙)。阶段 19(文档 + E2E 验证)尚未开始。 |
| **v1.2 — Console 与 agent 管理** | ✅ 前端切换与 agent 管理后端 —— 实时指标、RAG 健康度埋点、分类飞轮、带有 canary 发布和回滚功能的 prompt 版本控制 |
| **生产环境托管** | ⏸️ **已暂停。** Terraform、Bedrock embeddings、连接池、S3 上传以及并发安全的 workers 均已编写完成;但要启动它们需要一个真实的 AWS 账户。 |
**坦诚地说明这意味着什么:** 端到端的链路已经在真实环境中验证过 —— 一个基于事实支撑且包含引用的回答已经通过隧道成功提供给了浏览器 —— 但目前还没有处于 always-on 状态的部署,且 v1.2 的 migrations 尚未应用到线上数据库中。这是一个完整且经过测试、正等待基础设施的系统,而不是一个正在运行的服务。
## 本地运行
开发针对的是普通机器上的本地进程。没有 Docker 方式 —— 曾经尝试过,但在最低 6 GB+ 内存消耗时被放弃了。
```
redis-server # broker + SSE pub/sub
uvicorn app.main:app --reload # from apps/api
celery -A app.worker.celery_app worker -Q pipeline # ingestion
celery -A app.worker.celery_app worker -Q runtime # agent turns, evals, red team
pnpm dev # from apps/admin
```
```
cd apps/api && ./.venv/Scripts/python.exe -m pytest tests/unit -q
```
你需要一个 Neon API key、一个 Anthropic API key,以及 Bedrock 访问权限或 Voyage key 之一。详见 `.env.example`。
## 仓库布局
```
apps/api/ FastAPI app, Celery workers, services, both Alembic chains
apps/admin/ Next.js console
apps/widget/ Preact customer widget
.planning/ Phase plans, research, verification and security artifacts
docs/adr/ Architecture decision records
prototypes/ Design prototypes for the console
```
如果你想了解这个项目是如何构建的,值得看一下 `.planning/` 目录:每一个阶段都包含其研究记录、计划、验证报告,以及 —— 在相关的地方 —— 威胁登记册和安全审计。
标签:AI智能体, LLM应用平台, RAG检索增强生成, SaaS多租客, 大模型评估, 搜索引擎查询