# 行动召回功能需求设计文档

## 一、需求概述

### 1.1 功能定位
行动召回功能是对现有"行动查询接口"的封装和增强，旨在为 Agent 系统提供动态工具发现和调用能力。通过语义化的对象标识，自动检索相关的行动，并将这些行动转换为符合 OpenAI Function Call 规范的工具定义。

### 1.2 应用场景
- Agent 根据当前对话上下文中识别到的实体对象，动态获取可执行的行动工具
- 实现"所见即可为"的智能交互体验：当 Agent 获取到某个对象信息后，自动获取针对该对象的可用操作
- 支持知识网络与行动能力的动态绑定

### 1.3 核心价值
- **动态性**：运行时根据对象实例动态发现可用工具，无需预先注册
- **语义化**：基于对象的语义标识进行行动召回，而非硬编码的工具列表
- **标准化**：输出符合 OpenAI Function Call 标准的工具定义，便于 LLM 调用

---

## 二、技术架构

### 2.1 依赖服务

| 服务名称 | 接口 | 用途 |
|---------|------|------|
| ontology-query | `/api/ontology-query/v1/knowledge-networks/{kn_id}/action-types/{at_id}` | 行动查询：根据对象唯一标识查询可执行的行动列表 |
| agent-operator-integration | `/api/agent-operator-integration/internal-v1/tool-box/{box_id}/tool/{tool_id}` | 工具详情查询：获取工具的完整 OpenAPI 定义 |
| agent-operator-integration | `/api/agent-operator-integration/internal-v1/tool-box/{box_id}/proxy/{tool_id}` | 工具执行代理：实际执行工具调用的统一入口 |

### 2.2 服务流程

```
┌─────────────┐
│   调用方     │
└──────┬──────┘
       │ 1. 发起召回请求（单个 unique_identity）
       ↓
┌─────────────────────┐
│  行动召回接口（新）   │
└──────┬──────────────┘
       │ 2. 包装为数组调用
       ↓
┌─────────────────────┐
│  行动查询接口         │  (ontology-query)
└──────┬──────────────┘
       │ 3. 返回 actions + action_source
       ↓
┌─────────────────────┐
│  判断 action_source  │
└──────┬──────────────┘
       │ 4. 如果 type=tool
       ↓
┌─────────────────────┐
│  获取工具详情接口     │  (agent-operator-integration)
└──────┬──────────────┘
       │ 5. 返回工具的 OpenAPI 定义
       ↓
┌─────────────────────┐
│  格式转换处理        │
│  - OpenAPI → OpenAI │
│  - 参数映射          │
└──────┬──────────────┘
       │ 6. 返回 _dynamic_tools
       ↓
┌─────────────┐
│   调用方     │
└─────────────┘
```

### 2.3 关键设计决策

#### 2.3.1 为什么只处理 actions[0]？

**设计原因：**
- 当前版本的输入是 `unique_identity`（单数），表示针对单个对象实例进行行动召回
- 一个对象的一个行动类型（action_type），通常对应一个具体的工具实现
- actions 数组的多个元素，通常是同一行动针对不同对象实例的参数实例化结果
- 由于只传入一个对象，因此只需要处理第一个 action 即可

**后续扩展方向：**
- 下个版本可支持 `unique_identities`（复数），返回多个工具定义

#### 2.3.2 为什么不处理 dynamic_params？

**设计原因：**
- `dynamic_params` 表示需要由大模型根据上下文动态生成的参数
- 这些参数的值为 `null`，表示尚未实例化，需要 LLM 推理
- 行动召回接口的职责是"告诉 LLM 有哪些工具可用"，而不是"替 LLM 生成参数"

**分工明确：**
- **行动召回接口**：提供工具定义（name, description, parameters schema）和固定参数（fixed_params）
- **LLM**：根据工具定义和上下文，动态生成参数值
- **调用方**：合并 fixed_params 和 LLM 生成的参数，组装实际请求

#### 2.3.3 为什么参数位置判断交给调用方？

