概述
本文档详细描述LangChat Pro中各个核心场景的完整接口调用链路,帮助开发者理解从客户端请求到最终响应的完整流程。项目使用RESTFUL API接口进行通信。场景1: 聊天对话(Chat)
完整链路
Copy
┌─────────────────────────────────────────────────────────────┐
│ Client │
│ (前端/Vue/React) │
└────────────────────┬────────────────────────────────────┘
│ HTTP / REST API
│ POST /api/chat/chat
│ ChatReq (JSON)
▼
┌─────────────────────────────────────────────────────────────┐
│ LcChatController │
│ (REST API接口) │
└────────────────────┬────────────────────────────────────┘
│
│ 转发请求
▼
┌─────────────────────────────────────────────────────────────┐
│ ChatService │
│ (业务逻辑层) │
│ - 参数验证 │
│ - 权限检查 │
│ - 敏感词过滤 │
│ - 消息保存 │
└────────────────────┬────────────────────────────────────┘
│
│ LcChatReq
▼
┌─────────────────────────────────────────────────────────────┐
│ LcChatService │
│ (核心服务层) │
│ - 上下文构建 │
│ - 模型获取 │
│ - AI Service组装 │
│ - 流式调用 │
└────────────────────┬────────────────────────────────────┘
│
┌───────────┼───────────┬───────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐
│ContextBuilder│ │ModelFactory│ │AiService │ │RagBuilder│
│ (构建上下文) │ │(获取模型) │ │Builder │ │(构建RAG) │
└──────┬───────┘ └────┬─────┘ └────┬─────┘ └────┬──────┘
│ │ │ │
└───────────────┼────────────┴────────────┘
│
▼
┌──────────────────────────┐
│ LangChain4j Agent │
│ (AI推理) │
└───────────┬──────────┘
│
│ TokenStream
▼
┌──────────────────────────┐
│ ChatListener │
│ (事件监听) │
│ - Token统计 │
│ - 消息记录 │
└───────────┬──────────┘
│
│ 返回响应
▼
┌──────────────────────────┐
│ Client │
│ (接收响应) │
└───────────────────────┘
关键代码调用
1. REST API调用
Copy
// 前端 - HTTP请求
async function chat(chatReq) {
const response = await fetch('/api/chat/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify(chatReq)
});
// 处理流式响应
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
console.log('收到消息', text);
}
}
// 后端 - LcChatController
@PostMapping("/chat/chat")
public void chat(@RequestBody LcChatReq req, HttpServletResponse response) throws IOException {
// 设置响应头
response.setContentType("text/plain;charset=UTF-8");
response.setHeader("Transfer-Encoding", "chunked");
// 获取输出流
PrintWriter writer = response.getWriter();
// 调用聊天服务
TokenStream stream = lcChatService.streamingChat(req);
// 流式返回
stream.onNext(token -> {
writer.write(token);
writer.flush();
});
stream.onComplete(answer -> {
writer.write("\n[END]");
writer.flush();
});
stream.onError(error -> {
writer.write("\n[ERROR]" + error.getMessage());
});
}
2. 核心服务构建
Copy
// LcChatService
public TokenStream streamingChat(LcChatReq req) {
// 1. 生成chatId
String chatId = LcIdUtil.getUUID();
// 2. 构建ChatContext
BaseChatContext context = contextBuilder.build(req);
context.setChatId(chatId);
// 3. 获取StreamingChatModel
StreamingChatModel model = modelFactory.getStreamingModel(context, chatId);
// 4. 构建Agent
LangChatAgent agent = aiServiceBuilder.build(model, req, req.getConversationId());
// 5. 调用Agent
return agent.stream(req.getConversationId(), req.getMessage());
}
3. AI Service构建
Copy
// AiServiceBuilder
public LangChatAgent build(StreamingChatModel model, LcChatReq req, String conversationId) {
var builder = AiServices.builder(LangChatAgent.class)
.streamingChatModel(model)
.chatMemoryProvider(id -> buildChatMemory(conversationId, req));
// 系统提示词
var prompt = promptProcessor.process(req);
if (StrUtil.isNotBlank(prompt)) {
builder.systemMessageProvider(memoryId -> prompt);
}
// 动态工具
toolFactoryList.forEach(factory -> factory.dynamicTools(req, builder));
// RAG
var ragComponents = ragRetrieverBuilder.build(req);
if (CollUtil.isNotEmpty(ragComponents.retrievers())) {
var augmentor = DefaultRetrievalAugmentor.builder()
.contentAggregator(ragComponents.aggregator())
.queryRouter(new DefaultQueryRouter(ragComponents.retrievers()))
.build();
builder.retrievalAugmentor(augmentor);
}
return builder.build();
}
场景2: 知识库检索(RAG)
完整链路
Copy
┌─────────────────────────────────────────────────────────────┐
│ Client │
│ (用户提问) │
└────────────────────┬────────────────────────────────────┘
│
│ POST /api/chat/chat
│ 包含knowledgeIds
▼
┌─────────────────────────────────────────────────────────────┐
│ RagRetrieverBuilder │
│ (构建RAG组件) │
└────────────────────┬────────────────────────────────────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌────────────┐
│ Knowledge │ │ SQL │ │ Content │
│ Retriever │ │Retriever │ │ Aggregator │
│ (知识库检索) │ │(SQL检索) │ │ (内容聚合) │
└──────┬───────┘ └────┬─────┘ └─────┬──────┘
│ │ │
│ │ ▼
│ │ ┌──────────────┐
│ │ │ QueryRouter │
│ │ │ (查询路由) │
│ │ └──────┬───────┘
│ │ │
└───────────────┼─────────────┘
│
▼
┌──────────────────────────┐
│ RetrievalAugmentor │
│ (检索增强器) │
└───────────┬──────────┘
│
▼
┌──────────────────────────┐
│ 注入到Prompt │
└───────────┬──────────┘
│
▼
┌──────────────────────────┐
│ LLM │
│ (生成回答) │
└───────────────────────┘
知识库检索流程
1. 向量检索
Copy
// KnowledgeRetrieverBuilder
public ContentRetriever buildRetriever(AigcKnowledge knowledge) {
// 1. 获取向量库
AigcVectorStore vectorStore = vectorStoreService.getById(knowledge.getVectorStoreId());
// 2. 获取Embedding模型
EmbeddingModel embeddingModel = embeddingModelFactory.getEmbeddingModel(
knowledge.getEmbeddingModelId()
);
// 3. 创建向量存储
VectorStore vectorStore = createVectorStore(vectorStore, embeddingModel);
// 4. 创建检索器
return VectorStoreRetriever.from(vectorStore)
.maxResults(knowledge.getTopK())
.minScore(knowledge.getSimilarityThreshold())
.build();
}
2. Rerank聚合
Copy
// ReRankingContentAggregator
public List<Content> aggregate(Query query, List<Retrieval> retrievals) {
// 1. 合并所有检索结果
List<Content> allContents = retrievals.stream()
.flatMap(r -> r.contents().stream())
.collect(Collectors.toList());
// 2. 去重
Set<String> seen = new HashSet<>();
List<Content> uniqueContents = allContents.stream()
.filter(c -> seen.add(c.textSegment().text()))
.collect(Collectors.toList());
// 3. Rerank
List<ScoredText> scoredTexts = scoringModel.scoreAll(
query.text(),
uniqueContents
);
// 4. 返回Top-N
return scoredTexts.stream()
.sorted((a, b) -> Double.compare(b.score(), a.score()))
.limit(10)
.map(st -> Content.from(st.text()))
.collect(Collectors.toList());
}
场景3: 插件工具调用
完整链路
Copy
┌─────────────────────────────────────────────────────────────┐
│ LLM │
│ (分析问题) │
└────────────────────┬────────────────────────────────────┘
│
│ 识别需要调用工具
▼
┌─────────────────────────────────────────────────────────────┐
│ AiServices (LangChain4j) │
│ (工具调用) │
└────────────────────┬────────────────────────────────────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌───────────┐
│ PluginTool │ │ McpTool │ │ Custom │
│ Factory │ │ Factory │ │ Factory │
│ (插件工具) │ │(MCP工具) │ │(自定义) │
└──────┬───────┘ └────┬─────┘ └─────┬──────┘
│ │ │
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Time │ │ Search │ │ HTTP │
│ Plugin │ │ Plugin │ │ Request │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└──────────────┼──────────────┘
│
│ 工具执行结果
▼
┌──────────────────────────┐
│ LLM │
│ (生成回答) │
└───────────────────────┘
插件工具调用
Copy
// PluginToolFactory
@Override
public void dynamicTools(LcChatReq req, AiServices<LangChatAgent> aiServices) {
// 1. 获取启用的插件
var plugins = pluginService.getEnabledPlugins(req.getUserId(), req.getAppId());
// 2. 构建工具列表
var tools = plugins.stream()
.map(this::convertToTool)
.collect(Collectors.toList());
// 3. 添加到AiServices
aiServices.tools(tools);
}
// 工具定义
private Tool convertToTool(AigcPlugin plugin) {
return Tool.builder()
.name(plugin.getName())
.description(plugin.getDescription())
.addParameter("input",
JsonSchemaProperty.string()
.description("插件输入参数")
.build())
.execute(request -> {
try {
return executePlugin(plugin, request);
} catch (Exception e) {
log.error("插件执行失败: pluginName={}", plugin.getName(), e);
return "插件执行失败: " + e.getMessage();
}
})
.build();
}
场景4: 文档上传与向量化
完整链路
Copy
┌─────────────────────────────────────────────────────────────┐
│ Client │
│ (上传文档) │
└────────────────────┬────────────────────────────────────┘
│
│ POST /api/docs/upload
│ MultipartFile
▼
┌─────────────────────────────────────────────────────────────┐
│ AigcDocsController │
│ (文档上传接口) │
└────────────────────┬────────────────────────────────────┘
│
│ 上传到OSS
▼
┌─────────────────────────────────────────────────────────────┐
│ OSS存储 │
│ (阿里云OSS/腾讯云COS) │
└────────────────────┬────────────────────────────────────┘
│
│ 返回URL
▼
┌─────────────────────────────────────────────────────────────┐
│ 创建AigcDocs记录 │
│ - 保存文件信息 │
│ - 状态: processing │
└────────────────────┬────────────────────────────────────┘
│
│ 异步处理任务
▼
┌─────────────────────────────────────────────────────────────┐
│ DocsProcessor │
│ (文档处理) │
└────────────────────┬────────────────────────────────────┘
│
┌───────────┼───────────┬───────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐
│ 文档解析 │ │ 文本提取 │ │ 文本分块 │ │ 向量化 │
│ (Tika/Poi) │ │ │ │ (Splitter)│ │(Embedding) │
└──────┬───────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘
│ │ │ │
└───────────────┼──────────────┼──────────────┘
│ │
▼ ▼
┌──────────────────────────┐ ┌────────────┐
│ 文本分段 │ │ 向量 │
│ (AigcSegment) │ │ 列表 │
└───────────┬──────────┘ └─────┬──────┘
│ │
│ │
└──────────┬─────────┘
│
▼
┌──────────────────────────┐
│ 批量存储向量库 │
│ (PGVector/Milvus) │
└───────────┬──────────┘
│
│ 更新状态
▼
┌──────────────────────────┐
│ 更新AigcDocs状态 │
│ - status: completed │
│ - segmentCount: N │
└───────────────────────┘
文档处理代码
Copy
@Async
public void process(String docsId) {
AigcDocs docs = docsService.getById(docsId);
try {
// 1. 下载文件
String filePath = downloadFile(docs.getFileUrl());
// 2. 解析文档
String text = docsParser.parse(filePath, docs.getFileType());
// 3. 文本分块
AigcKnowledge knowledge = knowledgeService.getById(docs.getKnowledgeId());
List<String> chunks = textSplitter.split(
text,
knowledge.getSplitStrategy(),
knowledge.getChunkSize(),
knowledge.getOverlapSize()
);
// 4. 批量向量化
List<List<Float>> embeddings = embeddingService.embedBatch(
knowledge.getEmbeddingModelId(),
chunks
);
// 5. 批量插入向量库
List<AigcSegment> segments = createSegments(docsId, chunks, embeddings);
vectorStoreService.batchInsert(knowledge.getVectorStoreId(), segments);
// 6. 更新状态
docs.setStatus("completed");
docs.setSegmentCount(chunks.size());
docsService.updateById(docs);
} catch (Exception e) {
docs.setStatus("failed");
docs.setErrorMessage(e.getMessage());
docsService.updateById(docs);
}
}
场景5: Workflow执行
完整链路
Copy
┌─────────────────────────────────────────────────────────────┐
│ Client │
│ (启动Workflow) │
└────────────────────┬────────────────────────────────────┘
│
│ POST /api/workflow/execute
│ Flow + input (JSON)
▼
┌─────────────────────────────────────────────────────────────┐
│ WorkflowExecutor │
│ (工作流执行器) │
└────────────────────┬────────────────────────────────────┘
│
│ 构建DAG
▼
┌─────────────────────────────────────────────────────────────┐
│ DAG构建 │
│ - 添加节点 │
│ - 添加边 │
│ - 验证无环 │
└────────────────────┬────────────────────────────────────┘
│
│ 拓扑排序
▼
┌─────────────────────────────────────────────────────────────┐
│ 按顺序执行节点 │
└────────────────────┬────────────────────────────────────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌───────────┐
│ Start Node │ │ LLM Node │ │ If Node │
│ (开始节点) │ │(LLM调用) │ │ (条件判断) │
└──────┬───────┘ └────┬─────┘ └─────┬──────┘
│ │ │
│ │ │
└───────────────┼──────────────┘
│
▼
┌──────────────────────────┐
│ 分支执行 │
│ - True Branch │
│ - False Branch │
└───────────┬──────────┘
│
│ 节点输出
▼
┌──────────────────────────┐
│ Merge Node │
│ (合并节点) │
└───────────┬──────────┘
│
▼
┌──────────────────────────┐
│ End Node │
│ (结束节点) │
└───────────┬──────────┘
│
│ 返回结果
▼
┌──────────────────────────┐
│ 执行结果 │
└───────────────────────┘
Workflow执行代码
Copy
public WorkflowResult execute(Flow flow, Map<String, Object> input, WorkflowExecutionListener listener) {
// 1. 构建DAG
DAG dag = buildDAG(flow);
// 2. 创建执行上下文
WorkflowContext context = new WorkflowContext();
context.setFlow(flow);
context.setInput(input);
context.setExecutionId(LcIdUtil.getUUID());
// 3. 保存初始状态
stateStore.save(context.getExecutionId(), new WorkflowState());
// 4. 按拓扑顺序执行节点
List<String> executionOrder = dag.topologicalOrder();
Map<String, Object> result = new HashMap<>(input);
for (String nodeId : executionOrder) {
FlowNode node = flow.getNodes().stream()
.filter(n -> n.getId().equals(nodeId))
.findFirst()
.orElseThrow();
// 执行节点
listener.onNodeStart(nodeId);
try {
Map<String, Object> nodeInput = prepareNodeInput(node, result, dag);
Map<String, Object> nodeOutput = executeNode(node, nodeInput, context);
result.putAll(nodeOutput);
listener.onNodeComplete(nodeId, nodeOutput);
} catch (Exception e) {
listener.onNodeError(nodeId, e);
throw new WorkflowExecutionException("节点执行失败: " + nodeId, e);
}
}
return new WorkflowResult(result, context.getExecutionId());
}
场景6: Embedding向量化
完整链路
Copy
┌─────────────────────────────────────────────────────────────┐
│ Client │
│ (调用Embedding API) │
└────────────────────┬────────────────────────────────────┘
│
│ POST /api/embedding
│ modelId + text (JSON)
▼
┌─────────────────────────────────────────────────────────────┐
│ LcEmbeddingService │
│ (Embedding服务) │
└────────────────────┬────────────────────────────────────┘
│
│ 获取模型配置
▼
┌─────────────────────────────────────────────────────────────┐
│ AigcModelService │
│ (获取模型配置) │
└────────────────────┬────────────────────────────────────┘
│
│ 查询模型
▼
┌─────────────────────────────────────────────────────────────┐
│ EmbeddingModelFactory │
│ (创建Embedding模型) │
└────────────────────┬────────────────────────────────────┘
│
│ 检查缓存
▼
┌──────────────────────────┐
│ 模型实例缓存 │
│ (Caffeine Cache) │
└───────┬───────────────┘
│
│ 缓存未命中
▼
┌──────────────────────────┐
│ 创建Embedding模型 │
│ - OpenAI │
│ - Ollama │
│ - Qwen │
└───────┬──────────────┘
│
│ 调用模型API
▼
┌──────────────────────────┐
│ 向量化完成 │
│ 返回向量列表 │
└───────────────────────┘
性能优化点
1. 模型实例缓存
Copy
// ChatModelFactory
private Cache<String, ChatModel> chatModelCache;
public ChatModel getChatModel(String modelId) {
String cacheKey = "chat:" + modelId;
return chatModelCache.get(cacheKey, k -> createChatModel(modelId));
}
2. 批量向量化
Copy
// EmbeddingService
public List<List<Float>> embedBatch(String modelId, List<String> texts) {
// 分批处理
int batchSize = 100;
List<List<Float>> results = new ArrayList<>();
for (int i = 0; i < texts.size(); i += batchSize) {
int end = Math.min(i + batchSize, texts.size());
List<String> batch = texts.subList(i, end);
results.addAll(embedBatchInternal(modelId, batch));
}
return results;
}
3. 异步处理
Copy
@Async("workflowExecutor")
public void process(String docsId) {
// 异步处理文档
}
4. 连接池复用
Copy
// RestTemplate配置
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
// 配置连接池
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
CloseableHttpClient httpClient = HttpClients.custom()
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.build();
factory.setHttpClient(httpClient);
return new RestTemplate(factory);
}
}

