Browse Source

docs: 更新对话接口契约与管道说明

feature/test
wei-py 3 weeks ago
parent
commit
50fc660d08
  1. 48
      CLAUDE.md
  2. 58
      SDK-INTEGRATION.md
  3. 32
      client/README.md

48
CLAUDE.md

@ -16,18 +16,18 @@ AI 智能客服系统,基于 Spring AI Alibaba + 通义千问 + PGVector,支
./mvnw spring-boot:run
# 运行测试
./mvnw test
sh ./mvnw test -DskipTests=false
# 运行单个测试类
./mvnw test -Dtest=SupportBotApplicationTests
sh ./mvnw test -DskipTests=false -Dtest=SupportBotApplicationTests
# 运行单个测试方法
./mvnw test -Dtest=SupportBotApplicationTests#testRag
sh ./mvnw test -DskipTests=false -Dtest=SupportBotApplicationTests#testRag
```
**前提条件**: PostgreSQL 12+ 需运行且安装 PGVector 扩展,数据库 `support_bot` 需存在。`knowledge_category`、`knowledge_document`、`ai_model_config` 等表由 `DatabaseInitConfig` 自动创建,无需手动建表。
**测试说明**: `@SpringBootTest` 集成测试需要运行中的 PostgreSQL 和有效的 DashScope API Key;`ChatPipelineTests` 为隔离的 Mockito 编排测试,无需 DB 或 API Key。Surefire 默认跳过测试,执行时显式加 `-DskipTests=false`,例如 `./mvnw test -DskipTests=false -Dtest=ChatPipelineTests`。
**测试说明**: `@SpringBootTest` 集成测试需要运行中的 PostgreSQL 和有效的 DashScope API Key。低延迟契约测试无需 DB 或真实 API Key:`ChatPipelineTests` 覆盖 FAQ/分类隔离/查询策略,`ChatModelFactoryTests` 用本地 HTTP 服务验证实际模型请求,`AnswerTransportTests` 覆盖同次引用与流生命周期,`ChatResultEndpointTests` 覆盖同步接口与权限。Surefire 默认跳过测试,运行这些测试使用 `sh ./mvnw test -DskipTests=false -Dtest=ChatPipelineTests,ChatModelFactoryTests,AnswerTransportTests,ChatResultEndpointTests`。前端流协议测试:`node --test frontend/tests/chat-protocol.test.mjs`;SDK 传输与会话生命周期测试:在 `client/` 运行 `npm exec -- vitest run tests/api.test.ts tests/chat.test.ts`。
**访问地址**: 前端管理页面 `http://localhost:9090/index.html`,API 文档 `http://localhost:9090/doc.html`(Knife4j)
@ -46,29 +46,33 @@ AI 智能客服系统,基于 Spring AI Alibaba + 通义千问 + PGVector,支
当前使用 `DatabaseChatMemory`(PostgreSQL 持久化),无文件型 ChatMemory(早期的 `FileBasedChatMemory` 已删除,Kryo 依赖一并移除)。
### 统一对话管道(重构后)
对话管道由 `ChatPipeline`(编排层)+ `RagPipeline`(RAG 检索层)+ `AssistantApp`(执行层)组成:
对话管道由 `ChatPipeline`(编排层)+ `RagPipeline`(RAG 检索层)+ `AssistantApp`(执行层)组成(更新:2026-09-14,默认零 LLM 预处理):
```
用户请求
→ 鉴权/角色解析(Controller)
→ AssistantApp 熔断检查(熔断直接降级,不执行 FAQ / 检索)
→ ChatPipeline.buildRequest(ChatContext)
→ enableRag=false:普通对话(不调用 FAQ / 意图路由)
→ enableRag=true:完整 FAQ 三级匹配(角色分类隔离,命中直接返回,跳过意图分类)
→ 未命中:寒暄词快速路径 / IntentRouter 意图路由(CHITCHAT/FAQ/RAG)
→ 高置信 CHITCHAT:纯对话
→ 其余:RagPipeline.retrieve(FAQ 异常时重试 → 查询重写 → 统一检索)
→ 组装 finalMessage + finalSystemPrompt + 资料块
→ AssistantApp.chat / chatStream(构建 ChatClientRequestSpec → call/stream)
→ enableRag=false:普通对话(不调用 FAQ / 检索)
→ enableRag=true:完整 FAQ 三级匹配(角色分类隔离,命中直接返回)
→ 未命中:本地寒暄词判断(无意图分类 LLM)
→ 寒暄命中:纯对话
→ 其余:RagPipeline.retrieve(仅 FAQ 异常时重试 → 默认 NONE 原文检索)
→ 显式 REWRITE / TRANSLATION / COMPRESSION / MULTI_QUERY 才调用重写 LLM
→ 统一向量检索 → 命中/未命中日志仅记录一次
→ 组装 finalMessage + finalSystemPrompt + 资料块 + 当次命中文档
→ AssistantApp.chat / chatStream(安全检查 + 会话记忆 + 一次答案生成;FAQ 不生成)
→ 同步 JSON / SSE metadata 返回当次 sources(不二次检索)
```
- **ChatPipeline**: 纯编排,不持有 ChatClient;产出 `ChatRequest` 决策对象。启用 RAG 时先做完整 FAQ 匹配,标准答案命中不调用意图 LLM;仅 `completedCleanly=true` 的未命中允许 `retrieve(ctx, true)` 跳过重复 FAQ,异常未命中仍保留 RAG 中的 FAQ 重试
- **RagPipeline**: 统一 RAG 检索,所有策略(含 MULTI_QUERY)均走"手动检索 + 资料块注入 system prompt"模式,不再使用 `RetrievalAugmentationAdvisor` 的 query augmenter
- **RAG 查询重写策略**: 由 `RagPipeline` 统一路由,`AssistantApp` 等旧方法已移除
- **IntentRouter**: 已在 ChatPipeline 接入,`AiController.shouldBypassKnowledgeRetrieval` 已移除。`doubao-seed-2-0-*` 的意图分类请求单独设置 `reasoning_effort=minimal`(关闭深度思考)和 `max_tokens=128`,避免小型分类任务先等待长思维链;只覆盖当前分类请求,不修改缓存模型或主回答配置,其他模型保持原参数。请求序列化及降级由 `IntentRouterTests` 验证
- **ChatPipeline**: 纯编排,不持有 ChatClient;产出 `ChatRequest` 决策对象。启用 RAG 时先做完整 FAQ 匹配,命中直接返回;未命中仅做本地寒暄判断,其他问题直接检索。仅 `completedCleanly=true` 的未命中允许 `retrieve(ctx, true)` 跳过重复 FAQ,异常未命中仍保留 RAG 中的 FAQ 重试
- **RagPipeline**: 统一 RAG 检索,所有策略(含 MULTI_QUERY)均走"手动检索 + 资料块注入 system prompt"模式;检索命中/未命中日志仅由此层记录,不在编排层重复写入
- **RAG 查询重写策略**: 默认 `NONE`,省去串行意图分类和查询重写 LLM 调用。显式 `REWRITE` / `TRANSLATION` / `COMPRESSION` / `MULTI_QUERY` 保留;`COMPRESSION` 从会话记忆读取最近 10 条消息,将指代追问补全为独立检索问题。默认原文检索不做指代补全,主回答仍通过 `MessageChatMemoryAdvisor` 使用会话记忆
- **本地寒暄路径**: `ChatPipeline.isChitchat` 复用于对话和独立来源检索;不使用意图分类 LLM
- **分类过滤**: 统一由 `CategoryFilter` 工具类处理(`parse`/`normalize`/`buildExpression`)
- **AssistantApp 入口**: `chat(ChatContext)` / `chatStream(ChatContext)` / `retrieveSources(ChatContext)`,旧方法(`doChat*`、`doChatWithRag*`)已移除
- **Open API**: `OpenApiController` 已接入 `ChatPipeline`,补齐角色/RAG/FAQ/MCP/分类隔离能力
- **正文完成与引用加载解耦**: 管理聊天页及 SDK 测试页在正文完成后立即解除发送状态,引用来源后台补齐到原消息;请求参数固定为发送时的会话/角色/检索配置,清空或切换后的旧引用不会写入新对话
- **引用复用**: 自有客户端不调用 `/ai/chat/sources`。同步 `/ai/chat/result` 直接返回 `{text,mcpEvents,suggestions,sources}`;流式在正文完成后、stop / `[DONE]` 前发送一次 `choices:[]` + `sources` 的 OpenAI 扩展 metadata chunk,FAQ/非 RAG/熔断来源为空。已发布 `/ai/chat` 文本和 `/ai/chat/sources` 独立检索接口保留
### 文档处理管道
`DocumentService.uploadDocument()` 统一流程:文档提取(官方 `org.springframework.ai.reader.tika.TikaDocumentReader` / `MarkdownDocumentReader` / `JsonReader`)→ `OverlapTokenTextSplitter` 分块 → 为每块写 metadata → 按批向量化(默认 50 块/批,配置项 `knowledge.vector.batch-size`)`pgVectorVectorStore.add(batch)` 入库。每个分块的 metadata 注入 `documentId`、`chunkIndex`、`sourceName`、`title`、`categoryId`、`enabled` 关联 `knowledge_document` 表。
@ -117,6 +121,7 @@ AI 智能客服系统,基于 Spring AI Alibaba + 通义千问 + PGVector,支
- **启动校验**: `ModelConfigLoader` 在应用就绪后检查 DB 中每种 App 类型是否有活跃配置,并对 DashScope 提供商比较 DB 与 yml 的 API Key 一致性
- **API Key 脱敏**: 前端展示时只显示前 4 位 + `****` + 后 4 位
- **ChatModel 运行时切换**: 通过 `ChatModelFactory` 按 DB 活跃配置动态创建/缓存 ChatModel(包括 DashScope,不再复用 yml 自动配置的 Bean),配置变更时立即生效(无需重启)。**OpenAI 兼容路径使用自定义 `completionsPath`**(与 EmbeddingModelFactory 的 embeddingsPath 对应),baseUrl 已含版本段的厂商(moonshot `/v1`、volcengine `/api/v3`、zhipu `/api/paas/v4`)设为 `/chat/completions`,其余使用默认 `/v1/chat/completions`
- **Seed 2.0 思考策略**: `volcengine` + `doubao-seed-2-0-*` 的 `CHAT` 默认 `reasoning_effort=minimal`(关闭思考);高级参数 `extraConfig.reasoningEffort` 可显式设为 `minimal/low/medium/high`,清空恢复 CHAT 快速默认。`RAG_REWRITE` 留空及其他模型沿用厂商默认;工厂白名单仅作用于 Seed 2.0,前端编辑保留其他 `extraConfig` 字段
- **EmbeddingModel 运行时切换**: 通过 `EmbeddingModelFactory` + `DynamicEmbeddingModel` 代理,按 DB 活跃配置动态创建/缓存 EmbeddingModel,`PgVectorStoreConfig` 和 `InMemoryVectorStoreConfig` 注入 `DynamicEmbeddingModel`,向量化模型配置变更后无需重启即可生效
- **多提供商支持**: DashScope(通义千问)+ OpenAI 兼容提供商(DeepSeek / 豆包 / Kimi / 智谱 / OpenAI),ChatModel 和 EmbeddingModel 均通过对应 API 手动构建
- **缓存刷新**: 模型配置增删改激活时 Controller 自动调用 `ChatModelFactory.clearCache()` + `EmbeddingModelFactory.clearCache()` + `AssistantApp.clearCache()`;MCP Server 增删改/启停/全量刷新时 `McpServerConfigController` 亦会调用 `AssistantApp.clearCache()`(避免继续使用旧的 MCP 工具集)
@ -275,7 +280,7 @@ catch (e) { toast('操作失败', 'error') }
- `ChatPipeline.buildRequest()` 的决策分支(意图路由、FAQ、RAG、纯对话路径)发生变更
- `RagPipeline.retrieve()` 的检索流程(查询重写策略、检索方式、资料拼装)发生变更
- `AssistantApp` 的 Advisor 链成员或顺序发生变更(如新增/移除 Advisor)
- 新增管道阶段组件(如 `IntentRouter`、`SuggestionGenerator`、`SimpleCircuitBreaker` 等)或移除现有组件
- 新增管道阶段组件(如 `SuggestionGenerator`、`SimpleCircuitBreaker` 等)或移除现有组件
- 组件间调用关系调整(如原来 A→B 改为 A→C→B)
**图表元数据**: `PipelineFlow.vue` 中 DSL 首行有 `%%graph-meta` 注释标记最后更新时间,修改图表时必须更新该日期。
@ -345,8 +350,8 @@ catch (e) { toast('操作失败', 'error') }
- **Chat SDK**: `handleFeedback()` 已连接后端 API,同时保留 localStorage 作为乐观 UI 缓存
- **会话导出**: `ConversationService.exportConversation()` 导出的 TXT 中包含反馈信息
### 意图识别 + FAQ 精准匹配(P0-003)
- **IntentRouter**: LLM 单次调用做意图分类(FAQ/RAG/CHITCHAT);结构化输出由标准组件 `BeanOutputConverter<IntentResult>` 生成 JSON Schema 指令并反序列化结果,解析失败/结果非法降级为 RAG
### 本地路由 + FAQ 精准匹配(P0-003)
- **本地路由**: 完整 FAQ 优先,未命中后仅精确寒暄词跳过 RAG;默认不调用 LLM 做意图分类
- **FaqMatchEngine**: 三级匹配策略 — 精确匹配 → 关键词匹配 → 向量语义匹配(阈值 `knowledge.faq.semantic-threshold`,默认 0.85)
- **FAQ 向量化**: 复用现有 `DynamicEmbeddingModel`,向量存入 `faq_embedding` 表,新增/修改 FAQ 时异步计算
- **similar_questions 字段**: 使用 String 类型存储 JSON 数组字符串(PostgresJsonTypeHandler 期望对象格式,故不用 typeHandler)
@ -367,12 +372,11 @@ catch (e) { toast('操作失败', 'error') }
- **异步写入**:`LlmCallTraceService.recordAsync` 走 `@Async("traceExecutor")`(有界线程池见 `AsyncExecutorConfig`,替代默认 `SimpleAsyncTaskExecutor` 线程爆炸隐患;`RagHitLogService` 已一并切换)。
- **查询面板**:前端「系统设置 → 提示词追踪」(`PromptTracePanel.vue`,admin),支持筛选/搜索/详情(来源标注)/聚合统计/并排对比/跳转编辑(deep-link `?roleId=`、`?key=ai_system_prompt`)。
- **安全声明**:`system_prompt`/`user_message`/`ai_response` 属敏感数据(提示词为商业秘密),当前**明文存储**、仅 admin 可查,落库前做敏感词脱敏(`ContentSafetyService.mask`)+ AI 回复截断;保留期限由 `system_config.llm_trace_retention_days` 控制(默认 30 天,`@Scheduled` 每日 2 点自动清理)。字段级加密留二期。
- **预留**:`account_id`/`api_key_id` 列预留租户隔离与个人信息删除权,本期不写入;旁路 LLM 调用(意图识别/查询重写/建议生成)追踪留二期(表加 `trace_type`/`source` 字段)。
- **预留**:`account_id`/`api_key_id` 列预留租户隔离与个人信息删除权,本期不写入;旁路 LLM 调用(显式查询重写/建议生成)追踪留二期(表加 `trace_type`/`source` 字段)。
## 已知 TODO
- `DocumentService.updateDocumentMetadata()`: Spring AI 无直接更新 vector_store metadata 的 API,向量元数据同步留后续
- `DocumentService.searchDocuments()`: **分类过滤已实现**(`FilterExpressionBuilder` 组合 `enabled` + `categoryId` 过滤表达式,向量检索异常时回退到本地 metadata 过滤)—— 原「Spring AI filter 支持有限」的 TODO 已不成立
- `CompressionQueryRewriter`: 当前传入空历史列表
- MyBatis Plus `mybatis-plus-spring-boot3-starter` 不含 `PaginationInnerInterceptor`,分页通过 SQL `LIMIT/OFFSET` 手动实现
- `PgVectorStoreConfig.dimensions(1024)` 硬编码了向量维度,切换非 1024 维的 Embedding 模型时需修改并重建 vector_store 表 → **已修复:维度由 `knowledge.vector.dimension` 配置,启动时自动检测不匹配并告警**

