WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
+140
View File
@@ -0,0 +1,140 @@
# WeKnora API 文档
## 目录
- [概述](#概述)
- [最权威参考:Swagger UI](#最权威参考swagger-ui)
- [基础信息](#基础信息)
- [认证机制](#认证机制)
- [错误处理](#错误处理)
- [文件与图片引用(`resource://` 与直链)](#文件与图片引用resource-与直链)
- [API 概览](#api-概览)
## 概述
WeKnora 提供了一系列 RESTful API,用于创建和管理知识库、检索知识,以及进行基于知识的问答。本文档详细描述了这些 API 的使用方式。
## 最权威参考:Swagger UI
WeKnora 同时提供基于 OpenAPI 的 Swagger 文档。**启动服务后访问 `http://localhost:8080/swagger/index.html`**,可看到所有端点的完整参数、请求/响应 schema,并可直接在浏览器内试调——它随代码自动更新,是最准确的接口参考。
本目录下的 markdown 文档提供更易读的示例与场景说明,与 swagger 同步维护;当二者出现差异时,以 swagger 为准。
> Swagger UI 仅在非 release 模式(`GIN_MODE != release`)下挂载;生产部署默认关闭。
## 基础信息
- **基础 URL**: `/api/v1`
- **响应格式**: JSON
- **认证方式**: API Key
## 认证机制
所有 API 请求需要在 HTTP 请求头中包含 `X-API-Key` 进行身份认证:
```
X-API-Key: your_api_key
```
为便于问题追踪和调试,建议每个请求的 HTTP 请求头中添加 `X-Request-ID`:
```
X-Request-ID: unique_request_id
```
### 获取 API Key
在 web 页面完成账户注册后,请前往账户信息页面获取您的 API Key。
请妥善保管您的 API Key,避免泄露。API Key 代表您的账户身份,拥有完整的 API 访问权限。
## 错误处理
所有 API 使用标准的 HTTP 状态码表示请求状态,并返回统一的错误响应格式:
```json
{
"success": false,
"error": {
"code": "错误代码",
"message": "错误信息",
"details": "错误详情"
}
}
```
## 文件与图片引用(`resource://` 与直链)
响应里的图片、图表、附件默认以内部引用 `resource://<handle>` 返回,例如问答答案中的
`![示意图](resource://xifDo7NTSL300Lp1goVutw)`。这类引用不能被浏览器直接加载,客户端需要再
调用带鉴权的 `GET /files?file_path=<引用>` 代理去取字节流。
如果你在把 WeKnora 集成进自己的 App,可以让服务端直接返回**可加载的 http(s) 直链**,省掉这一次
额外请求:
| 方式 | 用法 | 生效范围 |
|------|------|----------|
| 单次请求 | 在 URL 上加 `?resource_urls=public` | 仅该次请求 |
| 整个部署 | 环境变量 `RESOURCE_URL_MODE=public` | 所有未显式传参的请求 |
`resource_urls` 取值为 `handle`(默认,保持内部引用)或 `public`(返回直链);传其它值返回
`400`。单次请求的参数优先于环境变量,因此把部署默认设成 `public` 后,仍可用
`?resource_urls=handle` 单独退回。
支持该参数的接口:
- `POST /api/v1/knowledge-chat/{session_id}`(SSE)
- `POST /api/v1/agent-chat/{session_id}`(SSE)
- `GET /api/v1/sessions/continue-stream/{session_id}`(SSE)
- `GET /api/v1/messages/{session_id}/load`
- `POST /api/v1/knowledge-search`
- `POST /api/v1/knowledge-bases/{id}/hybrid-search`(兼容 GET)
改写覆盖答案正文、检索结果 `content` / `image_info`、`knowledge_references`、Agent 执行步骤与工具结果,以及消息
上的图片附件。流式回答里跨两个 chunk 被截断的引用会先缓冲再改写,客户端拿到的始终是完整链接。
### 注意事项
- **需要外链能力。** 直链由存储后端预签名,或由 `APP_EXTERNAL_URL` + `/r/<token>` 提供。二者都不
可用时(例如 local 存储且未设 `APP_EXTERNAL_URL`),该引用**保持 `resource://` 原样**,客户端
仍可回退到 `/files` 代理。详见 `.env.example` 中的 `APP_EXTERNAL_URL` 说明。
- **直链是限时匿名可读的**(WeKnora 签发的 grant 2 小时,MinIO 预签名 24 小时)。任何拿到链接的
人在过期前都能读取该文件,请勿写入日志或转发给不应看到该文件的一方。
- **嵌入式(embed)渠道不支持该参数。** 其访客是匿名的,`/api/v1/embed/...` 下的接口会强制使用
`handle`(即使传了 `?resource_urls=public`、或部署默认是 `public`),图片仍走渠道维度的鉴权代理。
- **限定知识库的 API Key 不能使用 `public`**,返回 `403`。这类 Key 本身也被拒绝访问 `/files`
代理,若能拿到匿名直链等于绕过同一道限制。改用 `handle` 即可正常调用。
- **同一文件的直链会在有效期内复用**:重复请求不会反复签发凭证,也不会每次都拿到不同的 URL,客户端
和 CDN 的缓存因此可以命中。凭证被吊销或过期后链接立即失效。
## API 概览
WeKnora API 按功能分为以下几类:
| 分类 | 描述 | 文档链接 |
|------|------|----------|
| 认证管理 | 用户注册、登录、令牌管理;OIDC 流程 | [auth.md](./auth.md) · [OIDC认证调用流程.md](../OIDC认证调用流程.md) |
| 空间管理 | 创建和管理空间账户 | [tenant.md](./tenant.md) |
| 知识库管理 | 创建、查询和管理知识库 | [knowledge-base.md](./knowledge-base.md) |
| 知识管理 | 上传、检索和管理知识内容 | [knowledge.md](./knowledge.md) |
| 模型管理 | 配置和管理各种AI模型 | [model.md](./model.md) |
| 分块管理 | 管理知识的分块内容 | [chunk.md](./chunk.md) |
| 标签管理 | 管理知识库的标签分类 | [tag.md](./tag.md) |
| FAQ管理 | 管理FAQ问答对 | [faq.md](./faq.md) |
| 智能体管理 | 创建和管理自定义智能体 | [agent.md](./agent.md) |
| 会话管理 | 创建和管理对话会话 | [session.md](./session.md) |
| 知识搜索 | 在知识库中搜索内容 | [knowledge-search.md](./knowledge-search.md) |
| 聊天功能 | 基于知识库和 Agent 进行问答 | [chat.md](./chat.md) |
| 消息管理 | 获取和管理对话消息 | [message.md](./message.md) |
| 评估功能 | 评估模型性能 | [evaluation.md](./evaluation.md) |
| 初始化管理 | 知识库模型配置与 Ollama 管理 | [initialization.md](./initialization.md) |
| 系统管理 | 系统信息、解析引擎、存储引擎 | [system.md](./system.md) |
| MCP 服务 | MCP 工具服务管理 | [mcp-service.md](./mcp-service.md) |
| 组织管理 | 组织、成员、知识库/智能体共享 | [organization.md](./organization.md) |
| Skills | 预装与已安装的智能体技能、技能环境变量 | [skill.md](./skill.md) |
| 长期记忆 | 当前用户的跨会话记忆(设置、条目、主题、文档亲和度、导出、整理) | [memory.md](./memory.md) |
| 网络搜索 | 网络搜索服务商 | [web-search.md](./web-search.md) |
| 向量存储 | 向量数据库连接管理 | [vector-store.md](./vector-store.md) |
| 存储后端 | 对象/文件存储实例(多实例)管理 | [storage-backend.md](./storage-backend.md) |
| IM 渠道 | 企业微信 / 飞书 / Slack 等 IM 平台对接,含渠道 CRUD 与回调 | [../IM集成开发文档.md](../IM集成开发文档.md) |
| 数据源导入 | 飞书 / 企微 / Notion / Confluence 等外部数据源接入与同步 | [../数据源导入开发文档.md](../数据源导入开发文档.md) |
+553
View File
@@ -0,0 +1,553 @@
# 智能体(Agent)管理 API
[返回目录](./README.md)
## 概述
智能体 API 用于管理自定义智能体(Custom Agent)。系统提供了内置智能体,同时支持用户创建自定义智能体来满足不同的业务场景需求。
> 智能体的共享与跨组织分发(`/agents/:id/shares` 等)属于组织协作能力,文档见 [组织管理 API](./organization.md)。本文件只覆盖智能体自身的 CRUD、复制、占位符、类型预设以及推荐问题接口。
### 内置智能体
系统默认提供以下内置智能体:
| ID | 名称 | 描述 | 模式 |
|----|------|------|------|
| `builtin-quick-answer` | 快速问答 | 基于知识库的 RAG 问答,快速准确地回答问题 | quick-answer |
| `builtin-smart-reasoning` | 智能推理 | ReAct 推理框架,支持多步思考和工具调用 | smart-reasoning |
| `builtin-data-analyst` | 数据分析师 | 专业数据分析智能体,支持 CSV/Excel 文件的 SQL 查询与统计分析 | smart-reasoning |
### 智能体模式
| 模式 | 说明 |
|------|------|
| `quick-answer` | RAG 模式,快速问答,直接基于知识库检索结果生成回答 |
| `smart-reasoning` | ReAct 模式,支持多步推理和工具调用 |
## API 列表
| 方法 | 路径 | 描述 |
| ------ | -------------------------- | -------------------------- |
| POST | `/agents` | 创建智能体 |
| GET | `/agents` | 获取智能体列表 |
| GET | `/agents/:id` | 获取智能体详情 |
| PUT | `/agents/:id` | 更新智能体 |
| DELETE | `/agents/:id` | 删除智能体 |
| POST | `/agents/:id/copy` | 复制智能体 |
| GET | `/agents/placeholders` | 获取占位符定义 |
---
## POST `/agents` - 创建智能体
创建新的自定义智能体。成功返回 HTTP 201。
**请求体参数**:
| 参数 | 类型 | 必填 | 说明 |
| ------------- | ------ | ---- | ------------------------------------------------- |
| `name` | string | 是 | 智能体名称 |
| `description` | string | 否 | 智能体描述 |
| `avatar` | string | 否 | 头像(emoji 或图标名称) |
| `config` | object | 否 | 智能体配置,详见 [配置参数](#配置参数) |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/agents' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "我的智能体",
"description": "自定义智能体描述",
"avatar": "🤖",
"config": {
"agent_mode": "smart-reasoning",
"system_prompt": "你是一个专业的助手...",
"temperature": 0.7,
"max_iterations": 10,
"kb_selection_mode": "all",
"web_search_enabled": true,
"multi_turn_enabled": true,
"history_turns": 5
}
}'
```
**响应**:
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "我的智能体",
"description": "自定义智能体描述",
"avatar": "🤖",
"is_builtin": false,
"tenant_id": 1,
"created_by": "user-123",
"config": {
"agent_mode": "smart-reasoning",
"system_prompt": "你是一个专业的助手...",
"temperature": 0.7,
"max_iterations": 10
},
"created_at": "2025-01-19T10:00:00Z",
"updated_at": "2025-01-19T10:00:00Z"
}
}
```
**错误响应**:
| 状态码 | 错误码 | 错误 | 说明 |
| ------ | ------ | --------------------- | --------------------------------- |
| 400 | 1000 | Bad Request | 请求参数错误或智能体名称为空 |
| 500 | 1007 | Internal Server Error | 服务器内部错误 |
---
## GET `/agents` - 获取智能体列表
获取当前空间的所有智能体,包括内置智能体和自定义智能体。响应中额外返回 `disabled_own_agent_ids`,指示当前空间在前端对话下拉框中主动隐藏的本空间自有智能体 ID 列表(不影响其他空间)。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/agents' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": [
{
"id": "builtin-quick-answer",
"name": "快速问答",
"description": "基于知识库的 RAG 问答,快速准确地回答问题",
"avatar": "💬",
"is_builtin": true,
"tenant_id": 10000,
"created_by": "",
"config": {
"agent_mode": "quick-answer",
"temperature": 0.3,
"max_completion_tokens": 2048,
"kb_selection_mode": "all",
"web_search_enabled": false,
"multi_turn_enabled": true,
"history_turns": 5
},
"created_at": "2025-12-29T20:06:01.696308+08:00",
"updated_at": "2025-12-29T20:06:01.696308+08:00",
"deleted_at": null
},
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "我的智能体",
"is_builtin": false,
"config": {
"agent_mode": "smart-reasoning"
}
}
],
"disabled_own_agent_ids": []
}
```
**错误响应**:
| 状态码 | 错误码 | 错误 | 说明 |
| ------ | ------ | --------------------- | ------------------ |
| 401 | 1001 | Unauthorized | 缺少空间上下文 |
| 500 | 1007 | Internal Server Error | 服务器内部错误 |
---
## GET `/agents/:id` - 获取智能体详情
根据 ID 获取智能体的详细信息。
**路径参数**:
| 参数 | 类型 | 说明 |
| ---- | ------ | -------- |
| `id` | string | 智能体 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/agents/builtin-quick-answer' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": {
"id": "builtin-quick-answer",
"name": "快速问答",
"description": "基于知识库的 RAG 问答,快速准确地回答问题",
"is_builtin": true,
"tenant_id": 1,
"config": {
"agent_mode": "quick-answer",
"system_prompt": "",
"context_template": "请根据以下参考资料回答用户问题...",
"temperature": 0.7,
"max_completion_tokens": 2048,
"kb_selection_mode": "all",
"web_search_enabled": true,
"multi_turn_enabled": true,
"history_turns": 5
},
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
}
```
**错误响应**:
| 状态码 | 错误码 | 错误 | 说明 |
| ------ | ------ | --------------------- | ------------------ |
| 400 | 1000 | Bad Request | 智能体 ID 为空 |
| 404 | 1003 | Not Found | 智能体不存在 |
| 500 | 1007 | Internal Server Error | 服务器内部错误 |
---
## PUT `/agents/:id` - 更新智能体
更新智能体的名称、描述、头像和配置。内置智能体不可修改。
**路径参数**:
| 参数 | 类型 | 说明 |
| ---- | ------ | -------- |
| `id` | string | 智能体 ID |
**请求体参数**:
| 参数 | 类型 | 必填 | 说明 |
| ------------- | ------ | ---- | ------------ |
| `name` | string | 否 | 智能体名称 |
| `description` | string | 否 | 智能体描述 |
| `avatar` | string | 否 | 智能体头像 |
| `config` | object | 否 | 智能体配置 |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/agents/550e8400-e29b-41d4-a716-446655440000' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "更新后的智能体",
"description": "更新后的描述",
"config": {
"agent_mode": "smart-reasoning",
"temperature": 0.8,
"max_iterations": 20
}
}'
```
**响应**:
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "更新后的智能体",
"description": "更新后的描述",
"config": {
"agent_mode": "smart-reasoning",
"temperature": 0.8,
"max_iterations": 20
},
"updated_at": "2025-01-19T11:00:00Z"
}
}
```
**错误响应**:
| 状态码 | 错误码 | 错误 | 说明 |
| ------ | ------ | --------------------- | --------------------------------- |
| 400 | 1000 | Bad Request | 请求参数错误或智能体名称为空 |
| 403 | 1002 | Forbidden | 无法修改内置智能体 |
| 404 | 1003 | Not Found | 智能体不存在 |
| 500 | 1007 | Internal Server Error | 服务器内部错误 |
---
## DELETE `/agents/:id` - 删除智能体
删除指定的自定义智能体。内置智能体不可删除。
**路径参数**:
| 参数 | 类型 | 说明 |
| ---- | ------ | -------- |
| `id` | string | 智能体 ID |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/agents/550e8400-e29b-41d4-a716-446655440000' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"message": "Agent deleted successfully"
}
```
**错误响应**:
| 状态码 | 错误码 | 错误 | 说明 |
| ------ | ------ | --------------------- | --------------------- |
| 400 | 1000 | Bad Request | 智能体 ID 为空 |
| 403 | 1002 | Forbidden | 无法删除内置智能体 |
| 404 | 1003 | Not Found | 智能体不存在 |
| 500 | 1007 | Internal Server Error | 服务器内部错误 |
---
## POST `/agents/:id/copy` - 复制智能体
复制指定的智能体,创建一个新的副本,副本始终为自定义智能体。支持复制内置智能体。成功返回 HTTP 201。
**路径参数**:
| 参数 | 类型 | 说明 |
| ---- | ------ | -------------- |
| `id` | string | 源智能体 ID |
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/agents/builtin-smart-reasoning/copy' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "智能推理 (副本)",
"description": "ReAct 推理框架,支持多步思考和工具调用",
"is_builtin": false,
"config": {
"agent_mode": "smart-reasoning",
"max_iterations": 50
},
"created_at": "2025-01-19T12:00:00Z",
"updated_at": "2025-01-19T12:00:00Z"
}
}
```
**错误响应**:
| 状态码 | 错误码 | 错误 | 说明 |
| ------ | ------ | --------------------- | ------------------ |
| 400 | 1000 | Bad Request | 智能体 ID 为空 |
| 404 | 1003 | Not Found | 智能体不存在 |
| 500 | 1007 | Internal Server Error | 服务器内部错误 |
---
## GET `/agents/placeholders` - 获取占位符定义
获取所有可用的提示词占位符定义,按字段类型分组。这些占位符可用于系统提示词和上下文模板中。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/agents/placeholders' \
--header 'X-API-Key: your_api_key'
```
**响应**:
```json
{
"success": true,
"data": {
"all": [...],
"system_prompt": [...],
"agent_system_prompt": [...],
"context_template": [...],
"rewrite_system_prompt": [...],
"rewrite_prompt": [...],
"fallback_prompt": [...]
}
}
```
---
## 配置参数
智能体的 `config` 对象支持以下配置项:
### 基础设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `agent_mode` | string | - | 智能体模式:`quick-answer`(RAG)或 `smart-reasoning`(ReAct) |
| `system_prompt` | string | - | 系统提示词,支持使用占位符 |
| `system_prompt_id` | string | - | 系统提示词模板 ID(引用 `prompt_templates/` YAML 文件中的模板) |
| `context_template` | string | - | 上下文模板(仅 quick-answer 模式使用) |
| `context_template_id` | string | - | 上下文模板 ID(引用 `prompt_templates/` YAML 文件中的模板) |
### 模型设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `model_id` | string | - | 对话模型 ID |
| `rerank_model_id` | string | - | 重排序模型 ID |
| `temperature` | float | 0.7 | 温度参数(0-1) |
| `max_completion_tokens` | int | 2048 | 最大生成 token 数 |
| `thinking` | *bool | nil | 是否启用思考模式(适用于支持扩展思考的模型) |
### Agent 模式设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `max_iterations` | int | 10 | ReAct 最大迭代次数。`-1` 表示不限制,直到模型自然结束或用户停止;正数上限为 100 |
| `allowed_tools` | []string | - | 允许使用的工具列表 |
| `mcp_selection_mode` | string | - | MCP 服务选择模式:`all`/`selected`/`none` |
| `mcp_services` | []string | - | 选中的 MCP 服务 ID 列表 |
| `skills_selection_mode` | string | - | Skills 选择模式:`all`/`selected`/`none` |
| `selected_skills` | []string | - | 选中的 Skill 名称列表(mode 为 `selected` 时) |
### 知识库设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `kb_selection_mode` | string | - | 知识库选择模式:`all`/`selected`/`none` |
| `knowledge_bases` | []string | - | 关联的知识库 ID 列表 |
| `retrieve_kb_only_when_mentioned` | bool | false | 仅在用户通过 @ 显式提及时才检索知识库 |
| `supported_file_types` | []string | - | 支持的文件类型(如 `["csv", "xlsx"]`) |
### 图片上传 / 多模态设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `image_upload_enabled` | bool | false | 是否允许上传图片 |
| `vlm_model_id` | string | - | 图片分析所用的 VLM 模型 ID |
| `image_storage_provider` | string | - | 图片存储提供者:`local`/`minio`/`cos`/`tos`/`oss`,为空使用全局默认 |
### FAQ 策略设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `faq_priority_enabled` | bool | true | FAQ 优先策略开关 |
| `faq_direct_answer_threshold` | float | 0.9 | FAQ 直接回答阈值 |
| `faq_score_boost` | float | 1.2 | FAQ 分数加成系数 |
### 网络搜索设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `web_search_enabled` | bool | true | 是否启用网络搜索 |
| `web_search_max_results` | int | 5 | 网络搜索最大结果数 |
| `web_search_provider_id` | string | - | 网络搜索提供者 ID,为空使用空间默认提供者 |
| `web_fetch_enabled` | bool | false | 是否自动获取重排后的搜索结果页面全文 |
| `web_fetch_top_n` | int | 3 | 重排后获取全文的最大页面数 |
### 多轮对话设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `multi_turn_enabled` | bool | true | 是否启用多轮对话 |
| `history_turns` | int | 5 | 保留的历史轮次数 |
### 检索策略设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `embedding_top_k` | int | 10 | 向量检索 TopK |
| `keyword_threshold` | float | 0.3 | 关键词检索阈值 |
| `vector_threshold` | float | 0.5 | 向量检索阈值 |
| `rerank_top_k` | int | 5 | 重排序 TopK |
| `rerank_threshold` | float | 0.5 | 重排序阈值 |
### 推荐问题设置
`question_suggestions` 是智能体拥有的统一策略。网页嵌入等渠道只能关闭展示,不能覆盖内容或生成规则。
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `question_suggestions.starters.enabled` | bool | true | 是否在首次提问前展示开场问题 |
| `question_suggestions.starters.mode` | string | `hybrid` | `curated`、`knowledge` 或 `hybrid` |
| `question_suggestions.starters.items` | []string | `[]` | 运营配置的开场问题 |
| `question_suggestions.starters.count` | int | 6 | 展示数量,范围 1-8 |
| `question_suggestions.follow_ups.enabled` | bool | false | 是否在每次完整回答后异步生成追问 |
| `question_suggestions.follow_ups.mode` | string | `hybrid` | `generated`、`knowledge` 或 `hybrid` |
| `question_suggestions.follow_ups.count` | int | 3 | 生成数量,范围 1-5 |
| `question_suggestions.follow_ups.model_id` | string | - | 独立生成模型;为空使用本轮对话模型 |
| `question_suggestions.follow_ups.categories` | []string | `clarify,deepen,action` | 允许的问题类型 |
| `question_suggestions.follow_ups.max_context_turns` | int | 2 | 生成时使用的最近对话轮数,范围 1-5 |
| `question_suggestions.follow_ups.additional_instruction` | string | - | 智能体作者的附加生成要求 |
| `question_suggestions.follow_ups.suppress_on_fallback` | bool | true | 兜底回答后不展示 |
| `question_suggestions.follow_ups.suppress_when_answer_asks_question` | bool | true | 回答本身以问题结尾时不展示 |
| `question_suggestions.follow_ups.knowledge_fallback` | bool | true | 模型失败时使用知识库候选补位 |
| `question_suggestions.follow_ups.allow_regenerate` | bool | false | 是否允许用户换一批 |
旧 `suggested_prompts` 会在数据库迁移时一次性写入 `starters.items`,API 不再接受该字段。
### 高级设置
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `enable_query_expansion` | bool | true | 是否启用查询扩展 |
| `enable_rewrite` | bool | true | 是否启用多轮对话查询改写 |
| `rewrite_prompt_system` | string | - | 改写系统提示词 |
| `rewrite_prompt_user` | string | - | 改写用户提示词模板 |
| `fallback_strategy` | string | `model` | 回退策略:`fixed`(固定回复)或 `model`(模型生成);未设置时在服务端默认为 `model` |
| `fallback_response` | string | - | 固定回退回复(`fallback_strategy` 为 `fixed` 时使用) |
| `fallback_prompt` | string | - | 回退提示词(`fallback_strategy` 为 `model` 时使用) |
---
## 使用 Agent 进行问答
创建或获取智能体后,可以通过 `/agent-chat/:session_id` 接口使用智能体进行问答。详情请参考 [聊天功能 API](./chat.md)。
在问答请求中使用 `agent_id` 参数指定要使用的智能体:
```curl
curl --location 'http://localhost:8080/api/v1/agent-chat/session-123' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query": "帮我分析一下这份数据",
"agent_enabled": true,
"agent_id": "builtin-data-analyst"
}'
```
## 相关文档
- 智能体的组织共享、跨空间分发与禁用(`/agents/:id/shares`、`/shared-agents` 等):见 [组织管理 API](./organization.md)
- 智能体绑定 IM 渠道(`/agents/:id/im-channels`):见组织/IM 渠道相关文档
- 网络搜索提供者配置(被 `web_search_provider_id` 引用):见 [Web Search API](./web-search.md)
+449
View File
@@ -0,0 +1,449 @@
# 认证管理 API
[返回目录](./README.md)
OIDC 完整调用流程见 [../OIDC认证调用流程.md](../OIDC认证调用流程.md)。本文档作为端点参考。
## 说明
WeKnora 的 `/auth/*` 端点本身**不需要 X-API-Key**,但部分端点需要在 `Authorization: Bearer <token>` 头中携带由 `/auth/login` 或 `/auth/oidc/callback` 返回的 JWT:
| 端点 | 鉴权方式 |
| --- | --- |
| `/auth/register` `/auth/login` `/auth/config` | 无 |
| `/auth/oidc/config` `/auth/oidc/url` `/auth/oidc/callback` | 无 |
| `/auth/refresh` | refresh_token(请求体携带) |
| `/auth/validate` `/auth/me` `/auth/logout` `/auth/change-password` `/auth/switch-tenant` `/auth/me/preferences` | Bearer JWT |
注册接口可通过环境变量 `DISABLE_REGISTRATION=true` 关闭。密码策略默认 8–32 位且同时包含字母与数字;部署可通过环境变量 `WEKNORA_AUTH_COMPLEX_PASSWORD_ENABLED` 或系统设置 `auth.complex_password_enabled` 要求额外包含大小写字母与特殊字符。当前策略见 `GET /auth/config`。
## 端点一览
| 方法 | 路径 | 描述 |
| ---- | -------------------------- | ------------------------------------------ |
| GET | `/auth/config` | 公开认证配置(注册模式、密码复杂度) |
| POST | `/auth/register` | 用户注册 |
| POST | `/auth/login` | 用户登录 |
| GET | `/auth/oidc/config` | 获取 OIDC 配置元数据 |
| GET | `/auth/oidc/url` | 获取 OIDC 授权链接 |
| GET | `/auth/oidc/callback` | OIDC 授权回调(由 IdP 重定向触发) |
| POST | `/auth/refresh` | 用 refresh_token 换新的 access_token |
| GET | `/auth/validate` | 验证 JWT 有效性 |
| POST | `/auth/logout` | 退出登录 |
| GET | `/auth/me` | 获取当前用户信息 |
| PUT | `/auth/me/preferences` | 更新最近活跃空间等个人偏好 |
| POST | `/auth/switch-tenant` | 切换激活空间并换发 token |
| POST | `/auth/change-password` | 修改密码 |
---
## GET `/auth/config` - 公开认证配置
无需登录。前端用它决定是否展示注册入口,以及注册/改密表单应使用哪套密码规则。
**响应**:
```json
{
"success": true,
"registration_mode": "self_serve",
"complex_password_enabled": false
}
```
| 字段 | 说明 |
| ---- | ---- |
| `registration_mode` | `self_serve` 允许公开注册;`invite_only` 仅邀请 |
| `complex_password_enabled` | `true` 时新密码须含大小写字母、数字和特殊字符 `!@#$%^&*()_+-=[]{}|;:,.<>?` |
---
## POST `/auth/register` - 用户注册
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 校验 | 说明 |
| -------- | ------ | ---- | -------------------------- | --------- |
| username | string | 是 | 长度 2-50 | 用户名 |
| email | string | 是 | 邮箱格式 | 邮箱 |
| password | string | 是 | 8–32 位,须含字母与数字;若 `complex_password_enabled` 则还须含大小写与特殊字符 | 密码 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/auth/register' \
--header 'Content-Type: application/json' \
--data '{
"username": "alice",
"email": "alice@example.com",
"password": "secret123"
}'
```
**响应**(201 Created):
```json
{
"success": true,
"message": "Registration successful",
"user": {
"id": "usr-...",
"username": "alice",
"email": "alice@example.com",
"tenant_id": 1,
"is_active": true,
"created_at": "2026-05-11T10:00:00+08:00",
"updated_at": "2026-05-11T10:00:00+08:00"
},
"tenant": {
"id": 1,
"name": "alice's workspace",
"api_key": "sk-..."
}
}
```
**错误**: 注册被禁用 → 403;参数校验失败 → 400。
---
## POST `/auth/login` - 用户登录
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| -------- | ------ | ---- | ------------- |
| email | string | 是 | 注册邮箱 |
| password | string | 是 | 密码 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/auth/login' \
--header 'Content-Type: application/json' \
--data '{
"email": "alice@example.com",
"password": "secret123"
}'
```
**响应**:
```json
{
"success": true,
"message": "Login successful",
"user": { "id": "usr-...", "username": "alice", "email": "alice@example.com" },
"tenant": { "id": 1, "name": "alice's workspace", "api_key": "sk-..." },
"token": "eyJhbGciOi...",
"refresh_token": "eyJhbGciOi..."
}
```
**错误**: 邮箱或密码错误 → 401;账号被禁用 → 403。
---
## GET `/auth/oidc/config` - 获取 OIDC 配置元数据
返回 OIDC 是否启用以及 Provider 显示名,前端登录页据此决定是否展示 OIDC 登录按钮。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/auth/oidc/config'
```
**响应**:
```json
{
"success": true,
"enabled": true,
"provider_display_name": "WeKnora SSO"
}
```
---
## GET `/auth/oidc/url` - 获取 OIDC 授权链接
返回前端应跳转的 OIDC IdP 授权页 URL 与状态码。
**查询参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---------- | ------ | ---- | ------------------------------------------------------- |
| redirect | string | 否 | 登录成功后前端期望落地的路径(如 `/dashboard`),透传到 state |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/auth/oidc/url?redirect=%2Fdashboard'
```
**响应**:
```json
{
"success": true,
"provider_display_name": "WeKnora SSO",
"authorization_url": "https://idp.example.com/oauth/authorize?client_id=...&state=...",
"state": "abcdef..."
}
```
---
## GET `/auth/oidc/callback` - OIDC 授权回调
由 IdP 在用户授权后重定向到此端点。一般不需要客户端代码直接调用——它的作用是把登录结果通过浏览器 hash 传回前端首页。
**查询参数**:
| 字段 | 类型 | 必填 | 说明 |
| ----------------- | ------ | ---- | ----------------------------- |
| code | string | 是 | IdP 颁发的 authorization code |
| state | string | 是 | 与 `/auth/oidc/url` 返回值一致 |
| error | string | 否 | IdP 返回的错误标识 |
| error_description | string | 否 | IdP 返回的错误详情 |
**响应**:始终返回 `302 Found`,跳转到 `/`,并把结果编码进 URL hash:
- 成功:`/#oidc_result=<base64url(JSON payload)>`,其中 payload 包含 `success` / `user` / `tenant` / `token` / `refresh_token` / `is_new_user`,与登录响应一致。
- 失败:`/#oidc_error=<reason>[&oidc_error_description=<message>]`,常见 reason 包括 `invalid_state`、`missing_code`、`login_failed`、`payload_encode_failed`。
---
## GET `/auth/oidc/start` - 发起 OIDC 登录(直接 302)
与 `/auth/oidc/url` 不同,此端点**直接 302 重定向**到 OIDC Provider 的授权页,不返回 JSON,因此无需前端 JS 介入。适用于外部平台(如企业门户 / Nexus)直接给出一个链接即可触发 OIDC 授权码流程,借助 IdP 的 SSO session 实现免再次输密码。
回调地址由后端根据请求自身的 origin(`<scheme>://<host>/api/v1/auth/oidc/callback`)自动构造,无需调用方提供。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/auth/oidc/start'
```
**响应**:`302 Found`,`Location` 指向 IdP 授权页(含 `client_id` / `state` / `redirect_uri` / `scope`)。
> 登录成功后的回调行为与 `/auth/oidc/callback` 一致:302 回前端首页并把登录结果编码进 URL hash。当前登录后固定落到默认首页 `/platform/knowledge-bases`(直达指定业务页的 `next` 参数为未来扩展)。
---
## POST `/auth/refresh` - 刷新令牌
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ------------- | ------ | ---- | ----------------- |
| refreshToken | string | 是 | 登录时颁发的 refresh_token |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/auth/refresh' \
--header 'Content-Type: application/json' \
--data '{
"refreshToken": "eyJhbGciOi..."
}'
```
**响应**:
```json
{
"success": true,
"message": "Token refreshed successfully",
"access_token": "eyJhbGciOi...",
"refresh_token": "eyJhbGciOi..."
}
```
**错误**: refresh_token 无效或过期 → 401。
---
## GET `/auth/validate` - 验证 JWT
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/auth/validate' \
--header 'Authorization: Bearer eyJhbGciOi...'
```
**响应**:
```json
{
"success": true,
"valid": true,
"user_id": "usr-...",
"tenant_id": 1
}
```
无效 token 返回 401。
---
## POST `/auth/logout` - 退出登录
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/auth/logout' \
--header 'Authorization: Bearer eyJhbGciOi...'
```
**响应**: `{ "success": true, "message": "Logged out successfully" }`
---
## GET `/auth/me` - 获取当前用户信息
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/auth/me' \
--header 'Authorization: Bearer eyJhbGciOi...'
```
**响应**:
```json
{
"success": true,
"data": {
"user": {
"id": "usr-...",
"username": "alice",
"email": "alice@example.com",
"avatar": "",
"tenant_id": 1,
"is_active": true,
"can_access_all_tenants": false,
"created_at": "2026-05-11T10:00:00+08:00",
"updated_at": "2026-05-11T10:00:00+08:00"
},
"tenant": {
"id": 1,
"name": "My Workspace"
},
"memberships": [
{
"tenant_id": 1,
"tenant_name": "My Workspace",
"role": "owner"
}
],
"tenant_required": false,
"capabilities": {
"can_create_tenant": false,
"auto_accept_invitation": false
}
}
}
```
`capabilities` 供 SPA 读取部署级开关,无需调用超管设置 API:
| 字段 | 说明 |
| ---- | ---- |
| `can_create_tenant` | 当前用户是否可自助创建空间 |
| `auto_accept_invitation` | 全局 `tenant.auto_accept_invitation`:邮箱邀请已注册用户时是否直接加入(无需收件箱确认) |
---
## POST `/auth/switch-tenant` - 切换激活空间
为当前用户在目标空间重新签发 access / refresh token 对。调用者须在目标空间有 **active** 成员关系(`CanAccessAllTenants` 超级用户切到非 home 空间除外)。
成功换签会把目标空间写入账号级「最近活跃租户」偏好(`users.preferences.last_active_tenant_id`)。refresh JWT **不含** `tenant_id`,因此 **下次登录与 refresh 都按该偏好落点**;一次换签会改变该用户所有设备的落点。偏好写入失败则整次换签失败,**不会**发出新 token。
切回 home 时服务端写入 home ID(与 SPA 发送 `0` 清偏好在当前落点语义上等价)。Web UI 切空间走 `X-Tenant-ID` + `PUT /auth/me/preferences`,不调用本接口。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ------------- | ------ | ---- | ---------------------------- |
| tenant_id | uint64 | 是 | 目标空间 ID |
| refresh_token | string | 否 | 当前 refresh token,成功后撤销 |
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/auth/switch-tenant' \
--header 'Authorization: Bearer eyJhbGciOi...' \
--header 'Content-Type: application/json' \
--data '{
"tenant_id": 2
}'
```
**响应**: 与登录相同的 `LoginResponse`(`user` / `active_tenant` / `memberships` / `token` / `refresh_token`)。`user.preferences.last_active_tenant_id` 与目标空间一致。
**错误**: 无成员关系或偏好写入失败 → 403;参数校验失败 → 400。
---
## PUT `/auth/me/preferences` - 更新个人偏好
按 PATCH 语义合并 `users.preferences`(仅覆盖请求体里出现的字段)。SPA 在 UI 切空间后用此接口记住落点;`POST /auth/switch-tenant` 会在服务端写同一字段,API 客户端不必再补发本请求。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ---------------------- | ------- | ---- | ---- |
| last_active_tenant_id | *uint64 | 否 | 正整数 = 设置/替换;`0` = 清除(下次登录回 home);省略 = 不改 |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/auth/me/preferences' \
--header 'Authorization: Bearer eyJhbGciOi...' \
--header 'Content-Type: application/json' \
--data '{
"last_active_tenant_id": 2
}'
```
**响应**: `{ "success": true, "data": { "last_active_tenant_id": 2 } }`
---
## POST `/auth/change-password` - 修改密码
修改当前用户的登录密码。新密码须满足 **8–32 位**且**同时包含字母与数字**;当 `GET /auth/config` 的 `complex_password_enabled` 为 true 时,还须包含大小写字母与特殊字符。不能与当前密码相同。成功后**所有会话被撤销**,需使用新密码重新登录。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 校验 | 说明 |
| ------------- | ------ | ---- | ------- | --------- |
| old_password | string | 是 | | 当前密码 |
| new_password | string | 是 | 8–32 位,须含字母与数字(复杂模式另需大小写与特殊字符),且不同于旧密码 | 新密码 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/auth/change-password' \
--header 'Authorization: Bearer eyJhbGciOi...' \
--header 'Content-Type: application/json' \
--data '{
"old_password": "secret123",
"new_password": "newsecret456"
}'
```
**响应**: `{ "success": true, "message": "Password changed successfully" }`
**错误**(400):
| `error.details` | 含义 |
| --------------------- | ---------------------------- |
| `invalid_old_password` | 当前密码不正确 |
| `password_policy` | 新密码不满足长度/复杂度要求 |
| `same_password` | 新密码与当前密码相同 |
+190
View File
@@ -0,0 +1,190 @@
# 聊天功能 API
[返回目录](./README.md)
| 方法 | 路径 | 描述 |
| ---- | ----------------------------- | ------------------------ |
| POST | `/knowledge-chat/:session_id` | 基于知识库的问答 |
| POST | `/agent-chat/:session_id` | 基于 Agent 的智能问答 |
| POST | `/knowledge-search` | 基于知识库的搜索知识 |
| GET | `/sessions/:session_id/messages/:message_id/suggestions` | 获取已生成的回答后推荐 |
| POST | `/sessions/:session_id/messages/:message_id/suggestions` | 确保生成或换一批推荐 |
| POST | `/sessions/:session_id/suggestion-events` | 上报曝光、点击、关闭事件 |
## POST `/knowledge-chat/:session_id` - 基于知识库的问答
基于知识库的 RAG 问答,支持 SSE 流式响应。
**查询参数**:
| 参数 | 取值 | 说明 |
|------|------|------|
| `resource_urls` | `handle`(默认)/ `public` | `public` 让答案与引用里的图片直接返回可加载的 http(s) 链接,省去逐个调用 `/files` 代理。详见[文件与图片引用](./README.md#文件与图片引用resource-与直链) |
同样适用于下面的 `/agent-chat/:session_id`、`/knowledge-search`、`/knowledge-bases/:id/hybrid-search` 与 `/sessions/continue-stream/:session_id`。
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `query` | string | 是 | 查询文本 |
| `knowledge_base_ids` | string[] | 否 | 知识库 ID 列表 |
| `knowledge_ids` | string[] | 否 | 知识文件 ID 列表,指定具体文件进行检索 |
| `agent_id` | string | 否 | 自定义 Agent ID,指定使用的智能体 |
| `summary_model_id` | string | 否 | 覆盖默认的摘要模型 ID |
| `mentioned_items` | object[] | 否 | @提及的知识库和文件列表 |
| `disable_title` | bool | 否 | 是否禁用自动标题生成(默认 false) |
| `images` | object[] | 否 | 附带的图片(base64 格式),需要 Agent 启用图片上传 |
| `channel` | string | 否 | 来源渠道标识:`web`、`api`、`im`、`browser_extension` |
| `suggestion_attribution` | object | 否 | 用户从推荐问题发起本轮时传入 `{suggestion_set_id, question_id}`;服务端会校验归属 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-chat/ceb9babb-1e30-41d7-817d-fd584954304b' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query": "彗尾的形状",
"knowledge_base_ids": ["kb-00000001"],
"agent_id": "builtin-quick-answer"
}'
```
**响应格式**:
服务器端事件流(Server-Sent Events,Content-Type: text/event-stream)
**响应**:
```
event: message
data: {"id":"3475c004-0ada-4306-9d30-d7f5efce50d2","response_type":"references","content":"","done":false,"knowledge_references":[{"id":"c8347bef-...","content":"彗星xxx。","knowledge_id":"a6790b93-...","chunk_index":0,"knowledge_title":"彗星.txt","score":4.04,"match_type":3,"chunk_type":"text","knowledge_filename":"彗星.txt"}]}
event: message
data: {"id":"3475c004-0ada-4306-9d30-d7f5efce50d2","response_type":"answer","content":"彗尾的形状主要表现为...","done":false,"knowledge_references":null}
event: message
data: {"id":"3475c004-0ada-4306-9d30-d7f5efce50d2","response_type":"answer","content":"","done":true,"knowledge_references":null}
```
## POST `/agent-chat/:session_id` - 基于 Agent 的智能问答
Agent 模式支持更智能的问答,包括工具调用、网络搜索、多知识库检索等能力。
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `query` | string | 是 | 查询文本 |
| `knowledge_base_ids` | string[] | 否 | 知识库 ID 列表,可动态指定本次查询使用的知识库 |
| `knowledge_ids` | string[] | 否 | 知识文件 ID 列表,可动态指定本次查询使用的具体文件 |
| `agent_enabled` | bool | 否 | 是否启用 Agent 模式(默认 false,优先使用 Agent 配置) |
| `agent_id` | string | 否 | 自定义 Agent ID,指定使用的智能体(支持共享 Agent) |
| `web_search_enabled` | bool | 否 | 是否启用网络搜索(默认 false) |
| `summary_model_id` | string | 否 | 覆盖默认的摘要模型 ID |
| `mentioned_items` | object[] | 否 | @提及的知识库和文件列表 |
| `disable_title` | bool | 否 | 是否禁用自动标题生成(默认 false) |
| `images` | object[] | 否 | 附带的图片(base64 格式),需要 Agent 启用图片上传 |
| `channel` | string | 否 | 来源渠道标识:`web`、`api`、`im`、`browser_extension` |
| `suggestion_attribution` | object | 否 | 用户从推荐问题发起本轮时传入 `{suggestion_set_id, question_id}`;服务端会校验归属 |
## 回答后推荐问题
回答主消息完成后,服务端会异步生成推荐问题,不阻塞 SSE 的 `complete`/`done` 事件。生成结果按“空间、助手消息、位置、配置快照、语言”持久化并去重。
```http
POST /api/v1/sessions/{session_id}/messages/{message_id}/suggestions
Content-Type: application/json
{"regenerate": false}
```
状态包括 `generating`、`ready`、`suppressed`、`failed`。`ready` 时的每个问题都有稳定 `id`,点击后应先上报事件,并在下一次聊天请求中携带 `suggestion_attribution`。
```http
POST /api/v1/sessions/{session_id}/suggestion-events
Content-Type: application/json
{
"suggestion_set_id": "...",
"question_id": "...",
"event_type": "click"
}
```
网页嵌入提供同构接口:`/api/v1/embed/{channel_id}/sessions/{session_id}/...`,继续使用嵌入令牌和 `X-Embed-Session`。
**mentioned_items 结构**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 知识库或文件 ID |
| `name` | string | 显示名称 |
| `type` | string | 类型:`kb`(知识库)或 `file`(文件) |
| `kb_type` | string | 知识库类型:`document` 或 `faq`(仅 `type=kb` 时) |
**images 结构**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `data` | string | base64 编码的图片数据(`data:image/png;base64,...`) |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/agent-chat/ceb9babb-1e30-41d7-817d-fd584954304b' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query": "帮我查询今天的天气",
"agent_enabled": true,
"web_search_enabled": true,
"knowledge_base_ids": ["kb-00000001"],
"agent_id": "builtin-smart-reasoning",
"mentioned_items": [
{
"id": "kb-00000001",
"name": "天气知识库",
"type": "kb",
"kb_type": "document"
}
]
}'
```
**响应格式**:
服务器端事件流(Server-Sent Events,Content-Type: text/event-stream)
**响应类型说明**:
| response_type | 描述 |
|---------------|------|
| `agent_query` | Agent 开始处理查询 |
| `thinking` | Agent 思考过程 |
| `tool_call` | 工具调用信息 |
| `tool_result` | 工具调用结果 |
| `references` | 知识库检索引用 |
| `answer` | 最终回答内容 |
| `artifacts_pending` | Skill/沙箱产物正在上传;`data.count` 为待保存文件数。回答可能已经 `done`,文件按钮会在此期间显示加载态,直至 `complete` 带上 `artifacts` |
| `reflection` | Agent 反思内容 |
| `session_title` | 自动生成的会话标题 |
| `error` | 错误信息 |
**响应示例**:
```
event: message
data: {"id":"req-001","response_type":"thinking","content":"用户想查询天气,我需要使用网络搜索工具...","done":false}
event: message
data: {"id":"req-001","response_type":"tool_call","content":"","done":false,"data":{"tool_name":"web_search","arguments":{"query":"今天天气"}}}
event: message
data: {"id":"req-001","response_type":"tool_result","content":"搜索结果:今天晴,气温25°C...","done":false}
event: message
data: {"id":"req-001","response_type":"answer","content":"根据查询结果,今天天气晴朗,气温约25°C。","done":false}
event: message
data: {"id":"req-001","response_type":"answer","content":"","done":true}
```
+220
View File
@@ -0,0 +1,220 @@
# 分块管理 API
[返回目录](./README.md)
| 方法 | 路径 | 描述 |
| ------ | --------------------------------- | -------------------------- |
| GET | `/chunks/:knowledge_id` | 获取知识的分块列表 |
| PUT | `/chunks/:knowledge_id/:id` | 更新分块 |
| DELETE | `/chunks/:knowledge_id/:id` | 删除单个分块 |
| DELETE | `/chunks/:knowledge_id` | 删除知识下的所有分块 |
| GET | `/chunks/by-id/:id` | 根据分块 ID 直接获取分块 |
| DELETE | `/chunks/by-id/:id/questions` | 删除分块下的某个生成问题 |
## GET `/chunks/:knowledge_id` - 获取知识的分块列表
**路径参数**:
| 字段 | 类型 | 说明 |
| ------------- | ------ | ----------- |
| knowledge_id | string | 知识 ID |
**查询参数**:
| 字段 | 类型 | 默认 | 说明 |
| ---------- | ---- | ---- | ---------- |
| page | int | 1 | 页码 |
| page_size | int | 20 | 每页条数 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/chunks/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5?page=1&page_size=1' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"id": "df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7",
"tenant_id": 1,
"knowledge_id": "4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5",
"knowledge_base_id": "kb-00000001",
"tag_id": "",
"content": "彗星xxxx",
"chunk_index": 0,
"is_enabled": true,
"status": 2,
"start_at": 0,
"end_at": 964,
"pre_chunk_id": "",
"next_chunk_id": "",
"chunk_type": "text",
"parent_chunk_id": "",
"relation_chunks": null,
"indirect_relation_chunks": null,
"metadata": null,
"content_hash": "",
"image_info": "",
"created_at": "2025-08-12T11:52:36.168632+08:00",
"updated_at": "2025-08-12T11:52:53.376871+08:00",
"deleted_at": null
}
],
"page": 1,
"page_size": 1,
"success": true,
"total": 5
}
```
## PUT `/chunks/:knowledge_id/:id` - 更新分块
更新指定分块的内容和属性。所有字段均可选,未传则保留原值。
**路径参数**:
| 字段 | 类型 | 说明 |
| ------------- | ------ | ----------- |
| knowledge_id | string | 知识 ID |
| id | string | 分块 ID |
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ------------ | ------- | ---- | --------------------- |
| content | string | 否 | 分块内容 |
| chunk_index | int | 否 | 分块在知识中的序号 |
| is_enabled | boolean | 否 | 是否启用 |
| start_at | int | 否 | 起始位置(字符偏移) |
| end_at | int | 否 | 结束位置(字符偏移) |
| image_info | string | 否 | 图像分块的元信息(JSON 字符串) |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/chunks/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"content": "更新后的分块内容",
"is_enabled": true
}'
```
**响应**:
```json
{
"data": {
"id": "df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7",
"content": "更新后的分块内容",
"is_enabled": true,
"...": "其他字段同 GET 响应"
},
"success": true
}
```
## DELETE `/chunks/:knowledge_id/:id` - 删除单个分块
**路径参数**: 同 PUT。
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/chunks/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5/df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"message": "Chunk deleted",
"success": true
}
```
## DELETE `/chunks/:knowledge_id` - 删除知识下的所有分块
**路径参数**:
| 字段 | 类型 | 说明 |
| ------------- | ------ | ----------- |
| knowledge_id | string | 知识 ID |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/chunks/4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"message": "All chunks under knowledge deleted",
"success": true
}
```
## GET `/chunks/by-id/:id` - 根据 ID 直接获取分块
无需提供 `knowledge_id` 即可获取分块。常用于跨知识库的引用展示。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | ------- |
| id | string | 分块 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/chunks/by-id/df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**: 同 `GET /chunks/:knowledge_id` 列表中的单条 data。
## DELETE `/chunks/by-id/:id/questions` - 删除分块下的某个生成问题
删除指定分块关联的某条生成问题。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | ------- |
| id | string | 分块 ID |
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------- | ------ | ---- | ----------- |
| question_id | string | 是 | 问题 ID |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/chunks/by-id/df10b37d-cd05-4b14-ba8a-e1bd0eb3bbd7/questions' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"question_id": "q-00000001"
}'
```
**响应**:
```json
{
"message": "Question deleted successfully",
"success": true
}
```
+161
View File
@@ -0,0 +1,161 @@
# 评估功能 API
[返回目录](./README.md)
| 方法 | 路径 | 描述 |
| ---- | -------------- | --------------------- |
| GET | `/evaluation/` | 获取评估任务结果 |
| POST | `/evaluation/` | 创建评估任务 |
> 注:服务端路由带尾斜杠(Gin 会自动从 `/evaluation` 重定向到 `/evaluation/`),下方示例为方便阅读用了 `/evaluation`。
## GET `/evaluation` - 获取评估任务结果
**参数说明(查询参数)**:
| 字段 | 类型 | 必填 | 说明 |
| -------- | ------ | ---- | --------------------------------------------------- |
| task_id | string | 是 | 从 `POST /evaluation` 返回的任务 ID |
**请求**:
```bash
curl --location 'http://localhost:8080/api/v1/evaluation?task_id=c34563ad-b09f-4858-b72e-e92beb80becb' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"task": {
"id": "c34563ad-b09f-4858-b72e-e92beb80becb",
"tenant_id": 1,
"dataset_id": "default",
"start_time": "2025-08-12T14:54:26.221804768+08:00",
"status": 2,
"total": 1,
"finished": 1
},
"params": {
"session_id": "",
"knowledge_base_id": "2ef57434-8c8d-4442-b967-2f7fc578a2fc",
"vector_threshold": 0.5,
"keyword_threshold": 0.3,
"embedding_top_k": 10,
"vector_database": "",
"rerank_model_id": "b30171a1-787b-426e-a293-735cd5ac16c0",
"rerank_top_k": 5,
"rerank_threshold": 0.7,
"chat_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
"summary_config": {
"max_tokens": 0,
"repeat_penalty": 1,
"top_k": 0,
"top_p": 0,
"frequency_penalty": 0,
"presence_penalty": 0,
"prompt": "这是用户和助手之间的对话。",
"context_template": "你是一个专业的智能信息检索助手",
"no_match_prefix": "<think>\n</think>\nNO_MATCH",
"temperature": 0.3,
"seed": 0,
"max_completion_tokens": 2048
},
"fallback_strategy": "",
"fallback_response": "抱歉,我无法回答这个问题。"
},
"metric": {
"retrieval_metrics": {
"precision": 0,
"recall": 0,
"ndcg3": 0,
"ndcg10": 0,
"mrr": 0,
"map": 0
},
"generation_metrics": {
"bleu1": 0.037656734016532384,
"bleu2": 0.04067392145167686,
"bleu4": 0.048963321289052536,
"rouge1": 0,
"rouge2": 0,
"rougel": 0
}
}
},
"success": true
}
```
## POST `/evaluation` - 创建评估任务
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------------- | ------ | ---- | ----------------------------------------------- |
| dataset_id | string | 是 | 评估数据集,目前仅支持 `default`(官方测试集) |
| knowledge_base_id | string | 是 | 评估使用的知识库 ID |
| chat_id | string | 是 | 评估使用的对话模型 ID |
| rerank_id | string | 是 | 评估使用的重排序模型 ID |
**请求**:
```bash
curl --location 'http://localhost:8080/api/v1/evaluation' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"dataset_id": "default",
"knowledge_base_id": "kb-00000001",
"chat_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
"rerank_id": "b30171a1-787b-426e-a293-735cd5ac16c0"
}'
```
**响应**:
```json
{
"data": {
"task": {
"id": "c34563ad-b09f-4858-b72e-e92beb80becb",
"tenant_id": 1,
"dataset_id": "default",
"start_time": "2025-08-12T14:54:26.221804768+08:00",
"status": 1
},
"params": {
"session_id": "",
"knowledge_base_id": "2ef57434-8c8d-4442-b967-2f7fc578a2fc",
"vector_threshold": 0.5,
"keyword_threshold": 0.3,
"embedding_top_k": 10,
"vector_database": "",
"rerank_model_id": "b30171a1-787b-426e-a293-735cd5ac16c0",
"rerank_top_k": 5,
"rerank_threshold": 0.7,
"chat_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
"summary_config": {
"max_tokens": 0,
"repeat_penalty": 1,
"top_k": 0,
"top_p": 0,
"frequency_penalty": 0,
"presence_penalty": 0,
"prompt": "这是用户和助手之间的对话。",
"context_template": "你是一个专业的智能信息检索助手,xxx",
"no_match_prefix": "<think>\n</think>\nNO_MATCH",
"temperature": 0.3,
"seed": 0,
"max_completion_tokens": 2048
},
"fallback_strategy": "",
"fallback_response": "抱歉,我无法回答这个问题。"
}
},
"success": true
}
```
+562
View File
@@ -0,0 +1,562 @@
# FAQ管理 API
[返回目录](./README.md)
FAQ 接口分为两组:
- `/knowledge-bases/:id/faq/*`:知识库范围内的 FAQ 条目 CRUD、批量操作、搜索与导入导出。
- `/faq/import/progress/:task_id`:**不属于知识库分组**,用于查询异步导入/dry-run 任务的进度,仅需任务 ID 即可调用。
| 方法 | 路径 | 描述 |
| ------ | ----------------------------------------------------------------- | --------------------------------- |
| GET | `/knowledge-bases/:id/faq/entries` | 获取 FAQ 条目列表 |
| GET | `/knowledge-bases/:id/faq/entries/export` | 导出 FAQ 条目(CSV) |
| GET | `/knowledge-bases/:id/faq/entries/:entry_id` | 获取单个 FAQ 条目(按 seq_id) |
| POST | `/knowledge-bases/:id/faq/entries` | 批量 Upsert FAQ 条目(异步) |
| POST | `/knowledge-bases/:id/faq/entry` | 同步创建单个 FAQ 条目 |
| PUT | `/knowledge-bases/:id/faq/entries/:entry_id` | 更新单个 FAQ 条目 |
| POST | `/knowledge-bases/:id/faq/entries/:entry_id/similar-questions` | 为 FAQ 条目追加相似问 |
| PUT | `/knowledge-bases/:id/faq/entries/fields` | 批量更新字段(启用/推荐/标签) |
| PUT | `/knowledge-bases/:id/faq/entries/tags` | 批量更新标签 |
| DELETE | `/knowledge-bases/:id/faq/entries` | 批量删除 FAQ 条目 |
| POST | `/knowledge-bases/:id/faq/search` | FAQ 混合搜索 |
| PUT | `/knowledge-bases/:id/faq/import/last-result/display` | 更新上次导入结果卡片显示状态 |
| GET | `/faq/import/progress/:task_id` | 查询 FAQ 导入任务进度(公共) |
> **路径参数说明**:`:entry_id` 始终是 FAQ 条目的 `seq_id`(整数),不是字符串形式的 ID。同理,批量接口中的 `by_id` / `by_tag` / `exclude_ids` / `ids` 字段均为 `seq_id` 列表(整数)。
## GET `/knowledge-bases/:id/faq/entries` - 获取 FAQ 条目列表
支持分页、按标签过滤、关键字搜索与排序。
**查询参数**:
| 参数 | 类型 | 必填 | 说明 |
| ------------ | ------ | ---- | --------------------------------------------------------------------------------------------- |
| page | int | 否 | 页码,默认 1 |
| page_size | int | 否 | 每页数量,默认 20 |
| tag_id | int | 否 | 按标签 `seq_id` 过滤 |
| keyword | string | 否 | 关键字搜索 |
| search_field | string | 否 | 搜索字段:`standard_question` / `similar_questions` / `answers`,留空则全字段搜索 |
| sort_order | string | 否 | 排序方式,`asc` 表示按更新时间正序,默认按更新时间倒序 |
**请求**:
```curl
# 全字段搜索
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries?page=1&page_size=10&keyword=密码' \
--header 'X-API-Key: sk-xxxxx'
# 仅搜索标准问
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries?keyword=密码&search_field=standard_question' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"data": {
"total": 100,
"page": 1,
"page_size": 10,
"data": [
{
"id": 1,
"chunk_id": "chunk-00000001",
"knowledge_id": "knowledge-00000001",
"knowledge_base_id": "kb-00000001",
"tag_id": 12,
"tag_name": "账户",
"is_enabled": true,
"is_recommended": false,
"standard_question": "如何重置密码?",
"similar_questions": ["忘记密码怎么办", "密码找回"],
"negative_questions": ["如何修改用户名"],
"answers": ["您可以通过点击登录页面的'忘记密码'链接来重置密码。"],
"answer_strategy": "all",
"index_mode": "hybrid",
"chunk_type": "faq",
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T10:00:00+08:00"
}
]
},
"success": true
}
```
## GET `/knowledge-bases/:id/faq/entries/export` - 导出 FAQ 条目
将知识库下的所有 FAQ 条目导出为 CSV(UTF-8 带 BOM,Excel 兼容)。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/export' \
--header 'X-API-Key: sk-xxxxx' \
--output faq_export.csv
```
**响应**: `Content-Type: text/csv; charset=utf-8`,附带文件名 `faq_export.csv`。
## GET `/knowledge-bases/:id/faq/entries/:entry_id` - 获取单个 FAQ 条目
根据 `seq_id`(整数)获取单个 FAQ 条目详情。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/1' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"data": {
"id": 1,
"chunk_id": "chunk-00000001",
"knowledge_id": "knowledge-00000001",
"knowledge_base_id": "kb-00000001",
"tag_id": 12,
"tag_name": "账户",
"is_enabled": true,
"is_recommended": false,
"standard_question": "如何重置密码?",
"similar_questions": ["忘记密码怎么办", "密码找回"],
"negative_questions": [],
"answers": ["您可以通过点击登录页面的'忘记密码'链接来重置密码。"],
"answer_strategy": "all",
"index_mode": "hybrid",
"chunk_type": "faq",
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T10:00:00+08:00"
},
"success": true
}
```
## POST `/knowledge-bases/:id/faq/entries` - 批量 Upsert FAQ 条目(异步)
**异步**批量导入或更新 FAQ 条目。接口立即返回 `task_id`,调用方需通过 `GET /faq/import/progress/:task_id` 查询进度与结果。
支持 `dry_run=true`:异步执行仅校验(格式 / 批内重复 / 与库内重复 / 内容安全),不实际写入。
**请求体(`types.FAQBatchUpsertPayload`)**:
| 字段 | 类型 | 必填 | 说明 |
| ------------ | -------------------------- | ---- | ----------------------------------------------------------------------------------- |
| entries | `[]FAQEntryPayload` | 是 | FAQ 条目数组 |
| mode | string | 是 | `append` 或 `replace`(替换会清空已有条目) |
| knowledge_id | string | 否 | 关联的 FAQ Knowledge ID(不传则使用知识库默认 FAQ knowledge) |
| task_id | string | 否 | 任务 ID,不传则自动生成 UUID |
| dry_run | boolean | 否 | 仅验证不导入 |
`FAQEntryPayload` 字段:
| 字段 | 类型 | 必填 | 说明 |
| ------------------- | --------- | ---- | ------------------------------------------------------------- |
| id | int64 | 否 | 指定 `seq_id`(数据迁移场景,需小于自增起始值 100000000) |
| standard_question | string | 是 | 标准问 |
| similar_questions | string[] | 否 | 相似问列表 |
| negative_questions | string[] | 否 | 反例问题列表 |
| answers | string[] | 否 | 答案列表 |
| answer_strategy | string | 否 | 答案返回策略:`all` 或 `random` |
| tag_id | int64 | 否 | 标签 `seq_id` |
| tag_name | string | 否 | 标签名(用于按名匹配标签) |
| is_enabled | boolean | 否 | 是否启用 |
| is_recommended | boolean | 否 | 是否推荐 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"mode": "append",
"entries": [
{
"standard_question": "如何联系客服?",
"similar_questions": ["客服电话", "在线客服"],
"answers": ["您可以通过拨打400-xxx-xxxx联系我们的客服。"],
"tag_id": 1
},
{
"standard_question": "退款政策是什么?",
"answers": ["我们提供7天无理由退款服务。"]
}
]
}'
```
**响应**:
```json
{
"data": { "task_id": "task-00000001" },
"success": true
}
```
> 用 `GET /faq/import/progress/:task_id` 查询任务最终状态。
## POST `/knowledge-bases/:id/faq/entry` - 同步创建单个 FAQ 条目
**同步**创建单条 FAQ 条目,会即时校验标准问/相似问与库内已有条目的重复。
**请求体**: 同上 `FAQEntryPayload`(`standard_question` 必填)。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entry' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"standard_question": "如何联系客服?",
"similar_questions": ["客服电话", "在线客服"],
"answers": ["您可以通过拨打400-xxx-xxxx联系我们的客服。"],
"tag_id": 1,
"is_enabled": true
}'
```
**响应**:
```json
{
"data": {
"id": 1,
"chunk_id": "chunk-00000001",
"knowledge_id": "knowledge-00000001",
"knowledge_base_id": "kb-00000001",
"tag_id": 1,
"tag_name": "客服",
"is_enabled": true,
"is_recommended": false,
"standard_question": "如何联系客服?",
"similar_questions": ["客服电话", "在线客服"],
"negative_questions": [],
"answers": ["您可以通过拨打400-xxx-xxxx联系我们的客服。"],
"answer_strategy": "all",
"index_mode": "hybrid",
"chunk_type": "faq",
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T10:00:00+08:00"
},
"success": true
}
```
**错误响应**(标准问或相似问重复时):
```json
{
"success": false,
"error": {
"code": "BAD_REQUEST",
"message": "标准问与已有FAQ重复"
}
}
```
## PUT `/knowledge-bases/:id/faq/entries/:entry_id` - 更新单个 FAQ 条目
按 `seq_id` 更新单条 FAQ 条目,请求体同 `FAQEntryPayload`。
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/1' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"standard_question": "如何重置账户密码?",
"similar_questions": ["忘记密码怎么办", "密码找回", "重置密码"],
"answers": ["您可以通过以下步骤重置密码:1. 点击登录页面的\"忘记密码\" 2. 输入注册邮箱 3. 查收重置邮件"],
"is_enabled": true
}'
```
**响应**: 返回更新后的 FAQ 条目,结构同创建接口。
## POST `/knowledge-bases/:id/faq/entries/:entry_id/similar-questions` - 追加相似问
向指定 FAQ 条目(`seq_id`)追加相似问。
**请求体**:
| 字段 | 类型 | 必填 | 说明 |
| ----------------- | -------- | ---- | --------------------- |
| similar_questions | string[] | 是 | 要追加的相似问数组 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/1/similar-questions' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"similar_questions": ["怎样修改密码", "密码重置方法"]
}'
```
**响应**: 返回追加后的完整 FAQ 条目。
## PUT `/knowledge-bases/:id/faq/entries/fields` - 批量更新字段
**统一**的批量字段更新接口,支持同时更新 `is_enabled` / `is_recommended` / `tag_id`,并支持两种作用域:
- **按条目 ID** (`by_id`):键为条目 `seq_id`,值为该条目要更新的字段。
- **按标签 ID** (`by_tag`):键为标签 `seq_id`,对该标签下的所有条目应用相同的字段更新;可配合 `exclude_ids` 排除部分条目。
`by_id` 和 `by_tag` 至少传一项;二者可同时使用。
**请求体(`types.FAQEntryFieldsBatchUpdate`)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------- | --------------------------------- | ---- | ------------------------------------------ |
| by_id | `map[int64]FAQEntryFieldsUpdate` | 否 | 按条目 `seq_id` 更新 |
| by_tag | `map[int64]FAQEntryFieldsUpdate` | 否 | 按标签 `seq_id` 对该标签下所有条目更新 |
| exclude_ids | `int64[]` | 否 | 与 `by_tag` 配合使用,排除指定条目 `seq_id` |
`FAQEntryFieldsUpdate` 字段(全部可选,仅传入的字段会被更新):
| 字段 | 类型 | 说明 |
| -------------- | ------- | -------------- |
| is_enabled | boolean | 是否启用 |
| is_recommended | boolean | 是否推荐 |
| tag_id | int64 | 标签 `seq_id` |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/fields' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"by_id": {
"1": {"is_enabled": true, "is_recommended": false},
"2": {"is_enabled": false}
},
"by_tag": {
"100": {"is_recommended": true}
},
"exclude_ids": [3, 4]
}'
```
**响应**:
```json
{ "success": true }
```
## PUT `/knowledge-bases/:id/faq/entries/tags` - 批量更新标签
仅更新标签关联。键为条目 `seq_id`,值为目标标签 `seq_id`;值传 `null` 表示清除标签。
**请求体**:
| 字段 | 类型 | 必填 | 说明 |
| ------- | --------------------- | ---- | -------------------------------------------- |
| updates | `map[int64]int64?` | 是 | 键:条目 `seq_id`;值:标签 `seq_id` 或 `null` |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries/tags' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"updates": {
"1": 10,
"2": 11,
"3": null
}
}'
```
**响应**:
```json
{ "success": true }
```
## DELETE `/knowledge-bases/:id/faq/entries` - 批量删除
**请求体**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | --------- | ---- | --------------------------------- |
| ids | `int64[]` | 是 | 要删除的 FAQ 条目 `seq_id` 列表 |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/entries' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"ids": [1, 2, 3]
}'
```
**响应**:
```json
{ "success": true }
```
## POST `/knowledge-bases/:id/faq/search` - FAQ 混合搜索
向量 + 关键字混合检索,支持两级优先级标签召回。
**请求体(`types.FAQSearchRequest`)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------------------- | --------- | ---- | ----------------------------------------------------------------------------------- |
| query_text | string | 是 | 搜索文本 |
| vector_threshold | float | 否 | 向量相似度阈值(0–1) |
| match_count | int | 否 | 返回数量,默认 10,最大 200 |
| first_priority_tag_ids | `int64[]` | 否 | 第一优先级标签 `seq_id` 列表(最高优先召回范围) |
| second_priority_tag_ids | `int64[]` | 否 | 第二优先级标签 `seq_id` 列表 |
| only_recommended | boolean | 否 | 是否仅返回 `is_recommended=true` 的条目 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/search' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query_text": "如何重置密码",
"vector_threshold": 0.5,
"match_count": 10,
"first_priority_tag_ids": [12],
"only_recommended": false
}'
```
**响应**:
```json
{
"data": [
{
"id": 1,
"chunk_id": "chunk-00000001",
"knowledge_id": "knowledge-00000001",
"knowledge_base_id": "kb-00000001",
"tag_id": 12,
"tag_name": "账户",
"is_enabled": true,
"is_recommended": false,
"standard_question": "如何重置密码?",
"similar_questions": ["忘记密码怎么办", "密码找回"],
"answers": ["您可以通过点击登录页面的'忘记密码'链接来重置密码。"],
"answer_strategy": "all",
"chunk_type": "faq",
"score": 0.95,
"match_type": "vector",
"matched_question": "忘记密码怎么办",
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T10:00:00+08:00"
}
],
"success": true
}
```
## PUT `/knowledge-bases/:id/faq/import/last-result/display` - 更新上次导入结果显示状态
控制上次导入完成后,前端结果卡片的显示/隐藏。
**请求体**:
| 字段 | 类型 | 必填 | 说明 |
| -------------- | ------ | ---- | --------------------- |
| display_status | string | 是 | `open` 或 `close` |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/faq/import/last-result/display' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"display_status": "close"
}'
```
**响应**:
```json
{ "success": true }
```
## GET `/faq/import/progress/:task_id` - 查询 FAQ 导入进度
> **注意**:此接口**不在** `/knowledge-bases/:id/faq` 分组下,路径直接以 `/faq/import/progress/:task_id` 开头。任务 ID 由 `POST /knowledge-bases/:id/faq/entries` 返回。
**路径参数**:
| 参数 | 类型 | 说明 |
| ------- | ------ | -------------- |
| task_id | string | 导入任务的 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/faq/import/progress/task-00000001' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**(节选关键字段):
```json
{
"data": {
"task_id": "task-00000001",
"kb_id": "kb-00000001",
"knowledge_id": "knowledge-00000001",
"status": "completed",
"progress": 100,
"total": 100,
"processed": 100,
"success_count": 95,
"failed_count": 3,
"partial_failed_count": 2,
"skipped_count": 0,
"failed_entries": [
{
"index": 5,
"reason": "标准问与已有FAQ重复",
"standard_question": "重复的问题"
}
],
"success_entries": [
{ "index": 0, "seq_id": 101, "standard_question": "如何联系客服?" }
],
"message": "",
"error": "",
"created_at": 1736582400,
"updated_at": 1736582460,
"dry_run": false,
"import_mode": "append",
"imported_at": "2025-08-12T10:01:00+08:00",
"display_status": "open",
"processing_time": 60000
},
"success": true
}
```
`status` 可能取值:`pending` / `processing` / `completed` / `failed`。
当失败条目过多时,`failed_entries` 可能不直接返回,而通过 `failed_entries_url` 提供 CSV 下载地址。
`dry_run=true` 模式下的任务同样通过此接口查询,`success_entries` 中的 `seq_id` 不会真正写入。
+402
View File
@@ -0,0 +1,402 @@
# 初始化配置 API
[返回目录](./README.md)
| 方法 | 路径 | 描述 |
| ------ | ------------------------------------------------- | -------------------------- |
| GET | `/initialization/config/:kb_id` | 获取知识库初始化配置 |
| POST | `/initialization/initialize/:kb_id` | 初始化知识库模型配置 |
| PUT | `/initialization/config/:kb_id` | 更新知识库模型配置 |
| GET | `/initialization/ollama/status` | 检查 Ollama 状态 |
| GET | `/initialization/ollama/models` | 获取本地 Ollama 模型列表 |
| POST | `/initialization/ollama/models/check` | 检查 Ollama 模型是否可用 |
| POST | `/initialization/ollama/models/download` | 下载 Ollama 模型 |
| GET | `/initialization/ollama/download/progress/:task_id` | 获取下载进度 |
| GET | `/initialization/ollama/download/tasks` | 获取所有下载任务 |
| POST | `/initialization/remote/check` | 检查远程模型 API |
| POST | `/initialization/embedding/test` | 测试嵌入模型 |
| POST | `/initialization/rerank/check` | 检查重排序模型 |
| POST | `/initialization/multimodal/test` | 测试多模态模型 |
| POST | `/initialization/extract/text-relation` | 提取文本关系 |
## GET `/initialization/config/:kb_id` - 获取知识库初始化配置
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/config/kb-00000001' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"chat_model_id": "model-00000001",
"embedding_model_id": "model-00000002",
"rerank_model_id": "model-00000003",
"multimodal_id": "model-00000004"
},
"success": true
}
```
## POST `/initialization/initialize/:kb_id` - 初始化知识库模型配置
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/initialize/kb-00000001' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"chat_model_id": "model-00000001",
"embedding_model_id": "model-00000002",
"rerank_model_id": "model-00000003",
"multimodal_id": "model-00000004"
}'
```
**响应**:
```json
{
"success": true
}
```
## PUT `/initialization/config/:kb_id` - 更新知识库模型配置
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/initialization/config/kb-00000001' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"chat_model_id": "model-00000010",
"embedding_model_id": "model-00000002"
}'
```
**响应**:
```json
{
"success": true
}
```
## GET `/initialization/ollama/status` - 检查 Ollama 状态
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/ollama/status' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"available": true
},
"success": true
}
```
## GET `/initialization/ollama/models` - 获取本地 Ollama 模型列表
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/ollama/models' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"name": "llama3:8b",
"size": 4661211648,
"modified_at": "2025-08-10T15:30:00+08:00"
},
{
"name": "nomic-embed-text:latest",
"size": 274302976,
"modified_at": "2025-08-11T09:00:00+08:00"
}
],
"success": true
}
```
## POST `/initialization/ollama/models/check` - 检查 Ollama 模型是否可用
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/ollama/models/check' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"models": ["llama3:8b", "nomic-embed-text:latest", "mistral:7b"]
}'
```
**响应**:
```json
{
"data": {
"llama3:8b": true,
"nomic-embed-text:latest": true,
"mistral:7b": false
},
"success": true
}
```
## POST `/initialization/ollama/models/download` - 下载 Ollama 模型
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/ollama/models/download' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"model": "mistral:7b"
}'
```
**响应**:
```json
{
"data": {
"id": "task-00000001",
"modelName": "mistral:7b",
"status": "downloading",
"progress": 0,
"message": "开始下载",
"startTime": "2025-08-12T10:00:00+08:00"
},
"success": true
}
```
## GET `/initialization/ollama/download/progress/:task_id` - 获取下载进度
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/ollama/download/progress/task-00000001' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"id": "task-00000001",
"modelName": "mistral:7b",
"status": "downloading",
"progress": 45.6,
"message": "正在下载 2.1GB / 4.6GB",
"startTime": "2025-08-12T10:00:00+08:00"
},
"success": true
}
```
## GET `/initialization/ollama/download/tasks` - 获取所有下载任务
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/ollama/download/tasks' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"id": "task-00000001",
"modelName": "mistral:7b",
"status": "completed",
"progress": 100,
"message": "下载完成",
"startTime": "2025-08-12T10:00:00+08:00",
"endTime": "2025-08-12T10:15:00+08:00"
},
{
"id": "task-00000002",
"modelName": "llama3:70b",
"status": "downloading",
"progress": 30.2,
"message": "正在下载 12.5GB / 41.4GB",
"startTime": "2025-08-12T10:20:00+08:00"
}
],
"success": true
}
```
## POST `/initialization/remote/check` - 检查远程模型 API
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/remote/check' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"api_url": "https://api.openai.com/v1",
"api_key": "sk-xxxxx",
"model": "gpt-4o"
}'
```
**响应**:
```json
{
"data": {
"success": true,
"message": "模型可用"
},
"success": true
}
```
## POST `/initialization/embedding/test` - 测试嵌入模型
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/embedding/test' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"api_url": "https://api.openai.com/v1",
"api_key": "sk-xxxxx",
"model": "text-embedding-3-small"
}'
```
**响应**:
```json
{
"data": {
"success": true,
"message": "嵌入模型测试通过"
},
"success": true
}
```
## POST `/initialization/rerank/check` - 检查重排序模型
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/rerank/check' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"api_url": "https://api.cohere.ai/v1",
"api_key": "sk-xxxxx",
"model": "rerank-english-v3.0"
}'
```
**响应**:
```json
{
"data": {
"success": true,
"message": "重排序模型可用"
},
"success": true
}
```
## POST `/initialization/multimodal/test` - 测试多模态模型
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/multimodal/test' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"api_url": "https://api.openai.com/v1",
"api_key": "sk-xxxxx",
"model": "gpt-4o"
}'
```
**响应**:
```json
{
"data": {
"success": true,
"message": "多模态模型测试通过"
},
"success": true
}
```
## POST `/initialization/extract/text-relation` - 提取文本关系
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/initialization/extract/text-relation' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"text": "WeKnora 是一个知识管理平台,支持多种文档格式的解析和检索。",
"model_id": "model-00000001"
}'
```
**响应**:
```json
{
"data": {
"entities": [
{"name": "WeKnora", "type": "Product"},
{"name": "知识管理平台", "type": "Concept"}
],
"relations": [
{
"source": "WeKnora",
"target": "知识管理平台",
"relation": "is_a"
}
]
},
"success": true
}
```
+722
View File
@@ -0,0 +1,722 @@
# 知识库管理 API
[返回目录](./README.md)
**字段说明(知识库对象)**
- 知识库类型 `type` 为 `document`(文档)或 `faq`(FAQ),默认 `document`。
- JSON 中对象存储相关字段:**`storage_config`** 为序列化字段名(对应数据库列 `cos_config`,兼容旧数据)。旧客户端若仍发送或接收 `cos_config`,服务端会兼容解析;新集成请使用 **`storage_config`**。
- **`storage_provider_config`** 为新版存储提供者选择(如 `{"provider": "local"}`),与空间级存储引擎凭证配合使用;无配置时可为 `null`。
- 嵌套配置对象:`chunking_config`、`image_processing_config`、`vlm_config`、`asr_config`、`extract_config`、`faq_config`、`question_generation_config`、`auto_tag_config`。其中 `extract_config`、`faq_config`、`question_generation_config`、`auto_tag_config` 允许为 `null`。
- **`vector_store_id`** 为知识库绑定的向量存储 ID(参见 [vector-store.md](./vector-store.md))。未指定(或 `null`/`""`)时使用空间级默认的环境变量存储;一旦创建即不可修改。详情接口返回时会附带 `vector_store_name` / `vector_store_source` / `vector_store_engine_type` / `vector_store_status` 四个只读元数据字段,用于前端展示。
| 方法 | 路径 | 描述 |
| ------ | ----------------------------------------- | ------------------------ |
| POST | `/knowledge-bases` | 创建知识库 |
| GET | `/knowledge-bases` | 获取知识库列表 |
| GET | `/knowledge-bases/:id` | 获取知识库详情 |
| PUT | `/knowledge-bases/:id` | 更新知识库 |
| DELETE | `/knowledge-bases/:id` | 删除知识库 |
| PUT | `/knowledge-bases/:id/pin` | 置顶/取消置顶知识库 |
| POST | `/knowledge-bases/:id/hybrid-search` | 混合搜索(向量+关键词,推荐) |
| GET | `/knowledge-bases/:id/hybrid-search` | 混合搜索(兼容旧客户端,需 JSON 请求体) |
| POST | `/knowledge-bases/copy` | 拷贝知识库(异步任务) |
| GET | `/knowledge-bases/copy/progress/:task_id` | 获取拷贝进度 |
| POST | `/knowledge-bases/:id/duplicate` | 创建知识库副本(仅设置) |
| GET | `/knowledge-bases/:id/move-targets` | 获取可迁移目标知识库列表 |
## POST `/knowledge-bases` - 创建知识库
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------------------------- | ------- | ---- | --------------------------------------------------------------- |
| name | string | 是 | 知识库名称 |
| description | string | 否 | 知识库描述 |
| type | string | 否 | 知识库类型:`document`(默认)或 `faq` |
| is_temporary | boolean | 否 | 是否为临时知识库(默认 `false`,临时库通常不在 UI 列表中显示) |
| chunking_config | object | 否 | 分块配置(见下方示例) |
| image_processing_config | object | 否 | 图片处理配置 |
| embedding_model_id | string | 否 | Embedding 模型 ID |
| summary_model_id | string | 否 | 摘要模型 ID |
| vlm_config | object | 否 | VLM(视觉模型)配置 |
| asr_config | object | 否 | ASR(语音识别)配置 |
| storage_provider_config | object | 否 | 存储提供者选择,如 `{"provider": "local"}` |
| storage_config | object | 否 | 旧版 COS 存储凭证(兼容字段,新集成留空即可) |
| extract_config | object | 否 | 图谱抽取配置;`enabled=true` 时需提供 `text`/`tags`/`nodes`/`relations` |
| faq_config | object | 否 | FAQ 配置(仅 FAQ 类型知识库需要) |
| question_generation_config | object | 否 | 问题生成配置 |
| auto_tag_config | object | 否 | 文档自动标签配置,默认关闭;仅适用于 `document` 类型知识库 |
| vector_store_id | string | 否 | 绑定的向量存储 ID。不传或为空字符串等同于 `null`(使用环境变量默认存储)。指定时必须是调用者所在空间拥有的向量存储 UUID;创建后不可修改。无效 UUID / 跨空间 / 未注册到引擎的 ID 会返回 `400` |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: sk-xxxxx' \
--data '{
"name": "weknora",
"description": "weknora description",
"type": "document",
"is_temporary": false,
"chunking_config": {
"chunk_size": 1000,
"chunk_overlap": 200,
"separators": [
"."
],
"enable_multimodal": true,
"parser_engine_rules": [
{
"file_types": [".pdf", ".docx"],
"engine": "builtin"
}
],
"enable_parent_child": false,
"parent_chunk_size": 4096,
"child_chunk_size": 384
},
"image_processing_config": {
"model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e"
},
"embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
"summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
"vlm_config": {
"enabled": true,
"model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e"
},
"asr_config": {
"enabled": false,
"model_id": "",
"language": ""
},
"storage_provider_config": {
"provider": "local"
},
"storage_config": {
"secret_id": "",
"secret_key": "",
"region": "",
"bucket_name": "",
"app_id": "",
"path_prefix": ""
},
"extract_config": null,
"faq_config": null,
"question_generation_config": {
"enabled": false,
"question_count": 3
},
"auto_tag_config": {
"enabled": true,
"model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
"max_tags": 3,
"skip_if_tagged": true
},
"vector_store_id": "550e8400-e29b-41d4-a716-446655440000"
}'
```
**响应**:
```json
{
"data": {
"id": "b5829e4a-3845-4624-a7fb-ea3b35e843b0",
"name": "weknora",
"description": "weknora description",
"type": "document",
"is_temporary": false,
"tenant_id": 1,
"chunking_config": {
"chunk_size": 1000,
"chunk_overlap": 200,
"separators": [
"."
],
"enable_multimodal": true,
"parser_engine_rules": [
{
"file_types": [".pdf", ".docx"],
"engine": "builtin"
}
],
"enable_parent_child": false,
"parent_chunk_size": 4096,
"child_chunk_size": 384
},
"image_processing_config": {
"model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e"
},
"embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
"summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
"vlm_config": {
"enabled": true,
"model_id": "f2083ad7-63e3-486d-a610-e6c56e58d72e"
},
"asr_config": {
"enabled": false,
"model_id": "",
"language": ""
},
"storage_provider_config": {
"provider": "local"
},
"storage_config": {
"secret_id": "",
"secret_key": "",
"region": "",
"bucket_name": "",
"app_id": "",
"path_prefix": ""
},
"extract_config": null,
"faq_config": null,
"question_generation_config": {
"enabled": false,
"question_count": 3
},
"auto_tag_config": {
"enabled": true,
"model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
"max_tags": 3,
"skip_if_tagged": true
},
"is_pinned": false,
"pinned_at": null,
"knowledge_count": 0,
"chunk_count": 0,
"processing_count": 0,
"vector_store_id": "550e8400-e29b-41d4-a716-446655440000",
"vector_store_name": "elasticsearch-hot",
"vector_store_source": "user",
"vector_store_engine_type": "elasticsearch",
"vector_store_status": "available",
"created_at": "2025-08-12T11:30:09.206238645+08:00",
"updated_at": "2025-08-12T11:30:09.206238854+08:00",
"deleted_at": null
},
"success": true
}
```
### 自动标签配置
`auto_tag_config` 在文档解析完成后异步调用聊天模型,从知识库已有标签中选择匹配项并增量关联到文档。该过程不会创建新标签,也不会删除或覆盖人工添加的标签。
| 字段 | 类型 | 默认值 | 说明 |
| ---------- | ------- | ------ | ---- |
| `enabled` | boolean | `false` | 是否启用自动标签 |
| `model_id` | string | `""` | 使用的聊天模型 ID;为空时使用知识库的 `summary_model_id` |
| `max_tags` | integer | `3` | 单个文档最多自动关联的标签数,取值范围为 `1` 到 `10` |
| `skip_if_tagged` | boolean | `true` | 文档已有标签时是否跳过自动标签。开启时不会调用模型,可避免稀释人工分类;设为 `false` 则在已有标签基础上追加 |
自动标签仅对启用该配置后新解析或重新解析的文档生效。模型调用失败不会阻塞文档解析完成,异步任务会按照任务队列策略重试。
候选标签按知识库排序取前 500 个参与分类;标签数超出时会记录告警并使用该前缀,不会跳过任务。模型按候选序号返回结果,服务端会校验序号范围并映射回标签 ID,越界或重复的序号将被丢弃。
**`vector_store_*` 响应字段说明**:
| 字段 | 类型 | 说明 |
| -------------------------- | ------ | ---------------------------------------------------------------------------------------------------------- |
| `vector_store_id` | string | 绑定的向量存储 ID(创建时未指定时为 `null`,从响应中省略) |
| `vector_store_name` | string | 绑定存储的展示名。未绑定时返回 `"System default"`;跨空间共享 KB 视图中被隐藏 |
| `vector_store_source` | string | `"user"`(DB 中创建的存储)/ `"env"`(环境变量虚拟存储)/ `"shared"`(跨空间共享 KB)/ `"unavailable"`(绑定的存储已不可解析) |
| `vector_store_engine_type` | string | 引擎类型(`elasticsearch` / `qdrant` / `milvus` 等)。`shared` / `unavailable` 时为空 |
| `vector_store_status` | string | `"available"` / `"unavailable"`。`unavailable` 表示绑定的存储已被删除或不在内存注册表中,UI 可据此提示用户重新绑定 |
**错误码(Phase 2 新增)**:
| HTTP | code | 说明 |
| ---- | ---- | ----------------------------------------------------------------- |
| 400 | 2200 | `vector_store_id` 无效:格式错误、不存在或属于其他空间(统一返回,避免枚举泄漏) |
| 400 | 2201 | 指定的向量存储当前不可用:存在于数据库但未注册到引擎注册表,请检查 connection_config |
## GET `/knowledge-bases` - 获取知识库列表
返回当前空间拥有的全部知识库。当传入 `agent_id` 时,校验调用者对该共享智能体的访问权限后,返回该智能体配置可见的知识库范围(用于 `@` 提及)。
**Query 参数**:
| 字段 | 类型 | 必填 | 说明 |
| -------- | ------ | ---- | ---------------------------------------------------------- |
| agent_id | string | 否 | 共享智能体 ID;传入时按智能体配置(`all` / `selected` / `none`)过滤可见知识库 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**: `data` 为数组,每个元素的字段结构同 `POST /knowledge-bases` 响应,并额外携带 `knowledge_count` / `chunk_count` / `processing_count` / `share_count` / `is_pinned` / `pinned_at` 这些聚合与状态字段。
> **注意(Phase 2)**:列表接口不包含 `vector_store_name` / `vector_store_source` / `vector_store_engine_type` / `vector_store_status` 这四个解析后的元数据字段(避免 N+1 查询);仅 `vector_store_id` 来自数据库本身。需要展示存储名称时请单独调用详情接口或 `/vector-stores/:id`。
## GET `/knowledge-bases/:id` - 获取知识库详情
根据 ID 获取知识库详情。当通过共享智能体访问时,可传 `agent_id` 进行权限校验;此时返回对象会附加 `my_permission` 字段以指示当前用户对该知识库的角色(如 `viewer`)。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | --------- |
| id | string | 知识库 ID |
**Query 参数**:
| 字段 | 类型 | 必填 | 说明 |
| -------- | ------ | ---- | ------------------------------------------ |
| agent_id | string | 否 | 共享智能体 ID(用于校验该智能体是否有权访问) |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**: 字段结构同 `POST /knowledge-bases` 响应(包含 Phase 2 的 `vector_store_*` 元数据字段),并附 `is_pinned` / `pinned_at` / `knowledge_count` / `chunk_count` / `processing_count` 状态字段。通过共享智能体访问时还会附加 `my_permission`;同时 `vector_store_name` / `vector_store_engine_type` 会被隐藏(`vector_store_source` 返回 `"shared"`),避免跨空间泄漏存储展示名。
## PUT `/knowledge-bases/:id` - 更新知识库
仅知识库 owner(admin)或具备 `editor` 权限的用户可调用。注意:**`vector_store_id` 在创建后不可修改**,更新接口不接收该字段。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | --------- |
| id | string | 知识库 ID |
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------- | ------ | ---- | ------------------------------------------------------------- |
| name | string | 是 | 知识库名称 |
| description | string | 否 | 知识库描述 |
| config | object | 否 | 更新配置;包含 `chunking_config` / `image_processing_config` / `faq_config` / `wiki_config` / `indexing_strategy` |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/b5829e4a-3845-4624-a7fb-ea3b35e843b0' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: sk-xxxxx' \
--data '{
"name": "weknora new",
"description": "weknora description new",
"config": {
"chunking_config": {
"chunk_size": 1000,
"chunk_overlap": 200,
"separators": [
"\n\n",
"\n",
"。",
"!",
"?",
";",
";"
],
"enable_multimodal": true,
"parser_engine_rules": [
{
"file_types": [".md", ".txt"],
"engine": "builtin"
}
],
"enable_parent_child": true,
"parent_chunk_size": 4096,
"child_chunk_size": 384
},
"image_processing_config": {
"model_id": ""
}
}
}'
```
**响应**: 字段结构同 `POST /knowledge-bases` 响应(返回更新后的完整知识库对象,包含 Phase 2 的 `vector_store_*` 元数据字段。`vector_store_id` 与创建时保持一致,无法通过该接口更改)。
## DELETE `/knowledge-bases/:id` - 删除知识库
仅知识库 owner(与所属空间匹配的 admin)可调用,删除将级联清理知识库下所有知识与切片。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | --------- |
| id | string | 知识库 ID |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/b5829e4a-3845-4624-a7fb-ea3b35e843b0' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"message": "Knowledge base deleted successfully",
"success": true
}
```
## PUT `/knowledge-bases/:id/pin` - 置顶/取消置顶知识库
切换知识库的置顶状态。无需请求体,每次调用会自动反转当前 `is_pinned`。置顶时会同步写入 `pinned_at` 时间戳。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | --------- |
| id | string | 知识库 ID |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/pin' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**: 字段结构同 `POST /knowledge-bases` 响应(包含 Phase 2 的 `vector_store_*` 元数据字段),本接口操作后 `is_pinned` 翻转、`pinned_at` 同步更新。
## POST `/knowledge-bases/:id/hybrid-search` - 混合搜索
在指定知识库内执行向量召回 + 关键词召回的混合检索。请求参数通过 JSON 请求体传递(`SearchParams`)。
> **兼容说明**:`GET` 方法同样可用(需携带 JSON 请求体),供旧版客户端兼容;新集成请使用 `POST`。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | --------- |
| id | string | 知识库 ID |
**查询参数**:
- `resource_urls`: `handle`(默认)或 `public`。`public` 把检索结果 `content` / `image_info` 里的 `resource://` 引用换成可加载的 http(s) 链接,详见[文件与图片引用](./README.md#文件与图片引用resource-与直链)
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ------------------------ | -------- | ---- | ---------------------------------------------------------------- |
| query_text | string | 是 | 查询文本 |
| vector_threshold | number | 否 | 向量相似度阈值(0-1) |
| keyword_threshold | number | 否 | 关键词匹配阈值 |
| match_count | integer | 否 | 返回结果数量上限 |
| disable_keywords_match | boolean | 否 | 关闭关键词召回 |
| disable_vector_match | boolean | 否 | 关闭向量召回 |
| knowledge_ids | string[] | 否 | 仅在指定的知识 ID 范围内召回 |
| tag_ids | string[] | 否 | 标签过滤(FAQ 类型常用于优先级过滤) |
| only_recommended | boolean | 否 | 仅返回标记为推荐的内容 |
| knowledge_base_ids | string[] | 否 | 跨知识库召回(需共享相同 embedding 模型),优先级高于路径中的 `:id` |
| skip_context_enrichment | boolean | 否 | 跳过父子片段/相邻片段的上下文补全(chat 流程使用) |
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/hybrid-search?resource_urls=public' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query_text": "如何使用知识库",
"vector_threshold": 0.5,
"match_count": 10
}'
```
**响应**:
```json
{
"data": [
{
"id": "chunk-00000001",
"content": "知识库是用于存储和检索知识的系统...",
"knowledge_id": "knowledge-00000001",
"chunk_index": 0,
"knowledge_title": "知识库使用指南",
"start_at": 0,
"end_at": 500,
"seq": 1,
"score": 0.95,
"chunk_type": "text",
"image_info": "",
"metadata": {},
"knowledge_filename": "guide.pdf",
"knowledge_source": "file"
}
],
"success": true
}
```
## POST `/knowledge-bases/copy` - 拷贝知识库
异步拷贝整个知识库(配置 + 全部知识内容)。请求会被入队到 Asynq 后台任务(队列 `default`,最多重试 3 次),并立即返回 `task_id` 供轮询进度。
**约束**:源知识库 `source_id` 必须属于调用者所在空间;若指定 `target_id`,目标知识库同样必须属于调用者空间,否则返回 `403 Forbidden`。
**Phase 2 同步预检(当 `target_id` 非空时)**:
| 检查 | 失败时响应 |
| -------------- | ----------------------------------------------------------------------------- |
| 嵌入模型一致性 | `400` `source and target knowledge bases use different embedding models; clone into a target with the same embedding model` |
| 向量存储一致性 | `400` `source and target knowledge bases are bound to different vector stores; cross-store cloning is not yet supported` |
预检失败时任务不会入队、不会生成 `task_id`,调用方直接收到 `400`;这两个检查在异步 worker 中会再次执行(defense in depth),但在握手时即时拒绝是为了避免用户需要轮询 `progress` 才能看到错误。当 `target_id` 为空(新建目标库)时,目标库会自动复制源库的 `vector_store_id` 与 `embedding_model_id`,因此预检不会触发。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ---------- | ------ | ---- | ------------------------------------------------------------- |
| source_id | string | 是 | 源知识库 ID(必须属于当前空间) |
| target_id | string | 否 | 目标知识库 ID(若复用已存在知识库;同样必须属于当前空间) |
| task_id | string | 否 | 自定义任务 ID;不传则由服务端生成(基于空间、源 ID、时间戳) |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/copy' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"source_id": "kb-00000001"
}'
```
**响应**:
```json
{
"data": {
"task_id": "kb_clone_1_kb-00000001_1736582400",
"source_id": "kb-00000001",
"target_id": "",
"message": "Knowledge base copy task started"
},
"success": true
}
```
## GET `/knowledge-bases/copy/progress/:task_id` - 获取拷贝进度
查询拷贝任务的当前状态与进度(数据由 worker 写入 Redis)。
**路径参数**:
| 字段 | 类型 | 说明 |
| ------- | ------ | ------------------------------------------------- |
| task_id | string | 由 `POST /knowledge-bases/copy` 返回的任务 ID |
**响应字段(`data`)**:
| 字段 | 类型 | 说明 |
| ---------- | ------- | ------------------------------------------------------------ |
| task_id | string | 任务 ID |
| source_id | string | 源知识库 ID |
| target_id | string | 目标知识库 ID(任务开始后填入) |
| status | string | `pending` / `processing` / `completed` / `failed` |
| progress | integer | 进度百分比 0–100 |
| total | integer | 计划拷贝的知识总数 |
| processed | integer | 已处理的知识数 |
| message | string | 当前状态描述 |
| error | string | 失败时的错误信息 |
| created_at | integer | 任务创建时间(Unix 秒) |
| updated_at | integer | 最后更新时间(Unix 秒) |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/copy/progress/kb_clone_1_kb-00000001_1736582400' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"task_id": "kb_clone_1_kb-00000001_1736582400",
"source_id": "kb-00000001",
"target_id": "kb-00000002",
"status": "completed",
"progress": 100,
"total": 10,
"processed": 10,
"message": "Task completed successfully",
"error": "",
"created_at": 1736582400,
"updated_at": 1736582460
},
"success": true
}
```
## POST `/knowledge-bases/:id/duplicate` - 创建知识库副本
同步创建一个**仅包含设置**的新知识库副本。会复制分块、模型、索引策略、Wiki/FAQ 配置等设置字段,但**不会**复制知识条目、分块内容、FAQ 条目、Wiki 页面、向量/关键词索引、数据源绑定、分享关系或置顶状态。
与 `POST /knowledge-bases/copy` 的区别:
| 能力 | `/duplicate` | `/copy` |
| ---- | ------------ | ------- |
| 执行方式 | 同步,立即返回新 KB | 异步任务,需轮询 progress |
| 复制内容 | 仅设置 | 设置 + 全部知识内容 |
| 新 KB ID | 服务端自动生成 UUID | 可指定已有目标库或新建 |
**权限**:需要 `Contributor+`,且对源知识库至少有 `Viewer` 读权限(路由层 `KBAccessRead`)。源知识库必须属于调用者所在空间,否则返回 `403 Forbidden`。
**命名规则**:新 KB 名称在源名称后追加本地化后缀(依据 `Accept-Language` 或 `WEKNORA_LANGUAGE`),例如中文 `原名 副本`、英文 `Original Name Copy`;若同名已存在则递增为 `原名 副本 2`、`Original Name Copy 2` 等。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | ----------- |
| id | string | 源知识库 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/duplicate' \
--header 'Authorization: Bearer <token>' \
--header 'Accept-Language: zh-CN' \
--request POST
```
**响应**(HTTP 201):
```json
{
"success": true,
"data": {
"source_id": "kb-00000001",
"target_id": "kb-00000002",
"message": "Knowledge base duplicate created",
"knowledge_base": {
"id": "kb-00000002",
"name": "产品文档 副本",
"type": "document",
"description": "…",
"embedding_model_id": "embed-1",
"chunking_config": {},
"knowledge_count": 0,
"chunk_count": 0
}
}
}
```
**响应字段(`data`)**:
| 字段 | 类型 | 说明 |
| --------------- | ------ | ---------------------------- |
| source_id | string | 源知识库 ID |
| target_id | string | 新创建的知识库 ID |
| message | string | 操作结果描述 |
| knowledge_base | object | 新副本的完整知识库对象 |
**常见错误**:
| 场景 | HTTP | 说明 |
| -------------------- | ---- | ---- |
| 源知识库不存在 | 404 | `Source knowledge base not found` |
| 源库属于其他空间 | 403 | `No permission to duplicate this knowledge base` |
| 向量存储绑定无效 | 400 | 源库绑定的 vector store 不可用 |
## GET `/knowledge-bases/:id/move-targets` - 获取可迁移目标知识库列表
返回当前知识库的内容**可以迁移到**的目标知识库列表。筛选规则:
- 与源知识库 `type` 相同
- 与源知识库 `embedding_model_id` 相同
- 非临时知识库(`is_temporary = false`)
- 不包含源知识库自身
- 仅同空间的知识库
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | ------------- |
| id | string | 源知识库 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/move-targets' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"id": "kb-00000002",
"name": "技术文档知识库",
"description": "技术文档相关知识",
"type": "document",
"is_temporary": false,
"tenant_id": 1,
"chunking_config": {
"chunk_size": 1000,
"chunk_overlap": 200,
"separators": ["\n\n", "\n"],
"enable_multimodal": true,
"parser_engine_rules": [],
"enable_parent_child": false,
"parent_chunk_size": 4096,
"child_chunk_size": 384
},
"image_processing_config": {
"model_id": ""
},
"embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
"summary_model_id": "8aea788c-bb30-4898-809e-e40c14ffb48c",
"vlm_config": {
"enabled": false,
"model_id": ""
},
"asr_config": {
"enabled": false,
"model_id": "",
"language": ""
},
"storage_provider_config": {
"provider": "local"
},
"storage_config": {
"secret_id": "",
"secret_key": "",
"region": "",
"bucket_name": "",
"app_id": "",
"path_prefix": ""
},
"extract_config": null,
"faq_config": null,
"question_generation_config": null,
"is_pinned": false,
"pinned_at": null,
"knowledge_count": 8,
"chunk_count": 210,
"processing_count": 0,
"created_at": "2025-08-12T11:30:09.206238+08:00",
"updated_at": "2025-08-12T11:30:09.206238+08:00",
"deleted_at": null
}
],
"success": true
}
```
+101
View File
@@ -0,0 +1,101 @@
# 知识搜索 API
[返回目录](./README.md)
| 方法 | 路径 | 描述 |
| ---- | ------------------ | -------- |
| POST | `/knowledge-search` | 知识搜索 |
## POST `/knowledge-search` - 知识搜索
在知识库中搜索相关内容(不使用 LLM 总结),直接返回检索结果。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ------------------- | -------- | ---- | ---------------------------------------------------------- |
| query | string | 是 | 搜索查询文本 |
| knowledge_base_id | string | 否 | 单个知识库 ID(向后兼容);与 `knowledge_base_ids` 互斥 |
| knowledge_base_ids | string[] | 否 | 多个知识库 ID 列表,跨知识库搜索 |
| knowledge_ids | string[] | 否 | 进一步限定到指定知识(文件);不传则在整库范围内搜索 |
> 必须指定 `knowledge_base_id` 或 `knowledge_base_ids` 中的至少一个。
**查询参数**:
- `resource_urls`: `handle`(默认)或 `public`。`public` 把检索结果 `content` / `image_info` 里的 `resource://` 引用换成可加载的 http(s) 链接,详见[文件与图片引用](./README.md#文件与图片引用resource-与直链)
**请求**:
```curl
# 搜索单个知识库
curl --location 'http://localhost:8080/api/v1/knowledge-search' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query": "如何使用知识库",
"knowledge_base_id": "kb-00000001"
}'
# 搜索多个知识库
curl --location 'http://localhost:8080/api/v1/knowledge-search' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query": "如何使用知识库",
"knowledge_base_ids": ["kb-00000001", "kb-00000002"]
}'
# 搜索指定文件
curl --location 'http://localhost:8080/api/v1/knowledge-search' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query": "如何使用知识库",
"knowledge_ids": ["4c4e7c1a-09cf-485b-a7b5-24b8cdc5acf5"]
}'
```
**响应**:
```json
{
"data": [
{
"id": "chunk-00000001",
"content": "知识库是用于存储和检索知识的系统...",
"knowledge_id": "knowledge-00000001",
"chunk_index": 0,
"knowledge_title": "知识库使用指南",
"start_at": 0,
"end_at": 500,
"seq": 1,
"score": 0.95,
"chunk_type": "text",
"image_info": "",
"metadata": {},
"knowledge_filename": "guide.pdf",
"knowledge_source": "file"
}
],
"success": true
}
```
**响应字段说明(data[])**:
| 字段 | 类型 | 说明 |
| ------------------ | ------- | ------------------------------------- |
| id | string | 分块 ID |
| content | string | 命中的分块文本 |
| knowledge_id | string | 该分块所属的知识 ID |
| chunk_index | int | 分块在知识中的序号 |
| knowledge_title | string | 来源知识标题 |
| start_at / end_at | int | 分块在源文档中的字符偏移 |
| seq | int | 命中排序号 |
| score | number | 相似度(rerank 后归一化后的最终得分) |
| chunk_type | string | 分块类型(`text` / `image` / ...) |
| image_info | string | 图像分块的额外信息(JSON 字符串) |
| metadata | object | 自定义元数据 |
| knowledge_filename | string | 来源文件名 |
| knowledge_source | string | 来源类型(`file` / `url` / `manual`) |
File diff suppressed because it is too large Load Diff
+557
View File
@@ -0,0 +1,557 @@
# MCP Service API
[返回目录](./README.md)
MCP(Model Context Protocol)服务管理接口,提供 MCP 服务的 CRUD、连通性测试、工具/资源发现,以及工具人工审批策略配置。
| 方法 | 路径 | 描述 |
| ------ | ------------------------------------------------- | --------------------------------------------- |
| POST | `/mcp-services` | 创建 MCP 服务 |
| GET | `/mcp-services` | 获取当前空间的 MCP 服务列表 |
| GET | `/mcp-services/:id` | 获取 MCP 服务详情 |
| PUT | `/mcp-services/:id` | 更新 MCP 服务(部分字段更新) |
| DELETE | `/mcp-services/:id` | 删除 MCP 服务 |
| POST | `/mcp-services/:id/test` | 测试 MCP 服务连通性 |
| GET | `/mcp-services/:id/tools` | 获取 MCP 服务工具列表 |
| GET | `/mcp-services/:id/resources` | 获取 MCP 服务资源列表 |
| GET | `/mcp-services/:id/tool-approvals` | 列出该服务下各工具的人工审批策略 |
| PUT | `/mcp-services/:id/tool-approvals/:tool_name` | 设置/更新某工具的人工审批策略 |
| POST | `/agent/tool-approvals/:pending_id` | 处理 Agent 工具调用待审批请求 |
## POST `/mcp-services` - 创建 MCP 服务
**请求参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---------------- | ------- | ---- | --------------------------------------------------------------------------------------------- |
| name | string | 是 | 服务名称 |
| description | string | 否 | 服务描述 |
| transport_type | string | 是 | 传输类型,可选:`sse`、`http-streamable`、`stdio` |
| url | string | 条件 | 服务地址;当 `transport_type` 为 `sse` / `http-streamable` 时必填(受 SSRF 安全校验约束) |
| headers | object | 否 | 自定义请求头 |
| auth_config | object | 否 | 认证配置,支持 `api_key`、`token` |
| advanced_config | object | 否 | 高级配置,支持 `timeout`、`retry_count`、`retry_delay` |
| stdio_config | object | 条件 | stdio 传输配置,包含 `command`、`args`;当 `transport_type` 为 `stdio` 时必填 |
| env_vars | object | 否 | 环境变量(stdio 场景常用) |
| enabled | boolean | 否 | 是否启用 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/mcp-services' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "天气查询服务",
"description": "提供全球天气信息查询",
"transport_type": "sse",
"url": "https://mcp.example.com/weather/sse",
"headers": {
"X-Custom-Header": "value"
},
"auth_config": {
"api_key": "weather-api-key-xxxxx"
},
"advanced_config": {
"timeout": 30,
"retry_count": 3,
"retry_delay": 1
}
}'
```
**响应**:
```json
{
"data": {
"id": "mcp-00000001",
"tenant_id": 1,
"name": "天气查询服务",
"description": "提供全球天气信息查询",
"enabled": true,
"transport_type": "sse",
"url": "https://mcp.example.com/weather/sse",
"headers": {
"X-Custom-Header": "value"
},
"auth_config": {
"api_key": "weather-api-key-xxxxx"
},
"advanced_config": {
"timeout": 30,
"retry_count": 3,
"retry_delay": 1
},
"is_builtin": false,
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T10:00:00+08:00"
},
"success": true
}
```
**创建 stdio 类型的 MCP 服务示例**:
```curl
curl --location 'http://localhost:8080/api/v1/mcp-services' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "本地文件服务",
"description": "通过 stdio 访问本地文件系统",
"transport_type": "stdio",
"stdio_config": {
"command": "/usr/local/bin/mcp-file-server",
"args": ["--root", "/data"]
},
"env_vars": {
"MCP_LOG_LEVEL": "info"
}
}'
```
## GET `/mcp-services` - 获取 MCP 服务列表
返回当前空间已配置的所有 MCP 服务。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/mcp-services' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"id": "mcp-00000001",
"tenant_id": 1,
"name": "天气查询服务",
"description": "提供全球天气信息查询",
"enabled": true,
"transport_type": "sse",
"url": "https://mcp.example.com/weather/sse",
"headers": {},
"auth_config": {
"api_key": "weather-api-key-xxxxx"
},
"advanced_config": {
"timeout": 30,
"retry_count": 3,
"retry_delay": 1
},
"is_builtin": false,
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T10:00:00+08:00"
},
{
"id": "mcp-00000002",
"tenant_id": 1,
"name": "本地文件服务",
"description": "通过 stdio 访问本地文件系统",
"enabled": true,
"transport_type": "stdio",
"headers": {},
"auth_config": null,
"advanced_config": null,
"stdio_config": {
"command": "/usr/local/bin/mcp-file-server",
"args": ["--root", "/data"]
},
"env_vars": {
"MCP_LOG_LEVEL": "info"
},
"is_builtin": false,
"created_at": "2025-08-12T11:00:00+08:00",
"updated_at": "2025-08-12T11:00:00+08:00"
}
],
"success": true
}
```
## GET `/mcp-services/:id` - 获取 MCP 服务详情
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | -------------- |
| id | string | MCP 服务 ID |
> 注:内置(`is_builtin: true`)服务在响应中会隐藏敏感凭证字段。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/mcp-services/mcp-00000001' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"id": "mcp-00000001",
"tenant_id": 1,
"name": "天气查询服务",
"description": "提供全球天气信息查询",
"enabled": true,
"transport_type": "sse",
"url": "https://mcp.example.com/weather/sse",
"headers": {},
"auth_config": {
"api_key": "weather-api-key-xxxxx"
},
"advanced_config": {
"timeout": 30,
"retry_count": 3,
"retry_delay": 1
},
"is_builtin": false,
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T10:00:00+08:00"
},
"success": true
}
```
## PUT `/mcp-services/:id` - 更新 MCP 服务
支持部分字段更新,可传入下列任意子集:`name`、`description`、`enabled`、`transport_type`、`url`、`stdio_config`、`env_vars`、`headers`、`auth_config`、`advanced_config`。其中 `url` 若提供,会再次执行 SSRF 安全校验。
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/mcp-services/mcp-00000001' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "天气查询服务(更新)",
"description": "提供全球天气信息查询,支持实时数据",
"enabled": false
}'
```
**响应**:
```json
{
"data": {
"id": "mcp-00000001",
"tenant_id": 1,
"name": "天气查询服务(更新)",
"description": "提供全球天气信息查询,支持实时数据",
"enabled": false,
"transport_type": "sse",
"url": "https://mcp.example.com/weather/sse",
"headers": {},
"auth_config": {
"api_key": "weather-api-key-xxxxx"
},
"advanced_config": {
"timeout": 30,
"retry_count": 3,
"retry_delay": 1
},
"is_builtin": false,
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T12:00:00+08:00"
},
"success": true
}
```
## DELETE `/mcp-services/:id` - 删除 MCP 服务
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/mcp-services/mcp-00000001' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"success": true,
"message": "MCP service deleted successfully"
}
```
## POST `/mcp-services/:id/test` - 测试 MCP 服务连通性
后端会以已保存配置建立一次 MCP 连接,返回连接结果及探测到的工具/资源列表。连接失败时 HTTP 仍为 200,但 `data.success` 为 `false`,错误原因在 `data.message` 中。
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/mcp-services/mcp-00000001/test' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"success": true,
"message": "连接成功",
"description": "提供全球天气信息查询",
"tools": [
{
"name": "get_weather",
"description": "获取指定城市的天气信息",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"]
}
}
],
"resources": [
{
"uri": "weather://cities",
"name": "城市列表",
"description": "支持查询的城市列表",
"mimeType": "application/json"
}
]
},
"success": true
}
```
## GET `/mcp-services/:id/tools` - 获取 MCP 服务工具列表
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/mcp-services/mcp-00000001/tools' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"name": "get_weather",
"description": "获取指定城市的天气信息",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"]
}
},
{
"name": "get_forecast",
"description": "获取未来天气预报",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
},
"days": {
"type": "integer",
"description": "预报天数"
}
},
"required": ["city"]
}
}
],
"success": true
}
```
## GET `/mcp-services/:id/resources` - 获取 MCP 服务资源列表
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/mcp-services/mcp-00000001/resources' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"uri": "weather://cities",
"name": "城市列表",
"description": "支持查询的城市列表",
"mimeType": "application/json"
},
{
"uri": "weather://config",
"name": "服务配置",
"description": "当前服务配置信息",
"mimeType": "application/json"
}
],
"success": true
}
```
## GET `/mcp-services/:id/tool-approvals` - 列出工具人工审批策略
返回该 MCP 服务下各工具持久化的 `require_approval` 标记。仅返回数据库中已显式配置过的工具记录;未出现在列表中的工具默认无需审批。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | ----------- |
| id | string | MCP 服务 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/mcp-services/mcp-00000001/tool-approvals' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"data": [
{
"tool_name": "delete_file",
"require_approval": true,
"updated_at": "2025-09-20T15:30:00+08:00"
},
{
"tool_name": "get_weather",
"require_approval": false,
"updated_at": "2025-09-20T15:31:00+08:00"
}
],
"success": true
}
```
## PUT `/mcp-services/:id/tool-approvals/:tool_name` - 设置工具人工审批策略
为指定 MCP 服务下的某个工具设置/更新人工审批要求。当 `require_approval` 为 `true` 时,Agent 在调用该工具前会阻塞并产生一条待审批记录,需要前端调用 `POST /agent/tool-approvals/:pending_id` 完成审批。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---------- | ------ | ------------------------------------------------------------------- |
| id | string | MCP 服务 ID |
| tool_name | string | 工具名(由 Gin 自动 URL 解码,调用方需对名称中的 `%`、`/` 做 URL 编码) |
**请求体**:
| 字段 | 类型 | 必填 | 说明 |
| ----------------- | ------- | ---- | ----------------------------------- |
| require_approval | boolean | 是 | 是否要求人工审批后才能执行该工具 |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/mcp-services/mcp-00000001/tool-approvals/delete_file' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"require_approval": true
}'
```
**响应**:
```json
{
"success": true
}
```
## POST `/agent/tool-approvals/:pending_id` - 处理待审批工具调用
用于 Agent 在执行过程中阻塞等待人工审批的场景:当 Agent 命中一个 `require_approval = true` 的工具时会生成一条 `pending_id`,前端拿到这个 ID 后调用此接口将审批结果回传给 Agent,Agent 才会继续执行(或终止)。
**鉴权要求**:请求上下文中必须有已认证用户(`user_id`),且该用户必须是当前 pending 会话的所有者;空间与用户两层都会做 fail-close 校验。
**路径参数**:
| 字段 | 类型 | 说明 |
| ----------- | ------ | ------------------- |
| pending_id | string | 待审批记录 ID |
**请求体**:
| 字段 | 类型 | 必填 | 说明 |
| -------------- | ------ | ---- | ----------------------------------------------------------------------------------------------------------------- |
| decision | string | 是 | 审批结论,必须为 `approve` 或 `reject` |
| modified_args | object | 否 | 仅在 `approve` 时生效,允许人工修改本次工具调用的参数;必须是非 null 的 JSON 对象,否则返回 400 |
| reason | string | 否 | 审批理由(任意,便于审计) |
**请求(通过)**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/agent/tool-approvals/pending-abcdef123456' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"decision": "approve",
"modified_args": {
"path": "/tmp/safe-target.txt"
},
"reason": "已确认目标路径安全"
}'
```
**请求(驳回)**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/agent/tool-approvals/pending-abcdef123456' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"decision": "reject",
"reason": "目标路径在受保护目录"
}'
```
**响应**:
```json
{
"success": true
}
```
**错误码说明**:
| HTTP | 触发条件 |
| ---- | --------------------------------------------------------------------------------------- |
| 400 | `decision` 不是 `approve`/`reject`;或 `modified_args` 是 `null`/非对象;或空间/用户错配 |
| 401 | 上下文缺失认证用户(中间件未注入 `user_id`) |
| 404 | `pending_id` 不存在或已完成(超时/取消已先一步消费) |
+96
View File
@@ -0,0 +1,96 @@
# 长期记忆 API
[返回目录](./README.md)
长期记忆空间始终绑定在**当前调用者**上:路径里没有 subject id,服务端从凭证推导身份。因此 Viewer+ 的会话令牌,或 **full-access API Key** 才能访问;带知识库范围的集成 Key 不能继承某个人的记忆。
工作空间管理员先在设置里打开空间级开关,用户还可以再关掉自己的记忆。推断出的条目会停在 `pending`,确认后才进入提示词。
| 方法 | 路径 | 描述 |
| ---- | ---- | ---- |
| GET | `/memory/settings` | 获取合并后的记忆开关(空间级 + 个人级)与条数 |
| PUT | `/memory/settings` | 开启或关闭当前用户自己的长期记忆 |
| GET | `/memory/items` | 分页列出记忆,可按状态过滤 |
| POST | `/memory/items` | 手动新增一条记忆 |
| PUT | `/memory/items/{id}` | 修改内容与重要度(之后不会被后台抽取覆盖) |
| DELETE | `/memory/items/{id}` | 永久删除一条记忆 |
| POST | `/memory/items/{id}/confirm` | 确认一条推断出的记忆 |
| POST | `/memory/items/{id}/reject` | 否决一条推断出的记忆 |
| DELETE | `/memory/items` | 清空当前用户的全部记忆 |
| GET | `/memory/topics` | 列出尚未提升为长期关注的主题计数 |
| POST | `/memory/topics/{id}/promote` | 立即把主题记为长期关注 |
| DELETE | `/memory/topics/{id}` | 停止跟踪一个主题 |
| GET | `/memory/documents` | 列出反复引用的文档(未达习惯门槛的不展示) |
| DELETE | `/memory/documents/{id}` | 停止用某份文档做个性化检索 |
| GET | `/memory/export` | 以 JSON 导出全部记忆 |
| POST | `/memory/consolidate` | 立刻整理(合并近义条目、归档到期事项) |
## GET `/memory/settings`
```curl
curl --location 'http://localhost:8080/api/v1/memory/settings' \
--header 'Authorization: Bearer <token>'
```
**响应**:
```json
{
"success": true,
"data": {
"workspace_enabled": true,
"user_enabled": true,
"effective": true,
"write_mode": "auto",
"item_count": 12,
"max_items": 200
}
}
```
`write_mode` 为 `explicit_only`(只记用户明确要求记住的)或 `auto`(后台从对话蒸馏)。`effective` = 空间开关 ∧ 个人开关。
## PUT `/memory/settings`
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/memory/settings' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{"enabled": true}'
```
`enabled` 必填。只改个人开关,不能用这个接口改空间级配置。
## GET `/memory/items`
查询参数:
| 参数 | 说明 |
| ---- | ---- |
| `status` | 可选:`active` / `superseded` / `archived` / `pending`;省略则不过滤 |
| `limit` | 默认 50,最大 200 |
| `offset` | 默认 0 |
`kind` 取值:`profile` / `preference` / `fact` / `task` / `interest`。
## POST `/memory/items`
```json
{
"kind": "preference",
"content": "回答直接给结论,少铺垫",
"importance": 3
}
```
## POST `/memory/items/{id}/confirm` / `reject`
推断出的记忆(`status=pending`)确认后才注入提示词;否决会留下 tombstone,避免下一轮蒸馏把同一句话再写回来。
## GET `/memory/export`
返回 `{success, total, truncated, data}`,并带 `Content-Disposition: attachment; filename="weknora-memories.json"`。`truncated` 仅在触达导出上限(2 万条)时为 true。
## POST `/memory/consolidate`
不等待每日后台整理,立刻合并意思接近的条目并归档到期事项。返回 `merged` / `demoted` / `expired` / `reviewed` / `candidates`;若什么都没合并,`skipped` 会说明原因(例如 `too_few_items`)。
+267
View File
@@ -0,0 +1,267 @@
# 消息管理 API
[返回目录](./README.md)
| 方法 | 路径 | 描述 |
| ------ | ---------------------------- | ------------------------ |
| GET | `/messages/:session_id/load` | 获取最近的会话消息列表 |
| DELETE | `/messages/:session_id/:id` | 删除消息 |
| POST | `/messages/search` | 搜索历史对话 |
| GET | `/messages/chat-history-stats` | 获取聊天历史知识库统计 |
## GET `/messages/:session_id/load` - 获取最近的会话消息列表
**查询参数**:
- `before_time`: 上一次拉取的最早一条消息的 created_at 字段,为空拉取最近的消息
- `limit`: 每页条数(默认 20)
- `resource_urls`: `handle`(默认)或 `public`。`public` 把历史消息里的 `resource://` 图片引用换成可加载的 http(s) 链接,详见[文件与图片引用](./README.md#文件与图片引用resource-与直链)
**请求**:
```curl
curl --location --request GET 'http://localhost:8080/api/v1/messages/ceb9babb-1e30-41d7-817d-fd584954304b/load?limit=3&before_time=2030-08-12T14%3A35%3A42.123456789Z' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query": "彗尾的形状"
}'
```
**响应**:
```json
{
"data": [
{
"id": "b8b90eeb-7dd5-4cf9-81c6-5ebcbd759451",
"session_id": "ceb9babb-1e30-41d7-817d-fd584954304b",
"request_id": "hCA8SDjxcAvv",
"content": "<think>\n好的",
"role": "assistant",
"knowledge_references": [
{
"id": "c8347bef-127f-4a22-b962-edf5a75386ec",
"content": "彗星xxx",
"knowledge_id": "a6790b93-4700-4676-bd48-0d4804e1456b",
"chunk_index": 0,
"knowledge_title": "彗星.txt",
"start_at": 0,
"end_at": 2760,
"seq": 0,
"score": 4.038836479187012,
"match_type": 4,
"sub_chunk_id": [
"688821f0-40bf-428e-8cb6-541531ebeb76",
"c1e9903e-2b4d-4281-be15-0149288d45c2",
"7d955251-3f79-4fd5-a6aa-02f81e044091"
],
"metadata": {},
"chunk_type": "text",
"parent_chunk_id": "",
"image_info": "",
"knowledge_filename": "彗星.txt",
"knowledge_source": ""
},
{
"id": "fa3aadee-cadb-4a84-9941-c839edc3e626",
"content": "# 文档名称\n彗星.txt\n\n# 摘要\n彗星是由冰和尘埃构成的太阳系小天体,接近太阳时会释放气体形成彗发和彗尾。其轨道周期差异大,来源包括柯伊伯带和奥尔特云。彗星与小行星的区别逐渐模糊,部分彗星已失去挥发物质,类似小行星。目前已知彗星数量众多,且存在系外彗星。彗星在古代被视为凶兆,现代研究揭示其复杂结构与起源。",
"knowledge_id": "a6790b93-4700-4676-bd48-0d4804e1456b",
"chunk_index": 6,
"knowledge_title": "彗星.txt",
"start_at": 0,
"end_at": 0,
"seq": 6,
"score": 0.6131043121858466,
"match_type": 0,
"sub_chunk_id": null,
"metadata": {},
"chunk_type": "summary",
"parent_chunk_id": "c8347bef-127f-4a22-b962-edf5a75386ec",
"image_info": "",
"knowledge_filename": "彗星.txt",
"knowledge_source": ""
}
],
"agent_steps": [],
"is_completed": true,
"is_fallback": false,
"agent_duration_ms": 2500,
"channel": "web",
"created_at": "2025-08-12T10:24:38.370548+08:00",
"updated_at": "2025-08-12T10:25:40.416382+08:00",
"deleted_at": null
},
{
"id": "7fa136ae-a045-424e-baac-52113d92ae94",
"session_id": "ceb9babb-1e30-41d7-817d-fd584954304b",
"request_id": "3475c004-0ada-4306-9d30-d7f5efce50d2",
"content": "彗尾的形状",
"role": "user",
"knowledge_references": [],
"agent_steps": [],
"is_completed": true,
"mentioned_items": [],
"images": [],
"channel": "web",
"created_at": "2025-08-12T14:30:39.732246+08:00",
"updated_at": "2025-08-12T14:30:39.733277+08:00",
"deleted_at": null
},
{
"id": "9bcafbcf-a758-40af-a9a3-c4d8e0f49439",
"session_id": "ceb9babb-1e30-41d7-817d-fd584954304b",
"request_id": "3475c004-0ada-4306-9d30-d7f5efce50d2",
"content": "<think>\n好的",
"role": "assistant",
"knowledge_references": [
{
"id": "c8347bef-127f-4a22-b962-edf5a75386ec",
"content": "彗星xxx",
"knowledge_id": "a6790b93-4700-4676-bd48-0d4804e1456b",
"chunk_index": 0,
"knowledge_title": "彗星.txt",
"start_at": 0,
"end_at": 2760,
"seq": 0,
"score": 4.038836479187012,
"match_type": 3,
"sub_chunk_id": [
"688821f0-40bf-428e-8cb6-541531ebeb76",
"c1e9903e-2b4d-4281-be15-0149288d45c2",
"7d955251-3f79-4fd5-a6aa-02f81e044091"
],
"metadata": {},
"chunk_type": "text",
"parent_chunk_id": "",
"image_info": "",
"knowledge_filename": "彗星.txt",
"knowledge_source": ""
},
{
"id": "fa3aadee-cadb-4a84-9941-c839edc3e626",
"content": "# 文档名称\n彗星.txt\n\n# 摘要\n彗星是由冰和尘埃构成的太阳系小天体,接近太阳时会释放气体形成彗发和彗尾。其轨道周期差异大,来源包括柯伊伯带和奥尔特云。彗星与小行星的区别逐渐模糊,部分彗星已失去挥发物质,类似小行星。目前已知彗星数量众多,且存在系外彗星。彗星在古代被视为凶兆,现代研究揭示其复杂结构与起源。",
"knowledge_id": "a6790b93-4700-4676-bd48-0d4804e1456b",
"chunk_index": 6,
"knowledge_title": "彗星.txt",
"start_at": 0,
"end_at": 0,
"seq": 6,
"score": 0.6131043121858466,
"match_type": 3,
"sub_chunk_id": null,
"metadata": {},
"chunk_type": "summary",
"parent_chunk_id": "c8347bef-127f-4a22-b962-edf5a75386ec",
"image_info": "",
"knowledge_filename": "彗星.txt",
"knowledge_source": ""
}
],
"agent_steps": [],
"is_completed": true,
"is_fallback": false,
"agent_duration_ms": 2500,
"channel": "web",
"created_at": "2025-08-12T14:30:39.735108+08:00",
"updated_at": "2025-08-12T14:31:17.829926+08:00",
"deleted_at": null
}
],
"success": true
}
```
## DELETE `/messages/:session_id/:id` - 删除消息
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/messages/ceb9babb-1e30-41d7-817d-fd584954304b/9bcafbcf-a758-40af-a9a3-c4d8e0f49439' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"message": "Message deleted successfully",
"success": true
}
```
## POST `/messages/search` - 搜索历史对话
搜索历史对话消息,支持混合搜索、关键词搜索和向量搜索模式。
**请求参数**:
- `query`: 搜索关键词(必填)
- `mode`: 搜索模式,可选 `hybrid`、`keyword`、`vector`(可选,默认 `hybrid`)
- `limit`: 返回结果数量(可选,默认 20)
- `session_ids`: 限定搜索的会话ID列表(可选)
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/messages/search' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"query": "彗星的结构",
"mode": "hybrid",
"limit": 20,
"session_ids": []
}'
```
**响应**:
```json
{
"data": {
"items": [
{
"request_id": "3475c004-0ada-4306-9d30-d7f5efce50d2",
"session_id": "ceb9babb-1e30-41d7-817d-fd584954304b",
"session_title": "彗星知识问答",
"query_content": "彗尾的形状",
"answer_content": "彗尾的形状主要取决于...",
"score": 0.85,
"match_type": "hybrid",
"created_at": "2025-08-12T14:30:39.732246+08:00"
}
],
"total": 1
},
"success": true
}
```
## GET `/messages/chat-history-stats` - 获取聊天历史知识库统计
获取当前空间的聊天历史知识库索引统计信息。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/messages/chat-history-stats' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"enabled": true,
"embedding_model_id": "dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3",
"knowledge_base_id": "kb-chat-00000001",
"knowledge_base_name": "聊天历史知识库",
"indexed_message_count": 1024,
"has_indexed_messages": true
},
"success": true
}
```
+503
View File
@@ -0,0 +1,503 @@
# 模型管理 API
[返回目录](./README.md)
模型管理接口用于维护当前空间下可用的 LLM / Embedding / Rerank / VLLM / ASR 模型配置。
| 方法 | 路径 | 描述 |
| ------ | ------------------- | --------------------- |
| GET | `/models/providers` | 获取模型服务商列表 |
| POST | `/models` | 创建模型 |
| GET | `/models` | 获取模型列表 |
| GET | `/models/:id` | 获取模型详情 |
| PUT | `/models/:id` | 更新模型 |
| DELETE | `/models/:id` | 删除模型 |
## 服务商支持 (Provider Support)
WeKnora 支持多种主流 AI 模型服务商,在创建模型时可通过 `parameters.provider` 字段指定服务商类型以获得更好的兼容性。
### 支持的服务商列表
| 服务商标识 | 名称 | 支持的模型类型 |
| -------------- | ---------------------------- | ------------------------------- |
| `generic` | 自定义 (OpenAI 兼容接口) | Chat, Embedding, Rerank, VLLM |
| `openai` | OpenAI | Chat, Embedding, Rerank, VLLM |
| `aliyun` | 阿里云 DashScope | Chat, Embedding, Rerank, VLLM |
| `zhipu` | 智谱 BigModel | Chat, Embedding, Rerank, VLLM |
| `volcengine` | 火山引擎 Volcengine | Chat, Embedding, Rerank, VLLM |
| `hunyuan` | 腾讯混元 Hunyuan | Chat, Embedding |
| `deepseek` | DeepSeek | Chat |
| `minimax` | MiniMax | Chat |
| `mimo` | 小米 MiMo | Chat |
| `siliconflow` | 硅基流动 SiliconFlow | Chat, Embedding, Rerank, VLLM |
| `jina` | Jina | Embedding, Rerank |
| `openrouter` | OpenRouter | Chat, VLLM |
| `litellm` | LiteLLM (self-hosted proxy) | Chat, Embedding, VLLM |
| `requesty` | Requesty | Chat, Embedding, VLLM |
| `gemini` | Google Gemini | Chat |
| `modelscope` | 魔搭 ModelScope | Chat, Embedding, VLLM |
| `moonshot` | 月之暗面 Moonshot | Chat, VLLM |
| `qianfan` | 百度千帆 Baidu Cloud | Chat, Embedding, Rerank, VLLM |
| `qiniu` | 七牛云 Qiniu | Chat |
| `longcat` | LongCat AI | Chat |
| `gpustack` | GPUStack | Chat, Embedding, Rerank, VLLM |
> 实际可用的服务商以 `GET /models/providers` 返回为准。
>
> `litellm` 的目录默认地址是占位符 `http://your_litellm_proxy/v1`,保存前请换成实际可解析的主机名。`localhost` / `127.0.0.1` / `host.docker.internal` 默认会被 SSRF 拦截;自托管时把该主机加入 `SSRF_WHITELIST`。创建模型时 `source` 仍为 `remote`,厂商写在 `parameters.provider`。
## GET `/models/providers` - 获取模型服务商列表
根据模型类型获取支持的服务商列表及配置信息(系统级元数据,与空间无关)。
**查询参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---------- | ------ | ---- | --------------------------------------------------- |
| model_type | string | 否 | 模型类型,可选值:`chat` / `embedding` / `rerank` / `vllm` / `asr`;省略则返回全部 |
**请求**:
```curl
# 获取所有服务商
curl --location 'http://localhost:8080/api/v1/models/providers' \
--header 'X-API-Key: your_api_key'
# 获取支持 Embedding 类型的服务商
curl --location 'http://localhost:8080/api/v1/models/providers?model_type=embedding' \
--header 'X-API-Key: your_api_key'
```
**响应**:
```json
{
"success": true,
"data": [
{
"value": "aliyun",
"label": "阿里云 DashScope",
"description": "qwen-plus, tongyi-embedding-vision-plus, qwen3-rerank, etc.",
"defaultUrls": {
"chat": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"embedding": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"rerank": "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank"
},
"modelTypes": ["chat", "embedding", "rerank", "vllm"]
},
{
"value": "zhipu",
"label": "智谱 BigModel",
"description": "glm-4.7, embedding-3, rerank, etc.",
"defaultUrls": {
"chat": "https://open.bigmodel.cn/api/paas/v4",
"embedding": "https://open.bigmodel.cn/api/paas/v4/embeddings",
"rerank": "https://open.bigmodel.cn/api/paas/v4/rerank"
},
"modelTypes": ["chat", "embedding", "rerank", "vllm"]
}
]
}
```
## POST `/models` - 创建模型
为当前空间创建一个新的模型配置。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------- | ------ | ---- | --------------------------------------------------------------- |
| name | string | 是 | 模型名称(远程模型对应服务商的 model id,本地模型为 Ollama tag)|
| type | string | 是 | 模型类型,可选值:`KnowledgeQA` / `Embedding` / `Rerank` / `VLLM` / `ASR` |
| source | string | 是 | 模型来源,可选值:`local` / `remote` |
| description | string | 否 | 模型描述 |
| parameters | object | 是 | 模型参数,详见下方 [Parameters](#parameters-模型参数) |
> 当 `parameters.base_url` 不为空时,后端会执行 SSRF 校验,校验失败将返回 400。
### 创建对话模型(KnowledgeQA)
**本地 Ollama 模型**:
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "qwen3:8b",
"type": "KnowledgeQA",
"source": "local",
"description": "LLM Model for Knowledge QA",
"parameters": {
"base_url": "",
"api_key": ""
}
}'
```
**远程 API 模型(指定服务商)**:
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "qwen-plus",
"type": "KnowledgeQA",
"source": "remote",
"description": "阿里云 Qwen 大模型",
"parameters": {
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_key": "sk-your-dashscope-api-key",
"provider": "aliyun"
}
}'
```
### 创建嵌入模型(Embedding)
**本地 Ollama 模型**:
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "nomic-embed-text:latest",
"type": "Embedding",
"source": "local",
"description": "Embedding Model",
"parameters": {
"base_url": "",
"api_key": "",
"embedding_parameters": {
"dimension": 768,
"truncate_prompt_tokens": 0
}
}
}'
```
**远程 API 模型(阿里云 DashScope)**:
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "text-embedding-v3",
"type": "Embedding",
"source": "remote",
"description": "阿里云通义千问 Embedding 模型",
"parameters": {
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_key": "sk-your-dashscope-api-key",
"provider": "aliyun",
"embedding_parameters": {
"dimension": 1024,
"truncate_prompt_tokens": 0
}
}
}'
```
**远程 API 模型(Jina AI)**:
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "jina-embeddings-v3",
"type": "Embedding",
"source": "remote",
"description": "Jina AI Embedding 模型",
"parameters": {
"base_url": "https://api.jina.ai/v1",
"api_key": "jina_your_api_key",
"provider": "jina",
"embedding_parameters": {
"dimension": 1024,
"truncate_prompt_tokens": 0
}
}
}'
```
### 创建排序模型(Rerank)
**远程 API 模型(阿里云 DashScope)**:
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "gte-rerank",
"type": "Rerank",
"source": "remote",
"description": "阿里云 GTE Rerank 模型",
"parameters": {
"base_url": "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank",
"api_key": "sk-your-dashscope-api-key",
"provider": "aliyun"
}
}'
```
**远程 API 模型(Jina AI)**:
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "jina-reranker-v2-base-multilingual",
"type": "Rerank",
"source": "remote",
"description": "Jina AI Rerank 模型",
"parameters": {
"base_url": "https://api.jina.ai/v1",
"api_key": "jina_your_api_key",
"provider": "jina"
}
}'
```
**远程 API 模型(火山引擎 VikingDB)**:
火山 Rerank 使用 AK/SK 签名,不使用方舟 API Key。`api_key` 保存 Access Key ID,
`app_secret` 保存 Secret Access Key;两项均按模型凭证加密存储。
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "doubao-seed-rerank",
"type": "Rerank",
"source": "remote",
"description": "火山引擎托管 Rerank 模型",
"parameters": {
"base_url": "https://api-knowledgebase.mlp.cn-beijing.volces.com",
"api_key": "your-volcengine-access-key-id",
"app_secret": "your-volcengine-secret-access-key",
"provider": "volcengine"
}
}'
```
### 创建视觉模型(VLLM)
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "qwen-vl-plus",
"type": "VLLM",
"source": "remote",
"description": "阿里云通义千问视觉模型",
"parameters": {
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_key": "sk-your-dashscope-api-key",
"provider": "aliyun"
}
}'
```
**响应**:
```json
{
"success": true,
"data": {
"id": "09c5a1d6-ee8b-4657-9a17-d3dcbd5c70cb",
"tenant_id": 1,
"name": "text-embedding-v3",
"type": "Embedding",
"source": "remote",
"description": "阿里云通义千问 Embedding 模型",
"parameters": {
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"api_key": "sk-***",
"provider": "aliyun",
"embedding_parameters": {
"dimension": 1024,
"truncate_prompt_tokens": 0
}
},
"is_default": false,
"status": "active",
"created_at": "2025-08-12T10:39:01.454591766+08:00",
"updated_at": "2025-08-12T10:39:01.454591766+08:00",
"deleted_at": null
}
}
```
## GET `/models` - 获取模型列表
返回当前空间下的所有模型。内置模型(`is_builtin = true`)的 `base_url` 与 `api_key` 会被清空以隐藏敏感信息。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/models' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key'
```
**响应**: `data` 为数组,每个元素的字段结构同 `POST /models` 响应。内置模型的 `base_url` 与 `api_key` 字段为空字符串。
## GET `/models/:id` - 获取模型详情
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | -------- |
| id | string | 是 | 模型 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/models/dff7bc94-7885-4dd1-bfd5-bd96e4df2fc3' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key'
```
**响应**: 字段结构同 `POST /models` 响应。404 表示模型不存在。
## PUT `/models/:id` - 更新模型
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | -------- |
| id | string | 是 | 模型 ID |
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------- | ------ | ---- | --------------------------------------------------------------- |
| name | string | 否 | 模型名称(为空字符串时保留原值) |
| description | string | 否 | 模型描述(始终覆盖,传空字符串会清空) |
| type | string | 否 | 模型类型,取值同创建接口 |
| source | string | 否 | 模型来源,取值同创建接口 |
| parameters | object | 否 | 模型参数;`parameter_size` 由后端管理,请求中无需提供;`extra_config` 为空时会沿用旧值 |
> 同样会对 `parameters.base_url` 做 SSRF 校验,失败时返回 400。
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/models/8fdc464d-8eaa-44d4-a85b-094b28af5330' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key' \
--data '{
"name": "gte-rerank-v2",
"type": "Rerank",
"source": "remote",
"description": "阿里云 GTE Rerank 模型 V2",
"parameters": {
"base_url": "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank",
"api_key": "sk-your-new-api-key",
"provider": "aliyun"
}
}'
```
**响应**: 字段结构同 `POST /models` 响应,返回更新后的完整模型对象。
## DELETE `/models/:id` - 删除模型
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | -------- |
| id | string | 是 | 模型 ID |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/models/8fdc464d-8eaa-44d4-a85b-094b28af5330' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: your_api_key'
```
**响应**:
```json
{
"success": true,
"message": "Model deleted"
}
```
如果知识库、智能体或当前空间的长期记忆仍引用该模型,接口保持 HTTP 400,并返回专用错误码 `2300` 和结构化引用详情。例如:
```json
{
"success": false,
"error": {
"code": 2300,
"message": "model is used by 2 knowledge base(s); reconfigure or remove those references before deleting",
"details": {
"knowledge_bases": [
{"id": "kb-1", "name": "Product docs", "bindings": ["vlm_model"]},
{"id": "kb-2", "name": "Engineering", "bindings": ["vlm_model"]}
],
"agents": [],
"long_term_memory": {"bindings": []},
"knowledge_base_total": 2,
"agent_total": 0
}
}
}
```
`bindings` 是稳定的配置类型标识:知识库可能返回 `embedding_model`、`summary_model`、`image_processing_model`、`vlm_model`、`asr_model`、`wiki_synthesis_model`;智能体可能返回 `chat_model`、`rerank_model`、`vlm_model`、`asr_model`、`query_understand_model`、`follow_up_model`;长期记忆可能返回 `embedding_model`、`extract_model`。同一对象的多处引用会合并到一个 `bindings` 数组。`knowledge_bases` / `agents` 最多各返回 50 条;未截断的引用数在 `knowledge_base_total` / `agent_total`。404 表示模型不存在。
## 参数说明
### ModelType (模型类型)
| 值 | 前端别名 | 说明 | 用途 |
| ----------- | ----------- | ------------ | ------------------------------ |
| KnowledgeQA | `chat` | 对话模型 | 知识库问答、对话生成 |
| Embedding | `embedding` | 嵌入模型 | 文本向量化、知识库检索 |
| Rerank | `rerank` | 排序模型 | 检索结果重排序、相关性优化 |
| VLLM | `vllm` | 视觉语言模型 | 多模态分析、图文理解 |
| ASR | `asr` | 语音识别模型 | 音频转写 |
> 创建/更新接口请求体的 `type` 字段使用第一列的后端枚举值(如 `KnowledgeQA`);`GET /models/providers?model_type=` 查询参数使用第二列的前端别名(如 `chat`)。
### ModelSource (模型来源)
| 值 | 说明 | 配置要求 |
| -------- | ---------- | -------------------------------- |
| local | 本地模型 | 需要已安装 Ollama 并拉取模型 |
| remote | 远程 API | 需要提供 `base_url` 和 `api_key` |
### Parameters (模型参数)
| 字段 | 类型 | 必填 | 说明 |
| -------------------- | ----------------- | ---- | ---------------------------------------------------------- |
| base_url | string | 否 | API 服务地址;远程模型必填,会经过 SSRF 校验 |
| api_key | string | 否 | API 密钥;远程模型必填,存储时使用 AES-256 加密 |
| provider | string | 否 | 服务商标识(见上方支持列表),用于选择特定的 API 适配器 |
| interface_type | string | 否 | 接口风格标识(OpenAI 兼容请留空) |
| embedding_parameters | object | 否 | Embedding 模型专用参数,见下方 |
| parameter_size | string | 否 | 模型参数规模(如 `7B`/`13B`/`70B`),通常由后端写入 |
| extra_config | object<string,string> | 否 | 服务商特定的额外配置 |
| custom_headers | object<string,string> | 否 | 调用上游 API 时附加的自定义 HTTP 头;保留头会被忽略 |
| supports_vision | bool | 否 | 模型是否支持图像/多模态输入 |
### EmbeddingParameters (嵌入参数)
| 字段 | 类型 | 必填 | 说明 |
| ---------------------- | ---- | ---- | ------------------------------- |
| dimension | int | 否 | 向量维度(如 768、1024) |
| truncate_prompt_tokens | int | 否 | 截断 Token 数(0 表示不截断) |
File diff suppressed because it is too large Load Diff
+477
View File
@@ -0,0 +1,477 @@
# 会话管理 API
[返回目录](./README.md)
会话(Session)是纯粹的对话容器,仅存储基础信息(标题、描述、置顶状态等)。所有与知识库、模型、检索策略相关的配置均在查询时由 Custom Agent 提供,不再存储在会话中。
| 方法 | 路径 | 描述 |
| ------ | ------------------------------------------ | ----------------------------- |
| POST | `/sessions` | 创建会话 |
| DELETE | `/sessions/batch` | 批量删除会话 |
| GET | `/sessions/:id` | 获取会话详情 |
| GET | `/sessions` | 获取当前空间的会话列表 |
| PUT | `/sessions/:id` | 更新会话 |
| DELETE | `/sessions/:id` | 删除会话 |
| DELETE | `/sessions/:id/messages` | 清空会话消息 |
| POST | `/sessions/:session_id/generate_title` | 生成会话标题 |
| POST | `/sessions/:session_id/stop` | 停止生成 |
| POST | `/sessions/:session_id/pin` | 置顶会话 |
| DELETE | `/sessions/:id/pin` | 取消置顶会话 |
| GET | `/sessions/continue-stream/:session_id` | 继续未完成的流式响应 |
> **路由命名说明**:置顶接口的 POST 与 DELETE 使用了不同的路径参数名(POST 用 `:session_id`,DELETE 用 `:id`)。这是由于 gin 路由器为每个 HTTP 方法维护独立的 radix tree,且既有树中的通配符命名不同,必须保留以避免注册时的 `wildcard conflicts` panic。两者语义上都指会话 ID。
## POST `/sessions` - 创建会话
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/sessions' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"title": "我的新对话",
"description": "关于 AI 的讨论"
}'
```
**请求参数**:
| 字段 | 类型 | 必填 | 描述 |
| ------------- | ------ | ---- | -------- |
| `title` | string | 否 | 会话标题 |
| `description` | string | 否 | 会话描述 |
**响应**:
```json
{
"success": true,
"data": {
"id": "411d6b70-9a85-4d03-bb74-aab0fd8bd12f",
"title": "我的新对话",
"description": "关于 AI 的讨论",
"tenant_id": 1,
"user_id": "u-001",
"is_pinned": false,
"created_at": "2026-03-27T12:26:19.611616+08:00",
"updated_at": "2026-03-27T12:26:19.611616+08:00",
"deleted_at": null
}
}
```
> 通过 API-Key 调用时 `user_id` 可能为空,此时会话以空间级可见。
## DELETE `/sessions/batch` - 批量删除会话
支持两种模式:按 ID 列表批量删除,或删除当前空间的所有会话。
**请求 - 按 ID 列表删除**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/sessions/batch' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"ids": [
"411d6b70-9a85-4d03-bb74-aab0fd8bd12f",
"ceb9babb-1e30-41d7-817d-fd584954304b"
]
}'
```
**请求 - 删除所有会话**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/sessions/batch' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"delete_all": true
}'
```
**请求参数**:
| 字段 | 类型 | 必填 | 描述 |
| ------------ | -------- | ---- | ---------------------------------------------------------- |
| `ids` | string[] | 否 | 要删除的会话 ID 列表(`delete_all` 为 `false` 时必填) |
| `delete_all` | bool | 否 | 设为 `true` 时删除当前空间的所有会话,忽略 `ids` 字段 |
**响应**:
```json
{
"success": true,
"message": "Sessions deleted successfully"
}
```
`delete_all=true` 时 `message` 为 `"All sessions deleted successfully"`。
## GET `/sessions/:id` - 获取会话详情
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/sessions/ceb9babb-1e30-41d7-817d-fd584954304b' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**路径参数**:
| 字段 | 类型 | 必填 | 描述 |
| ---- | ------ | ---- | ------- |
| `id` | string | 是 | 会话 ID |
**响应**:
```json
{
"success": true,
"data": {
"id": "ceb9babb-1e30-41d7-817d-fd584954304b",
"title": "模型优化策略",
"description": "",
"tenant_id": 1,
"user_id": "u-001",
"is_pinned": true,
"pinned_at": "2026-04-01T09:12:33.123456+08:00",
"created_at": "2026-03-27T10:24:38.308596+08:00",
"updated_at": "2026-03-27T10:25:41.317761+08:00",
"deleted_at": null
}
}
```
会话不存在时返回 `404`。
## GET `/sessions` - 获取当前空间的会话列表
获取当前空间的会话列表,支持分页、关键字搜索、按来源 / Agent 过滤。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/sessions?page=1&page_size=10&keyword=AI&source=web' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**查询参数**:
| 字段 | 类型 | 必填 | 描述 |
| ----------- | ------ | ---- | ----------------------------------------------------------------- |
| `page` | int | 否 | 页码(默认 1) |
| `page_size` | int | 否 | 每页数量(默认 10) |
| `keyword` | string | 否 | 按标题模糊匹配(ILIKE `%keyword%`) |
| `source` | string | 否 | 来源过滤:`web`(无 IM 映射)或 IM 平台名,如 `feishu`、`wechat`、`slack` |
| `agent_id` | string | 否 | 按 Agent 过滤(仅对 IM 会话生效) |
**响应**:
```json
{
"success": true,
"data": [
{
"id": "411d6b70-9a85-4d03-bb74-aab0fd8bd12f",
"title": "我的新对话",
"description": "",
"tenant_id": 1,
"user_id": "u-001",
"is_pinned": true,
"pinned_at": "2026-04-01T09:12:33.123456+08:00",
"created_at": "2026-03-27T12:26:19.611616+08:00",
"updated_at": "2026-03-27T12:26:19.611616+08:00",
"deleted_at": null,
"im_platform": "feishu",
"im_chat_id": "oc_xxx",
"im_agent_id": "agent-001"
}
],
"total": 1,
"page": 1,
"page_size": 10
}
```
> 列表项始终包含置顶状态字段,IM 来源相关字段(`im_platform`、`im_chat_id`、`im_thread_id`、`im_user_id`、`im_agent_id`、`im_channel_id`)仅对 IM 创建的会话填充,Web 会话省略。
## PUT `/sessions/:id` - 更新会话
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/sessions/411d6b70-9a85-4d03-bb74-aab0fd8bd12f' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"title": "WeKnora 技术讨论",
"description": "关于 WeKnora 架构的讨论"
}'
```
**路径参数**:
| 字段 | 类型 | 必填 | 描述 |
| ---- | ------ | ---- | ------- |
| `id` | string | 是 | 会话 ID |
**请求参数**:
| 字段 | 类型 | 必填 | 描述 |
| ------------- | ------ | ---- | -------- |
| `title` | string | 否 | 会话标题 |
| `description` | string | 否 | 会话描述 |
**响应**:
```json
{
"success": true,
"data": {
"id": "411d6b70-9a85-4d03-bb74-aab0fd8bd12f",
"title": "WeKnora 技术讨论",
"description": "关于 WeKnora 架构的讨论",
"tenant_id": 1,
"user_id": "u-001",
"is_pinned": false,
"created_at": "2026-03-27T12:26:19.611616+08:00",
"updated_at": "2026-03-27T14:20:56.738424+08:00",
"deleted_at": null
}
}
```
会话不存在时返回 `404`。
## DELETE `/sessions/:id` - 删除会话
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/sessions/411d6b70-9a85-4d03-bb74-aab0fd8bd12f' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**路径参数**:
| 字段 | 类型 | 必填 | 描述 |
| ---- | ------ | ---- | ------- |
| `id` | string | 是 | 会话 ID |
**响应**:
```json
{
"success": true,
"message": "Session deleted successfully"
}
```
## DELETE `/sessions/:id/messages` - 清空会话消息
删除会话中的所有消息,同时清除 LLM 上下文和聊天历史知识库条目。会话本身保留。
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/sessions/ceb9babb-1e30-41d7-817d-fd584954304b/messages' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**路径参数**:
| 字段 | 类型 | 必填 | 描述 |
| ---- | ------ | ---- | ------- |
| `id` | string | 是 | 会话 ID |
**响应**:
```json
{
"success": true,
"message": "Session messages cleared successfully"
}
```
## POST `/sessions/:session_id/generate_title` - 生成会话标题
根据消息内容自动生成会话标题。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/sessions/ceb9babb-1e30-41d7-817d-fd584954304b/generate_title' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"messages": [
{
"role": "user",
"content": "你好,我想了解关于人工智能的知识"
},
{
"role": "assistant",
"content": "人工智能是计算机科学的一个分支..."
}
]
}'
```
**路径参数**:
| 字段 | 类型 | 必填 | 描述 |
| ------------ | ------ | ---- | ------- |
| `session_id` | string | 是 | 会话 ID |
**请求参数**:
| 字段 | 类型 | 必填 | 描述 |
| ---------- | --------- | ---- | ---------------------------- |
| `messages` | Message[] | 是 | 用作标题生成上下文的消息列表 |
**响应**:
```json
{
"success": true,
"data": "人工智能基础知识"
}
```
## POST `/sessions/:session_id/stop` - 停止生成
停止当前正在进行的助手回复生成任务。后端会向流中追加一条 `stop` 事件,由活跃的 SSE 处理协程感知并发起取消。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/sessions/7c966c74-610e-4516-8d5b-05e14b2e4ee0/stop' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"message_id": "ebbf7e53-dfe6-44d5-882f-36a4104910b5"
}'
```
**路径参数**:
| 字段 | 类型 | 必填 | 描述 |
| ------------ | ------ | ---- | ------- |
| `session_id` | string | 是 | 会话 ID |
**请求参数**:
| 字段 | 类型 | 必填 | 描述 |
| ------------ | ------ | ---- | ----------------------- |
| `message_id` | string | 是 | 要停止生成的助手消息 ID |
**响应**:
```json
{
"success": true,
"message": "Generation stopped"
}
```
若消息已完成(无需停止):
```json
{
"success": true,
"message": "Message already completed"
}
```
> 消息不属于当前会话返回 `403`;消息或会话不存在返回 `404`。
## POST `/sessions/:session_id/pin` - 置顶会话
将指定会话置顶(用户维度)。
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/sessions/ceb9babb-1e30-41d7-817d-fd584954304b/pin' \
--header 'X-API-Key: sk-xxxxx'
```
**路径参数**:
| 字段 | 类型 | 必填 | 描述 |
| ------------ | ------ | ---- | ------- |
| `session_id` | string | 是 | 会话 ID |
**响应**:
```json
{
"success": true,
"is_pinned": true
}
```
会话不存在或对当前用户不可见时返回 `404`。
## DELETE `/sessions/:id/pin` - 取消置顶会话
取消指定会话的置顶。
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/sessions/ceb9babb-1e30-41d7-817d-fd584954304b/pin' \
--header 'X-API-Key: sk-xxxxx'
```
**路径参数**:
| 字段 | 类型 | 必填 | 描述 |
| ---- | ------ | ---- | ------- |
| `id` | string | 是 | 会话 ID |
**响应**:
```json
{
"success": true,
"is_pinned": false
}
```
> 同上,`POST /pin` 与 `DELETE /pin` 路径参数名不同,但语义上都是会话 ID。
## GET `/sessions/continue-stream/:session_id` - 继续未完成的流式响应
用于在 SSE 连接断开后重新连接正在进行的流式响应:先回放该消息已产生的所有事件,再继续推送后续事件,直至 `complete`。
**路径参数**:
| 字段 | 类型 | 必填 | 描述 |
| ------------ | ------ | ---- | ------- |
| `session_id` | string | 是 | 会话 ID |
**查询参数**:
| 字段 | 类型 | 必填 | 描述 |
| ------------ | ------ | ---- | ------------------------------------------------------------------------------- |
| `message_id` | string | 是 | 从 `/messages/:session_id/load` 接口中获取的 `is_completed` 为 `false` 的消息 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/sessions/continue-stream/ceb9babb-1e30-41d7-817d-fd584954304b?message_id=b8b90eeb-7dd5-4cf9-81c6-5ebcbd759451' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应格式**:
服务器端事件流(Server-Sent Events),事件结构与 `/knowledge-chat/:session_id`、`/agent-chat/:session_id` 返回结果一致。若该消息当前在流中已无事件返回 `404 No stream events found`;若消息记录不存在返回 `404 Incomplete message not found`。
+305
View File
@@ -0,0 +1,305 @@
# Skills API
[返回目录](./README.md)
| 方法 | 路径 | 描述 |
| ---- | --------- | ------------------ |
| GET | `/skills` | 获取预装 Skills 列表 |
| POST | `/sandbox-configs/{id}/skills` | 安装技能(zip 上传或托管平台 source) |
| POST | `/sandbox-configs/{id}/skills/{skillId}/reinstall` | 用已保存的安装包重试安装 |
| POST | `/sandbox-configs/{id}/skills/{skillId}/stop` | 停止卡住的安装 |
| GET | `/sandbox-configs/{id}/skills/{skillId}/files` | 列出已安装技能的文件 |
| GET | `/sandbox-configs/{id}/skills/{skillId}/files/content` | 读取已安装技能中的单个文件 |
| PATCH | `/sandbox-configs/{id}/skills/{skillId}` | 启用/停用技能,或设置空间级环境变量 |
| GET | `/me/env-vars` | 列出自己的环境变量与各技能的声明 |
| PUT | `/me/env-vars/skill` | 设置自己在某个技能上的变量值 |
| DELETE | `/me/env-vars/skill` | 删除自己在某个技能上的变量值 |
| PUT | `/me/env-vars/sandbox` | 设置自己在某个沙箱配置上的变量值 |
| DELETE | `/me/env-vars/sandbox` | 删除自己在某个沙箱配置上的变量值 |
## GET `/skills` - 获取预装 Skills 列表
获取系统中所有预装的智能体技能列表。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/skills' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"name": "web_search",
"description": "搜索互联网获取最新信息"
},
{
"name": "code_interpreter",
"description": "执行代码并返回结果"
},
{
"name": "image_generation",
"description": "根据文本描述生成图片"
}
],
"skills_available": true,
"success": true
}
```
当系统未配置 Skills 时,`skills_available` 返回 `false`,`data` 为空数组:
```json
{
"data": [],
"skills_available": false,
"success": true
}
```
## POST `/sandbox-configs/{id}/skills` - 安装技能
把技能安装到指定沙箱配置的镜像上。安装会启动沙箱并运行数分钟,本接口只负责受理,随后通过
`GET /sandbox-configs/{id}/skills/{skillId}/install-events` 跟随进度。
两种请求体二选一:
### 1. 上传 zip(multipart)
```curl
curl --location 'http://localhost:8080/api/v1/sandbox-configs/{id}/skills' \
--header 'X-API-Key: sk-xxxxx' \
--form 'file=@"skill.zip"'
```
### 2. 从托管平台安装(JSON)
`source` 只接受一种明确写法,不会根据下载结果猜测:
| 输入 | 含义 |
| --- | --- |
| `@owner/slug`、`@owner/slug@1.2.0` | ClawHub(默认 registry) |
| `my-skill`、`my-skill@1.2.0`(不含 `/`) | ClawHub slug |
| `https://clawhub.ai/skills-sh/owner/repo/slug`、`skills-sh:owner/repo/slug` | ClawHub 上的 skills.sh 联邦条目(经 ClawHub install API 解析到钉死的 GitHub commit;仓库内路径可能比 URL slug 更深) |
| `https://clawhub.ai/...`、`https://skillhub.cn/...`、自托管 SkillHub 页面 | 对应 registry |
| `https://github.com/...`、`https://gitlab.com/...`、`https://skills.sh/...` | Git 托管;`skills.sh` 的 `owner/repo/slug` 页面同样走 ClawHub resolver |
| `https://…/foo.zip` 或 `…/SKILL.md` | 直接下载 |
`owner/slug`(无 `@`、无 URL)会 400:它既是 ClawHub id 也是 GitHub 仓库,请改成 `@owner/slug` 或粘贴完整链接。
来源必须可匿名读取:服务端不会为这次下载附带任何凭据,因此私有仓库/私有 registry 需要先自行导出 zip 再上传。
```curl
curl --location 'http://localhost:8080/api/v1/sandbox-configs/{id}/skills' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{"source":"@owner/slug"}'
```
**响应**(202):
```json
{
"success": true,
"data": {
"skill_id": "..."
}
}
```
## POST `/sandbox-configs/{id}/skills/{skillId}/reinstall` - 重试安装
用服务端已保存的安装包重新跑一遍安装,无需重新上传 zip 或重新提供 source。适用于安装失败的原因与安装包本身无关的情况:沙箱不可达、依赖源超时、安装过程被中断等。
与安装接口一样只负责受理,进度同样通过
`GET /sandbox-configs/{id}/skills/{skillId}/install-events` 跟随。技能会复用同一个 `skill_id`,不会产生新记录。
已经在当前镜像中正常服务、且安装包未变的技能会被跳过,不会重复构建快照。若该技能的安装包已不在存储中,返回 400,此时只能重新上传。
```curl
curl --location --request POST \
'http://localhost:8080/api/v1/sandbox-configs/{id}/skills/{skillId}/reinstall' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**(202):
```json
{
"success": true,
"data": {
"skill_id": "..."
}
}
```
## POST `/sandbox-configs/{id}/skills/{skillId}/stop` - 停止安装
中止进行中的安装,之后可以重试或卸载。服务重启后安装行可能一直停在 `installing` 且没有存活进程,本接口会立刻改写该行,不必等 stuck-run reaper。卸载不受影响。
状态变为 `failed`,错误为「安装已停止」。
```curl
curl --location --request POST \
'http://localhost:8080/api/v1/sandbox-configs/{id}/skills/{skillId}/stop' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**(200):
```json
{
"success": true,
"data": {
"id": "...",
"status": "failed",
"error": "安装已停止"
}
}
```
若技能不是 `installing`(且不是已经 `failed`),返回 400。已经 `failed` 的停止是幂等成功。
## GET `/sandbox-configs/{id}/skills/{skillId}/files` - 列出技能文件
返回该技能存档里的文件路径与大小。路径相对技能根目录(`SKILL.md` 所在目录),不启动沙箱。
```curl
curl --location 'http://localhost:8080/api/v1/sandbox-configs/{id}/skills/{skillId}/files' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": [
{ "path": "SKILL.md", "size": 412 },
{ "path": "scripts/extract.py", "size": 1280 }
]
}
```
## GET `/sandbox-configs/{id}/skills/{skillId}/files/content` - 读取技能文件
`path` 为技能根目录相对路径。文本以 UTF-8 返回;较小的图片为 base64;其它二进制文件不返回正文,并设置 `binary: true`。
```curl
curl --location 'http://localhost:8080/api/v1/sandbox-configs/{id}/skills/{skillId}/files/content?path=SKILL.md' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": {
"path": "SKILL.md",
"size": 412,
"encoding": "utf-8",
"media_type": "text/markdown",
"content": "---\nname: pdf-tools\n..."
}
}
```
## 技能的环境变量
技能安装时会声明自己需要哪些环境变量(名称、说明、是否必填)。变量的**值**分两层,执行时按「本人的值 → 空间级值」的顺序取,取不到必填项则拒绝执行:
| 层级 | 谁能写 | 作用范围 | 接口 |
| --- | --- | --- | --- |
| 空间级 | Admin+ | 该空间所有人 | `PATCH /sandbox-configs/{id}/skills/{skillId}` |
| 个人级 | 任何登录成员 | 仅本人 | `PUT /me/env-vars/skill` |
任何接口都**不会**回读已保存的值,只返回 `unset` / `workspace` / `user` 三种状态。清空一个值用 DELETE,而不是写入空字符串——「没填」和「不需要」是两种状态。
个人级的值按**调用身份**存放,而不是按用户 ID。用 API Key 驱动的调用与网页登录是不同身份:在网页 Settings 里填的值不会作用于 API Key 发起的执行,反之亦然。集成方若通过 API Key 运行需要凭据的技能,请让管理员配置空间级值。
### PATCH `/sandbox-configs/{id}/skills/{skillId}` - 更新技能
`enabled` 与 `envs` 都是可选的,可只送其一,但不能都不送(400)。`envs` 只写技能已声明的名称,未声明的名称会被忽略而不是报错;值为空字符串表示清空该值并保留声明。
```curl
curl --location --request PATCH \
'http://localhost:8080/api/v1/sandbox-configs/{id}/skills/{skillId}' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{"enabled":true,"envs":{"TAVILY_API_KEY":"tvly-xxxxx"}}'
```
### GET `/me/env-vars` - 列出自己的环境变量
按沙箱配置分组,返回本人的配置级变量,以及该配置下每个已启用技能声明的变量。`source` 表示这次执行实际会用哪一层的值。
```curl
curl --location 'http://localhost:8080/api/v1/me/env-vars' \
--header 'Authorization: Bearer <token>'
```
**响应**:
```json
{
"success": true,
"data": [
{
"sandbox_config_id": "cfg-1",
"sandbox_config_name": "默认沙箱",
"description": "日常对话用的沙箱",
"vars": [
{ "name": "HTTP_PROXY", "source": "user", "updated_at": "2026-08-27T10:00:00Z" }
],
"skills": [
{
"skill_id": "sk-1",
"skill_name": "web-search",
"description": "通过 Tavily 检索网页",
"vars": [
{ "name": "TAVILY_API_KEY", "description": "Tavily 搜索密钥", "required": true, "source": "workspace" },
{ "name": "REGION", "source": "unset" }
]
}
]
}
]
}
```
### PUT / DELETE `/me/env-vars/skill` - 设置或删除自己在技能上的值
`name` 必须是该技能声明过的名称,否则 400。删除后该技能重新回落到空间级值(若有)。
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/me/env-vars/skill' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{"skill_id":"sk-1","name":"TAVILY_API_KEY","value":"tvly-xxxxx"}'
```
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/me/env-vars/skill' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{"skill_id":"sk-1","name":"TAVILY_API_KEY"}'
```
删除一个本就没设过的值返回 404。
### PUT / DELETE `/me/env-vars/sandbox` - 设置或删除自己的配置级变量
配置级变量不依附于任何技能,名称自定,会注入本人在该沙箱配置上的每一次技能脚本与 shell 命令。`WEKNORA_` 前缀与 `PATH` 等保留名会被拒绝。
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/me/env-vars/sandbox' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{"sandbox_config_id":"cfg-1","name":"HTTP_PROXY","value":"http://127.0.0.1:7890"}'
```
+341
View File
@@ -0,0 +1,341 @@
# Storage Backend API
[返回目录](./README.md)
存储后端(StorageBackend)API 用于管理空间的对象/文件存储实例。一个空间可以注册多个存储实例(`local`、`minio`、`cos`、`tos`、`s3`、`oss`、`ks3`、`obs`),并将不同知识库绑定到不同实例;空间维度还有一个默认实例(`default_storage_backend_id`),未显式绑定的新知识库使用该默认实例。
接口同时管理用户创建的实例(`source: "user"`)以及从环境变量快照生成的只读实例(`source: "env"`)。存储后端 CRUD 需要 **Admin+** 角色;对 API Key 需具备 `manage_storage_backends` capability(或 full-access)。
| 方法 | 路径 | 描述 | 最低权限 |
| ------ | --------------------------------- | -------------------------------------- | -------- |
| GET | `/storage-backends/types` | 获取 `STORAGE_ALLOW_LIST` 允许的存储类型 | Viewer+ |
| POST | `/storage-backends/test` | 使用原始配置测试连通性(不落库) | Admin+ |
| POST | `/storage-backends` | 创建存储实例 | Admin+ |
| GET | `/storage-backends` | 获取存储实例列表 | Viewer+ |
| GET | `/storage-backends/:id` | 获取存储实例详情 | Viewer+ |
| PUT | `/storage-backends/:id` | 更新存储实例(名称/凭据/状态) | Admin+ |
| DELETE | `/storage-backends/:id` | 删除存储实例(软删除) | Admin+ |
| POST | `/storage-backends/:id/test` | 测试已保存实例的连通性 | Admin+ |
| PUT | `/storage-backends/:id/default` | 设为空间默认存储实例 | Admin+ |
> 所有响应中的敏感字段(`access_key_id`、`secret_access_key`)都会被掩码。更新时若提交掩码占位符,则保留库中原有的真实凭据,不会被占位符覆盖。
## 存储配置字段(`config`)
不同 provider 使用同一套归一化的配置对象,按 provider 取用其中的子集:
| 字段 | 类型 | 说明 |
| ------------------- | ------- | ------------------------------------------------------------- |
| mode | string | MinIO 模式:`docker`(复用环境变量凭据)或 `remote` |
| endpoint | string | 对象存储 endpoint(COS 使用 region,不需要 endpoint) |
| region | string | 区域 |
| access_key_id | string | 访问密钥 ID(COS 对应 SecretID);响应中掩码 |
| secret_access_key | string | 访问密钥 Secret(COS 对应 SecretKey);响应中掩码 |
| bucket_name | string | Bucket 名称 |
| path_prefix | string | 对象前缀,必须为相对路径,禁止 `/` 开头或 `..` 上跳 |
| app_id | string | 腾讯云 COS AppID |
| use_ssl | boolean | 是否使用 SSL |
| force_path_style | boolean | S3 是否使用 path-style 寻址 |
| use_temp_bucket | boolean | OSS 是否使用临时 bucket |
| temp_bucket_name | string | 临时 bucket 名称 |
| temp_region | string | 临时 bucket 区域 |
> `endpoint`、`region`、`bucket_name`、`path_prefix` 决定对象的物理位置,**创建后不可变更**(更新时会被拒绝);如需迁移请使用存储迁移流程。凭据可通过更新单独轮换。
## GET `/storage-backends/types` - 获取允许的存储类型
返回 `STORAGE_ALLOW_LIST` 允许的 provider 列表,可用于前端动态表单生成。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/storage-backends/types' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": ["local", "minio", "cos", "s3"]
}
```
## POST `/storage-backends/test` - 使用原始配置测试连通性
用前端表单中尚未保存的配置执行一次连通性测试,不会写入数据库。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| -------- | ------ | ---- | ------------------------------------------- |
| name | string | 是 | 实例显示名 |
| provider | string | 是 | 存储类型,取自 `/storage-backends/types` |
| config | object | 否 | 该 provider 对应的存储配置字段 |
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/storage-backends/test' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "s3-hot",
"provider": "s3",
"config": {
"endpoint": "https://s3.example.com",
"region": "ap-test-1",
"access_key_id": "AKID",
"secret_access_key": "SECRET",
"bucket_name": "weknora"
}
}'
```
**响应(成功)**:
```json
{
"success": true
}
```
**响应(失败)**:
```json
{
"success": false,
"error": "连接被拒绝,请确认服务已启动且端口正确"
}
```
> 测试失败时 HTTP 状态码仍为 `200`,错误信息通过 `success: false` + `error` 返回;`error` 已脱敏,不会泄漏内部主机名、IP、端口或 TLS 细节。
## POST `/storage-backends` - 创建存储实例
为当前空间创建一个新的存储实例。创建前会先校验配置、执行 SSRF 校验(本地存储与 docker 模式 MinIO 除外),并执行一次连通性测试;任一环节失败都会返回 `400`。同一空间内实例名称不允许重复。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| -------- | ------ | ---- | ------------------------------------------- |
| name | string | 是 | 实例显示名(空间内唯一) |
| provider | string | 是 | 存储类型,取自 `/storage-backends/types` |
| config | object | 否 | 该 provider 对应的存储配置字段 |
| status | string | 否 | `active`(默认)或 `disabled` |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/storage-backends' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "s3-hot",
"provider": "s3",
"config": {
"endpoint": "https://s3.example.com",
"region": "ap-test-1",
"access_key_id": "AKID",
"secret_access_key": "SECRET",
"bucket_name": "weknora",
"path_prefix": "prod"
}
}'
```
**响应** (201):
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "s3-hot",
"provider": "s3",
"config": {
"endpoint": "https://s3.example.com",
"region": "ap-test-1",
"access_key_id": "***",
"secret_access_key": "***",
"bucket_name": "weknora",
"path_prefix": "prod"
},
"source": "user",
"status": "active",
"legacy_alias": false,
"created_at": "2026-07-15T10:00:00Z",
"updated_at": "2026-07-15T10:00:00Z"
}
}
```
## GET `/storage-backends` - 获取存储实例列表
返回当前空间的所有存储实例(凭据已掩码),并在顶层返回空间默认实例 id。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/storage-backends' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "s3-hot",
"provider": "s3",
"config": { "endpoint": "https://s3.example.com", "access_key_id": "***", "secret_access_key": "***", "bucket_name": "weknora" },
"source": "user",
"status": "active",
"legacy_alias": false
}
],
"default_storage_backend_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
## GET `/storage-backends/:id` - 获取存储实例详情
根据 ID 获取当前空间下的单个存储实例,凭据已掩码。
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | ------------- |
| id | string | 是 | 存储实例 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/storage-backends/550e8400-e29b-41d4-a716-446655440000' \
--header 'X-API-Key: sk-xxxxx'
```
## PUT `/storage-backends/:id` - 更新存储实例
更新实例的可变字段(`name`、凭据、`status`)。`provider` 与物理位置字段(`endpoint`、`region`、`bucket_name`、`path_prefix`)不可变更,尝试更改会返回 `400`。环境变量来源(`source: "env"`)的实例只读,不可更新。更新同样会执行校验与连通性测试。
> 若 `access_key_id` / `secret_access_key` 提交为掩码占位符(`***`),则保留库中原有真实凭据。
**禁用保护**:将当前为默认实例、或仍有知识库绑定的实例改为 `disabled` 会被拒绝(`400`)。
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | ------------- |
| id | string | 是 | 存储实例 ID |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/storage-backends/550e8400-e29b-41d4-a716-446655440000' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "s3-hot-renamed",
"provider": "s3",
"config": {
"access_key_id": "***",
"secret_access_key": "NEW_SECRET"
}
}'
```
## DELETE `/storage-backends/:id` - 删除存储实例
对存储实例执行软删除。以下情况删除会被拒绝(`400`):实例是空间默认实例、仍有知识库绑定、环境变量来源(只读)、或为 legacy 别名(旧文件路径可能仍引用它)。删除在事务中执行;PostgreSQL 上对目标行加行锁以避免并发绑定竞态。
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | ------------- |
| id | string | 是 | 存储实例 ID |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/storage-backends/550e8400-e29b-41d4-a716-446655440000' \
--header 'X-API-Key: sk-xxxxx'
```
**响应(成功)**:
```json
{
"success": true
}
```
## POST `/storage-backends/:id/test` - 测试已保存实例的连通性
对已保存的存储实例用其存储的凭据执行一次连通性测试。
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | ------------- |
| id | string | 是 | 存储实例 ID |
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/storage-backends/550e8400-e29b-41d4-a716-446655440000/test' \
--header 'X-API-Key: sk-xxxxx'
```
**响应(成功)**:
```json
{
"success": true
}
```
> 与 `/storage-backends/test` 一致,测试失败时 HTTP 状态码仍为 `200`,错误经脱敏后通过 `success: false` + `error` 返回。
## PUT `/storage-backends/:id/default` - 设为空间默认实例
将某个存储实例标记为空间默认实例。仅 `active` 状态的实例可以设为默认。未显式绑定存储实例的新知识库将使用默认实例。
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | ------------- |
| id | string | 是 | 存储实例 ID |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/storage-backends/550e8400-e29b-41d4-a716-446655440000/default' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true
}
```
## 环境变量存储实例
通过 `STORAGE_TYPE` 等环境变量配置的存储会以只读实例(`source: "env"`、`legacy_alias: true`)形式参与实例解析,使 env-only 部署与用户管理的实例走同一套解析路径。这类实例在每次启动时按环境变量刷新,且不可通过 API 更新或删除。
## 错误码
| HTTP 状态码 | 含义 |
| ----------- | ---------------------------------------------------------------- |
| 400 | 请求参数错误、校验失败、SSRF 校验失败、连通性测试失败、尝试更改不可变字段、修改只读实例、删除受保护实例、禁用被引用实例、设为默认时实例非 active |
| 401 | 未认证(缺少空间上下文或 API Key) |
| 403 | 权限不足(需 Admin+ 或 API Key `manage_storage_backends` capability) |
| 404 | 存储实例不存在 |
| 409 | 同名存储实例已存在 |
+255
View File
@@ -0,0 +1,255 @@
# 系统管理 API
[返回目录](./README.md)
| 方法 | 路径 | 描述 |
| ------ | --------------------------------- | ---------------------- |
| GET | `/system/capabilities` | 获取部署能力清单 |
| GET | `/system/info` | 获取系统信息 |
| GET | `/system/parser-engines` | 获取解析引擎列表 |
| POST | `/system/parser-engines/check` | 检查解析引擎可用性 |
| POST | `/system/docreader/reconnect` | 重连文档解析服务 |
| GET | `/system/storage-engine-status` | 获取存储引擎状态 |
| POST | `/system/storage-engine-check` | 检查存储引擎连通性 |
## GET `/system/capabilities` - 获取部署能力清单
返回当前部署版本,以及各功能模块是否已在后端注册对应路由。`supported: false` 表示 SPA 应隐藏相关入口;字段缺失或接口不可用时不应据此清空整个菜单(fail-open),但 Lite 版会始终将 `organizations` 标记为不支持。
**权限**:Viewer+(租户成员);任意有效 API Key 可读(`apiKeyAny`)。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/system/capabilities' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"code": 0,
"msg": "success",
"data": {
"edition": "standard",
"capabilities": {
"organizations": { "supported": true },
"agents": { "supported": true },
"integrations.im": { "supported": true },
"integrations.embed": { "supported": false, "reason": "route_not_registered" },
"integrations.api": { "supported": true },
"settings.mcp": { "supported": true },
"settings.websearch": { "supported": true },
"settings.vectorstore": { "supported": true },
"settings.storage": { "supported": true },
"settings.sandbox": { "supported": true }
}
}
}
```
Lite 版示例(共享空间不可用):
```json
{
"capabilities": {
"organizations": {
"supported": false,
"reason": "not_supported_in_lite"
}
}
}
```
## GET `/system/info` - 获取系统信息
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/system/info' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"version": "1.2.0",
"edition": "community",
"commit_id": "a1b2c3d",
"build_time": "2025-08-12T08:00:00Z",
"go_version": "go1.21.5",
"keyword_index_engine": "bleve",
"vector_store_engine": "milvus",
"graph_database_engine": "neo4j",
"minio_enabled": true,
"db_version": "20250810_001"
},
"success": true
}
```
## GET `/system/parser-engines` - 获取解析引擎列表
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/system/parser-engines' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"name": "docreader",
"label": "DocReader",
"description": "高精度文档解析引擎",
"available": true
},
{
"name": "tika",
"label": "Apache Tika",
"description": "通用文档解析引擎",
"available": false
}
],
"connected": true,
"success": true
}
```
## POST `/system/parser-engines/check` - 检查解析引擎可用性
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/system/parser-engines/check' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"addr": "http://docreader:8000"
}'
```
**响应**:
```json
{
"data": [
{
"name": "docreader",
"label": "DocReader",
"description": "高精度文档解析引擎",
"available": true
}
],
"success": true
}
```
## POST `/system/docreader/reconnect` - 重连文档解析服务
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/system/docreader/reconnect' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"addr": "http://docreader:8000"
}'
```
**响应**:
```json
{
"success": true
}
```
## GET `/system/storage-engine-status` - 获取存储引擎状态
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/system/storage-engine-status' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"engines": [
{
"name": "minio",
"available": true,
"description": "MinIO 对象存储"
},
{
"name": "cos",
"available": false,
"description": "腾讯云 COS 对象存储"
},
{
"name": "s3",
"available": false,
"description": "AWS S3 对象存储"
},
{
"name": "oss",
"available": false,
"description": "阿里云 OSS 对象存储"
}
],
"minio_env_available": true
},
"success": true
}
```
## POST `/system/storage-engine-check` - 检查存储引擎连通性
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/system/storage-engine-check' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"provider": "minio",
"minio": {
"endpoint": "localhost:9000",
"access_key": "minioadmin",
"secret_key": "minioadmin",
"bucket": "weknora",
"use_ssl": false
}
}'
```
**响应**:
```json
{
"data": {
"ok": true,
"message": "连接成功",
"bucket_created": false
},
"success": true
}
```
+183
View File
@@ -0,0 +1,183 @@
# 标签管理 API
[返回目录](./README.md)
| 方法 | 路径 | 描述 |
| ------ | ------------------------------------- | ------------------------ |
| GET | `/knowledge-bases/:id/tags` | 获取知识库标签列表 |
| POST | `/knowledge-bases/:id/tags` | 创建标签 |
| PUT | `/knowledge-bases/:id/tags/:tag_id` | 更新标签 |
| DELETE | `/knowledge-bases/:id/tags/:tag_id` | 删除标签 |
## GET `/knowledge-bases/:id/tags` - 获取知识库标签列表
**查询参数**:
- `page`: 页码(默认 1)
- `page_size`: 每页条数(默认 20)
- `keyword`: 标签名称关键字搜索(可选)
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/tags?page=1&page_size=10' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"total": 2,
"page": 1,
"page_size": 10,
"data": [
{
"id": "tag-00000001",
"tenant_id": 1,
"knowledge_base_id": "kb-00000001",
"name": "技术文档",
"color": "#1890ff",
"sort_order": 1,
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T10:00:00+08:00",
"knowledge_count": 5,
"chunk_count": 120
},
{
"id": "tag-00000002",
"tenant_id": 1,
"knowledge_base_id": "kb-00000001",
"name": "常见问题",
"color": "#52c41a",
"sort_order": 2,
"created_at": "2025-08-12T10:00:00+08:00",
"updated_at": "2025-08-12T10:00:00+08:00",
"knowledge_count": 3,
"chunk_count": 45
}
]
},
"success": true
}
```
## POST `/knowledge-bases/:id/tags` - 创建标签
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | ----------- |
| id | string | 知识库 ID |
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ---------- | ------ | ---- | ------------------------ |
| name | string | 是 | 标签名(同库内唯一) |
| color | string | 否 | 标签颜色(CSS 颜色字符串) |
| sort_order | int | 否 | 排序值(数值越小越靠前) |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/tags' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "产品手册",
"color": "#faad14",
"sort_order": 3
}'
```
**响应**:
```json
{
"data": {
"id": "tag-00000003",
"tenant_id": 1,
"knowledge_base_id": "kb-00000001",
"name": "产品手册",
"color": "#faad14",
"sort_order": 3,
"created_at": "2025-08-12T11:00:00+08:00",
"updated_at": "2025-08-12T11:00:00+08:00"
},
"success": true
}
```
## PUT `/knowledge-bases/:id/tags/:tag_id` - 更新标签
**路径参数**:
| 字段 | 类型 | 说明 |
| ------ | ------ | ----------- |
| id | string | 知识库 ID |
| tag_id | string | 标签 ID |
**参数说明(请求体)**: 同创建接口,所有字段均可选;未传则保留原值。
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/tags/tag-00000003' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "产品手册更新",
"color": "#ff4d4f"
}'
```
**响应**:
```json
{
"data": {
"id": "tag-00000003",
"tenant_id": 1,
"knowledge_base_id": "kb-00000001",
"name": "产品手册更新",
"color": "#ff4d4f",
"sort_order": 3,
"created_at": "2025-08-12T11:00:00+08:00",
"updated_at": "2025-08-12T11:30:00+08:00"
},
"success": true
}
```
## DELETE `/knowledge-bases/:id/tags/:tag_id` - 删除标签
**路径参数**:
| 字段 | 类型 | 说明 |
| ------ | ------ | -------- |
| id | string | 知识库 ID |
| tag_id | string | 标签 ID |
**查询参数**:
| 字段 | 类型 | 默认 | 说明 |
| ----- | ------- | ----- | --------------------------------------------- |
| force | boolean | false | 设置为 `true` 时强制删除(即使标签被引用) |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/knowledge-bases/kb-00000001/tags/tag-00000003?force=true' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"success": true
}
```
+649
View File
@@ -0,0 +1,649 @@
# 空间管理 API
[返回目录](./README.md)
包含两组接口:
- 空间 CRUD(`/tenants`、`/tenants/:id`):当前认证用户对自己所属空间进行管理;跨空间访问需要管理员权限。
- 跨空间接口(`/tenants/all`、`/tenants/search`):**需要服务端启用 `EnableCrossTenantAccess` 且当前用户具备 `CanAccessAllTenants` 权限**,否则返回 403。
- 空间 KV 配置(`/tenants/kv/:key`):当前空间级别的通用配置项,**`tenant_id` 从认证上下文中获取,不在 URL 中传入**。
| 方法 | 路径 | 描述 |
| ------ | -------------------------- | ------------------------------------------------- |
| GET | `/tenants/all` | 获取所有空间列表(需跨空间权限) |
| GET | `/tenants/search` | 分页搜索空间(需跨空间权限) |
| POST | `/tenants` | 创建新空间 |
| GET | `/tenants/:id` | 获取指定空间信息 |
| PUT | `/tenants/:id` | 更新空间信息 |
| DELETE | `/tenants/:id` | 删除空间 |
| GET | `/tenants/:id/api-keys` | 列出空间 API Key(Owner) |
| POST | `/tenants/:id/api-keys` | 创建带角色的 API Key(Owner) |
| DELETE | `/tenants/:id/api-keys/:key_id` | 吊销指定 API Key(Owner) |
| GET | `/tenants/:id/api-principal-config` | 获取 API Key 用户身份配置(Owner) |
| PUT | `/tenants/:id/api-principal-config` | 更新 API Key 用户身份配置(Owner) |
| GET | `/tenants` | 获取当前用户可见的空间列表 |
| GET | `/tenants/kv/:key` | 获取当前空间的 KV 配置(空间由认证上下文确定) |
| PUT | `/tenants/kv/:key` | 更新当前空间的 KV 配置(空间由认证上下文确定) |
## GET `/tenants/all` - 获取所有空间列表
获取系统中所有空间列表,需要跨空间权限。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/tenants/all' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: sk-An7_t_izCKFIJ4iht9Xjcjnj_MC48ILvwezEDki9ScfIa7KA'
```
**响应**:
```json
{
"data": {
"items": [
{
"id": 10001,
"name": "weknora-1",
"description": "weknora workspaces 1",
"status": "active",
"business": "wechat",
"created_at": "2025-08-11T20:37:28.39698+08:00",
"updated_at": "2025-08-11T20:37:28.405693+08:00"
},
{
"id": 10002,
"name": "weknora-2",
"description": "weknora workspaces 2",
"status": "active",
"business": "wechat",
"created_at": "2025-08-11T20:52:58.05679+08:00",
"updated_at": "2025-08-11T20:52:58.060495+08:00"
}
]
},
"success": true
}
```
## GET `/tenants/search` - 搜索空间
按关键词搜索空间,需要跨空间权限。
**查询参数**:
- `keyword`: 搜索关键词(可选)
- `tenant_id`: 按空间ID筛选(可选)
- `page`: 页码(默认 1)
- `page_size`: 每页条数(默认 20)
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/tenants/search?keyword=weknora&page=1&page_size=10' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: sk-An7_t_izCKFIJ4iht9Xjcjnj_MC48ILvwezEDki9ScfIa7KA'
```
**响应**:
```json
{
"data": {
"items": [
{
"id": 10002,
"name": "weknora",
"description": "weknora workspaces",
"status": "active",
"business": "wechat",
"created_at": "2025-08-11T20:52:58.05679+08:00",
"updated_at": "2025-08-11T20:52:58.060495+08:00"
}
],
"total": 1,
"page": 1,
"page_size": 10
},
"success": true
}
```
## POST `/tenants` - 创建新空间
创建一个新的空间。**默认不会**自动发放 API Key;请在创建后通过 `POST /tenants/:id/api-keys` 创建密钥。从旧版本升级时,原有 `tenants.api_key` 会迁移到 `tenant_api_keys` 表并继续可用,直至被吊销。
> **兼容旧行为(可选)**:如需恢复旧版「创建空间即下发默认 API Key」的行为,可将系统设置 `tenant.auto_create_api_key` 置为 `true`(或设置环境变量 `WEKNORA_TENANT_AUTO_CREATE_API_KEY=true`)。开启后,创建空间会自动生成一个 `full_access` 权限的 API Key,并在响应体 `data.api_key` 中返回其明文 token(仅本次创建响应返回,请妥善保存)。默认 `false`。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------------- | ------ | ---- | ------------------------------------------------------ |
| name | string | 是 | 空间名称 |
| description | string | 否 | 空间描述 |
| business | string | 否 | 业务标识(如 `wechat`) |
| retriever_engines | object | 否 | 检索引擎组合配置(`engines` 数组:每项含 `retriever_type` 与 `retriever_engine_type`) |
| storage_quota | int | 否 | 存储配额(字节) |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/tenants' \
--header 'Content-Type: application/json' \
--data '{
"name": "weknora",
"description": "weknora workspaces",
"business": "wechat",
"retriever_engines": {
"engines": [
{
"retriever_type": "keywords",
"retriever_engine_type": "postgres"
},
{
"retriever_type": "vector",
"retriever_engine_type": "postgres"
}
]
}
}'
```
**响应**(默认,不含 API Key):
```json
{
"data": {
"id": 10000,
"name": "weknora",
"description": "weknora workspaces",
"status": "active",
"retriever_engines": {
"engines": [
{
"retriever_engine_type": "postgres",
"retriever_type": "keywords"
},
{
"retriever_engine_type": "postgres",
"retriever_type": "vector"
}
]
},
"business": "wechat",
"storage_quota": 10737418240,
"storage_used": 0,
"created_at": "2025-08-11T20:37:28.396980093+08:00",
"updated_at": "2025-08-11T20:37:28.396980301+08:00",
"deleted_at": null
},
"success": true
}
```
当开启 `tenant.auto_create_api_key`(或 `WEKNORA_TENANT_AUTO_CREATE_API_KEY=true`)时,响应的 `data` 中会额外包含 `api_key` 字段(`full_access` 密钥的明文 token):
```json
{
"data": {
"id": 10000,
"name": "weknora",
"description": "weknora workspaces",
"api_key": "sk-aaLRAgvCRJcmtiL2vLMeB1FB5UV0Q-qB7DlTE1pJ9KA93XZG",
"status": "active",
"business": "wechat",
"storage_quota": 10737418240,
"storage_used": 0,
"created_at": "2025-08-11T20:37:28.396980093+08:00",
"updated_at": "2025-08-11T20:37:28.396980301+08:00",
"deleted_at": null
},
"success": true
}
```
## GET `/tenants/:id` - 获取指定空间信息
获取指定 ID 的空间详情。只能访问自己所属空间;访问其他空间需要跨空间权限,否则返回 403。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ---- | ------- |
| id | int | 空间 ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/tenants/10000' \
--header 'X-API-Key: sk-aaLRAgvCRJcmtiL2vLMeB1FB5UV0Q-qB7DlTE1pJ9KA93XZG' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"id": 10000,
"name": "weknora",
"description": "weknora workspaces",
"api_key": "sk-aaLRAgvCRJcmtiL2vLMeB1FB5UV0Q-qB7DlTE1pJ9KA93XZG",
"status": "active",
"retriever_engines": {
"engines": [
{
"retriever_engine_type": "postgres",
"retriever_type": "keywords"
},
{
"retriever_engine_type": "postgres",
"retriever_type": "vector"
}
]
},
"business": "wechat",
"storage_quota": 10737418240,
"storage_used": 0,
"created_at": "2025-08-11T20:37:28.39698+08:00",
"updated_at": "2025-08-11T20:37:28.405693+08:00",
"deleted_at": null
},
"success": true
}
```
## PUT `/tenants/:id` - 更新空间信息
更新指定空间的基础信息。访问规则同 `GET /tenants/:id`。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ---- | ------- |
| id | int | 空间 ID |
**参数说明(请求体)**: 与 `POST /tenants` 相同字段;未传字段保持原值。
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/tenants/10000' \
--header 'X-API-Key: sk-aaLRAgvCRJcmtiL2vLMeB1FB5UV0Q-qB7DlTE1pJ9KA93XZG' \
--header 'Content-Type: application/json' \
--data '{
"name": "weknora new",
"description": "weknora workspaces new",
"status": "active",
"retriever_engines": {
"engines": [
{
"retriever_engine_type": "postgres",
"retriever_type": "keywords"
},
{
"retriever_engine_type": "postgres",
"retriever_type": "vector"
}
]
},
"business": "wechat",
"storage_quota": 10737418240
}'
```
**响应**:
```json
{
"data": {
"id": 10000,
"name": "weknora new",
"description": "weknora workspaces new",
"api_key": "sk-aaLRAgvCRJcmtiL2vLMeB1FB5UV0Q-qB7DlTE1pJ9KA93XZG",
"status": "active",
"retriever_engines": {
"engines": [
{
"retriever_engine_type": "postgres",
"retriever_type": "keywords"
},
{
"retriever_engine_type": "postgres",
"retriever_type": "vector"
}
]
},
"business": "wechat",
"storage_quota": 10737418240,
"storage_used": 0,
"created_at": "2025-08-11T20:37:28.39698+08:00",
"updated_at": "2025-08-11T20:49:02.13421034+08:00",
"deleted_at": null
},
"success": true
}
```
## DELETE `/tenants/:id` - 删除空间
删除指定空间。访问规则同 `GET /tenants/:id`。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ---- | ------- |
| id | int | 空间 ID |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/tenants/10000' \
--header 'X-API-Key: sk-aaLRAgvCRJcmtiL2vLMeB1FB5UV0Q-qB7DlTE1pJ9KA93XZG' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"message": "Workspace deleted successfully",
"success": true
}
```
## API Key 管理(`tenant_api_keys`)
自 scoped API Key 改造后,密钥以独立记录存储,支持:
- **role**:`viewer`(只读 + 语义检索 POST)、`contributor`(知识库写入)、`admin`(空间级管理,不含 `/api-keys` 管理面)
- **knowledge_base_ids**:可选,将 Key 限制在指定知识库
- **吊销**:`DELETE /tenants/:id/api-keys/:key_id`
- **过期**:创建时可选 `expires_at_unix`
空间 Key 固定绑定创建时的空间。路由级 capability 鉴权与 KB 访问守卫会在 `X-API-Key` 认证后继续强制执行。
### 平台 API Key
系统管理员可在“系统管理 → 平台 API Key”创建不绑定单一空间的 Key。平台 Key 默认可以选择任意存在的空间,但每项操作仍必须具备对应 capability;平台 Key 不支持 `full_access`。
- 管理接口:`GET/POST /system/admin/api-keys`、`DELETE /system/admin/api-keys/:key_id`,仅人类 SystemAdmin 会话可调用,平台 Key 不能创建或吊销其他平台 Key。
- 调用普通空间 API 时必须同时传 `X-Tenant-ID: <空间 ID>`;服务端解析目标空间后继续复用原有空间 Context、路由 capability 和知识库范围检查。
- 调用明确开放的 `/system/admin/*` 控制面接口时不需要 `X-Tenant-ID`,需要 `system_*` capability。
- 平台 Key 明文仅在创建响应的 `data.token` 返回一次;列表仅返回脱敏值。
```bash
curl 'http://localhost:8080/api/v1/knowledge-bases' \
-H 'X-API-Key: <platform-api-key>' \
-H 'X-Tenant-ID: 10000'
```
平台 capability:
| capability | 权限 |
| --- | --- |
| `system_tenants_read` | 列出、搜索、查看全部空间 |
| `system_tenants_manage` | 创建、更新、删除空间以及应用全局空间配置 |
| `system_settings_read` | 读取系统设置 |
| `system_settings_manage` | 更新、重置系统设置 |
| `system_runtime_read` | 查看运行时队列和任务 |
| `system_runtime_manage` | 重试、立即执行、取消、删除运行时任务 |
| `system_audit_read` | 读取平台审计日志 |
平台 Key 也可以携带现有空间 capability,例如 `retrieve`、`ingest`、`manage_kbs`;这些能力作用于请求中 `X-Tenant-ID` 指定的空间。
## API Key Principal:隔离边界与安全说明
`api-principal-config` 控制 `X-API-Key` 请求如何映射为终端 **Principal**。请先理解以下边界,再选择模式。
### Principal 隔离范围(当前实现)
Principal **仅**用于按终端用户隔离以下能力:
- **对话 Session**(创建、列表、读取按外部用户分开;`仅空间` 模式仍共用空间级 Session)
- **MCP OAuth** 访问令牌(同一空间下不同外部用户各自授权,token 互不共用)
- 对话内 MCP OAuth 提示、MCP 工具审批等与终端用户绑定的流程
Principal **不会**缩小 API Key 的 HTTP 路由权限:路由访问由 Key 的 `role` 控制;空间内 RBAC 角色与 `role` 一致。知识库、Agent 等资源的细粒度访问另受 KB 守卫约束。
### 模式与安全假设
| mode | 适用场景 | 安全假设 |
| ---- | -------- | -------- |
| `tenant` | 无 per-user MCP 需求 | 全空间共用一个 MCP OAuth 身份 |
| `direct_header` | 仅可信服务端到服务端 | 用户 ID 来自调用方请求头,**可被持有 API Key 的任意调用方伪造**(冒充其他外部用户并共用/劫持其 MCP OAuth 授权)。面向终端用户或不可信客户端时**禁止**使用;若必须使用,请开启 `require_direct_header` 并确保 API Key 仅保存在可信后端 |
| `signed_token` | 面向终端用户的集成(**推荐**) | 由业务后端使用 `hmac_secret` 为外部用户签发短期 HS256 JWT;无效或缺失 token 返回 401,**不回退**为空间级 Principal |
`direct_header` 模式下,若未携带用户 ID 请求头:`require_direct_header=false` 时回退为空间级 Principal;`require_direct_header=true` 时返回 401。
## GET `/tenants/:id/api-principal-config` - 获取 API Key 用户身份配置
返回空间级 API Key 请求如何映射为终端 Principal 的配置。**需要 Owner 权限**。
**响应字段**:
| 字段 | 类型 | 说明 |
| ---- | ---- | ---- |
| mode | string | `tenant` / `direct_header` / `signed_token` |
| direct_header_name | string | 直接传用户 ID 时的请求头名,默认 `X-External-User-ID` |
| signed_token_header_name | string | 签名 token 模式请求头名,默认 `X-External-User-Token` |
| require_direct_header | bool | `direct_header` 模式下是否强制要求用户 ID 请求头 |
| has_hmac_secret | bool | 是否已配置 HMAC secret(不返回明文) |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/tenants/10000/api-principal-config' \
--header 'Authorization: Bearer <token>'
```
**响应**:
```json
{
"success": true,
"data": {
"mode": "signed_token",
"direct_header_name": "X-External-User-ID",
"signed_token_header_name": "X-External-User-Token",
"require_direct_header": false,
"has_hmac_secret": true
}
}
```
## PUT `/tenants/:id/api-principal-config` - 更新 API Key 用户身份配置
更新 API Key 请求的 Principal 映射方式。**需要 Owner 权限**。
**请求体**:
| 字段 | 类型 | 说明 |
| ---- | ---- | ---- |
| mode | string | 必填,`tenant` / `direct_header` / `signed_token` |
| direct_header_name | string | 可选 |
| signed_token_header_name | string | 可选 |
| require_direct_header | bool | 可选,`direct_header` 模式下缺 header 是否 401 |
| hmac_secret | string | 可选,`signed_token` 模式 HMAC 密钥;省略则保留现有值 |
`signed_token` 模式首次启用时必须提供 `hmac_secret`。
外部用户 JWT 要求:HS256 签名、`aud=weknora`、包含 `sub` 与 `tenant_id`、有效期不超过 24 小时。
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/tenants/10000/api-principal-config' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"mode": "direct_header",
"direct_header_name": "X-External-User-ID",
"require_direct_header": true
}'
```
## GET `/tenants` - 获取空间列表
返回当前认证上下文对应的空间(普通用户为单条;管理员仍只返回自身空间)。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/tenants' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": {
"items": [
{
"id": 10002,
"name": "weknora",
"description": "weknora workspaces",
"api_key": "sk-An7_t_izCKFIJ4iht9Xjcjnj_MC48ILvwezEDki9ScfIa7KA",
"status": "active",
"retriever_engines": {
"engines": [
{
"retriever_engine_type": "postgres",
"retriever_type": "keywords"
},
{
"retriever_engine_type": "postgres",
"retriever_type": "vector"
}
]
},
"business": "wechat",
"storage_quota": 10737418240,
"storage_used": 0,
"created_at": "2025-08-11T20:52:58.05679+08:00",
"updated_at": "2025-08-11T20:52:58.060495+08:00",
"deleted_at": null
}
]
},
"success": true
}
```
## GET `/tenants/kv/:key` - 获取空间 KV 配置
获取当前空间的 KV 配置项。**空间 ID 从认证上下文中获取**(即由 `X-API-Key` / Bearer Token 对应的空间决定),URL 中不需要也不接受 tenant_id。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | ---------------------------------------------- |
| key | string | 配置键名(见下方支持的 key 列表,不支持的键返回 400) |
**支持的 key 值**:
| key | 说明 |
| ---------------------- | ----------------------------- |
| `agent-config` | Agent 配置(最大迭代次数、温度、System Prompt、可用工具等) |
| `web-search-config` | 网页搜索配置 |
| `conversation-config` | 普通模式会话/对话配置 |
| `prompt-templates` | 系统提示词模板(只读,按用户语言本地化) |
| `parser-engine-config` | 解析引擎配置(如 MinerU) |
| `storage-engine-config`| 存储引擎配置(Local/MinIO/COS) |
| `chat-history-config` | 聊天历史索引配置 |
| `retrieval-config` | 全局检索配置 |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/tenants/kv/agent-config' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应(以 `agent-config` 为例)**:
```json
{
"data": {
"max_iterations": 10,
"allowed_tools": ["knowledge_search", "web_search"],
"temperature": 0.3,
"system_prompt": "...",
"use_custom_system_prompt": false,
"available_tools": [
{ "name": "knowledge_search", "label": "知识库检索", "description": "..." }
],
"available_placeholders": [
{ "name": "web_search_status", "label": "联网搜索状态", "description": "..." }
]
},
"success": true
}
```
失败时(不支持的键):
```json
{ "success": false, "error": "unsupported key" }
```
## PUT `/tenants/kv/:key` - 更新空间 KV 配置
更新当前空间的 KV 配置项。**空间 ID 从认证上下文中获取**,请求体结构按 `key` 不同而异。`prompt-templates` 为只读,不支持 PUT。
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | ----------------------------- |
| key | string | 配置键名(见 GET 接口的支持列表,`prompt-templates` 除外) |
**请求(以 `agent-config` 为例)**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/tenants/kv/agent-config' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"max_iterations": 20,
"temperature": 0.3,
"system_prompt": ""
}'
```
**响应**:
```json
{
"data": {
"max_iterations": 20,
"allowed_tools": ["knowledge_search", "web_search"],
"temperature": 0.3,
"system_prompt": "",
"use_custom_system_prompt": false
},
"message": "Agent configuration updated successfully",
"success": true
}
```
**约束**:
- `agent-config`: `max_iterations` 为正整数时上限 100,`-1` 表示不限制;`temperature` 取值范围 `[0, 2]`。
- `web-search-config`: `max_results` 取值范围 `[1, 50]`。
- `conversation-config`: 包含多项阈值校验(如 `keyword_threshold` / `vector_threshold` ∈ `[0, 1]`,`rerank_threshold` ∈ `[-10, 10]`,`temperature` ∈ `[0, 2]`,`max_completion_tokens` ∈ `[1, 100000]` 等)。
- `retrieval-config`: `embedding_top_k` / `rerank_top_k` ∈ `[0, 200]`;阈值范围同上。
- `storage-engine-config`: `default_provider` 必须在 `STORAGE_ALLOW_LIST` 允许的列表内。
- `chat-history-config`: 启用且设置了 `embedding_model_id` 而尚未关联知识库时,会自动创建一个隐藏知识库并将其 ID 写入配置。
## 空间邀请(邮箱邀请已注册用户)
`POST /tenants/:id/invitations` 通过邮箱邀请**已注册**用户。全局开关 `tenant.auto_accept_invitation`(环境变量 `WEKNORA_TENANT_AUTO_ACCEPT_INVITATION`,默认 `false`)控制行为:
| 开关 | 行为 | 成功响应 `data` 形状 |
| ---- | ---- | -------------------- |
| `false` | 创建 pending 邀请,受邀人须在 `/me/invitations` 接受 | `TenantInvitation`(含 `id`、`status: pending`) |
| `true` | 直接写入 `tenant_members`,并 reconcile 已有 pending 行 | `TenantMember`(含 `user_id`、`status: active`) |
开启 auto-accept 时,无默认空间的受邀人会将该空间设为 home tenant(与手动接受邀请一致)。SPA 通过 `GET /auth/me` 的 `capabilities.auto_accept_invitation` 感知开关,无需读取系统设置 API。
+437
View File
@@ -0,0 +1,437 @@
# Vector Store API
[返回目录](./README.md)
向量存储(VectorStore)API 用于管理空间的向量数据库连接配置,支持 Elasticsearch、PostgreSQL、Qdrant、Milvus、Weaviate、Tencent VectorDB、SQLite 等引擎。接口同时管理用户在 DB 中创建的配置(`source: "user"`)以及通过 `RETRIEVE_DRIVER` 环境变量配置的虚拟存储(`source: "env"`,只读)。
| 方法 | 路径 | 描述 |
| ------ | ---------------------------- | -------------------------------- |
| GET | `/vector-stores/types` | 获取支持的引擎类型及字段元数据 |
| POST | `/vector-stores/test` | 使用原始凭据测试连接(不落库) |
| POST | `/vector-stores` | 创建向量存储 |
| GET | `/vector-stores` | 获取向量存储列表 |
| GET | `/vector-stores/:id` | 获取向量存储详情 |
| PUT | `/vector-stores/:id` | 更新向量存储(仅名称可改) |
| DELETE | `/vector-stores/:id` | 删除向量存储(软删除) |
| POST | `/vector-stores/:id/test` | 测试已保存或环境变量存储的连通性 |
## GET `/vector-stores/types` - 获取支持的引擎类型
返回所有支持的引擎类型及其连接配置字段、索引配置字段的定义,可用于前端动态表单生成。系统级元数据,无需鉴权感知,但仍需 `X-API-Key`。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/vector-stores/types' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": [
{
"type": "elasticsearch",
"display_name": "Elasticsearch (Keywords + Vector)",
"connection_fields": [
{ "name": "addr", "type": "string", "required": true, "description": "Elasticsearch URL (e.g., http://localhost:9200)" },
{ "name": "username", "type": "string", "required": false },
{ "name": "password", "type": "string", "required": false, "sensitive": true }
],
"index_fields": [
{ "name": "index_name", "type": "string", "required": false, "default": "xwrag_default" },
{ "name": "number_of_shards", "type": "number", "required": false },
{ "name": "number_of_replicas", "type": "number", "required": false }
]
},
{
"type": "postgres",
"display_name": "PostgreSQL (Keywords + Vector)",
"connection_fields": [
{ "name": "use_default_connection", "type": "boolean", "required": false, "default": true, "description": "Use the application's default database connection" },
{ "name": "addr", "type": "string", "required": false, "description": "PostgreSQL connection string (required if use_default_connection is false)" },
{ "name": "username", "type": "string", "required": false },
{ "name": "password", "type": "string", "required": false, "sensitive": true }
]
}
]
}
```
## POST `/vector-stores/test` - 使用原始凭据测试连接
用前端表单中尚未保存的凭据执行一次连通性测试,不会写入数据库。成功时返回自动检测到的服务器版本(如 ES 版本号);某些引擎(如 Milvus、SQLite)无法检测版本,`version` 会返回空字符串。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------------- | ------ | ---- | ------------------------------------------------------------- |
| engine_type | string | 是 | 引擎类型,取自 `/vector-stores/types` 的 `type` |
| connection_config | object | 是 | 该引擎对应的连接配置字段(与 `connection_fields` 对应) |
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/vector-stores/test' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"engine_type": "elasticsearch",
"connection_config": {
"addr": "http://es:9200",
"username": "elastic",
"password": "changeme"
}
}'
```
**响应(成功)**:
```json
{
"success": true,
"version": "7.10.1"
}
```
**响应(失败)**:
```json
{
"success": false,
"error": "failed to connect to elasticsearch: connection refused or authentication failed"
}
```
> 注意:测试失败时 HTTP 状态码仍为 `200`,错误信息通过 `success: false` + `error` 字段返回。
## POST `/vector-stores` - 创建向量存储
为当前空间创建一个新的向量存储配置。同一 endpoint + index 组合在空间内不允许重复(与环境变量配置的存储也会冲突)。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------------- | ------ | ---- | --------------------------------------------------------------- |
| name | string | 是 | 存储显示名(空间内友好名) |
| engine_type | string | 是 | 引擎类型,取自 `/vector-stores/types` |
| connection_config | object | 是 | 连接配置(与所选引擎的 `connection_fields` 对应) |
| index_config | object | 否 | 索引配置(与所选引擎的 `index_fields` 对应) |
> Tencent VectorDB 使用 `engine_type: "tencent_vectordb"`。`connection_config` 中 `addr`、`username`、`api_key` 必填,`database` 可选;`index_config.collection_name` 表示集合名前缀,实际集合会按向量维度追加后缀(例如 `weknora_embeddings_768`);`index_config.replica_number` 表示创建集合时使用的副本数。该适配器同时支持向量检索和基于 BM25 sparse vector 的关键词检索;旧版本已创建且没有 `sparse_vector` 索引的集合需要重建并重新导入数据后才能启用关键词检索。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/vector-stores' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "elasticsearch-hot",
"engine_type": "elasticsearch",
"connection_config": {
"addr": "http://es-hot:9200",
"username": "elastic",
"password": "changeme"
},
"index_config": {
"index_name": "my_index"
}
}'
```
**Tencent VectorDB 请求示例**:
```curl
curl --location 'http://localhost:8080/api/v1/vector-stores' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "tencent-vectordb",
"engine_type": "tencent_vectordb",
"connection_config": {
"addr": "http://your-instance.tencentvectordb.com",
"username": "root",
"api_key": "your_api_key",
"database": "weknora"
},
"index_config": {
"collection_name": "weknora_embeddings",
"replica_number": 1
}
}'
```
**响应** (201):
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "elasticsearch-hot",
"engine_type": "elasticsearch",
"connection_config": {
"addr": "http://es-hot:9200",
"username": "elastic",
"password": "***"
},
"index_config": {
"index_name": "my_index"
},
"source": "user",
"readonly": false,
"created_at": "2026-04-07T10:00:00Z",
"updated_at": "2026-04-07T10:00:00Z"
}
}
```
> 响应中的敏感字段(`password`、`api_key` 等)会被掩码为 `"***"`。`connection_config.version` 字段在连接测试成功后才会自动填充,创建时为空。
## GET `/vector-stores` - 获取向量存储列表
返回当前空间的所有向量存储,包含 `RETRIEVE_DRIVER` 环境变量配置的虚拟存储(`source: "env"`、`readonly: true`)和用户在 DB 中创建的存储(`source: "user"`、`readonly: false`)。环境变量存储排列在前。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/vector-stores' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": [
{
"id": "__env_postgres__",
"name": "postgres (env)",
"engine_type": "postgres",
"connection_config": {
"use_default_connection": true
},
"source": "env",
"readonly": true
},
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "elasticsearch-hot",
"engine_type": "elasticsearch",
"connection_config": {
"addr": "http://es-hot:9200",
"username": "elastic",
"password": "***"
},
"source": "user",
"readonly": false
}
]
}
```
## GET `/vector-stores/:id` - 获取向量存储详情
根据 ID 获取单个向量存储。支持 DB 存储 UUID 和 `__env_*` 形式的环境变量存储 ID(例如 `__env_postgres__`)。
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | --------------------------------------------------- |
| id | string | 是 | 向量存储 ID(DB UUID 或 `__env_{driver}__`) |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/vector-stores/550e8400-e29b-41d4-a716-446655440000' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "elasticsearch-hot",
"engine_type": "elasticsearch",
"connection_config": {
"addr": "http://es-hot:9200",
"username": "elastic",
"password": "***",
"version": "7.10.1"
},
"index_config": {
"index_name": "my_index"
},
"source": "user",
"readonly": false,
"created_at": "2026-04-07T10:00:00Z",
"updated_at": "2026-04-07T10:00:00Z"
}
}
```
## PUT `/vector-stores/:id` - 更新向量存储
仅支持更新 `name`。`engine_type`、`connection_config`、`index_config` 创建后不可变更;环境变量存储不可修改(返回 `400`)。
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | -------------- |
| id | string | 是 | 向量存储 ID |
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | ----------------- |
| name | string | 是 | 新的存储显示名 |
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/vector-stores/550e8400-e29b-41d4-a716-446655440000' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "elasticsearch-hot-renamed"
}'
```
**响应**:
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "elasticsearch-hot-renamed",
"engine_type": "elasticsearch",
"connection_config": {
"addr": "http://es-hot:9200",
"username": "elastic",
"password": "***"
},
"index_config": {
"index_name": "my_index"
},
"source": "user",
"readonly": false,
"created_at": "2026-04-07T10:00:00Z",
"updated_at": "2026-04-07T10:05:00Z"
}
}
```
## DELETE `/vector-stores/:id` - 删除向量存储
对 DB 中的存储执行软删除。环境变量存储不可删除(返回 `400`)。
**Phase 2 — 绑定保护**:
删除请求在事务中执行,并按 `(tenant_id, vector_store_id)` 复合索引统计当前空间中仍绑定到该存储的活跃知识库数量。**只要存在任意绑定的知识库(已软删除的 KB 不计入),删除即被拒绝**,调用者必须先解绑或删除这些知识库才能继续。在 PostgreSQL 上,事务期间会对 `vector_stores` 行加 `SELECT … FOR UPDATE` 行锁,阻止并发的知识库创建请求悄悄落到正在被删除的存储上(SQLite 上则依赖 WAL + 单写入序列化达成同样语义)。
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | -------------- |
| id | string | 是 | 向量存储 ID |
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/vector-stores/550e8400-e29b-41d4-a716-446655440000' \
--header 'X-API-Key: sk-xxxxx'
```
**响应(成功)**:
```json
{
"success": true
}
```
**响应(绑定保护触发)**:
```json
{
"success": false,
"error": {
"code": 1000,
"message": "vector store still has 3 knowledge base(s) bound to it; unbind or delete them before removing the store"
}
}
```
HTTP `400`。错误消息中包含具体的知识库数量(便于运营定位),但不包含任何 KB 的 ID/名称,以避免跨空间信息泄漏。删除被拒绝时,DB 中的存储行保持原状,进程内引擎注册表也不会被清除。
## POST `/vector-stores/:id/test` - 测试已保存或环境变量存储的连接
对已保存的 DB 存储或环境变量虚拟存储执行一次连接测试。成功时返回检测到的服务器版本;对 DB 存储,检测到的版本会被自动写回 `connection_config.version`,环境变量存储不会更新。
**路径参数**:
| 字段 | 类型 | 必填 | 说明 |
| ---- | ------ | ---- | --------------------------------------------------- |
| id | string | 是 | 向量存储 ID(DB UUID 或 `__env_{driver}__`) |
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/vector-stores/550e8400-e29b-41d4-a716-446655440000/test' \
--header 'X-API-Key: sk-xxxxx'
```
**响应(成功)**:
```json
{
"success": true,
"version": "7.10.1"
}
```
**响应(失败)**:
```json
{
"success": false,
"error": "failed to connect to elasticsearch: connection refused or authentication failed"
}
```
> 与 `/vector-stores/test` 一致,测试失败时 HTTP 状态码仍为 `200`,错误通过 `success: false` + `error` 返回。
## 环境变量存储
通过 `RETRIEVE_DRIVER` 环境变量配置的向量存储以虚拟条目形式出现在列表和详情中。这些条目的特征:
- **ID 格式**:`__env_{driver}__`(如 `__env_postgres__`、`__env_elasticsearch_v8__`)
- **source**:`"env"`
- **readonly**:`true`
- **不可修改/删除**:`PUT` 和 `DELETE` 返回 `400`
- **可测试连通性**:`POST /vector-stores/:id/test` 正常工作
- **被知识库绑定时**:未指定 `vector_store_id` 创建的知识库默认使用环境变量存储;这种知识库在响应中显示为 `vector_store_name="System default"` + `vector_store_source="env"`。
Tencent VectorDB 环境变量存储可通过 `TENCENT_VECTORDB_REPLICA_NUMBER` 覆盖默认集合副本数。默认值为 `1`;单节点 QA 环境可设为 `0`,生产环境可按 Tencent VectorDB 集群规模调整。
## 错误码
| HTTP 状态码 | code | 含义 |
| ----------- | ---- | --------------------------------------------------- |
| 400 | 1000 | 请求参数错误、校验失败、尝试修改环境变量存储、删除时仍有知识库绑定 |
| 400 | 2200 | 知识库创建时引用的 `vector_store_id` 无效(不存在或属于其他空间) |
| 400 | 2201 | 知识库创建时引用的存储当前不可用(DB 中存在但未注册到引擎) |
| 401 | 1001 | 未认证(缺少空间上下文或 API Key) |
| 404 | 1003 | 向量存储不存在 |
| 409 | 1005 | 同一 endpoint + index 组合已存在 |
| 500 | 1007 | 内部服务器错误 |
> `2200` / `2201` 由 `POST /knowledge-bases` 等知识库创建路径返回(详见 [knowledge-base.md](./knowledge-base.md)),列于此处仅为完整覆盖与向量存储相关的所有错误码。
+278
View File
@@ -0,0 +1,278 @@
# Web Search API
[返回目录](./README.md)
包含两组接口:
- `/web-search/providers`:返回**可用的 provider 类型**(只读元数据)
- `/web-search-providers/*`:当前空间**自定义保存**的 provider CRUD 与连通性测试
| 方法 | 路径 | 描述 |
| ------ | ------------------------------------- | ----------------------------------- |
| GET | `/web-search/providers` | 获取网络搜索服务商类型列表 |
| GET | `/web-search-providers/types` | 获取 Provider 类型元数据(含参数定义) |
| POST | `/web-search-providers/test` | 使用原始凭证测试连通性(不落库) |
| POST | `/web-search-providers` | 创建空间级 Provider 配置 |
| GET | `/web-search-providers` | 获取当前空间已保存的 Provider 列表 |
| GET | `/web-search-providers/:id` | 获取指定 Provider 详情 |
| PUT | `/web-search-providers/:id` | 更新 Provider |
| DELETE | `/web-search-providers/:id` | 删除 Provider |
| POST | `/web-search-providers/:id/test` | 使用已保存凭证测试连通性 |
## GET `/web-search/providers` - 获取网络搜索服务商类型列表
获取系统中可用的网络搜索服务商列表(系统级元数据,与空间无关)。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/web-search/providers' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json'
```
**响应**:
```json
{
"data": [
{
"name": "google",
"label": "Google Search",
"description": "通过 Google 自定义搜索 API 进行网络搜索",
"enabled": true
},
{
"name": "bing",
"label": "Bing Search",
"description": "通过 Bing Search API 进行网络搜索",
"enabled": true
},
{
"name": "serpapi",
"label": "SerpAPI",
"description": "通过 SerpAPI 进行搜索引擎结果抓取",
"enabled": false
}
],
"success": true
}
```
## GET `/web-search-providers/types` - 获取 Provider 类型元数据
返回 UI 表单需要的所有 provider 类型及参数定义(每种 provider 需要哪些字段、类型、是否必填)。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/web-search-providers/types' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"data": [
{
"provider": "google",
"label": "Google Search",
"description": "...",
"parameter_schema": [
{ "name": "api_key", "label": "API Key", "type": "string", "required": true },
{ "name": "cx", "label": "Search Engine ID", "type": "string", "required": true }
]
}
],
"success": true
}
```
## POST `/web-search-providers/test` - 使用原始凭证测试连通性
前端表单"测试连接"按钮使用:用尚未保存的凭证发起一次样本搜索。
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ---------- | ------ | ---- | --------------------------------- |
| provider | string | 是 | provider 类型(如 `google`、`bing`) |
| parameters | object | 是 | 该 provider 所需凭证与参数(与 `/types` 中 `parameter_schema` 对应) |
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/web-search-providers/test' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"provider": "google",
"parameters": {
"api_key": "AIza...",
"cx": "0123456789:abcdefg"
}
}'
```
**响应**:
```json
{ "success": true }
```
失败时:
```json
{ "success": false, "error": "google api: 403 forbidden" }
```
### 智谱 AI 配置
智谱使用独立的 Web Search API。`search_engine` 和 `content_size` 存放在
`parameters.extra_config` 中;未指定时分别使用 `search_std` 和 `medium`。
```json
{
"provider": "zhipu",
"parameters": {
"api_key": "your-zhipu-api-key",
"extra_config": {
"search_engine": "search_std",
"content_size": "medium"
}
}
}
```
`search_engine` 支持 `search_std`、`search_pro`、`search_pro_sogou` 和
`search_pro_quark`;`content_size` 支持 `medium` 和 `high`。
## POST `/web-search-providers` - 创建 Provider
**参数说明(请求体)**:
| 字段 | 类型 | 必填 | 说明 |
| ----------- | ------- | ---- | ------------------------------------------ |
| name | string | 是 | Provider 显示名(在空间内唯一友好名) |
| provider | string | 是 | Provider 类型(来自 `/web-search-providers/types`) |
| description | string | 否 | 备注 |
| parameters | object | 否 | 凭证与参数 |
| is_default | boolean | 否 | 是否设为当前空间默认 Provider |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/web-search-providers' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "公司 Google CSE",
"provider": "google",
"description": "用于内网搜索",
"parameters": {
"api_key": "AIza...",
"cx": "0123456789:abcdefg"
},
"is_default": true
}'
```
**响应**:
```json
{
"data": {
"id": "wsp-...",
"tenant_id": 1,
"name": "公司 Google CSE",
"provider": "google",
"is_default": true,
"parameters": { "api_key": "***", "cx": "0123456789:abcdefg" }
},
"success": true
}
```
## GET `/web-search-providers` - 获取 Provider 列表
返回当前空间已保存的所有 Provider。
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/web-search-providers' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**:
```json
{
"data": [
{ "id": "wsp-001", "name": "公司 Google CSE", "provider": "google", "is_default": true }
],
"success": true
}
```
## GET `/web-search-providers/:id` - 获取 Provider 详情
**路径参数**:
| 字段 | 类型 | 说明 |
| ---- | ------ | ----------- |
| id | string | Provider ID |
**请求**:
```curl
curl --location 'http://localhost:8080/api/v1/web-search-providers/wsp-001' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**: 同创建接口。404 表示不存在。
## PUT `/web-search-providers/:id` - 更新 Provider
**说明**:`provider` 字段(类型)创建后不可修改,仅支持更新 `name` / `description` / `parameters` / `is_default`。
**参数说明(请求体)**: 同创建接口,但不包含 `provider`。
**请求**:
```curl
curl --location --request PUT 'http://localhost:8080/api/v1/web-search-providers/wsp-001' \
--header 'X-API-Key: sk-xxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "公司 Google CSE (v2)",
"parameters": { "api_key": "NEW...", "cx": "0123456789:abcdefg" },
"is_default": false
}'
```
**响应**: `{ "data": {...更新后实体...}, "success": true }`
## DELETE `/web-search-providers/:id` - 删除 Provider
**请求**:
```curl
curl --location --request DELETE 'http://localhost:8080/api/v1/web-search-providers/wsp-001' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**: `{ "success": true }`
## POST `/web-search-providers/:id/test` - 测试已保存的 Provider
使用数据库中已保存的凭证发起一次样本搜索。
**请求**:
```curl
curl --location --request POST 'http://localhost:8080/api/v1/web-search-providers/wsp-001/test' \
--header 'X-API-Key: sk-xxxxx'
```
**响应**: 同 `POST /web-search-providers/test`。