Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ docs/site/
# Never send populated deployment credentials to the Docker build context.
deploy/service.env
deploy/*.env

**/node_modules/
trpcservice/web/dist/
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# syntax=docker/dockerfile:1

FROM node:22-bookworm-slim AS web-build

WORKDIR /src/trpcservice/web

COPY trpcservice/web/package.json trpcservice/web/package-lock.json ./
RUN npm ci

COPY trpcservice/web/ ./
RUN npm run build

FROM golang:1.21-bookworm AS build

ARG TARGETARCH
Expand All @@ -18,6 +28,7 @@ FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /app
COPY --from=build /out/trpc-service /app/trpc-service
COPY --from=build /out/trpc-healthcheck /app/trpc-healthcheck
COPY --from=web-build /src/trpcservice/web/dist /app/web

USER nonroot:nonroot
EXPOSE 8080
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,30 @@ IM / HTTP -> Channel Adapter -> Gateway -> Queue/Outbox -> Agent Worker
<img src="docs/docs/assets/channel-integrations/wecom-aibot-live-integration.gif" alt="企业微信 AI Bot 真实对话收发验证" width="720">
</p>

## 前端多租户渠道验收

下面的步骤验证两个租户分别使用各自 Agent,通过 Telegram 和企业微信 AI Bot 完成真实会话。完整的命令、日志证据和故障排查见[多租户多 Agent Telegram / WeCom 验收链路](docs/docs/multitenant-telegram-acceptance.md)。

### Telegram

1. 使用 `deploy/service.env` 启动服务,并确认 `http://localhost:8080/readyz` 返回 `ready`。
2. 打开 `http://localhost:8080`,以管理员账号登录,选择目标租户并确认 Agent 已发布且为 active。
3. 选择 **Telegram**,填写 BotFather 提供的 Bot ID 和 Bot Token,点击 **Connect channel**。
4. 页面显示 `Your agent is connected` 后,打开 Telegram 向该 Bot 发送唯一 marker,例如 `tenant-a-session-001`。
5. 确认 Bot 回复,并检查服务日志中的 `tenant_id`、`app_id` 与所选租户/Agent 一致。
6. 切换到第二个租户,使用第二个 Telegram Bot 重复步骤 2–5;确认两边的消息和会话不会串用。

### WeCom AI Bot

1. 在 WebUI 选择已配置并发布 Agent 的租户,选择 **WeCom** channel。
2. 填写企业微信 **AI Bot ID** 和同一个 Bot 的 **AI Bot Secret**,点击 **Connect channel**。
3. 不要填写 Corp ID、Agent ID、应用 Secret、回调 Token 或 EncodingAESKey;此入口使用 `wss://openws.work.weixin.qq.com` AI Bot 长连接。
4. 页面显示 `Your agent is connected` 后,打开企业微信客户端找到该 AI Bot,发送唯一 marker,例如 `wecom-tenant-b-session-001`。
5. 确认 Bot 在同一会话回复,并检查日志中的认证状态、`tenant_id`、`app_id` 与所选租户/Agent 一致。
6. 为第二个租户连接它自己的 AI Bot,重复步骤 1–5,确认两个租户的会话和 Agent 行为保持隔离。

服务重启会清除进程内保存的 Telegram/AI Bot Secret;重启后需在前端重新连接对应 Bot,再发送新 marker 验收。

## 快速开始:离线 Golden Path

