# 子图查询接口实现设计文档

## 1. 需求概述

### 1.1 目标
在 agent-retrieval 服务中代理本体查询服务的子图查询接口，统一对外提供服务，便于 Agent/大模型调用。

### 1.2 接口信息
- **路由**: `POST /kn/query_instance_subgraph/:kn_id`
- **目标接口**: `POST /api/ontology-query/in/v1/knowledge-networks/{kn_id}/subgraph`
- **功能**: 基于关系路径查询知识图谱中的对象子图

## 2. 技术架构设计

### 2.1 整体架构
采用项目现有的分层架构：
```
HTTP请求 → driveradapters (处理器层)
         → logics (业务逻辑层)
         → drivenadapters (外部服务调用层)
         → ontology-query服务
```

### 2.2 设计理念

**透传设计原则**：
- 对于底层接口的复杂数据结构（如 `RelationTypePaths`、`Entries`），使用 `interface{}` 类型进行透传
- 避免在当前服务中明确定义底层接口的数据结构，减少维护成本
- 当底层接口结构变动时，无需修改当前服务的代码，提高系统的灵活性
- 请求体和响应体直接透传，保持数据的完整性和准确性

**固定参数处理**：
- `QueryType` 参数固定为 `"relation_path"`，在内部实现中自动添加，不对外暴露
- `X-HTTP-Method-Override` header 固定为 `"GET"`，在内部实现中自动添加，不对外暴露
- 简化调用方的使用复杂度，降低出错概率

### 2.3 模块划分

#### 2.3.1 driveradapters 层
- **文件**: `server/driveradapters/knquerysubgraph/index.go`
- **职责**:
  - HTTP 请求参数绑定和校验
  - 调用业务逻辑层
  - 响应封装
- **处理器**: `KnQuerySubgraphHandler`

#### 2.3.2 logics 层
- **文件**: `server/logics/knquerysubgraph/index.go`
- **职责**:
  - 业务逻辑处理
  - 错误信息包装（大模型可理解）
- **服务**: `KnQuerySubgraphService`

#### 2.3.3 drivenadapters 层
- **文件**: `server/drivenadapters/ontology_query.go` (扩展现有文件)
- **职责**:
  - 调用 ontology-query 服务的子图查询接口
  - HTTP 请求/响应处理

#### 2.3.4 interfaces 层
- **文件**: `server/interfaces/driven_ontology_query.go` (扩展现有文件)
- **职责**:
  - 定义子图查询相关的接口和数据结构

## 3. 数据结构设计

### 3.1 请求结构

```go
// QueryInstanceSubgraphReq 子图查询请求
type QueryInstanceSubgraphReq struct {
    // Header 参数
    XAccountID   string `header:"x-account-id" binding:"required"`
    XAccountType string `header:"x-account-type" binding:"required"`

    // Path 参数
    KnID string `uri:"kn_id" binding:"required"`

    // Query 参数
    IncludeLogicParams bool `form:"include_logic_params"`

    // Body 参数 - 使用 interface{} 避免明确定义结构
    // 对应 ontology-query 接口的 SubGraphQueryBaseOnTypePath 结构
    RelationTypePaths interface{} `json:"relation_type_paths" binding:"required"`
}
```

**设计说明**：
- 使用 `interface{}` 类型定义 `RelationTypePaths`，避免明确定义底层接口的复杂结构
- 底层接口结构变动时，无需修改当前服务的代码
- 请求体直接透传给 ontology-query 服务
- `QueryType` 参数固定为 `relation_path`，不在请求结构中暴露

### 3.2 响应结构

```go
// QueryInstanceSubgraphResp 子图查询响应
type QueryInstanceSubgraphResp struct {
    // 使用 interface{} 直接返回底层接口的原始结构
    // 对应 ontology-query 接口的 PathEntries 结构
    Entries interface{} `json:"entries"`
}
```

**设计说明**：
- 使用 `interface{}` 类型定义 `Entries`，直接返回底层接口的原始结构
- 避免在当前服务中明确定义 ObjectSubGraphResponse、ObjectInfoInSubgraph 等复杂结构
- 底层接口响应结构变动时，无需修改当前服务的代码
- 响应体直接透传给调用方，保持数据完整性

## 4. 接口设计

### 4.1 DrivenOntologyQuery 接口扩展

在 `server/interfaces/driven_ontology_query.go` 中添加新方法：