**设计原因：**
- 行动召回接口专注于"工具发现"，而非"工具调用"
- 不同的调用方可能有不同的参数组装策略
- 通过返回 `original_schema` 和 `fixed_params`，给调用方最大的灵活性

**职责边界：**
- **行动召回接口**：返回工具元数据（schema + 固定参数）
- **调用方/中间层**：根据元数据组装实际的 HTTP 请求
- 这样设计符合单一职责原则，便于维护和扩展

#### 2.3.4 为什么 Schema 不保留位置信息？

**设计原因：**
- OpenAI Function Call 的 parameters schema 不需要位置信息
- LLM 只需要知道"有哪些参数、类型是什么、描述是什么"，不需要知道参数来自 path/query/header/body
- 参数位置信息已经包含在 `original_schema` 中，调用方可以根据需要使用

**简化设计：**
- 扁平化的 parameters schema 更符合 OpenAI 规范
- 降低了 LLM 理解的复杂度
- 保持了接口的简洁性

---

## 三、接口设计

### 3.1 行动召回接口（新建）

#### 3.1.1 接口定义

```
POST /api/agent-retrieval/in/v1/kn/get_action_info
```

#### 3.1.2 请求参数

**Path Parameters:**
- `kn_id` (string, required): 业务知识网络 ID
- `at_id` (string, required): 行动类型 ID

**Headers:**
- `x-account-id` (string, required): 账户 ID
- `x-account-type` (string, required): 账户类型

**Request Body:**
```json
{
    "unique_identity": {
        "disease_id": "disease_000001"
    }
}
```

**字段说明:**
- `unique_identity` (object, required): 对象的唯一标识，键值对形式，key 为主键属性名，value 为属性值

#### 3.1.3 响应格式

```json
{
    "_dynamic_tools": [
        {
            "name": "search_object_instance",
            "description": "该接口基于业务知识网络语义检索接口返回的对象类定义，查询具体的对象实例数据。",
            "parameters": {
                "type": "object",
                "properties": {
                    "kn_id": {
                        "type": "string",
                        "description": "业务知识网络ID"
                    },
                    "ot_id": {
                        "type": "string",
                        "description": "对象类ID"
                    },
                    "condition": {
                        "type": "object",
                        "description": "过滤条件"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "返回的数量，默认值 10。范围 1-10000"
                    }
                },
                "required": ["kn_id", "ot_id", "limit"]
            },
            "api_url": "http://agent-operator-integration:9000/api/agent-operator-integration/internal-v1/tool-box/e59f35f8-65f3-46bb-b5f1-a428380b3edc/proxy/7acb8add-ecd0-45cf-bd4d-57674c3c69b0",
            "original_schema": {
                // 工具详情接口返回的 api_spec 完整内容
            },
            "fixed_params": {
                "header": {
                    "x-account-id": "test",
                    "x-account-type": "user"
                },
                "path": {},
                "query": {},
                "body": {}
            }
        }
    ]
}
```

**字段说明:**

| 字段 | 类型 | 说明 |
|------|------|------|
| `_dynamic_tools` | array | 动态工具列表 |
| `_dynamic_tools[].name` | string | 工具名称，来自工具详情的 `name` 字段 |
| `_dynamic_tools[].description` | string | 工具描述，来自工具详情的 `description` 字段 |
| `_dynamic_tools[].parameters` | object | 符合 OpenAI Function Call 规范的参数 Schema |
| `_dynamic_tools[].api_url` | string | 工具执行的代理 URL，格式见下文 |
| `_dynamic_tools[].original_schema` | object | 工具的原始 OpenAPI 定义（api_spec） |
| `_dynamic_tools[].fixed_params` | object | 已实例化的固定参数，来自行动查询的 parameters |

---

## 四、核心处理逻辑

### 4.1 接口封装逻辑

#### 4.1.1 请求参数转换
```
输入：unique_identity (单个对象)
    ↓
转换：unique_identities: [unique_identity]
    ↓
调用：行动查询接口
```

#### 4.1.2 行动源类型处理

当前版本仅处理 `type=tool` 的行动源：

