fix(portal-auth): 修复 dev mode JWT 验证误激活 + README 文档同步

## Portal 401 根因修复

问题:AGENTKIT_JWT_SECRET 未设置时,jwt_utils 生成 ephemeral 非空 secret,
该 secret 被传给 AuthMiddleware 后 _is_dev_mode() 返回 False(not "" = False),
导致无 JWT/API key 的请求被拒为 401(17 个 portal 测试失败)。

修复:分离 explicit_jwt_secret 与 jwt_secret —
- explicit_jwt_secret = get_jwt_secret()  # None when env unset
- jwt_secret = explicit_jwt_secret or get_or_create_jwt_secret()  # for signing
- AuthMiddleware(jwt_secret=explicit_jwt_secret or "")  # only explicit activates JWT verify

ephemeral secret 仅供 token 签名 routes,不激活 middleware 的 JWT 验证。
生产环境(AGENTKIT_JWT_SECRET 已设置)行为不变。

验证:
- _is_dev_mode(): False → True
- GET /api/v1/portal/conversations: 401 → 200
- 27 个 portal 测试全部通过(之前 17 失败)
- 232 个测试通过 (portal + auth + calendar),0 失败

## README 文档同步

代码中 CostAwareRouter / RegexRules / HeuristicClassifier / SemanticRouter / LLMClassifier
类已完全删除,仅 RequestPreprocessor 存在。README.md 6 处过时引用同步:

- 第 4 节"意图路由"改为引用 RequestPreprocessor(详见第 7 节)
- 第 7 节重写为"请求预处理(RequestPreprocessor)",按 AGENTS.md 架构描述
- 第 8 节"语义路由"删除(合并入第 7 节历史说明)
- 架构图 CostAwareRouter → RequestPreprocessor,22→28 路由模块
- 模块详解 chat/skill_routing + chat/semantic_router 合并为 chat/request_preprocessor
- 模块详解 router/intent 描述更新为"未接入 chat 流程"
- 目录注释 CostAwareRouter → RequestPreprocessor
- 章节重新编号 1-16 连续(原 1-17 跳过 9)
This commit is contained in:
chiguyong 2026-06-28 15:26:42 +08:00
parent c9ce15fa4b
commit d27681a93c
2 changed files with 50 additions and 53 deletions

View File