该路径使用 PostgreSQL、fake model 和固定响应验证从空数据库到第一条对话,不需要 OpenAI、IM 或 Secret Manager 凭据。
Expand Down Expand Up @@ -222,6 +246,7 @@ CI 在 push/PR 时执行格式、静态检查、测试、覆盖率、race 和部
- [部署、配置与快速开始](docs/docs/deployment.md):Compose、Kubernetes、环境变量和 GHCR 镜像
- [首次运行初始化](docs/docs/issue-67-first-run-init.md):`trpc-service init` 与幂等边界
- [Gateway、Execution Plan 与 HTTP/SSE](docs/docs/gateway.md):请求契约、鉴权、限流和流式响应
- [多租户多 Agent Telegram / WeCom 验收链路](docs/docs/multitenant-telegram-acceptance.md):前端配置、双渠道连接、真实会话验证和故障排查
- [PostgreSQL 控制面与启动装配](docs/docs/postgresql-control-plane.md):migration、repository 和 bootstrap
- [原始任务书](docs/docs/project-brief.md):项目最初的背景、要求、交付物和验收标准
- [完整文档站](https://xnlemon.github.io/trpc-agent-service/)
5 changes: 5 additions & 0 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ services:
TRPC_API_TOKEN: ${TRPC_API_TOKEN:-local-api-token}
TRPC_TENANT_ID: ${TRPC_TENANT_ID:-t_01ARZ3NDEKTSV4RRFFQ69G5FAV}
TRPC_APP_ID: ${TRPC_APP_ID:-app_01ARZ3NDEKTSV4RRFFQ69G5FAV}
TRPC_INIT_TENANT_KEY: ${TRPC_INIT_TENANT_KEY:-local}
TRPC_INIT_TENANT_NAME: ${TRPC_INIT_TENANT_NAME:-Local Tenant}
TRPC_INIT_APP_KEY: ${TRPC_INIT_APP_KEY:-assistant}
TRPC_INIT_APP_NAME: ${TRPC_INIT_APP_NAME:-Local Assistant}
TRPC_INIT_APP_DESCRIPTION: ${TRPC_INIT_APP_DESCRIPTION:-Local assistant for frontend channel testing}
TRPC_ADMIN_TOKEN: ${TRPC_ADMIN_TOKEN:-local-admin-token}
TRPC_ADMIN_TENANTS: ${TRPC_ADMIN_TENANTS:-*}
TRPC_ADMIN_USERNAME: ${TRPC_ADMIN_USERNAME:-admin}
Expand Down
2 changes: 1 addition & 1 deletion deploy/kubernetes/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ spec:
type: RuntimeDefault
containers:
- name: service
image: ghcr.io/xnlemon/trpc-agent-service:0.1.0
image: ghcr.io/xnlemon/trpc-agent-service:0.1.1
imagePullPolicy: IfNotPresent
args: ["-addr", "0.0.0.0:8080"]
ports:
Expand Down
2 changes: 1 addition & 1 deletion deploy/kubernetes/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ resources:
# tag with the promoted tag or an image digest before applying the resources.
images:
- name: ghcr.io/xnlemon/trpc-agent-service
newTag: 0.1.0
newTag: 0.1.1
2 changes: 1 addition & 1 deletion docs/docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ TRPC_MODEL_API_KEY

### 2. 固定镜像版本并应用

base 使用当前服务版本 `0.1.0`,而不是可变的 `latest`。每次发布都应在部署清单的
base 使用当前服务版本 `0.1.1`,而不是可变的 `latest`。每次发布都应在部署清单的
`images` 块中更新 release tag,或改用已经由发布系统解析出的 digest;这样 PodTemplate 会
变化,Deployment 才会创建新的 ReplicaSet,并保留可回滚的版本目标。例如:

Expand Down
2 changes: 2 additions & 0 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
限流、幂等和服务生命周期契约。
- [Telegram 长轮询 Adapter](telegram.md):Issue #31 的交付契约,固定单 Binding、Bot
身份校验、普通文本映射、Dispatch 聚合回复和生命周期边界。
- [多租户多 Agent Telegram / WeCom 验收链路](multitenant-telegram-acceptance.md):从 Compose 启动、WebUI 配置、双 Bot 连接到跨租户真实会话和故障定位。
- [企业微信自建应用 Channel Adapter](wecom.md):Issue #60/#98 的契约,固定 callback
验签/AES 解密、可信 Binding 路由、文本/媒体入站和可靠回复边界。
- [Telegram live E2E 示例](https://github.com/XnLemon/trpc-agent-service/tree/main/examples/telegram-e2e):
Expand Down Expand Up @@ -90,6 +91,7 @@ cd trpc-agent-service
- [数据模型](data-model.md) — 核心表结构、Session/Event/Memory/Summary/Audit 和租户约束
- [Channel Binding](channel-binding.md) — 租户级通道绑定、候选发现与可信入站路由
- [Telegram 长轮询 Adapter](telegram.md) — 单 Binding Telegram long polling、文本映射与安全边界
- [多租户多 Agent Telegram / WeCom 验收链路](multitenant-telegram-acceptance.md) — 两租户、两 Agent、双渠道真实对话验收操作单
- [企业微信自建应用 Channel Adapter](wecom.md) — 自建应用 callback、文本/媒体入站与回复 Outbox
- [企业微信 AI Bot 长连接](wecom-aibot.md) — `wecom_aibot` WebSocket、认证、重连与流式回复
- [Gateway、Execution Plan 与 HTTP/SSE](gateway.md) — 可信主体、固定执行计划、Runner Registry、
Expand Down
236 changes: 236 additions & 0 deletions docs/docs/multitenant-telegram-acceptance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# 多租户多 Agent Telegram / WeCom 验收链路

本文是本地 Compose 环境中验证“两个租户、两个 Agent、Telegram 与 WeCom AI Bot、同一模型和 endpoint”完整闭环的操作单。目标是确认每个 Channel Binding 都能把消息路由到所属租户的 Agent,并且会话、审计、回复不会串租户。

## 验收范围

验收通过需要同时满足以下条件:

- PostgreSQL、Redis 和服务容器健康;
- 两个租户均为 `active`,各自拥有一个 `active` Agent App;
- 每个 Agent 都有已发布 Revision、Model Profile 和 Backend Profile;
- 两个 Model Profile 可以使用同一个 provider、model 和 endpoint;
- Telegram 或 WeCom AI Bot Binding 分别属于对应租户,并绑定正确 Bot;
- 从两个渠道发起新会话都能得到回复;
- 同一个用户在两个 Bot 上的会话不会复用另一租户的 Runner session;
- 服务重启后重新连接 Bot,两个租户仍能完成对话。

## 1. 准备环境

复制并编辑本地环境文件。真实密钥只放在本机 `deploy/service.env`,不要提交到 Git:

```bash
cp deploy/example.env deploy/service.env
```

至少确认以下配置:

```ini
TRPC_ADMIN_USERNAME=admin
TRPC_ADMIN_PASSWORD=<本地管理员密码>
TRPC_ADMIN_TOKEN=<独立管理 token>
TRPC_ADMIN_TENANTS=*

TRPC_MODEL_PROVIDER=openai
TRPC_MODEL_API_KEY=<模型 API key>
TRPC_MODEL_NAMES=gpt-5.6-terra
TRPC_MODEL_ENDPOINT_HOSTS=api.adlzw.sbs
```

`TRPC_MODEL_ENDPOINT_HOSTS` 只填写域名,不填写协议、路径或端口。WebUI 中的 endpoint 可以填写完整 URL,例如 `https://api.adlzw.sbs/v1`,但它的 host 必须出现在该环境变量中。

使用环境文件启动服务:

```bash
docker compose --env-file deploy/service.env \
-p trpcfresh -f deploy/docker-compose.yml \
up -d --build service
curl -fsS http://localhost:8080/readyz
```

期望输出为 `ready`。确认容器实际加载的配置,避免 Compose 回退到默认值:

```bash
docker inspect trpcfresh-service-1 \
--format '{{range .Config.Env}}{{println .}}{{end}}' \
| rg '^TRPC_MODEL_(PROVIDER|NAMES|ENDPOINT_HOSTS)='
```

## 2. 在 WebUI 配置租户和 Agent

1. 打开 `http://localhost:8080`,使用管理员账号登录。
2. 创建或选择租户 A,确保状态为 `active`。
3. 注册 Agent A,填写 Agent 名称、指令、模型 `gpt-5.6-terra` 和 endpoint `https://api.adlzw.sbs/v1`。
4. 完成发布并将 Agent A 设为租户 A 的默认 Agent。
5. 创建或选择租户 B,重复上述步骤注册 Agent B,并将 Agent B 设为租户 B 的默认 Agent。
6. 在 Agent 详情中核对 Model、Endpoint、Instructions 和发布状态。

两个租户可以使用同一个 Model Profile 内容。租户隔离由租户 ID、Agent App、Revision、Provider Registry 和运行时计划共同保证,不要求每个租户使用不同模型。

## 3. 连接两个 Telegram Bot

从 BotFather 为两个 Bot 分别取得 Bot Token。Token 只在 WebUI 的连接表单中输入,不写入文档、日志或数据库控制面记录。

对租户 A:

1. 进入租户 A 的 Agent 页面。
2. 选择 Telegram channel。
3. 输入 Bot A Token 并连接。
4. 确认连接状态为 active,并核对 Bot username/账号展示信息。

对租户 B 重复上述步骤,使用 Bot B Token。一个 Binding 只代表一个 Bot 和一个可信 Routing Target;不要把 Bot B 连接到租户 A 的 Binding。

> 服务重启会清除进程内保存的 Telegram Bot secret。每次重启后都必须在 WebUI 重新连接两个 Bot。

## 4. 前端验收企业微信 AI Bot

WebUI 中的 `WeCom` 入口对应企业微信 **AI Bot 长连接**(`wecom_aibot`),服务端会主动连接 `wss://openws.work.weixin.qq.com`。它不是企业微信自建应用回调入口,因此表单中必须使用 AI Bot 管理页面生成的两项凭据:

- **AI Bot ID**:AI Bot 的 `bot_id`/`aibotid`;
- **AI Bot secret**:与该 AI Bot ID 配对的长连接密钥。

不要在这里填写 Corp ID、Agent ID、应用 Secret、回调 Token 或 EncodingAESKey。协议说明见[企业微信 AI Bot 长连接文档](wecom-aibot.md)和[企业微信帮助中心](https://open.work.weixin.qq.com/help2/pc/21663)。

在前端完成一次真实验收:

1. 选择已经配置并发布 Agent 的租户,确认 Agent 状态为 active。
2. 选择 **WeCom** channel。
3. 在 `AI Bot ID` 中填入企业微信 AI Bot 设置页的 ID,在 `AI Bot secret` 中填入同一个 Bot 的 Secret。
4. 点击 **Connect channel**,等待页面显示 `Your agent is connected`。
5. 打开企业微信客户端,找到该 AI Bot,发送一条唯一 marker,例如 `wecom-tenant-b-session-001`。
6. 确认 Bot 在同一会话返回回复,并且回复内容符合当前租户所选 Agent 的指令。

前端连接成功只代表 WebSocket 已完成认证;必须再从企业微信客户端发一条消息,才能验收入站路由、Agent 执行和回复发送。两个租户分别验收时,必须为每个租户连接其对应的 AI Bot Binding。

服务端证据:

```bash
docker logs --since 5m trpcfresh-service-1 2>&1 \
| rg 'AI Bot|wecom-aibot|tenant_id|app_id|authentication|websocket|error_detail'
```

期望看到连接认证成功后的运行日志,以及消息对应的 tenant/app 路由;不应出现 `connection_failed`、`authentication_rejected`、`authentication_timeout` 或 `websocket_dial`。认证失败日志只会记录错误码和非敏感错误消息,不会记录 Secret。

如果页面仍显示 `Connection failed`,先确认容器是用 `deploy/service.env` 启动的,然后按日志中的阶段定位:

| 日志阶段 | 含义 | 首要检查 |
| --- | --- | --- |
| `binding_create` | 控制面 Binding 创建失败 | 租户/Agent 是否 active;运行是否为最新镜像 |
| `target_resolve` | 可信租户路由解析失败 | Binding 的 tenant、app 和 revision 是否匹配 |
| `websocket_dial` | 未建立企业微信 WebSocket | 出站网络、代理和 `wss://openws.work.weixin.qq.com` |
| `authentication_rejected` | 企业微信拒绝凭据 | AI Bot ID 与 Secret 是否来自同一个 Bot |
| `authentication_timeout` | 未收到订阅确认 | AI Bot 长连接能力、网络和服务版本 |

服务重启会清除进程内保存的 AI Bot Secret。重启后需要在 WebUI 为每个租户重新连接,然后再执行一次真实消息验收。

## 5. 跨租户、跨 Agent、跨会话验收

准备两个 Telegram 对话窗口,分别向 Bot A 和 Bot B 发送不同的 marker,例如:

```text
tenant-a-session-001
tenant-b-session-001
```

验收记录至少包含:

| 检查项 | 期望结果 |
| --- | --- |
| Bot A 回复 | 由 Agent A 的指令和行为生成 |
| Bot B 回复 | 由 Agent B 的指令和行为生成 |
| A 的消息到 B | 不会出现在 B 的会话上下文 |
| B 的消息到 A | 不会出现在 A 的会话上下文 |
| 同一 Bot 的连续消息 | 能恢复该 Bot 对应的 session |
| 服务重启后重新连接 | 两个 Bot 均可重新对话 |
| 相同 model/endpoint | 不影响租户和 Agent 路由 |

如果两个 Agent 使用相同指令,给它们设置不同的短标识或首句,便于人工确认实际命中了哪个 Agent。

## 6. 服务端证据

发送消息后立即查看日志,确认 tenant 和 app 成对出现:

```bash
docker logs --since 5m trpcfresh-service-1 2>&1 \
| rg 'telegram|dispatch|tenant_id|app_id|error_detail'
```

重点核对:

- Bot A 的 `tenant_id` 与 Agent A 的 `app_id` 匹配;
- Bot B 的 `tenant_id` 与 Agent B 的 `app_id` 匹配;
- 没有 `dispatch failed`;
- 没有 `audit tenant scope violation`;
- 正常回复会进入 Reply Outbox 并由对应 Binding 发送。

PostgreSQL 中可检查控制面对象:

```bash
docker exec trpcfresh-postgres-1 psql -U trpc -d trpc_agent -c \
"select tenant_id, display_name, status, default_agent_app_id, default_backend_profile_id from tenant order by created_at;"
```

审计检查应按两个租户分别出现执行事件。不要只看全局最新一条记录,要用 `tenant_id`、`agent_app_id` 和 request ID 关联一次完整请求。

## 7. 重启恢复验收

```bash
docker compose --env-file deploy/service.env \
-p trpcfresh -f deploy/docker-compose.yml \
up -d --build service
curl -fsS http://localhost:8080/readyz
```

服务恢复后:

1. 在 WebUI 重新连接 Bot A 和 Bot B;
2. 分别发送新的 marker;
3. 检查两个 Bot 都返回回复;
4. 检查新请求的 tenant/app 路由没有变化。

服务重启不会删除 PostgreSQL 中的租户、Agent、Revision、Model Profile 或 Binding;只会使进程内 Telegram 连接失效。

## 8. 常见故障定位

### 提示“model and endpoint match the server environment”

WebUI 会把多个 `invalid_request` 统一显示成这句话。先确认:

```bash
docker inspect trpcfresh-service-1 \
--format '{{range .Config.Env}}{{println .}}{{end}}' \
| rg '^TRPC_MODEL_(PROVIDER|NAMES|ENDPOINT_HOSTS)='
```

然后确认模型名称完全匹配 `TRPC_MODEL_NAMES`,endpoint 的 host 完全匹配 `TRPC_MODEL_ENDPOINT_HOSTS`。如果实际容器仍显示 `gpt-4o-mini` 或 `api.openai.com`,说明启动时没有使用 `--env-file deploy/service.env`。

### 租户 2 进入正确 tenant/app 但对话失败

查看 dispatch 的 `error_detail`:

```bash
docker logs --since 5m trpcfresh-service-1 2>&1 \
| rg 'dispatch failed|error_detail|tenant_id|app_id'
```

历史上该链路曾因固定租户的审计 writer 报 `audit tenant scope violation`,导致第二租户在执行前失败。当前实现使用按事件 `tenant_id` 动态路由的多租户审计 writer;若再次出现该错误,应先确认运行的是最新构建镜像。

### 第二个 Bot 没有任何反应

- 检查服务是否刚重启;若是,重新连接 Bot;
- 检查 Bot token 对应的 Telegram account 是否与 Binding 的 `provider_account_id` 一致;
- 检查同一 Bot 是否仍被其他进程或 webhook 消费;
- 检查日志中是否有 polling、identity mismatch 或 binding inactive;
- 确认租户、Agent、Revision 和 Binding 都是 active/published 状态。

### 两个 Bot 回复内容串了

核对每条请求的 `tenant_id`、`binding_id`、`app_id` 和 session identity。Telegram 消息不能自行提供 tenant ID;可信租户必须来自 active Channel Binding。若只通过普通 `/v1/chat` API 测试,还要确认 API token 映射的是哪个 tenant,因为普通 API identity 与 Telegram Binding 路由是两条入口。

## 实现边界

- Agent、Model Profile、Backend Profile 和 Revision 在控制面动态创建;不需要为新增租户重启服务。
- Tenant Runtime 按租户懒加载,并以 tenant/app/revision/profile 版本和内容摘要区分计划缓存。
- Telegram Bot secret 当前保存在服务进程内,重启后需要重新连接;生产环境应接入持久化 Secret 管理方案。
- 同一 endpoint 可以服务多个租户,但模型密钥、权限、审计和会话仍按租户边界处理。
- 本文验证的是人工真实 Telegram 对话闭环;协议适配器的 deterministic 单测和独立 live E2E 仍按 [Telegram 文档](telegram.md) 执行。
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ nav:
- "Issue #82 Agent App Registry and Tenant Canary": issue-82-agent-app-registry.md
- Gateway、Execution Plan 与 HTTP/SSE: gateway.md
- Telegram 长轮询 Adapter: telegram.md
- 多租户多 Agent Telegram / WeCom 验收链路: multitenant-telegram-acceptance.md
- 企业微信自建应用 Channel Adapter: wecom.md
- 企业微信 AI Bot 长连接: wecom-aibot.md
- "Issue #77 Channel Capabilities": issue-77-channel-capabilities.md
Expand Down
2 changes: 1 addition & 1 deletion scripts/validate-deployment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ POSTGRES_DB=validation-db \
grep -Fq -- 'postgres://validation-user:validation-pass@postgres:5432/validation-db?sslmode=disable' "$custom_compose_config"

kubectl kustomize deploy/kubernetes >"$kustomize_output"
grep -Fq -- 'image: ghcr.io/xnlemon/trpc-agent-service:0.1.0' "$kustomize_output"
grep -Fq -- 'image: ghcr.io/xnlemon/trpc-agent-service:0.1.1' "$kustomize_output"
grep -Fq -- '0.0.0.0:8080' "$kustomize_output"

echo "deployment manifests and build-context guards validated"
Loading
Loading