```python
if action_source.type == "tool":
    # 调用工具详情接口
    tool_detail = get_tool_detail(action_source.box_id, action_source.tool_id)
    # 继续处理
elif action_source.type == "mcp":
    # TODO: 预留 MCP 类型实现
    pass
```

#### 4.1.3 行动列表处理规则

**重要约束：仅处理 actions 数组的第一个元素**

```python
actions = response.get("actions", [])
if len(actions) > 0:
    first_action = actions[0]
    # 仅处理 first_action
```

**原因：**
- 简化首版实现逻辑
- actions 数组中的多个元素通常是针对不同对象实例的同一个行动，取第一个作为代表即可
- 后续版本可根据实际需求扩展支持多个 actions

### 4.2 URL 生成规则

#### 4.2.1 API URL 模板

```
http://agent-operator-integration:9000/api/agent-operator-integration/internal-v1/tool-box/{box_id}/proxy/{tool_id}
```

**变量来源：**
- `box_id`: 来自 `action_source.box_id`
- `tool_id`: 来自 `action_source.tool_id`

#### 4.2.2 完整示例

```json
{
    "action_source": {
        "type": "tool",
        "box_id": "e59f35f8-65f3-46bb-b5f1-a428380b3edc",
        "tool_id": "7acb8add-ecd0-45cf-bd4d-57674c3c69b0"
    }
}
```

生成的 URL：
```
http://agent-operator-integration:9000/api/agent-operator-integration/internal-v1/tool-box/e59f35f8-65f3-46bb-b5f1-a428380b3edc/proxy/7acb8add-ecd0-45cf-bd4d-57674c3c69b0
```

### 4.3 固定参数映射

#### 4.3.1 参数来源

固定参数来自行动查询接口返回的 `actions[0].parameters`

**示例输入：**
```json
{
    "actions": [
        {
            "parameters": {
                "x-account-id": "test",
                "x-account-type": "user"
            },
            "dynamic_params": {
                "include_type_info": null,
                "condition": {...},
                "limit": null,
                "kn_id": null,
                "ot_id": null
            }
        }
    ]
}
```

**重要说明：**
- ✅ **处理 `parameters`**: 这些是已实例化的固定参数，需要映射到 `fixed_params`
- ❌ **忽略 `dynamic_params`**: 这些参数由大模型动态生成，行动召回接口不处理
- 📌 `dynamic_params` 中值为 `null` 的字段，表示这些参数需要 LLM 根据上下文动态填充

#### 4.3.2 映射规则

**方案选择：基于 OpenAPI 定义的精确映射（推荐）**

根据 `original_schema.api_spec.parameters` 的定义，精确判断每个参数应该放在哪个位置：

```python
def map_parameters_by_schema(action_parameters, api_spec):
    """
    根据 OpenAPI 定义精确映射参数位置

    处理逻辑：
    1. 遍历 api_spec.parameters，建立参数名 -> 位置(in) 的映射表
    2. 遍历 action_parameters，根据映射表分类到对应位置
    3. 特殊处理：
       - path 参数：放入 fixed_params.path
       - query 参数：放入 fixed_params.query
       - header 参数：放入 fixed_params.header
       - body 参数（request_body）：放入 fixed_params.body
    """
    fixed_params = {"header": {}, "path": {}, "query": {}, "body": {}}

    # 建立参数名到位置的映射表
    param_location_map = {}
    for param in api_spec.get("parameters", []):
        param_location_map[param["name"]] = param["in"]

    # 根据映射表分类参数
    for key, value in action_parameters.items():
        location = param_location_map.get(key, "body")  # 默认放 body
        fixed_params[location][key] = value

    return fixed_params
```

**备用方案：简化映射（如果 API Spec 解析困难）**

按照命名规则简单分类：

```python
def map_parameters_by_naming(action_parameters):
    """
    根据参数命名规则简化映射（fallback 方案）
    """
    fixed_params = {"header": {}, "path": {}, "query": {}, "body": {}}

    for key, value in action_parameters.items():
        if key.startswith("x-") or key.lower() in ["authorization", "content-type"]:
            fixed_params["header"][key] = value
        else:
            fixed_params["body"][key] = value

    return fixed_params
```

