解构 Manifest 的核心领域模型:Request、Attempt、Tenant 与 Agent 的边界定义
2026/9/16 18:41:26 网站建设 项目流程

解构 Manifest 的核心领域模型:Request、Attempt、Tenant 与 Agent 的边界定义

【免费下载链接】llm-gatewayConnect Your Agents And Harnesses With Any Provider 🦚项目地址: https://gitcode.com/GitHub_Trending/manifest7/llm-gateway

Manifest 是一个开源 LLM 网关,它的可观测性与路由复杂度源于一个关键设计:调用方眼中的“一次请求”与网关对上游 Provider 的“一次调用”并不是同一个东西。本文以仓库中的 AGENTS.md 为骨架,完整展开它定义的四个核心领域概念——Manifest Request、Provider Attempt、Tenant、Agent——并结合 docs/glossary.md 规范契约与后端实体源码,帮你建立一套与 Manifest 代码库完全对齐的术语体系:读完你可以准确判断任意一行数据库记录、任意一块 Dashboard 指标属于“哪个世界”,以及为什么两者的总量天然不相等。

一、AGENTS.md 的定位:一份方向性的领域术语契约

AGENTS.md 开篇即声明 “Manifest terminology is directional”(Manifest 的术语具有方向性)。这句话是理解整个领域模型的钥匙:Manifest 中所有数据实体都沿着请求的流动方向命名,方向错了,统计口径就会错。