@ -42,7 +42,7 @@ Skill = SkillConfig + 绑定 Tools。一个 Skill 代表一个可执行技能,
### 4. 意图路由 ### 4. 意图路由
两级路由Level 1 关键词匹配(零成本,~0msLevel 2 LLM 分类(回退方案,~200 tokens。自动将用户输入路由到最佳匹配的 Skill 请求预处理(`RequestPreprocessor`,详见第 7 节)按前缀分流:`@skill:xxx` 显式选技能、琐碎输入走 `DIRECT_CHAT`、其余走 `REACT`。旧的 3 层 `CostAwareRouter`(含 `RegexRules` / `HeuristicClassifier` / `SemanticRouter` / `Vickrey Auction`)已被 `RequestPreprocessor` 替换;`IntentRouter``router/intent.py`)存在但未接入 chat 流程
### 5. 记忆系统 ### 5. 记忆系统
@ -70,28 +70,21 @@ Skill = SkillConfig + 绑定 Tools。一个 Skill 代表一个可执行技能,
- **Prompt 优化** -- 遗传算法 + A/B 测试自动优化 Prompt - **Prompt 优化** -- 遗传算法 + A/B 测试自动优化 Prompt
- **路径优化** -- 分析工具调用路径,推荐更优执行策略 - **路径优化** -- 分析工具调用路径,推荐更优执行策略
### 7. 三层意图路由 ### 7. 请求预处理RequestPreprocessor
CostAwareRouter 三层路由,从零成本到高成本逐层升级 `RequestPreprocessor``chat/request_preprocessor.py`)按前缀分流,零成本路由
| Layer | 方法 | 延迟 | Token 消耗 | 说明 | | Layer | 触发条件 | 延迟 | Token | 路由结果 |
|-------|------|------|-----------|------| |-------|---------|------|-------|---------|
| 0 | 正则规则 | ~0ms | 0 | 问候/简单对话/@team/@skill 前缀直接回复 | | 0 | `@skill:xxx` 前缀 | ~0ms | 0 | 显式技能选择(`SKILL_REACT` 或技能配置的模式) |
| 1 | 启发式分类 | ~0ms | 0 | 关键词 + 模式匹配 + 复杂度评估 | | 1 | 琐碎输入正则(问候/身份/事实/数学/翻译) | ~0ms | 0 | `DIRECT_CHAT`(由 `_TOOL_CONTEXT_RE` 守护) |
| 1.5 | 语义路由 | ~0ms | 0 | 向量相似度匹配(可选) | | 默认 | 其他输入 | ~0ms | 0 | `REACT`LLM 在 agent 循环中自主决定工具使用) |
| 2 | LLM 分类 | ~500ms | ~200 | 回退方案LLM 判断意图 |
路由结果携带 `ExecutionMode` 枚举(`DIRECT_CHAT` / `REACT` / `SKILL_REACT` / `TEAM_COLLAB`),作为路由层与执行层的架构契约,杜绝硬编码 `@board` 前缀走 `BoardRouter`(多轮讨论),`@team` 前缀走 `ExpertTeamRouter`(流水线协作),均在 `RequestPreprocessor` 之前分流
### 8. 语义路由 路由结果携带 `ExecutionMode` 枚举(`DIRECT_CHAT` / `REACT` / `SKILL_REACT` / `REWOO` / `REFLEXION` / `PLAN_EXEC` / `TEAM_COLLAB`),作为路由层与执行层的架构契约,杜绝硬编码。
基于向量相似度的意图路由,作为关键词匹配的补充: ### 8. LLM 响应缓存
- **SemanticRouter** -- 将用户输入和 Skill 描述向量化,通过余弦相似度匹配
- **缓存友好** -- 向量缓存避免重复计算
- **平滑降级** -- 语义路由失败时自动回退到启发式/LLM 分类
### 9. LLM 响应缓存
语义相似度缓存,减少重复 LLM 调用: 语义相似度缓存,减少重复 LLM 调用:
@ -99,7 +92,7 @@ CostAwareRouter 三层路由,从零成本到高成本逐层升级:
- **语义匹配** -- 相似 prompt 命中缓存,避免重复调用 - **语义匹配** -- 相似 prompt 命中缓存,避免重复调用
- **TTL 管理** -- 缓存条目自动过期,支持手动失效 - **TTL 管理** -- 缓存条目自动过期,支持手动失效
### 10. 级联检测与状态持久化 ### 9. 级联检测与状态持久化
生产级故障防护: 生产级故障防护:
@ -108,15 +101,15 @@ CostAwareRouter 三层路由,从零成本到高成本逐层升级:
- **session_ttl** -- 可配置的会话 TTL自动清理过期状态 - **session_ttl** -- 可配置的会话 TTL自动清理过期状态
- **优雅降级** -- Redis 不可用时自动降级到 InMemory保持服务可用 - **优雅降级** -- Redis 不可用时自动降级到 InMemory保持服务可用
### 11. 产出质量管理 ### 10. 产出质量管理
四维质量检查必填字段、最低字数、JSON Schema 校验、自定义验证器。检查不通过时自动重试(可配置 max_retries重试时携带质量反馈信息。 四维质量检查必填字段、最低字数、JSON Schema 校验、自定义验证器。检查不通过时自动重试(可配置 max_retries重试时携带质量反馈信息。
### 12. 标准化输出 ### 11. 标准化输出
Schema 验证 + 字段类型归一化str -> int/float/bool+ 元数据附加version、produced_at、quality_score。所有 Skill 产出统一为 StandardOutput 格式。 Schema 验证 + 字段类型归一化str -> int/float/bool+ 元数据附加version、produced_at、quality_score。所有 Skill 产出统一为 StandardOutput 格式。
### 13. 内置工具集 ### 12. 内置工具集
开箱即用的工具插件,覆盖常见 Agent 需求: 开箱即用的工具插件,覆盖常见 Agent 需求:
@ -136,7 +129,7 @@ Schema 验证 + 字段类型归一化str -> int/float/bool+ 元数据附
工具组合:`SequentialChain`(顺序链)、`ParallelFanOut`(并行扇出)、`DynamicSelector`(动态选择)。 工具组合:`SequentialChain`(顺序链)、`ParallelFanOut`(并行扇出)、`DynamicSelector`(动态选择)。
### 14. Pipeline 编排 ### 13. Pipeline 编排
多 Agent 协同编排,支持复杂工作流: 多 Agent 协同编排,支持复杂工作流:
@ -146,7 +139,7 @@ Schema 验证 + 字段类型归一化str -> int/float/bool+ 元数据附
- **PipelineReflector** -- 执行反思与重规划 - **PipelineReflector** -- 执行反思与重规划
- **HandoffManager** -- Agent 间任务移交 - **HandoffManager** -- Agent 间任务移交
### 15. Expert Team Mode ### 14. Expert Team Mode
多专家协作执行复杂任务B+C 混合模式(结构化协作计划 + 去中心化执行),前端以多角色对话流呈现: 多专家协作执行复杂任务B+C 混合模式(结构化协作计划 + 去中心化执行),前端以多角色对话流呈现:
@ -217,7 +210,7 @@ result = await orchestrator.execute_plan(plan)
用户也可在聊天中通过 `@team:researcher,writer,reviewer 任务描述` 前缀触发团队模式。 用户也可在聊天中通过 `@team:researcher,writer,reviewer 任务描述` 前缀触发团队模式。
### 16. 企业级客户端-服务端架构 ### 15. 企业级客户端-服务端架构
将 AgentKit 从纯本地运行架构演进为企业级客户端+服务端架构。客户端Tauri 桌面端)作为 AI 工作台本地执行 Agent/终端/文件操作,服务端作为企业平台提供 LLM 网关(统一 Key 管理)、用户权限、审计日志、知识库共享能力。 将 AgentKit 从纯本地运行架构演进为企业级客户端+服务端架构。客户端Tauri 桌面端)作为 AI 工作台本地执行 Agent/终端/文件操作,服务端作为企业平台提供 LLM 网关(统一 Key 管理)、用户权限、审计日志、知识库共享能力。
@ -298,7 +291,7 @@ provider = RemoteLLMProvider(
response = await provider.chat(request) response = await provider.chat(request)
``` ```
### 17. 文档处理能力 ### 16. 文档处理能力
Agent 内置文档生成与读取能力Agent 通过 `DocumentTool` 自主创建 Word/Excel/PDF 文档、填充 Word 模板、读取多格式文档,无需用户手动操作 Office 软件。 Agent 内置文档生成与读取能力Agent 通过 `DocumentTool` 自主创建 Word/Excel/PDF 文档、填充 Word 模板、读取多格式文档,无需用户手动操作 Office 软件。
@ -402,15 +395,16 @@ result = await tool.execute(
│ portal.py · chat.py · evolution.py · workflows.py │ │ portal.py · chat.py · evolution.py · workflows.py │
│ auth.py · terminal_server.py · terminal_whitelist.py │ │ auth.py · terminal_server.py · terminal_whitelist.py │
│ llm_gateway.py · config_sync.py · system.py · ... │ │ llm_gateway.py · config_sync.py · system.py · ... │
│ 22个路由模块 · Agent Pool · Expert Team · Memory Store │ │ 28个路由模块 · Agent Pool · Expert Team · Memory Store │
└──────────────────────────┼───────────────────────────────────┘ └──────────────────────────┼───────────────────────────────────┘
┌──────────────┼──────────────┐ ┌──────────────┼──────────────┐
│ CostAwareRouter │ │ RequestPreprocessor │
│ Layer 0: 正则规则 (0ms) │ @board → BoardRouter │
│ Layer 1: 启发式分类 (0ms) │ @team → ExpertTeamRouter │
│ Layer 1.5: 语义路由 (可选) │ │ Layer 0: @skill:xxx 前缀 │
│ Layer 2: LLM分类 (~500ms) │ │ Layer 1: 琐碎输入正则 (0ms) │
│ 默认: REACT (LLM 自主决策) │
│ → ExecutionMode 枚举契约 │ │ → ExecutionMode 枚举契约 │
└──────┬───────────────┬───────┘ └──────┬───────────────┬───────┘
│ │ │ │
@ -1220,26 +1214,19 @@ ReActEngine 实现 Think -> Act -> Observe 循环:
危险工具确认流:非白名单命令触发 `needs_confirmation`,用户确认后以 `_skip_dangerous_check=True` 重新执行,避免无限循环。 危险工具确认流:非白名单命令触发 `needs_confirmation`,用户确认后以 `_skip_dangerous_check=True` 重新执行,避免无限循环。
### chat/skill_routing -- CostAwareRouter 三层路由 ### chat/request_preprocessor -- 请求预处理
三层路由从零成本到高成本逐层升级 按前缀分流的零成本路由
| Layer | 组件 | 延迟 | Token | | Layer | 触发条件 | 延迟 | Token | 路由结果 |
|-------|------|------|-------| |-------|---------|------|-------|---------|
| 0 | `RegexRules` | ~0ms | 0 | | 0 | `@skill:xxx` 前缀 | ~0ms | 0 | 显式技能选择(`SKILL_REACT` 或技能配置的模式) |
| 1 | `HeuristicClassifier` | ~0ms | 0 | | 1 | 琐碎输入正则 | ~0ms | 0 | `DIRECT_CHAT`(由 `_TOOL_CONTEXT_RE` 守护) |
| 1.5 | `SemanticRouter` | ~0ms | 0 | | 默认 | 其他输入 | ~0ms | 0 | `REACT`LLM 自主决策) |
| 2 | `LLMClassifier` | ~500ms | ~200 |
路由结果包含 `ExecutionMode` 枚举(`DIRECT_CHAT` / `REACT` / `SKILL_REACT` / `TEAM_COLLAB`),作为路由层与执行层的架构契约。`complexity` 评分使用 `if is not None` 判断,避免 `0.0 or default` 误覆盖。`@team:expert1,expert2` 前缀直接路由到 `TEAM_COLLAB` 模式。 路由结果包含 `ExecutionMode` 枚举(`DIRECT_CHAT` / `REACT` / `SKILL_REACT` / `REWOO` / `REFLEXION` / `PLAN_EXEC` / `TEAM_COLLAB`),作为路由层与执行层的架构契约。`@team:expert1,expert2` 前缀直接路由到 `TEAM_COLLAB` 模式(由 `ExpertTeamRouter` 处理),`@board` 前缀走 `BoardRouter`(多轮讨论)
### chat/semantic_router -- 语义路由 旧的 3 层 `CostAwareRouter`(含 `RegexRules` / `HeuristicClassifier` / `SemanticRouter` / `Vickrey Auction`)已被 `RequestPreprocessor` 替换;`IntentRouter``router/intent.py`)存在但未接入 chat 流程;`AuctionHouse`Vickrey 拍卖)位于 `marketplace/auction.py`(属于 marketplace 子系统,非路由)。
基于向量相似度的意图路由,作为关键词匹配的补充:
- **SemanticRouter** -- 将用户输入和 Skill 描述向量化,通过余弦相似度匹配
- **缓存友好** -- 向量缓存避免重复计算
- **平滑降级** -- 语义路由失败时自动回退到启发式/LLM 分类
### llm/gateway -- LLM Gateway ### llm/gateway -- LLM Gateway
@ -1280,9 +1267,9 @@ SkillRegistry 管理 Skill 的注册、发现、更新。
团队生命周期FORMING -> PLANNING -> EXECUTING -> SYNTHESIZING -> COMPLETED。失败时自动回退到单 Agent 模式lead 或首个活跃专家)。 团队生命周期FORMING -> PLANNING -> EXECUTING -> SYNTHESIZING -> COMPLETED。失败时自动回退到单 Agent 模式lead 或首个活跃专家)。
### router/intent -- 意图路由(已升级为 chat/skill_routing ### router/intent -- 意图路由(未接入 chat 流程
原两级路由已升级为 CostAwareRouter 三层路由(详见 chat/skill_routing 模块详解) `IntentRouter` 存在但未接入 chat 流程;当前 chat 请求由 `RequestPreprocessor`(详见 `chat/request_preprocessor` 模块详解)处理。本模块保留供未来扩展
### quality/gate -- 产出质量管理 ### quality/gate -- 产出质量管理
@ -1613,7 +1600,7 @@ async def generate_content(keyword: str, brand: str) -> dict:
fischer-agentkit/ fischer-agentkit/
├── src/agentkit/ # Python 后端 ├── src/agentkit/ # Python 后端
│ ├── bus/ # 消息总线MemoryBus + RedisBus │ ├── bus/ # 消息总线MemoryBus + RedisBus
│ ├── chat/ # 聊天路由(CostAwareRouter + ExecutionMode │ ├── chat/ # 聊天路由(RequestPreprocessor + ExecutionMode
│ ├── cli/ # CLI 命令Typer │ ├── cli/ # CLI 命令Typer
│ ├── client/ # 客户端 SDKConfigSync + RemoteLLMProvider 集成) │ ├── client/ # 客户端 SDKConfigSync + RemoteLLMProvider 集成)
│ ├── core/ # 核心引擎ReAct/Reflexion/ReWOO/ConfigDriven + HandoffTransport │ ├── core/ # 核心引擎ReAct/Reflexion/ReWOO/ConfigDriven + HandoffTransport