**最终由调用方处理：**
- 行动召回接口提供 `fixed_params` 和 `original_schema`
- 调用方（Agent 或中间层）根据这两者，将参数正确组装到实际的 HTTP 请求中
- 这样设计保持了接口的简单性，同时给调用方最大的灵活性

### 4.4 Schema 转换逻辑（TODO）

#### 4.4.1 转换目标

将工具的 OpenAPI Schema 转换为 OpenAI Function Call 的 Input Schema

**输入格式（OpenAPI）:**
```json
{
    "api_spec": {
        "parameters": [
            {
                "name": "kn_id",
                "in": "path",
                "description": "业务知识网络ID",
                "required": true,
                "schema": {
                    "type": "string"
                }
            }
        ],
        "request_body": {
            "content": {
                "application/json": {
                    "schema": {
                        "$ref": "#/components/schemas/FirstQueryWithSearchAfter"
                    }
                }
            }
        },
        "components": {
            "schemas": {...}
        }
    }
}
```

**输出格式（OpenAI Function Call）:**
```json
{
    "type": "object",
    "properties": {
        "kn_id": {
            "type": "string",
            "description": "业务知识网络ID"
        },
        "condition": {
            "type": "object",
            "description": "过滤条件"
        }
    },
    "required": ["kn_id"]
}
```

#### 4.4.2 转换规则

**核心原则（已澄清）：**
1. ✅ **扁平化处理**: 将 path/query/header/body 中的所有参数扁平化到一个 properties 对象中
2. ✅ **不保留位置信息**: 转换后的 schema 不需要标记参数来自哪个位置（path/query/header/body）
3. ✅ **忽略 dynamic_params**: 不处理 `dynamic_params`，这些由 LLM 动态生成
4. ⚠️ **$ref 解析**: 递归解析 `$ref` 引用，展开为完整的 schema 定义（MVP 版本可简化处理）
5. ⚠️ **必填项合并**: 合并所有位置的 required 参数到顶层 required 数组
6. ✅ **类型映射**: OpenAPI 类型 → JSON Schema 类型的对应关系

**实现方式（MVP 版本）：**
```python
def convert_openapi_to_function_schema(api_spec):
    """
    OpenAPI Schema → OpenAI Function Call Schema

    Args:
        api_spec: 工具详情接口返回的 api_spec

    Returns:
        符合 OpenAI Function Call 规范的 parameters schema
    """
    properties = {}
    required = []

    # 1. 处理 parameters（path/query/header）
    for param in api_spec.get("parameters", []):
        param_name = param["name"]
        param_schema = param.get("schema", {})

        properties[param_name] = {
            "type": param_schema.get("type", "string"),
            "description": param.get("description", "")
        }

        # 处理枚举
        if "enum" in param_schema:
            properties[param_name]["enum"] = param_schema["enum"]

        # 收集必填参数
        if param.get("required", False):
            required.append(param_name)

    # 2. 处理 request_body（body 参数）
    request_body = api_spec.get("request_body", {})
    if request_body:
        content = request_body.get("content", {}).get("application/json", {})
        body_schema = content.get("schema", {})

        # MVP: 简化处理，直接合并 properties
        # TODO: 完整版需要递归解析 $ref
        if "$ref" in body_schema:
            # 跳过 $ref 解析，标记为 TODO
            # 可以添加一个通用的 body 参数
            properties["body"] = {
                "type": "object",
                "description": "请求体参数，详见 original_schema"
            }
        elif "properties" in body_schema:
            # 直接展开 properties
            for prop_name, prop_def in body_schema["properties"].items():
                properties[prop_name] = {
                    "type": prop_def.get("type", "string"),
                    "description": prop_def.get("description", "")
                }

            # 合并必填项
            if "required" in body_schema:
                required.extend(body_schema["required"])

    return {
        "type": "object",
        "properties": properties,
        "required": required
    }
```