文档给出四条核心定义:

  • Manifest Request:从 Agent 发往 Manifest 的一次逻辑请求,存于requests表;
  • Provider Attempt:Manifest 发往 AI Provider 的一次请求,存于agent_messages表;
  • Tenant:用户的数据边界,在首次创建 Agent 时由user.id派生;
  • Agent:归属于某个 Tenant 的 AI Agent,Dashboard 上将其显示为Harnesses(导航位于/harnesses,旧版/agents/*URL 会被重定向)——这仅是 UI 文案层面的差异,后端代码、数据库表和 API 路由仍然全部使用agent

最后一段划出了本文其余部分的边界:

docs/glossary.mdis the canonical contract for statuses, ordering, recovery, database mapping, and counting rules. Do not duplicate those definitions in agent guides.

即:状态值、排序规则、恢复(Recovery)判定、数据库映射与计数规则以 docs/glossary.md 为唯一权威契约,其余指南不得重复其定义。下文将逐一定义展开,并在需要引用计数规则时直接指向该规范,而不是复制粘贴。

二、Manifest Request:Agent 侧的逻辑请求

方向:agent → Manifest。一次 Manifest Request 代表调用方(Coding Agent、应用等)向网关发出的一个逻辑请求。无论 Manifest 内部尝试了多少个 Provider、触发过多少次 fallback 或 Autofix 重试,调用方只看到一个最终结果。

从源码结构看,packages/backend/src/entities/request.entity.ts 中的ManifestRequest实体(映射到requests表)印证了这一定义的几个关键特征:

  1. 没有 Attempt 的行也是合法行。实体注释明确写道:“Provider work belongs inagent_messages. A row with no attempts is valid: Manifest may reject a request before choosing or contacting a provider.” 也就是说,Manifest 在本地就拒绝了请求(例如配额耗尽、路由策略拦截)时,requests里仍有一条failed记录,而agent_messages中一行都没有。
  2. 调用方可见结果是requests.status的权威字段。该实体同时携带error_messageerror_http_statuserror_codeerror_originerror_class一组错误列,以及duration_ms(注释为 “End-to-end latency experienced by the caller”)与requested_model(“Model requested by the caller, before routing and fallbacks”)——这些都是调用方视角的字段。
  3. autofix_status是请求级的唯一 Autofix 判定,实体注释注明 “NULL means it was not recorded”。根据 docs/glossary.md 的规范,其取值为no_patchresolvingretry_succeededretry_failedservice_error,其中只有retry_succeeded表示“被 Autofix 恢复”。
  4. 归属字段tenant_idagent_idagent_nametrace_idsession_key等构成了 Request 的身份与作用域;api_mode记录调用方走的是哪个 API 面(chat_completions/responses/messages)。

实体上的复合索引(如(tenant_id, agent_id, timestamp)(tenant_id, status, timestamp))也说明了requests表的读模式:几乎所有分析查询都是“按 Tenant 作用域 + 时间/状态过滤”进行的。

三、Provider Attempt:Manifest 侧的路由评估

方向:Manifest → AI Provider(或在本地被 Manifest 拒绝的内部路由)。一个 Request 可以拥有零个或多个 Attempt;每一次“路由评估”都算一个 Attempt,即使它最终没有真正打到上游。

docs/glossary.md 对 Attempt 的定义比 AGENTS.md 更精确:

Every provider call counts as an Attempt, including failed calls, fallback attempts, and Autofix retries. Manifest-local failures such as a route skipped during provider cooldown also count as failed Attempts, but their Manifest error origin keeps them out of provider-reliability metrics.

即:失败的调用、fallback 尝试、Autofix 重试、乃至 Provider 冷却期内被跳过的路由,都会以“失败 Attempt”的形式留痕,保证完整的路由链在数据库里可见;但这类 Manifest 本地失败的error_origin会让它被排除在 Provider 可靠性指标之外。

packages/backend/src/entities/agent-message.entity.ts 中的AgentMessage实体是 Attempt 的物理载体,其字段设计几乎逐条对应规范契约:

字段作用对应规范
request_id指向父 Request(requests.id)。注释注明 “NULL only while the historical backfill is running”Attempt 的父子关系
attempt_number同一 Request 内的路由尝试顺序号,注释强调 “Positive route-attempt order”规范要求 Attempt 顺序只能用attempt_number判断,不得从时间戳推导
status(默认pending规范状态值pending/cancelled/success/failed写入方必须使用规范值
superseded布尔标记:本次 Attempt 失败后 Manifest 继续尝试了别的路由被取代的 Attempt 计入 Attempt 指标,但不决定 Request 结果
fallback_from_model/fallback_indexfallback 上下文快照描述“为什么是这次尝试”,不描述“结果”
autofix_applied/autofix_group_id/autofix_role/autofix_operations/autofix_decisionAutofix 审计链:一个被治愈的请求记录为两行(original失败行 +retry成功行),共享同一个autofix_group_id恢复(Recovery)是 Request 级概念
tenant_provider_id精确标识服务本次 Attempt 的 Tenant 连接(tenant_providers行),注释说明其 NULL 场景:历史数据、本地 Provider(如 Ollama)、盲代理路径“Connection Attempts”只是按该字段过滤的 Attempt,不是独立事件
error_origin/error_class/error_code机器化错误分类:error_origin区分 “谁的错”(provider / transport / config / policy / internal / request),error_class归一化错误类别(rate_limit / auth / timeout / …)实体注释指明分类逻辑由manifest-shared中的classifyMessageError()统一提供
Token 与成本列input_tokensoutput_tokenscache_read_tokenscache_creation_tokenscost_usdAttempt 级用量归属

遗留命名是刻意保留的。物理表名agent_messages并未随概念重命名,docs/glossary.md 的 “Legacy naming and statuses” 一节解释了原因:保留旧表名可以让新旧版本应用在滚动部署(rolling deploy)期间安全地同时读写;AgentMessage/api/v1/messages、前端Message*系列名称同样是遗留代码名,它们不定义分析单元。该规范还给出历史行与新写入行的状态映射:读取方需把ok归一为success,把errorrate_limitedfallback_errorauto_fixed归一为failed;而写入方必须使用pending/cancelled/success/failed这四个规范值。此外,物理 Autofix 列名仍是agent_messages.autofix_phoenix,实体与 API 层则将其暴露为autofix_decision

四、Tenant:由 user.id 派生的数据边界

AGENTS.md 对 Tenant 的定义是:“用户的数据边界,首次创建 Agent 时从user.id派生”。这句话在源码中可以得到精确印证。

packages/backend/src/common/services/tenant-cache.service.ts 的TenantCacheService是全库唯一执行 “user → tenant” 解析的地方(类注释自称 “The ONE place user→tenant resolution happens”)。它的ensureForUser()方法实现了文档所说的“惰性创建”:

  • 先查已有 Tenant,没有则insert({ id, name: userId, owner_user_id: userId, is_active: true })创建——owner_user_id即文档中说的user.id来源;
  • 并发竞态时(两个请求同时触发首次创建),依赖owner_user_id上的唯一索引裁决,后到者复用先到者的行;
  • 缓存策略上,只缓存“确有 Tenant”的正结果,绝不缓存 null——源码注释解释了原因:Tenant 是惰性创建的,若把“还没有 Tenant”缓存整整 5 分钟 TTL,用户创建第一个 Agent 之后的所有租户级接口(尤其是连接 Provider)都会持续 404 长达五分钟。

packages/backend/src/entities/tenant.entity.ts 中的Tenant实体进一步展示了数据边界的形态:owner_user_id被注释标记为“当前唯一的 user→tenant 合法关联”(“the ONLY sanctioned user→tenant link”),且可为空以预留未来“无单一属主用户”的团队场景;limit_overrides字段允许按租户覆盖套餐级请求限额。所有下游逻辑(请求、消息、Provider 连接、成本)都以tenant_id做作用域——这与 docs/glossary.md 中 “dashboard metrics use completed Requests and Attempts within the selected tenant, agent, and time filters” 的统计边界完全一致。

一个容易混淆的点是agent_messages.user_id:实体注释将其标记为DEPRECATED——“informational attribution only, written by the proxy recorder. Never filter, scope, key, or authorize by this column; all scoping goes throughtenant_id.” 也就是说,行级归属一律走tenant_iduser_id只是保留在热表上的只读展示信息。

五、Agent:后端叫 Agent,Dashboard 叫 Harness

AGENTS.md 中关于 Agent 的定义包含一个重要的“文案分层”提醒:Harnesses 只是 UI 用语。后端代码、数据库表(agents)与 API 路由全部使用agent。前端路由表可以验证这一点:packages/frontend/src/index.tsx 中定义了/harnessesWorkspace/harnesses/:agentNameAgentDetail等新路由,并保留了旧版/agents/*/harnesses的重定向逻辑(注释为 “Legacy /agents redirects → /harnesses (keep bookmarks alive)”)。

packages/backend/src/entities/agent.entity.ts 展示了 Agent 作为“Tenant 名下实体”的完整形态:

  • 通过tenant_id外键关联tenantsonDelete: 'CASCADE'),且存在部分唯一索引(tenant_id, name) WHERE deleted_at IS NULL,保证同一 Tenant 下 Agent 名称在未删除状态下唯一;
  • autofix_enabled为三态可空布尔:NULL 表示“未显式选择,继承部署模式默认值”(云端默认开、自托管默认关),由 Autofix 服务的resolveEnabled()解析;
  • record_messages对新创建的 Agent 默认为 true,控制是否录制消息;
  • is_playground标记每个 Tenant 预留的 Playground 虚拟 Agent,它不出现在 Agent 列表与计数中;
  • 1:1 关联AgentApiKey,即每个 Agent 持有自己的 API Key。