```go
// DrivenOntologyQuery 本体查询接口
type DrivenOntologyQuery interface {
    // QueryObjectInstances 检索指定对象类的对象的详细数据
    QueryObjectInstances(ctx context.Context, req *QueryObjectInstancesReq) (resp *QueryObjectInstancesResp, err error)
    // QueryLogicProperties 查询逻辑属性值
    QueryLogicProperties(ctx context.Context, req *QueryLogicPropertiesReq) (resp *QueryLogicPropertiesResp, err error)
    // QueryActions 查询行动
    QueryActions(ctx context.Context, req *QueryActionsRequest) (resp *QueryActionsResponse, err error)
    // QueryInstanceSubgraph 查询对象子图
    QueryInstanceSubgraph(ctx context.Context, req *QueryInstanceSubgraphReq) (resp *QueryInstanceSubgraphResp, err error)
}
```

**接口设计说明**：
- 请求和响应结构体中的复杂字段（如 `RelationTypePaths`、`Entries`）使用 `interface{}` 类型
- 这样可以避免在当前服务中明确定义底层接口的复杂数据结构
- 当底层接口结构变动时，无需修改当前服务的代码
- 请求体和响应体直接透传，保持数据的完整性和灵活性

### 4.2 IKnQuerySubgraphService 接口定义

在 `server/interfaces/interface.go` 中添加：

```go
// IKnQuerySubgraphService 子图查询服务接口
type IKnQuerySubgraphService interface {
    // QueryInstanceSubgraph 查询对象子图
    QueryInstanceSubgraph(ctx context.Context, req *QueryInstanceSubgraphReq) (resp *QueryInstanceSubgraphResp, err error)
}
```

## 5. 实现细节

### 5.1 driveradapters 层实现

**文件**: `server/driveradapters/knquerysubgraph/index.go`

```go
package knquerysubgraph

import (
    "net/http"
    "sync"

    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/infra/config"
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/infra/errors"
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/infra/rest"
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/interfaces"
    logicskn "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/logics/knquerysubgraph"
    "github.com/creasty/defaults"
    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

// KnQuerySubgraphHandler 子图查询处理器
type KnQuerySubgraphHandler interface {
    QueryInstanceSubgraph(c *gin.Context)
}

type knQuerySubgraphHandler struct {
    Logger                  interfaces.Logger
    KnQuerySubgraphService  interfaces.IKnQuerySubgraphService
}

var (
    kqsOnce    sync.Once
    kqsHandler KnQuerySubgraphHandler
)

// NewKnQuerySubgraphHandler 新建 KnQuerySubgraphHandler
func NewKnQuerySubgraphHandler() KnQuerySubgraphHandler {
    kqsOnce.Do(func() {
        conf := config.NewConfigLoader()
        kqsHandler = &knQuerySubgraphHandler{
            Logger:                 conf.GetLogger(),
            KnQuerySubgraphService: logicskn.NewKnQuerySubgraphService(),
        }
    })
    return kqsHandler
}

// QueryInstanceSubgraph 查询对象子图
func (h *knQuerySubgraphHandler) QueryInstanceSubgraph(c *gin.Context) {
    var err error
    req := &interfaces.QueryInstanceSubgraphReq{}

    // 绑定 Header
    if err = c.ShouldBindHeader(req); err != nil {
        err = errors.DefaultHTTPError(c.Request.Context(), http.StatusBadRequest, err.Error())
        rest.ReplyError(c, err)
        return
    }

    // 绑定 Path Parameters
    if err = c.ShouldBindUri(req); err != nil {
        err = errors.DefaultHTTPError(c.Request.Context(), http.StatusBadRequest, err.Error())
        rest.ReplyError(c, err)
        return
    }

    // 绑定 Query Parameters
    if err = c.ShouldBindQuery(req); err != nil {
        err = errors.DefaultHTTPError(c.Request.Context(), http.StatusBadRequest, err.Error())
        rest.ReplyError(c, err)
        return
    }

    // 绑定 JSON Body
    if err = c.ShouldBindJSON(req); err != nil {
        err = errors.DefaultHTTPError(c.Request.Context(), http.StatusBadRequest, err.Error())
        rest.ReplyError(c, err)
        return
    }

    // 设置默认值
    if err = defaults.Set(req); err != nil {
        err = errors.DefaultHTTPError(c.Request.Context(), http.StatusBadRequest, err.Error())
        rest.ReplyError(c, err)
        return
    }

    // 参数校验
    err = validator.New().Struct(req)
    if err != nil {
        rest.ReplyError(c, err)
        return
    }

    // 调用业务逻辑
    resp, err := h.KnQuerySubgraphService.QueryInstanceSubgraph(c.Request.Context(), req)
    if err != nil {
        h.Logger.Errorf("[KnQuerySubgraphHandler#QueryInstanceSubgraph] QueryInstanceSubgraph failed, err: %v", err)
        rest.ReplyError(c, err)
        return
    }

    // 返回成功响应
    rest.ReplyOK(c, http.StatusOK, resp)
}
```