**待后续增强的功能：**
- [ ] 完整的 $ref 递归解析（从 components.schemas 中查找并展开）
- [ ] 处理 `allOf`, `oneOf`, `anyOf` 等组合 schema
- [ ] 处理复杂嵌套对象的扁平化策略
- [ ] 支持自定义的 schema 转换规则

**转换示例：**

输入（OpenAPI）：
```json
{
    "parameters": [
        {"name": "kn_id", "in": "path", "required": true, "schema": {"type": "string"}},
        {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer"}}
    ],
    "request_body": {
        "content": {
            "application/json": {
                "schema": {
                    "properties": {
                        "condition": {"type": "object", "description": "过滤条件"}
                    }
                }
            }
        }
    }
}
```

输出（OpenAI Function Call）：
```json
{
    "type": "object",
    "properties": {
        "kn_id": {"type": "string", "description": ""},
        "limit": {"type": "integer", "description": ""},
        "condition": {"type": "object", "description": "过滤条件"}
    },
    "required": ["kn_id"]
}
```

---

## 五、数据模型

### 5.1 请求模型

#### ActionRecallRequest
```python
class ActionRecallRequest(BaseModel):
    """行动召回请求"""
    unique_identity: Dict[str, Union[str, int, float, bool]]

    class Config:
        schema_extra = {
            "example": {
                "unique_identity": {
                    "disease_id": "disease_000001"
                }
            }
        }
```

### 5.2 响应模型

#### DynamicTool
```python
class DynamicTool(BaseModel):
    """动态工具定义"""
    name: str
    description: str
    parameters: Dict[str, Any]  # OpenAI Function Call Schema
    api_url: str
    original_schema: Dict[str, Any]  # 原始 OpenAPI Schema
    fixed_params: FixedParams
```

#### FixedParams
```python
class FixedParams(BaseModel):
    """固定参数"""
    header: Dict[str, Any] = {}
    path: Dict[str, Any] = {}
    query: Dict[str, Any] = {}
    body: Dict[str, Any] = {}
```

#### ActionRecallResponse
```python
class ActionRecallResponse(BaseModel):
    """行动召回响应"""
    _dynamic_tools: List[DynamicTool]

    class Config:
        schema_extra = {
            "example": {
                "_dynamic_tools": [
                    {
                        "name": "search_object_instance",
                        "description": "查询对象实例数据",
                        "parameters": {...},
                        "api_url": "http://...",
                        "original_schema": {...},
                        "fixed_params": {...}
                    }
                ]
            }
        }
```

---

## 六、异常处理

### 6.1 异常场景

| 场景 | HTTP 状态码 | 错误码 | 处理方式 |
|------|------------|--------|---------|
| 行动查询接口调用失败 | 502 | SERVICE_UNAVAILABLE | 返回上游错误信息 |
| 工具详情接口调用失败 | 502 | SERVICE_UNAVAILABLE | 返回上游错误信息 |
| action_source.type 不支持 | 400 | UNSUPPORTED_ACTION_TYPE | 返回错误提示，目前仅支持 tool |
| actions 数组为空 | 200 | - | 返回空的 _dynamic_tools 数组 |
| unique_identity 格式错误 | 400 | INVALID_REQUEST | 返回参数校验错误信息 |
| Schema 转换失败 | 500 | SCHEMA_CONVERSION_ERROR | 记录错误日志，返回通用错误 |

### 6.2 错误响应格式

```json
{
    "code": "UNSUPPORTED_ACTION_TYPE",
    "message": "当前仅支持 type=tool 的行动源",
    "detail": {
        "action_source": {
            "type": "mcp",
            "box_id": "xxx",
            "tool_id": "yyy"
        }
    }
}
```

---

## 七、实现计划

### 7.1 第一阶段：MVP（最小可行产品）

**目标：** 实现基本的行动召回能力

**范围：**
- ✅ 接口定义和路由
- ✅ 参数转换（unique_identity → unique_identities）
- ✅ 调用行动查询接口
- ✅ 调用工具详情接口（仅 type=tool）
- ✅ 仅处理 actions[0]（第一个行动）
- ✅ 生成 api_url
- ✅ fixed_params 映射（基于 OpenAPI 定义或命名规则）
- ✅ 忽略 dynamic_params（由 LLM 动态生成）
- ✅ 基础的 Schema 转换（扁平化 parameters 和简单的 request_body，不保留位置信息）
- ✅ 异常处理（type != tool 时返回错误）

