知识库助手
大约 5 分钟
第十二章:实战:知识库助手(RAG + 工具 + 流式)
12.1 需求与目标
我们做一个可上线的“知识库助手”,满足:
- 用户提问时优先基于知识库回答(RAG)
- 支持流式输出(SSE),提升体验
- 支持工具调用:当知识库不足以回答时,允许调用“FAQ 查询/工单创建”等后端工具
- 有基本治理:并发限制、脱敏、指标与 requestId
12.2 项目结构建议
一个可维护的结构通常是:
com.example.saa
├── api // Controller 层,只做参数与返回
├── rag // 检索与上下文拼装
├── tools // 工具定义与执行
├── observability // 统一调用与指标
└── security // 脱敏、鉴权12.3 配置(application.yml)
spring:
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY:}
chat:
options:
model: ${DASHSCOPE_CHAT_MODEL:qwen-plus}
temperature: 0.2
model:
embedding: dashscope
dashscope:
embedding:
options:
model: ${DASHSCOPE_EMBEDDING_MODEL:text-embedding-v2}12.4 核心流程:检索 → 工具(可选)→ 生成
建议把“回答”分为三步:
- 先做 RAG:检索 TopK 片段作为上下文
- 再允许工具:把 FAQ/工单工具注册给模型(白名单)
- 最后生成:让模型基于上下文与工具结果输出最终答案
12.5 关键实现 1:RAG 服务(复用第七章)
这里直接复用第七章的 InMemoryVectorIndex 与 RagService。真实生产中你会替换为 VectorStore(pgvector/Redis/ES),上层结构不变。
12.6 关键实现 2:工具定义(FAQ 查询 + 工单创建)
12.6.1 工具执行服务(示例)
package com.example.saa.tools;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.constraints.NotBlank;
import java.util.Map;
import java.util.Set;
import org.springframework.stereotype.Service;
@Service
public class SupportToolsService {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public Map<String, Object> searchFaq(Map<String, Object> arguments) {
SearchFaqArgs args = new SearchFaqArgs((String) arguments.get("keyword"));
validate(args);
return Map.of(
"keyword", args.keyword(),
"hits", 2,
"items", new String[]{
"如何重置密码:进入个人中心-安全设置。",
"如何开票:进入订单详情页-申请发票。"
}
);
}
public Map<String, Object> createTicket(Map<String, Object> arguments) {
CreateTicketArgs args = new CreateTicketArgs((String) arguments.get("title"), (String) arguments.get("detail"));
validate(args);
return Map.of(
"ticketId", "T-" + System.currentTimeMillis(),
"status", "CREATED",
"title", args.title()
);
}
private void validate(Object args) {
Set<ConstraintViolation<Object>> violations = validator.validate(args);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
public record SearchFaqArgs(@NotBlank String keyword) {
}
public record CreateTicketArgs(@NotBlank String title, @NotBlank String detail) {
}
}12.6.2 注册工具给模型(FunctionCallback + Schema)
package com.example.saa.tools;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.Schema;
import org.springframework.ai.model.function.SchemaProperty;
import org.springframework.ai.model.function.SchemaType;
import org.springframework.stereotype.Component;
@Component
public class SupportToolRegistry {
private final SupportToolsService tools;
public SupportToolRegistry(SupportToolsService tools) {
this.tools = tools;
}
public FunctionCallback searchFaq() {
return new FunctionCallback(
"searchFaq",
"在 FAQ 中搜索答案。参数 keyword 为关键字。",
new Schema(SchemaType.OBJECT)
.addProperty("keyword", SchemaProperty.of(SchemaType.STRING, "关键字,例如 发票"))
.addRequired("keyword"),
tools::searchFaq
);
}
public FunctionCallback createTicket() {
return new FunctionCallback(
"createTicket",
"创建工单。参数 title 为标题,detail 为详细描述。",
new Schema(SchemaType.OBJECT)
.addProperty("title", SchemaProperty.of(SchemaType.STRING, "标题"))
.addProperty("detail", SchemaProperty.of(SchemaType.STRING, "详细描述"))
.addRequired("title")
.addRequired("detail"),
tools::createTicket
);
}
}12.7 关键实现 3:统一回答服务(RAG + Tools)
package com.example.saa.app;
import com.example.saa.rag.InMemoryVectorIndex;
import com.example.saa.tools.SupportToolRegistry;
import java.util.List;
import java.util.StringJoiner;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.stereotype.Service;
@Service
public class KnowledgeAssistantService {
private final ChatClient chatClient;
private final InMemoryVectorIndex vectorIndex;
private final SupportToolRegistry toolRegistry;
public KnowledgeAssistantService(ChatClient chatClient, InMemoryVectorIndex vectorIndex, SupportToolRegistry toolRegistry) {
this.chatClient = chatClient;
this.vectorIndex = vectorIndex;
this.toolRegistry = toolRegistry;
}
public String ask(String question) {
List<InMemoryVectorIndex.Entry> contexts = vectorIndex.search(question, 4);
StringJoiner ctx = new StringJoiner("\n---\n");
for (InMemoryVectorIndex.Entry e : contexts) {
ctx.add(e.text());
}
FunctionCallback searchFaq = toolRegistry.searchFaq();
FunctionCallback createTicket = toolRegistry.createTicket();
return chatClient.prompt()
.system("""
你是企业知识库助手。
规则:
1) 优先基于【上下文】回答。
2) 如果上下文不足,可以调用 searchFaq。
3) 仍然无法解决且用户明确要人工处理时,才调用 createTicket。
4) 不要泄露敏感信息;不确定就说明不确定。
""")
.user("""
【上下文】
%s
【问题】
%s
""".formatted(ctx.toString(), question))
.functions(searchFaq, createTicket)
.call()
.content();
}
}12.8 API:同步与流式两个入口
12.8.1 同步接口
package com.example.saa.api;
import com.example.saa.app.KnowledgeAssistantService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class KnowledgeAssistantController {
private final KnowledgeAssistantService service;
public KnowledgeAssistantController(KnowledgeAssistantService service) {
this.service = service;
}
@GetMapping(value = "/kb/ask", produces = MediaType.TEXT_PLAIN_VALUE)
public String ask(@RequestParam String q) {
return service.ask(q);
}
}12.8.2 流式接口(SSE)
流式版本建议复用第 4 章写法:把 chatClient.prompt().stream() 映射为 ServerSentEvent。当你把 KnowledgeAssistantService 进一步拆成“返回 Prompt”的形式,就可以复用同一套上下文拼装逻辑。
12.9 单元测试建议(覆盖边界)
建议至少覆盖这些边界:
- 空知识库:
vectorIndex.search返回空时,模型是否会明确“不确定” - 工具参数校验:keyword/title/detail 为空时必须失败
- 工具白名单:只允许注册的两个工具,不能执行未注册动作
12.10 本章小结
你已经拥有一个可上线的最小知识库助手雏形:
- RAG:用 embedding 检索上下文
- Tools:用函数调用把 FAQ 与工单能力接进来
- Streaming:用 SSE 提升交互体验
- 治理:并发、脱敏、指标与 requestId
下一章我们会把常见问题整理成一份排障清单,让你遇到 401/429/断流/解析失败时能快速定位与处理。 点击这里👇🏻获取:100万QPS短链系统、复杂的商城微服务系统、智能翻译助手AI Agent、SaaS点餐系统、刷题吧小程序、商城系统、秒杀系统、AI项目、代码生成神器、苏三demo项目、智能天气播报AI Agent、智能代码审查AI Agent等 10 个项目的:项目源代码、开发教程和技术答疑