58
SDK-INTEGRATION.md

@ -276,9 +276,10 @@ POST /open-api/auth/token
| 接口 | 方法 | 说明 |
|---|---|---|
| `/ai/chat` | GET | 同步对话(已统一为 ChatPipeline,支持普通/RAG 自动判断) |
| `/ai/chat/stream` | GET | SSE 流式对话(OpenAI Chat Completions 兼容格式,已统一为 ChatPipeline) |
| `/ai/chat/sources` | GET | 获取 RAG 引用来源 |
| `/ai/chat/result` | GET | SDK/管理后台同步对话,直接返回完整 JSON 结果(正文、工具事件、建议、实际引用) |
| `/ai/chat` | GET | 已发布的外部纯文本同步接口,继续保留;SDK 不回退到此接口 |
| `/ai/chat/stream` | GET | SSE 流式对话(OpenAI Chat Completions 兼容格式,流内携带实际引用) |
| `/ai/chat/sources` | GET | 已发布的独立显式检索接口,继续保留;对话客户端不调用此接口二次检索 |
| `/ai/sdk/conversation/list` | GET | 会话列表 |
| `/ai/sdk/conversation/{id}/messages` | GET | 会话消息 |
| `/ai/sdk/conversation/{id}` | DELETE | 删除会话 |
@ -286,7 +287,48 @@ POST /open-api/auth/token
| `/category/tree` | GET | 知识库分类树 |
| `/feedback` | POST | 消息反馈 |
> **已废弃**:`/ai/assistant_app/chat/server_sent_event` 和 `/ai/assistant_app/chat/sse_emitter` 已移除。旧路径 `/ai/assistant_app/chat/sync`、`/ai/assistant_app/chat/sse`、`/ai/assistant_app/chat/rag/sse`、`/ai/assistant_app/rag/sources` 仍保留(向后兼容)但已废弃,请统一使用 `/ai/chat`、`/ai/chat/stream`、`/ai/chat/sources`。
> **已废弃**:`/ai/assistant_app/chat/server_sent_event` 和 `/ai/assistant_app/chat/sse_emitter` 已移除。旧路径 `/ai/assistant_app/chat/sync`、`/ai/assistant_app/chat/sse`、`/ai/assistant_app/chat/rag/sse`、`/ai/assistant_app/rag/sources` 仍保留(向后兼容)但已废弃。自有对话客户端统一使用 `/ai/chat/result`、`/ai/chat/stream`,引用复用同次回答的实际命中,不额外发起检索。
#### 同步结果与引用契约
`GET /ai/chat/result` 与 `/ai/chat` 使用完全相同的参数:`message`(必填)、`chatId`、`roleId`、`accountId`、`systemPrompt`、`enableRag`、`rewriteStrategy`、`categoryId`、`categoryIds`、`imageUrls`。图片仍按逗号分隔并解码 URL;角色提示词、MCP 工具与分类权限仍由服务端解析,客户端不能用分类参数覆盖角色范围。`/ai/**` 由 SDK JWT / 管理后台 JWT 鉴权守卫。
响应直接是 JSON(**没有** `success/data` 外层):
```json
{
"text": "回答正文",
"mcpEvents": [],
"suggestions": [],
"sources": [{
"documentId": "1960000000000000001",
"title": "退货说明",
"sourceName": "售后手册.pdf",
"chunkIndex": 0,
"score": 0.12,
"snippet": "实际用于本次回答的知识库片段"
}]
}
```
`sources` 只来自本次授权检索后注入答案的命中文档。每项固定包含 `documentId: string|null`、`title: string|null`、`sourceName: string|null`、`chunkIndex: number|null`、`score: number|null`、`snippet: string|null`。ID 始终为字符串,不能转为 JavaScript Number;snippet 含截断标记在内最多 160 字符。score 保留原来源接口的 distance 语义(检索实现只提供 score 时使用 score),不要一律假定越大越相关。FAQ、无 RAG、熔断或错误降级返回 `sources: []`。推荐问题仍按需另行生成。
#### 流内引用(同时适用于 Open API)
`/ai/chat/stream` 和 `/open-api/chat/stream` 保留 OpenAI envelope。正常正文结束后、`finish_reason: "stop"` 与 `[DONE]` **之前**恰好追加一条扩展元数据 chunk:
```text
data: {"id":"chatcmpl-example","object":"chat.completion.chunk","created":1789344000,"model":"configured-model","choices":[{"index":0,"delta":{"content":"回答正文"},"finish_reason":null}]}
data: {"id":"chatcmpl-example","object":"chat.completion.chunk","created":1789344000,"model":"configured-model","choices":[],"sources":[]}
data: {"id":"chatcmpl-example","object":"chat.completion.chunk","created":1789344000,"model":"configured-model","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
```
上例为无引用的回答;RAG 回答的 `sources` 使用上述相同结构。所有 chunk 共享 `id/created/model`。引用元数据不是正文,不得拼入回答;通用 OpenAI 客户端可忽略该扩展字段。完整 `[DONE]` 事件到达即结束读取,无需等待网络 EOF;主动取消或异常不会重跑生成/检索。首条仅含 `role` 的协议帧不是答案首 token。
### 6.3 Open API 对话接口(第三方系统直接调用)
@ -301,10 +343,12 @@ POST /open-api/auth/token
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `categoryIds` | Query | 否 | 知识库分类 ID,逗号分隔(非角色场景用) |
| `rewriteStrategy` | Query | 否 | RAG 查询重写策略:REWRITE / TRANSLATION / COMPRESSION / MULTI_QUERY(默认) |
| `rewriteStrategy` | Query | 否 | NONE(默认,原问题直接检索)/ REWRITE / TRANSLATION / COMPRESSION / MULTI_QUERY |
| `enableRag` | Query | 否 | 是否启用 RAG 检索(默认 true),false 时走普通对话 |
**认证方式:** 所有 `/open-api/**` 和 `/ai/**` 请求通过 `X-API-Key` Header 或 Bearer Token 鉴权。
`POST /open-api/chat` 保留 `success/data` 包装与 `data.reply`、`data.chatId`,并在 `data` 内增加 `mcpEvents`、`suggestions`、`sources`,与同步完整结果使用相同引用结构。
**认证方式:** `/open-api/**` 通过 API Key 鉴权;`/ai/**` 使用 `Authorization: Bearer <SDK JWT 或管理后台 JWT>`,不能直接用 API Key 代替 JWT。
### 6.3 管理接口(需要管理后台 JWT)
@ -342,7 +386,7 @@ POST /open-api/auth/token
| `theme` | String | 'light' | 界面主题:light / dark |
| `streaming` | Boolean | true | 流式回复 |
| `enableRag` | Boolean | true | RAG 知识库检索 |
| `rewriteStrategy` | String | 'REWRITE' | 查询重写策略 |
| `rewriteStrategy` | String | 'NONE' | 默认不调用 LLM 预处理;可显式选择查询重写策略 |
| `quickReplies` | String[] | [] | 快捷问题列表 |
| `showClear` | Boolean | true | 显示清空按钮 |
| `showAdminPanel` | Boolean | false | 显示管理入口 |