**不包含：**
- ❌ 复杂的 $ref 递归解析
- ❌ MCP 类型支持（下个版本）
- ❌ 多个 actions 的处理（下个版本）
- ❌ allOf/oneOf/anyOf 等复杂 schema 组合

### 7.2 第二阶段：Schema 转换增强

**目标：** 实现完整的 OpenAPI → OpenAI Schema 转换

**范围：**
- [ ] $ref 递归解析（从 components.schemas 中查找并展开）
- [ ] 复杂 schema 类型支持（allOf, oneOf, anyOf）
- [ ] 嵌套对象的完整展开
- [ ] Schema 缓存机制（避免重复解析）
- [ ] 转换规则配置化（支持自定义映射规则）
- [ ] Schema 转换的单元测试覆盖

### 7.3 第三阶段：扩展支持

**目标：** 支持更多行动源类型和高级特性

**范围：**
- [ ] **MCP 类型行动源支持**（已明确为下个版本）
  - 定义 MCP 类型的接口规范
  - 实现 MCP 工具详情获取逻辑
  - 适配 MCP 到 OpenAI Function Call 的转换
- [ ] **多个 actions 的处理策略**（已明确为下个版本）
  - 支持 unique_identities（复数）入参
  - 返回多个 _dynamic_tools
  - 增加 action_id 或其他标识区分不同的 action
- [ ] 行动召回结果缓存（提升性能）
- [ ] 可观测性增强（日志、监控、追踪）
- [ ] 支持行动的条件匹配（根据 action_type.condition 智能选择）

---

## 八、技术约束与假设

### 8.1 技术约束

1. **依赖服务可用性**: 依赖 ontology-query 和 agent-operator-integration 服务稳定运行
2. **Schema 兼容性**: 假设工具定义遵循 OpenAPI 3.0 规范
3. **网络环境**: 服务间通过内部网络通信，URL 使用服务名

### 8.2 技术假设

1. **唯一行动**: 假设一个对象的一个行动类型，通常只有一个具体的行动实现（因此只取 actions[0]）
2. **参数来源**: 假设 actions[0].parameters 中的参数已经正确实例化
3. **工具可用性**: 假设 action_source 中引用的工具在工具箱中确实存在且可用
4. **参数职责分离**:
   - `parameters` (fixed_params) 由行动查询接口实例化
   - `dynamic_params` 由 LLM 根据上下文动态生成
   - 调用方负责合并两者并组装最终请求
5. **调用方能力**: 假设调用方有能力根据 `original_schema` 和 `fixed_params` 组装正确的 HTTP 请求

---

## 九、测试策略

### 9.1 单元测试

- [ ] 参数转换逻辑
- [ ] URL 生成逻辑
- [ ] Fixed params 映射逻辑
- [ ] Schema 转换逻辑（各种边界情况）

### 9.2 集成测试

- [ ] 完整流程测试（mock 依赖服务）
- [ ] 异常场景测试（依赖服务不可用、返回错误等）
- [ ] 不同 action_source.type 的测试

### 9.3 端到端测试

- [ ] 真实环境下的完整调用链路测试
- [ ] 与 Agent 系统的集成测试

---

## 十、待澄清问题

### 10.1 Schema 转换相关

1. **Q1**: dynamic_params 的作用是什么？它与 parameters 有何区别？
   - ✅ **已澄清**: `dynamic_params` 表示需要由大模型动态生成的参数
   - **结论**: 我们不需要解析 `dynamic_params`，这部分由 LLM 根据上下文动态填充
   - **实现影响**: 在行动召回接口中，仅处理 `actions[0].parameters`（固定参数），忽略 `dynamic_params`