View File

@ -800,7 +800,17 @@ def create_app(
# that login (signing), whoami (verifying), and the middleware all use # that login (signing), whoami (verifying), and the middleware all use
# the same secret. Without this, get_or_create_jwt_secret() would mint # the same secret. Without this, get_or_create_jwt_secret() would mint
# a different random secret on every call and tokens could never verify. # a different random secret on every call and tokens could never verify.
jwt_secret = get_jwt_secret() or get_or_create_jwt_secret() #
# ponytail: ephemeral secret is for token-signing only — passing it to
# AuthMiddleware would activate JWT verification in dev mode, breaking
# _is_dev_mode() and rejecting unauthenticated requests with 401 (root
# cause of test_portal_routes 17 failures). Only explicit AGENTKIT_JWT_SECRET
# activates middleware JWT verification; ephemeral stays on app.state for
# signing routes (auth.py). Ceiling: dev tokens signed with ephemeral
# secret still verify if a client somehow obtains them, but ephemeral
# secret is non-persistent and invalidated on restart by design.
explicit_jwt_secret = get_jwt_secret() # None when AGENTKIT_JWT_SECRET unset
jwt_secret = explicit_jwt_secret or get_or_create_jwt_secret() # for signing
client_keys: dict[str, str] = {} client_keys: dict[str, str] = {}
try: try:
from agentkit.server.middleware import _load_client_keys from agentkit.server.middleware import _load_client_keys
@ -811,7 +821,7 @@ def create_app(
app.add_middleware( app.add_middleware(
AuthMiddleware, AuthMiddleware,
jwt_secret=jwt_secret or "", jwt_secret=explicit_jwt_secret or "", # only explicit secret activates JWT verify
api_key=effective_api_key, api_key=effective_api_key,
client_keys=client_keys, client_keys=client_keys,
) )