### 5.2 logics 层实现

**文件**: `server/logics/knquerysubgraph/index.go`

```go
package knquerysubgraph

import (
    "context"
    "fmt"
    "sync"

    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/drivenadapters"
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/infra/config"
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/interfaces"
)

// KnQuerySubgraphService 子图查询服务
type KnQuerySubgraphService interface {
    QueryInstanceSubgraph(ctx context.Context, req *interfaces.QueryInstanceSubgraphReq) (resp *interfaces.QueryInstanceSubgraphResp, err error)
}

type knQuerySubgraphService struct {
    Logger        interfaces.Logger
    OntologyQuery interfaces.DrivenOntologyQuery
}

var (
    kqsServiceOnce sync.Once
    kqsService     KnQuerySubgraphService
)

// NewKnQuerySubgraphService 新建 KnQuerySubgraphService
func NewKnQuerySubgraphService() KnQuerySubgraphService {
    kqsServiceOnce.Do(func() {
        conf := config.NewConfigLoader()
        kqsService = &knQuerySubgraphService{
            Logger:        conf.GetLogger(),
            OntologyQuery: drivenadapters.NewOntologyQueryAccess(),
        }
    })
    return kqsService
}

// QueryInstanceSubgraph 查询对象子图
func (s *knQuerySubgraphService) QueryInstanceSubgraph(ctx context.Context, req *interfaces.QueryInstanceSubgraphReq) (resp *interfaces.QueryInstanceSubgraphResp, err error) {
    // 调用 drivenadapters 层查询子图
    resp, err = s.OntologyQuery.QueryInstanceSubgraph(ctx, req)
    return
}
```

### 5.3 drivenadapters 层实现

```go
const (
    // ... 现有常量 ...

    // https://{host}:{port}/api/ontology-query/in/v1/knowledge-networks/:kn_id/subgraph
    queryInstanceSubgraphURI = "/in/v1/knowledge-networks/%s/subgraph"
)

// QueryInstanceSubgraph 查询对象子图
func (o *ontologyQueryClient) QueryInstanceSubgraph(ctx context.Context, req *interfaces.QueryInstanceSubgraphReq) (resp *interfaces.QueryInstanceSubgraphResp, err error) {
    // 构建查询参数 - QueryType 固定为 "relation_path"
    queryParams := []string{}
    if req.IncludeLogicParams {
        queryParams = append(queryParams, fmt.Sprintf("include_logic_params=%v", req.IncludeLogicParams))
    }
    // 固定 query_type 为 relation_path
    queryParams = append(queryParams, "query_type=relation_path")

    queryStr := ""
    if len(queryParams) > 0 {
        queryStr = "?" + queryParams[0]
        for i := 1; i < len(queryParams); i++ {
            queryStr += "&" + queryParams[i]
        }
    }

    uri := fmt.Sprintf(queryInstanceSubgraphURI, req.KnID) + queryStr
    url := fmt.Sprintf("%s%s", o.baseURL, uri)

    // 构建请求体 - 直接透传 RelationTypePaths (interface{})
    body := map[string]interface{}{
        "relation_type_paths": req.RelationTypePaths,
    }

    // 记录请求日志
    bodyJSON, _ := json.Marshal(body)
    o.logger.WithContext(ctx).Debugf("[OntologyQuery#QueryInstanceSubgraph] URL: %s", url)
    o.logger.WithContext(ctx).Debugf("[OntologyQuery#QueryInstanceSubgraph] Request Body: %s", string(bodyJSON))

    // 构建请求头
    header := common.GetHeaderFromCtx(ctx)
    header[rest.ContentTypeKey] = rest.ContentTypeJSON
    header["x-http-method-override"] = "GET"

    // 发送请求
    _, respBody, err := o.httpClient.Post(ctx, url, header, body)
    if err != nil {
        o.logger.WithContext(ctx).Errorf("[OntologyQuery#QueryInstanceSubgraph] Request failed, err: %v", err)
        return nil, err
    }

    // 解析响应 - 直接解析到 interface{}
    resp = &interfaces.QueryInstanceSubgraphResp{}
    resultByt := utils.ObjectToByte(respBody)
    err = json.Unmarshal(resultByt, resp)
    if err != nil {
        o.logger.WithContext(ctx).Errorf("[OntologyQuery#QueryInstanceSubgraph] Unmarshal failed, body: %s, err: %v", string(resultByt), err)
        err = infraErr.DefaultHTTPError(ctx, http.StatusInternalServerError, fmt.Sprintf("解析子图查询响应失败: %v", err))
        return nil, err
    }

    // 记录响应日志
    respJSON, _ := json.Marshal(resp)
    o.logger.WithContext(ctx).Debugf("[OntologyQuery#QueryInstanceSubgraph] Response: %s", string(respJSON))

    return resp, nil
}
```