2. **Q2**: 如何判断参数应该放在 header/path/query/body 的哪个位置？
   - ✅ **已澄清**: 参数位置判断交给调用方处理
   - **结论**: 行动召回接口不负责判断参数位置，仅将 `fixed_params` 按照现有结构（header/path/query/body）返回给调用方
   - **实现影响**:
     - 保持简化的映射逻辑（如 x- 开头放 header，其余放 body）
     - 或者根据 `api_spec.parameters[].in` 进行准确映射
     - 最终由调用方（Agent 或中间层）根据 `original_schema` 和 `fixed_params` 组装实际请求

3. **Q3**: 转换后的 parameters schema 需要保留参数位置信息吗？
   - ✅ **已澄清**: 不需要保留参数位置信息
   - **结论**: OpenAI Function Call 的 `parameters` schema 只需要扁平化的参数定义，不需要标记参数来自 path/query/header/body
   - **实现影响**: Schema 转换时，将所有参数扁平化到一个 `properties` 对象中即可

### 10.2 行动召回相关

4. **Q4**: actions 数组有多个元素时，为什么只取第一个？
   - ✅ **已澄清**: 当前版本暂时只支持单个 action 的情况
   - **结论**: 这就是为什么入参使用 `unique_identity`（单数）而不是 `unique_identities`（复数）的原因
   - **设计合理性**:
     - 一个对象的一个行动类型，通常对应一个具体的工具
     - 如果有多个 actions，说明可能是针对不同条件或场景的变体，首版取第一个即可满足需求
   - **后续扩展**: 如果未来需要支持多个 actions，可以考虑：
     - 方案 A: 返回多个 `_dynamic_tools`，每个对应一个 action
     - 方案 B: 增加条件匹配逻辑，智能选择最合适的 action

5. **Q5**: MCP 类型的行动源预期何时支持？
   - ✅ **已澄清**: 下个版本再支持，当前不做
   - **结论**: 第一版本（MVP）仅支持 `type=tool`，遇到 `type=mcp` 返回错误提示
   - **实现影响**:
     - 代码中预留 MCP 类型的处理分支（返回 `UNSUPPORTED_ACTION_TYPE` 错误）
     - 在接口文档中明确说明当前仅支持 tool 类型

### 10.3 工具执行相关

6. **Q6**: Agent 调用 api_url 时，如何传递参数？
   - ⚠️ **部分澄清**:
     - `fixed_params`: 已实例化的固定参数（来自 `actions[0].parameters`）
     - 动态参数: 由 LLM 根据 `parameters` schema 生成
   - **推荐方案**:
     - 调用方将 LLM 生成的参数与 `fixed_params` 合并
     - 优先级: LLM 动态参数 > fixed_params（允许 LLM 覆盖固定参数）
     - 根据 `original_schema` 判断每个参数应该放在请求的哪个位置
   - **待进一步确认**: 如果需要，可以在下一次迭代中提供参数合并的辅助工具或示例代码

---

## 十一、附录

### 11.1 完整请求响应示例

#### 请求示例

```bash
curl --location 'http://action-recall-svc:8000/api/action-recall/v1/knowledge-networks/kn_medical/action-types/generate_treatment_plan/recall' \
--header 'x-account-id: bdb78b62-6c48-11f0-af96-fa8dcc0a06b2' \
--header 'x-account-type: user' \
--header 'Content-Type: application/json' \
--data '{
    "unique_identity": {
        "disease_id": "disease_000001"
    }
}'
```

#### 响应示例