从源码结构看,Agent 是 Manifest 中“请求发起方”的建模单元:requests.agent_id/requests.agent_name把每条 Request 归属到具体 Agent,tenant_providers则把 Provider 连接挂在 Tenant 之下——这正好解释了 glossary 中 “Requests belong to agents and to the global Overview; Attempts belong to providers, Provider Connections, and models” 的归属划分。

六、两个世界的指标边界:为什么 Request 数与 Attempt 数不相等

AGENTS.md 把状态、排序、恢复与计数规则全部委托给 docs/glossary.md。作为术语契约的收尾,这里总结“两个世界”划分对实际读数的影响,细节判定顺序请以该规范为准:

  1. 归属划分:Request 属于 Agent 与全局 Overview;Attempt 属于 Provider、Provider Connection 与模型。“Recovered”(被恢复)只可能是 Request 的属性——Provider、连接、模型、Attempt 都不存在“恢复”指标。
  2. 判定顺序:每个 Request 有且仅有一个结果类别,按pendingcancelledfailedRecovered by Autofixstatus = 'success'autofix_status = 'retry_succeeded')→Recovered by fallback(成功且 Last Attempt 带fallback_from_model)→ 普通Success的顺序判定;Autofix 在两者条件冲突时充当裁决者。
  3. 计数口径:Pending 与 Cancelled 的行不进成功率分母;Playground 流量被排除;一个 Request 在 Request 侧只计一次,一个 Attempt 在 Attempt 侧各计一次——两个总量回答的是不同的问题,规范明确“not expected to match”。
  4. 典型场景对照(摘自 docs/glossary.md 的 Examples 表):主 Provider 直接成功 → 1 Request / 1 Attempt;主 Attempt 失败、fallback 成功 → 1 Request / 2 Attempt(状态failed,success,恢复方式 Fallback);Autofix 重试失败后 fallback 成功 → 1 Request / 3 Attempt(failed,failed,success,恢复方式 Fallback,因为autofix_status不是retry_succeeded)。

七、延伸阅读:如何沿源码继续深入

本文涉及的权威文件均位于当前仓库,可按下列路径继续验证:

主题文件
领域术语契约(本文骨架)AGENTS.md
状态/排序/恢复/计数规范契约docs/glossary.md
Request 实体(requests表)packages/backend/src/entities/request.entity.ts
Attempt 实体(agent_messages表)packages/backend/src/entities/agent-message.entity.ts
Tenant 实体与数据边界packages/backend/src/entities/tenant.entity.ts
user→tenant 惰性解析与创建packages/backend/src/common/services/tenant-cache.service.ts
Agent 实体与 Harness 路由重定向packages/backend/src/entities/agent.entity.ts、packages/frontend/src/index.tsx

掌握这套“方向性术语”后,阅读 Manifest 的任何分析服务、迁移脚本或 Dashboard 代码,你都能立刻回答三个问题:这一行数据属于哪个世界(Request 还是 Attempt)?它的权威状态字段在哪张表?它按什么口径被计入指标?这正是 AGENTS.md 作为领域指南想要传递给每一位维护者(包括 AI Agent)的核心能力。

【免费下载链接】llm-gatewayConnect Your Agents And Harnesses With Any Provider 🦚项目地址: https://gitcode.com/GitHub_Trending/manifest7/llm-gateway

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询