32
client/README.md

@ -78,7 +78,8 @@ SDK 产物位于 `client/dist/` 目录:
| `requestDomain` | `string` | ✅ | — | P0 | 后端 API 域名 |
| `userId` | `string` | ❌ | — | P0 | 宿主用户标识 → 后端 `accountId` |
| `roleId` | `number` | ❌ | — | P0 | 客服角色 ID |
| `enableRag` | `boolean` | ❌ | `false` | P1 | 启用 RAG 知识库检索对话(走 `/ai/chat/stream` 接口,`enableRag=true`) |
| `enableRag` | `boolean` | 可选 | `true` | P1 | 同步和流式对话均启用 RAG 知识库检索 |
| `rewriteStrategy` | `string` | 可选 | `"NONE"` | P1 | 默认不调用查询重写模型;显式支持 `REWRITE` / `TRANSLATION` / `COMPRESSION` / `MULTI_QUERY` |
| `categoryId` | `number` | ❌ | — | P1 | 默认知识库分类 |
| `showCategorySwitch` | `boolean` | ❌ | `false` | P1 | 是否显示知识库下拉切换 |
| `title` | `string` | ❌ | `"AI 智能助手"` | P0 | 弹窗标题 |
@ -92,7 +93,7 @@ SDK 产物位于 `client/dist/` 目录:
| `theme` | `string` | ❌ | `"light"` | P2 | 主题模式:`"light"` / `"dark"` |
| `showTeaser` | `boolean` | ❌ | `true` | P1 | 首访提示气泡(延迟 1.5s 弹出) |
| `teaserText` | `string` | ❌ | i18n 默认 | P1 | 提示气泡文字,留空使用语言包默认值 |
| `streaming` | `boolean` | ❌ | `true` | P0 | 是否启用 SSE 流式输出 |
| `streaming` | `boolean` | 可选 | `true` | P0 | `true` 使用 SSE;`false` 使用 `/ai/chat/result` 获取完整 JSON 答案及同次来源 |
| `locale` | `string` | ❌ | `"zh-CN"` | P2 | 界面语言:`zh-CN` / `en` |
| `debug` | `boolean` | ❌ | `true` | P0 | 是否输出调试日志 |
@ -118,7 +119,7 @@ SDK 产物位于 `client/dist/` 目录:
默认开启(`streaming: true`),AI 回复逐字输出,支持:
- 流式追加到气泡,实时滚动到底部
- 流中断兜底:保留已接收内容 + 灰色提示
- 无流内容时自动降级为同步请求
- 收到完整 `[DONE]` 行立即结束并释放 reader,不等待服务器关闭连接;空流不再发起同步重试
### 4.2 Markdown 渲染
@ -187,7 +188,7 @@ ChatbotSDK.init({
- 默认折叠,只显示标题行,点击展开/折叠
- 显示文档名称、摘要、来源文件、分块编号、相关度
- 来源数据从 `/ai/chat/sources` 接口获取
- 来源来自本次答案实际命中的文档,与正文使用同一个请求;SDK 不再调用 `/ai/chat/sources` 做二次检索
---
@ -247,18 +248,37 @@ SDK 全流程结构化日志,带 `[ChatbotSDK]` 前缀:
### P0 — 基础对话
```
GET /ai/chat # 同步对话
GET /ai/chat/result # 同步 JSON 对话(streaming=false)
GET /ai/chat/stream # SSE 流式对话
```
### P1 — 知识库联动
```
GET /ai/chat/stream # RAG 增强流式对话(enableRag=true)
GET /ai/chat/sources # RAG 引用来源
GET /category/tree # 分类树(下拉框数据源)
GET /category/list # 分类列表
```
### 答案与来源协议
默认 `rewriteStrategy: 'NONE'`,主链不再使用 LLM 意图分类,也不默认做查询重写;本地寒暄和 FAQ 匹配仍有效。用户显式配置其他重写策略时保持该策略,不强制覆盖。
同步 `/ai/chat/result` 直接返回 JSON(没有 `success/data` 包装):
```json
{"text":"答案正文","mcpEvents":[],"suggestions":[],"sources":[]}
```
流式 `/ai/chat/stream` 继续使用 OpenAI `chat.completion.chunk` envelope。正文完成后、`finish_reason: "stop"` / `[DONE]` 之前发送一次元数据 chunk:
```json
{"id":"chat-id","object":"chat.completion.chunk","created":1700000000,"model":"model-name","choices":[],"sources":[{"documentId":"1234567890123456789","title":"文档标题","sourceName":"manual.pdf","chunkIndex":0,"score":0.9,"snippet":"160 字以内摘要"}]}
```
`sources` 各字段允许 `null`;`documentId` 始终为字符串以保持雪花 ID 精度。FAQ、无 RAG、熔断答案返回空数组。SDK 仅将 `choices[].delta.content` 渲染为正文,来源扩展字段交给对应消息的来源卡片,未知 OpenAI 客户端可忽略该扩展。
解析器支持 UTF-8 分片、CRLF、多行 data 与 EOF 残留;`[DONE]` 终止整个流且完成回调只执行一次。取消或切换会话不会把旧请求引用写入新消息。原 `/ai/chat` 文本接口和 `/ai/chat/sources` 显式检索接口仍是后端发布 API,但不是 SDK 对话回退路径。
### P2 — 会话管理
```
GET /conversation/list # 会话列表

Loading…
Cancel
Save