```json
{
    "_dynamic_tools": [
        {
            "name": "根据单个对象类查询对象实例",
            "description": "该接口基于业务知识网络语义检索接口返回的对象类定义，查询具体的对象实例数据。",
            "parameters": {
                "type": "object",
                "properties": {
                    "kn_id": {
                        "type": "string",
                        "description": "业务知识网络ID"
                    },
                    "ot_id": {
                        "type": "string",
                        "description": "对象类ID"
                    },
                    "include_type_info": {
                        "type": "boolean",
                        "description": "是否包含对象类信息, 默认false，不包含"
                    },
                    "include_logic_params": {
                        "type": "boolean",
                        "description": "包含逻辑属性的计算参数，默认false"
                    },
                    "condition": {
                        "type": "object",
                        "description": "过滤条件"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "返回的数量，默认值 10。范围 1-10000"
                    },
                    "sort": {
                        "type": "array",
                        "description": "排序字段",
                        "items": {
                            "type": "object",
                            "properties": {
                                "field": {
                                    "type": "string"
                                },
                                "direction": {
                                    "type": "string",
                                    "enum": ["asc", "desc"]
                                }
                            }
                        }
                    }
                },
                "required": ["kn_id", "ot_id"]
            },
            "api_url": "http://agent-operator-integration:9000/api/agent-operator-integration/internal-v1/tool-box/e59f35f8-65f3-46bb-b5f1-a428380b3edc/proxy/7acb8add-ecd0-45cf-bd4d-57674c3c69b0",
            "original_schema": {
                "parameters": [...],
                "request_body": {...},
                "components": {...}
            },
            "fixed_params": {
                "header": {
                    "x-account-id": "test",
                    "x-account-type": "user",
                    "X-HTTP-Method-Override": "GET"
                },
                "path": {},
                "query": {},
                "body": {}
            }
        }
    ]
}
```

### 11.2 术语表

| 术语 | 说明 |
|------|------|
| 行动 (Action) | 针对特定对象可执行的操作，通常绑定到某个工具或服务 |
| 行动类型 (Action Type) | 行动的分类，如"生成治疗方案"、"更新数据"等 |
| 行动源 (Action Source) | 行动的实际执行载体，可以是 tool、mcp 等类型 |
| 对象唯一标识 (Unique Identity) | 对象实例的主键信息，用于唯一确定一个对象 |
| 动态工具 (Dynamic Tool) | 运行时动态发现和生成的工具定义，而非预先注册的静态工具 |
| Function Call | OpenAI 定义的函数调用规范，LLM 可以根据此规范调用外部工具 |
| OpenAPI | RESTful API 的标准化描述规范（原名 Swagger） |

### 11.3 参考资料

- [OpenAPI 3.0 规范](https://swagger.io/specification/)
- [OpenAI Function Calling 文档](https://platform.openai.com/docs/guides/function-calling)
- ontology-query 服务接口文档: `docs/prd/feature-799472/99-依赖接口/ontology_query_action.yaml`
- operator-toolbox 服务接口文档: `docs/prd/feature-799472/99-依赖接口/operator_toolbox.yaml`

---

## 文档变更记录

| 版本 | 日期 | 作者 | 变更说明 |
|------|------|------|---------|
| v1.0 | 2025-12-23 | - | 初始版本，基于原始需求整理 |
| v1.1 | 2025-12-23 | - | 澄清关键问题，更新设计决策和实现计划 |

### v1.1 变更详情

**澄清的问题：**
1. ✅ **dynamic_params 不需要处理**：由 LLM 动态生成，行动召回接口忽略
2. ✅ **参数位置判断交给调用方**：接口只返回元数据，调用方负责组装请求
3. ✅ **Schema 不保留位置信息**：扁平化处理，符合 OpenAI Function Call 规范
4. ✅ **仅处理 actions[0]**：当前版本只支持单个 action，使用 unique_identity（单数）入参
5. ✅ **MCP 类型下个版本支持**：当前版本遇到 MCP 返回错误提示

**新增内容：**
- 2.3 关键设计决策章节，解释核心设计理由
- 4.3.1 补充 dynamic_params 说明
- 4.3.2 提供基于 OpenAPI 定义的精确映射方案
- 4.4.2 完善 Schema 转换规则和示例代码
- 第十章待澄清问题全部更新为已澄清状态

**调整内容：**
- 7.1 MVP 范围更明确，增加"忽略 dynamic_params"等条目
- 7.3 第三阶段明确 MCP 和多 actions 支持为下个版本

---

**文档状态**: ✅ 已澄清核心问题，待技术评审

**优先级**: P0 (核心功能)

**预计工作量**:
- MVP (第一阶段): 3-5 人日
- Schema 转换增强 (第二阶段): 5-8 人日
- 扩展支持 (第三阶段): 3-5 人日