**实现说明**：
- `QueryType` 参数固定为 `"relation_path"`，不对外暴露
- `RelationTypePaths` 使用 `interface{}` 类型，直接透传给底层接口
- `X-HTTP-Method-Override` header 固定为 `"GET"`，不对外暴露，内部自动处理
- 响应直接解析到 `interface{}`，保持原始结构

### 5.4 路由注册

**文件**: `server/driveradapters/rest_private_handler.go`

修改现有文件，添加路由注册：

```go
package driveradapters

import (
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/driveradapters/knactionrecall"
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/driveradapters/knlogicpropertyresolver"
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/driveradapters/knqueryobjectinstance"
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/driveradapters/knquerysubgraph"  // 新增
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/driveradapters/knretrieval"
    "devops.aishu.cn/AISHUDevOps/DIP/_git/agent-retrieval/server/interfaces"
    "github.com/gin-gonic/gin"
)

type restPrivateHandler struct {
    KnRetrievalHandler             knretrieval.KnRetrievalHandler
    KnLogicPropertyResolverHandler knlogicpropertyresolver.KnLogicPropertyResolverHandler
    KnActionRecallHandler          knactionrecall.KnActionRecallHandler
    KnQueryObjectInstanceHandler   knqueryobjectinstance.KnQueryObjectInstanceHandler
    KnQuerySubgraphHandler        knquerysubgraph.KnQuerySubgraphHandler  // 新增
    Logger                         interfaces.Logger
}

// NewRestPrivateHandler 创建restHandler实例
func NewRestPrivateHandler(logger interfaces.Logger) interfaces.HTTPRouterInterface {
    return &restPrivateHandler{
        KnRetrievalHandler:             knretrieval.NewKnRetrievalHandler(),
        KnLogicPropertyResolverHandler: knlogicpropertyresolver.NewKnLogicPropertyResolverHandler(),
        KnActionRecallHandler:          knactionrecall.NewKnActionRecallHandler(),
        KnQueryObjectInstanceHandler:   knqueryobjectinstance.NewKnQueryObjectInstanceHandler(),
        KnQuerySubgraphHandler:        knquerysubgraph.NewKnQuerySubgraphHandler(),  // 新增
        Logger:                         logger,
    }
}

// RegisterRouter 注册路由
func (r *restPrivateHandler) RegisterRouter(engine *gin.RouterGroup) {
    mws := []gin.HandlerFunc{}
    mws = append(mws, middlewareRequestLog(r.Logger), middlewareTrace, middlewareHeaderAuthContext())
    engine.Use(mws...)

    engine.POST("/kn/semantic-search", r.KnRetrievalHandler.SemanticSearch)
    engine.POST("/kn/logic-property-resolver", r.KnLogicPropertyResolverHandler.ResolveLogicProperties)
    engine.POST("/kn/get_action_info", r.KnActionRecallHandler.GetActionInfo)
    engine.POST("/kn/query_object_instance", r.KnQueryObjectInstanceHandler.QueryObjectInstance)
    engine.POST("/kn/query_instance_subgraph/:kn_id", r.KnQuerySubgraphHandler.QueryInstanceSubgraph)  // 新增
}
```

## 6. 错误处理设计

### 6.1 错误分类

1. **参数错误** (400)
   - 缺少必需参数
   - 参数格式错误
   - 参数值不合法

2. **权限错误** (403)
   - 无权访问该知识网络
   - 账户信息无效

3. **数据不存在** (404)
   - 知识网络不存在
   - 对象类不存在
   - 关系类不存在

4. **服务错误** (500)
   - ontology-query 服务不可用
   - 网络超时
   - 数据解析失败

## 7. 日志设计

### 7.1 日志级别

- **DEBUG**: 记录完整的请求和响应数据（用于问题排查）
- **INFO**: 记录关键操作节点
- **WARN**: 记录可恢复的错误
- **ERROR**: 记录需要关注的错误

