feat(harness): freeze aci tool contracts
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Archive-ready after implementation and focused verification on 2026-07-21.
|
||||
@@ -0,0 +1 @@
|
||||
Committed after strict validation on 2026-07-21.
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-07-21
|
||||
@@ -0,0 +1,89 @@
|
||||
## Context
|
||||
|
||||
当前 `LookupKnowledgeTool` 返回 `LookupResult`,包含 `ContextPack`、`RetrievalTrace`、`RerankTrace` 等内部检索信息;`QueryLogsTools` 暴露 region、TopicId、limit、Topic discovery 和旧 `success/total/logs` 结构。MySQL evidence Tool 尚不存在。阶段 0 已创建共享 `InvocationStatus` 与 `EvidenceStatus`,但尚无三类 Agent-facing DTO 或稳定 Tool 描述。
|
||||
|
||||
本地依赖验证显示,Spring AI 1.1.7 的 `AssistantMessage.ToolCall` 持有 `id`,Spring AI Alibaba 1.1.2.0 的 `ToolCallRequest` 将其暴露为 `getToolCallId()` 并允许通过 `ToolInterceptor` 包装调用;普通 Spring AI `ToolContext` 只包含调用方传入的 context 和 history,不自动提供当前 Tool Call ID。因此后续 Harness 必须以 Alibaba interceptor request 为 ID 接入边界。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 用 Java 类型冻结 RAG、日志和 MySQL 的最小 Agent Request/Result JSON Schema。
|
||||
- 复用统一证据状态,并保持 invocation lifecycle 与 evidence result 两套语义正交。
|
||||
- 保证所有可引用结果只携带框架 Tool Call ID,不生成替代 ID。
|
||||
- 冻结简短、面向行动的 Tool 名称和描述,禁止基础设施/审计实现泄漏。
|
||||
- 通过三个独立契约测试锁定字段、集合不可变性、描述边界和 Mock 来源标识。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不修改旧 `@Tool` 方法、当前 Chat/AIOps 工具注册或返回行为。
|
||||
- 不实现 `ToolInterceptor`、Pre/Post Tool、Redis invocation store 或 ToolResultProjector。
|
||||
- 不实现真实 CLS/MCP、MySQL 连接、JSqlParser 校验、allowlist 或脱敏。
|
||||
- 不删除旧 DTO、Topic discovery 或审计服务;这些在后续切片接入和清理。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 契约放在独立 Harness Tool 包
|
||||
|
||||
新增类型放在 `com.superbiz.agent.harness.tool.contract`,共享状态继续引用 `com.superbiz.agent.harness.contract`。这使新 Harness 边界与旧 `dto`、旧工具内部模型明确隔离。
|
||||
|
||||
替代方案是在旧工具类内新增嵌套 DTO;这会继续把运行实现与 Agent Contract 绑定,并妨碍三个 projector 复用,故不采用。
|
||||
|
||||
### 2. Tool Call ID 是结果字段,不是 Agent 输入字段
|
||||
|
||||
三类 Request 均不包含 `tool_call_id`。后续 Harness 从 `ToolCallRequest.getToolCallId()` 取得 ID,在投影 Result 时写入;模型不能选择或覆盖该值。`EVIDENCE_FOUND` 和 `NO_EVIDENCE` 结果必须具备合法 ID;如果 Pre-Tool 失败原因就是 ID 缺失/非法,`ERROR` 可以没有可引用 ID。
|
||||
|
||||
替代方案是让模型把 ID 作为参数回传,或由 Harness 生成 UUID;两者都会形成不可信输入或第二套标识,违反阶段 0 决策。
|
||||
|
||||
### 3. Result 直接携带 evidence status,不携带 invocation status
|
||||
|
||||
`RagToolResult`、`QueryLogsToolResult` 和 `MysqlToolResult` 只包含 `EvidenceStatus`。`InvocationStatus` 继续描述 Redis canonical invocation 的 `PROJECTING/READY/ERROR` 生命周期,由阶段 3A 的存储模型承载。契约测试分别锁定两个枚举,防止把 `READY` 当成“找到证据”或把 `NO_EVIDENCE` 当成生命周期状态。
|
||||
|
||||
### 4. 使用 record 和 defensive copy
|
||||
|
||||
Request/Result 使用 Java 17 record,并用 Jackson `@JsonProperty` 固定 snake_case。所有 list 和 row map 在构造时 defensive copy,避免后续投影、存储或测试在对象创建后改变 Agent-visible 结果。
|
||||
|
||||
替代方案是沿用 Lombok mutable bean;它更贴近旧代码,但无法天然表达冻结后的值对象边界。
|
||||
|
||||
### 5. 三类最小 Schema
|
||||
|
||||
- RAG Request 只有 `query`;Result 只有查询回显、有界 evidence、计数和截断标记。
|
||||
- Logs Request 只有逻辑 `topic`、`query` 和可选 `lookback_minutes`;Result 保留 `source_kind`、完整 scope、聚合 patterns、少量 timeline events、计数和截断标记。
|
||||
- MySQL Request 只有逻辑 `data_source`、参数化 `sql` 和 `params`;Result 保留 columns、结构化 rows、计数和截断标记。
|
||||
|
||||
日志逻辑 Topic 首版冻结为 `APPLICATION`、`DATABASE_SLOW_QUERY`、`SYSTEM_EVENTS`。旧 `system-metrics` 不纳入日志 Contract;指标 Tool 不属于本 Issue。`SourceKind` 首版只有 `MOCK`,真实适配器以后在保持字段语义的前提下扩展。
|
||||
|
||||
### 6. Tool 描述作为常量契约
|
||||
|
||||
使用单一 `AgentToolContracts` 定义三个 snake_case Tool 名称和 ACI 描述。测试禁止描述中出现 Milvus、L0/L1、rerank、CLS region/TopicId、连接、凭据、topK、limit、Redis、Trace 等实现词。旧 `@Tool` 注解暂不引用这些常量,以免本阶段提前改变模型可观察行为。
|
||||
|
||||
## Module Flow
|
||||
|
||||
```text
|
||||
ReactAgent tool call
|
||||
-> [later] Alibaba ToolInterceptor obtains framework tool_call_id
|
||||
-> typed Request contract
|
||||
-> [later] data adapter + canonical persistence + ToolResultProjector
|
||||
-> typed bounded Result contract
|
||||
-> Agent observation
|
||||
```
|
||||
|
||||
阶段 1 只实现图中的 typed contracts 和描述常量。旧 `ChatService/AiOpsService -> LookupKnowledgeTool/QueryLogsTools` 调用链保持不变。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [新旧 Contract 短期并存,容易误判迁移已完成] -> 契约放独立包,proposal/spec/tasks 明确禁止本阶段改旧工具,并用 `rg` 验证旧注册路径未切换。
|
||||
- [DTO 不能自行保证 ID 来自框架] -> spec 明确 provenance,阶段 2/3 在 `ToolInterceptor` 测试真实 `ToolCallRequest.getToolCallId()` 传播。
|
||||
- [record 只做结构冻结,不做业务校验] -> 参数范围、SQL allowlist、ID 格式和状态组合由后续 Pre-Tool/Projector validator 实现;本阶段不在构造器复制安全策略。
|
||||
- [逻辑 Topic 与旧 TopicId 需要映射] -> mapping 属于日志 adapter/projector 阶段,Agent Contract 不接受 region 或物理 TopicId。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 本阶段新增契约和测试,不切换消费者;回滚只需删除新增类型。
|
||||
2. 阶段 2/3A 从 Alibaba `ToolInterceptor` 接入框架 ID 和 invocation lifecycle。
|
||||
3. 阶段 3B/3C 分别让 RAG/日志/MySQL adapter 输出本契约。
|
||||
4. 阶段 6B 原子切换公开 Chat;旧 Contract 最终由阶段 7 清理。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。真实日志 adapter 的 `SourceKind` 扩展和值映射在后续接入 change 决策,不阻塞本阶段。
|
||||
@@ -0,0 +1,43 @@
|
||||
## Why
|
||||
|
||||
现有 `lookup_knowledge` 和 `query_logs` 将检索实现、基础设施参数、审计数据和不一致的成功语义暴露给 Agent;新增 MySQL Tool 也缺少可复用的 Agent-facing 类型。进入 Harness 和投影实现前,需要先把三类 evidence Tool 的最小输入、稳定有界输出、状态语义与框架 Tool Call 引用冻结为代码契约,避免后续阶段继续依赖字符串和旧 DTO。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增 RAG、日志和只读 MySQL 三类 Agent-facing Request/Result 契约,JSON 字段严格使用 ISS-014 已确认的 snake_case Schema。
|
||||
- 三类结果统一复用 `EvidenceStatus`,并携带框架提供的 `tool_call_id`;契约代码不生成、替换或推导第二套调用 ID。
|
||||
- 冻结 `InvocationStatus=PROJECTING/READY/ERROR` 与 `EvidenceStatus=EVIDENCE_FOUND/NO_EVIDENCE/ERROR` 的独立语义,并通过测试禁止混用。
|
||||
- 冻结三类 Tool 的短名称与 ACI 描述,描述只说明用途、输入和禁用场景,不泄露 L0/L1、Milvus、CLS region/TopicId、连接、凭据、topK、limit、rerank 或审计实现。
|
||||
- 冻结日志 `source_kind=MOCK` 及逻辑 Topic 边界,为后续真实适配器保留同一 Contract;本阶段不新增 CLS/MCP 适配器。
|
||||
- 添加三类独立契约测试,覆盖序列化字段、不可变集合、状态与描述边界。
|
||||
- 本阶段不修改旧 Tool 的执行签名、返回值或 Chat/AIOps 注册路径,不实现 ResultProjector、Harness invocation store、MySQL SQL 校验或数据库访问。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `aci-evidence-tool-contracts`: 定义 RAG、日志和 MySQL evidence Tool 的 Agent-facing ACI Schema、状态语义、框架调用引用和描述边界。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- None. 当前公开运行链路仍使用旧 Tool Contract;新契约将在后续 Harness/Tool 投影和 SSE 切换 change 中接入。
|
||||
|
||||
## Context Constraints
|
||||
|
||||
- `tool_call_id` 的真理源是 Spring AI Alibaba `ToolCallRequest.getToolCallId()`;普通 Spring AI `ToolContext` 不保证包含该 ID。
|
||||
- `NO_EVIDENCE` 只表示当前查询范围没有匹配证据,只能支持 `NEGATIVE_OBSERVATION`,不能表达工具失败或系统健康。
|
||||
- 生命周期 `status` 属于 canonical invocation,`evidence_status` 属于 Agent-facing 查询结果,两者不得互相替代。
|
||||
- Agent 不可控制日志 region/TopicId/limit、RAG topK/filter 或 MySQL 连接与资源上限。
|
||||
- Mock 日志必须显式保留 `source_kind=MOCK`,不得被后续 Harness 表述为生产实时事实。
|
||||
|
||||
## Interface Impact
|
||||
|
||||
- 等级:L2(内部接口,前置冻结)。本 change 新增未来 Harness 内部使用的 Agent-facing DTO 和描述常量,所有消费者均在 ISS-014 后续实施范围内。
|
||||
- 当前运行中的旧 Tool 方法、Controller、ChatService 和 AiOpsService 不切换,因此本阶段没有对外可观察行为变化。
|
||||
- 阶段 6B 切换公开 `/api/chat` 时属于独立的 L4 破坏性变更,必须使用该阶段自己的迁移和回滚规格。
|
||||
|
||||
## Risks
|
||||
|
||||
- 仅有 DTO 不能证明框架 ID 已贯穿执行;阶段 2/3 必须在 Alibaba `ToolInterceptor` 边界接收并校验 `ToolCallRequest.getToolCallId()`。
|
||||
- 新旧 Contract 会短期并存;旧运行工具不得被误认为已符合新 ACI 输出,真正接入留给 RAG/日志投影和 MySQL Tool 阶段。
|
||||
- Provider 侧旧凭据轮换仍是外部安全前置,不因本阶段契约完成而视为关闭。
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Evidence result semantics SHALL be distinct from invocation lifecycle
|
||||
The system SHALL expose `EVIDENCE_FOUND`, `NO_EVIDENCE`, and `ERROR` as Agent-facing evidence result semantics while retaining `PROJECTING`, `READY`, and `ERROR` only for canonical invocation lifecycle. `NO_EVIDENCE` SHALL mean that the executed query found no evidence within its recorded scope and SHALL NOT mean tool failure or system health.
|
||||
|
||||
#### Scenario: Successful empty query result
|
||||
- **WHEN** an evidence Tool completes successfully with no matching evidence in its recorded scope
|
||||
- **THEN** its Agent-facing result uses `evidence_status=NO_EVIDENCE` and the invocation lifecycle may independently reach `status=READY`
|
||||
|
||||
#### Scenario: Tool execution fails
|
||||
- **WHEN** schema, authorization, execution, or projection fails
|
||||
- **THEN** the Agent-facing result uses `evidence_status=ERROR` and SHALL NOT report `NO_EVIDENCE`
|
||||
|
||||
### Requirement: Referencable Tool results SHALL use the framework Tool Call ID
|
||||
Each `EVIDENCE_FOUND` or `NO_EVIDENCE` result SHALL contain the non-blank `tool_call_id` supplied by the framework Tool Call request. Agent inputs SHALL NOT contain `tool_call_id`, and contract code SHALL NOT generate, replace, or derive a second call ID.
|
||||
|
||||
#### Scenario: Framework requests a Tool call
|
||||
- **WHEN** Spring AI Alibaba exposes an `AssistantMessage.ToolCall.id` through `ToolCallRequest.getToolCallId()`
|
||||
- **THEN** the bounded Agent result carries exactly that ID as `tool_call_id`
|
||||
|
||||
#### Scenario: Framework ID is invalid
|
||||
- **WHEN** the Tool request has a missing or invalid framework Tool Call ID
|
||||
- **THEN** the call returns an `ERROR` result without inventing a referencable ID
|
||||
|
||||
### Requirement: RAG Tool contract SHALL expose only bounded document evidence
|
||||
The RAG Request SHALL contain only `query`. The RAG Result SHALL contain `evidence_status`, `tool_call_id`, `query`, bounded `evidence`, `returned_count`, and `truncated`; each evidence item SHALL contain only `document_id`, `source`, `title`, `breadcrumb`, and an exact `excerpt`.
|
||||
|
||||
#### Scenario: RAG evidence is serialized
|
||||
- **WHEN** a RAG result contains a matching document excerpt
|
||||
- **THEN** its JSON matches the frozen snake_case fields and excludes ContextPack, RetrievalTrace, RerankTrace, raw scores, fallback attempts, metadata, and full document bodies
|
||||
|
||||
#### Scenario: RAG query has no evidence
|
||||
- **WHEN** RAG executes successfully without a usable document excerpt
|
||||
- **THEN** it returns `NO_EVIDENCE`, preserves the original query and framework Tool Call ID, and returns an empty evidence list
|
||||
|
||||
### Requirement: Log Tool contract SHALL use logical scope and retain Mock provenance
|
||||
The log Request SHALL contain logical `topic`, `query`, and optional `lookback_minutes` only. The log Result SHALL contain `evidence_status`, `tool_call_id`, `source_kind`, complete query `scope`, `match_count`, `returned_count`, bounded `patterns`, bounded timeline `events`, and `truncated`. The initial logical topics SHALL be `APPLICATION`, `DATABASE_SLOW_QUERY`, and `SYSTEM_EVENTS`, and the initial source kind SHALL be `MOCK`.
|
||||
|
||||
#### Scenario: Mock log evidence is serialized
|
||||
- **WHEN** the existing Mock source returns matching application events
|
||||
- **THEN** the Agent result records `source_kind=MOCK`, the logical query scope, aggregate patterns, bounded timeline events, and distinct match and returned counts
|
||||
|
||||
#### Scenario: Agent creates a log request
|
||||
- **WHEN** the Agent requests log evidence
|
||||
- **THEN** it selects a logical topic and lookback window without supplying region, physical TopicId, credentials, or result limit and without calling a Topic discovery Tool first
|
||||
|
||||
### Requirement: MySQL Tool contract SHALL expose a logical read-only query interface
|
||||
The MySQL Request SHALL contain only logical `data_source`, parameterized `sql`, and `params`. The MySQL Result SHALL contain `evidence_status`, `tool_call_id`, `columns`, bounded structured `rows`, `returned_count`, and `truncated`; it SHALL NOT expose connection details, credentials, internal stack traces, or resource-limit controls.
|
||||
|
||||
#### Scenario: MySQL evidence is serialized
|
||||
- **WHEN** a future read-only adapter returns authorized rows
|
||||
- **THEN** the Agent result preserves column order, structured row values, the framework Tool Call ID, returned count, and truncation state using the frozen JSON fields
|
||||
|
||||
#### Scenario: Agent creates a MySQL request
|
||||
- **WHEN** the Agent requests business database evidence
|
||||
- **THEN** it supplies a logical data source, SQL placeholders, and parameter values without supplying JDBC connection information or security policy
|
||||
|
||||
### Requirement: Tool descriptions SHALL be concise and implementation-neutral
|
||||
The system SHALL define stable snake_case names and concise descriptions for `lookup_knowledge`, `query_logs`, and `query_mysql`. Each description SHALL state when to call the Tool, its minimal input, and what it cannot query, and SHALL NOT describe retrieval internals, infrastructure coordinates, credentials, audit storage, retries, result limits, or ranking implementation.
|
||||
|
||||
#### Scenario: Agent receives Tool definitions
|
||||
- **WHEN** a future Agent adapter registers the frozen Tool definitions
|
||||
- **THEN** the definitions describe available actions and boundaries without exposing Milvus, L0/L1, rerank, CLS region/TopicId, Redis, JDBC credentials, topK, limit, or Trace internals
|
||||
|
||||
### Requirement: Contract freeze SHALL NOT cut over the current runtime
|
||||
This change SHALL add contract types, descriptions, and tests without changing the current `LookupKnowledgeTool`, `QueryLogsTools`, ChatService, AiOpsService, Controller, Tool registration, or Agent-visible runtime results.
|
||||
|
||||
#### Scenario: Stage 1 tests pass
|
||||
- **WHEN** all ACI contract tests pass
|
||||
- **THEN** the current public Chat and AIOps paths still execute the old Tool implementations until their later projector and cutover changes
|
||||
@@ -0,0 +1,26 @@
|
||||
## 1. Shared ACI Contract
|
||||
|
||||
- [x] 1.1 Add stable snake_case Tool names and concise implementation-neutral descriptions for RAG, logs, and MySQL.
|
||||
- [x] 1.2 Reuse and lock the independent invocation lifecycle and evidence result enums without adding a second Tool Call ID type.
|
||||
- [x] 1.3 Add a shared defensive-copy helper for immutable Agent-facing list and row values.
|
||||
|
||||
## 2. RAG Contract
|
||||
|
||||
- [x] 2.1 Implement the RAG Request, Result, and document evidence records with the frozen snake_case fields.
|
||||
- [x] 2.2 Add an independent RAG contract test covering exact JSON fields, framework ID preservation, immutability, and description leakage.
|
||||
|
||||
## 3. Log Contract
|
||||
|
||||
- [x] 3.1 Implement logical log Topic, source kind, Request, Result, Scope, Pattern, and Event records.
|
||||
- [x] 3.2 Add an independent log contract test covering Mock provenance, scope/count semantics, exact JSON fields, immutability, and excluded infrastructure inputs.
|
||||
|
||||
## 4. MySQL Contract
|
||||
|
||||
- [x] 4.1 Implement the logical MySQL Request and bounded Result records with immutable params, columns, and rows.
|
||||
- [x] 4.2 Add an independent MySQL contract test covering parameterized input, structured output, exact JSON fields, deep immutability, and secret/connection exclusion.
|
||||
|
||||
## 5. Verification
|
||||
|
||||
- [x] 5.1 Run the three ACI contract test classes together and confirm lifecycle/evidence status separation.
|
||||
- [x] 5.2 Run the stage 0 Harness contract and existing RAG/log focused tests to prove the old runtime path remains unchanged.
|
||||
- [x] 5.3 Verify references and diff scope show no changes to old Tool methods, Chat/AIOps registration, Controller, persistence, datasource, or public protocol.
|
||||
Reference in New Issue
Block a user