### 7.2 日志内容

```go
// 请求日志
o.logger.WithContext(ctx).Debugf("[OntologyQuery#QueryInstanceSubgraph] URL: %s", url)
o.logger.WithContext(ctx).Debugf("[OntologyQuery#QueryInstanceSubgraph] Request Body: %s", string(bodyJSON))

// 响应日志
o.logger.WithContext(ctx).Debugf("[OntologyQuery#QueryInstanceSubgraph] Response: %s", string(respJSON))

// 错误日志
h.Logger.Errorf("[KnQuerySubgraphHandler#QueryInstanceSubgraph] QueryInstanceSubgraph failed, err: %v", err)
```

## 8. 测试计划

### 8.1 单元测试

1. **driveradapters 层测试**
   - 参数绑定测试
   - 参数校验测试
   - 错误处理测试

2. **logics 层测试**
   - 业务逻辑测试
   - 错误包装测试

3. **drivenadapters 层测试**
   - HTTP 请求构建测试
   - 响应解析测试
   - 错误处理测试

### 8.2 集成测试

1. **端到端测试**
   - 正常流程测试
   - 异常流程测试
   - 边界条件测试

2. **性能测试**
   - 并发请求测试
   - 大数据量测试
   - 响应时间测试

## 9. 部署计划

### 9.1 部署步骤

1. 代码审查
2. 合并到开发分支
3. 运行单元测试
4. 运行集成测试
5. 部署到测试环境
6. 功能验证
7. 部署到生产环境

### 9.2 回滚计划

如果部署后出现问题，可以通过以下方式回滚：
1. 回滚代码到上一个稳定版本
2. 重新部署
3. 验证功能正常

## 10. 未来扩展

### 10.1 MCP Server 封装

未来可以将此接口封装成 MCP Server 对外提供服务：

```go
// MCP Server 接口定义
type MCPTool struct {
    Name        string
    Description string
    InputSchema map[string]interface{}
    Handler     func(ctx context.Context, input map[string]interface{}) (map[string]interface{}, error)
}

// 子图查询 MCP Tool
var QuerySubgraphTool = MCPTool{
    Name:        "query_instance_subgraph",
    Description: "基于关系路径查询知识图谱中的对象子图",
    InputSchema: map[string]interface{}{
        "type": "object",
        "properties": map[string]interface{}{
            "kn_id": map[string]interface{}{
                "type":        "string",
                "description": "知识网络ID",
            },
            "relation_type_paths": map[string]interface{}{
                "type":        "array",
                "description": "关系路径数组",
            },
        },
        "required": []string{"kn_id", "relation_type_paths"},
    },
    Handler: handleQuerySubgraph,
}
```

### 10.2 性能优化

1. **缓存策略**
   - 对频繁查询的子图结果进行缓存
   - 设置合理的缓存过期时间

2. **并发优化**
   - 支持批量查询
   - 并行处理多条路径查询

3. **数据压缩**
   - 对大数据量响应进行压缩
   - 减少网络传输时间

## 11. 风险评估

### 11.1 技术风险

| 风险项 | 影响 | 概率 | 应对措施 |
|--------|------|------|----------|
| ontology-query 服务不稳定 | 高 | 中 | 增加重试机制和熔断机制 |
| 大数据量查询超时 | 中 | 高 | 增加查询超时控制和分页支持 |
| 数据结构变更 | 中 | 低 | 使用版本化的 API 接口 |

### 11.2 业务风险

| 风险项 | 影响 | 概率 | 应对措施 |
|--------|------|------|----------|
| 错误信息不够友好 | 中 | 中 | 持续优化错误提示信息 |
| 性能不满足需求 | 高 | 低 | 进行性能测试和优化 |

## 12. 总结

本设计文档详细描述了子图查询接口的实现方案，包括：

1. **架构设计**：采用分层架构，职责清晰
2. **数据结构**：定义了完整的请求和响应结构
3. **接口设计**：定义了各层之间的接口
4. **实现细节**：提供了各层的具体实现代码
5. **错误处理**：设计了完善的错误处理机制
6. **日志设计**：定义了详细的日志记录方案
7. **测试计划**：制定了完整的测试方案
8. **部署计划**：规划了部署和回滚方案
9. **未来扩展**：考虑了 MCP Server 封装和性能优化
10. **风险评估**：识别了潜在风险并制定了应对措施

该设计方案遵循了项目现有的架构模式，具有良好的可维护性和可扩展性。
