package com.xly.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.xly.agent.AgentIdentity; import com.xly.agent.ReActAgent; import com.xly.agent.ToolScope; import com.xly.service.AuditService; import com.xly.service.ErpClient; import com.xly.service.FormResolverService; import com.xly.service.OpService; import com.xly.service.SkillService; import com.xly.service.SystemPromptService; import com.xly.tool.ErpReadTool; import com.xly.tool.FormCollectTool; import com.xly.tool.InteractionTool; import com.xly.tool.KgQueryTool; import com.xly.tool.ProposeWriteTool; import com.xly.tool.QueryTool; import com.xly.tool.SkillTool; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.service.AiServices; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; /** * 按「每次请求的身份」组装单一 ReAct agent(架构 §5/§7 的 per-call context)。 * *

为什么按请求新建:LangChain4j 流式工具执行发生在模型 HTTP 回调线程,而非控制器工作线程, * ThreadLocal 不可靠。于是把 {@link AgentIdentity}(透传 token + 权限集)直接注入到**每请求新建**的 * 工具实例里(ErpReadTool/ProposeWriteTool/QueryTool/FormCollectTool),从根上保证鉴权与 token 正确。 * *

无状态、全局的工具({@link KgQueryTool} 表单目录/KG、{@link SkillTool} Skill 加载、 * {@link InteractionTool} AskUser)是单例、跨请求复用。模型、记忆存储、system prompt 也复用。 */ @Component public class AgentFactory { private final StreamingChatModel streamingModel; private final ChatModel sqlModel; private final RedisChatMemoryStore memoryStore; private final SystemPromptService systemPromptService; private final ErpClient erp; private final JdbcTemplate jdbc; private final FormResolverService resolver; private final OpService ops; private final ObjectMapper mapper; private final AuditService audit; private final SkillService skillService; private final KgQueryTool kgQueryTool; private final SkillTool skillTool; private final InteractionTool interactionTool; public AgentFactory(@Qualifier("agentStreamingModel") StreamingChatModel streamingModel, @Qualifier("sqlChatModel") ChatModel sqlModel, RedisChatMemoryStore memoryStore, SystemPromptService systemPromptService, ErpClient erp, JdbcTemplate jdbc, FormResolverService resolver, OpService ops, ObjectMapper mapper, AuditService audit, SkillService skillService, KgQueryTool kgQueryTool, SkillTool skillTool, InteractionTool interactionTool) { this.streamingModel = streamingModel; this.sqlModel = sqlModel; this.memoryStore = memoryStore; this.systemPromptService = systemPromptService; this.erp = erp; this.jdbc = jdbc; this.resolver = resolver; this.ops = ops; this.mapper = mapper; this.audit = audit; this.skillService = skillService; this.kgQueryTool = kgQueryTool; this.skillTool = skillTool; this.interactionTool = interactionTool; } /** 兜底:全部工具(意图门失败时用)。 */ public ReActAgent build(AgentIdentity identity) { return build(identity, ToolScope.FULL); } /** * 按意图范围组装 ReAct agent:只暴露该范围相关的工具 + 对应版本的 system prompt。 * *

收窄可见工具(3-5 个)显著提升弱模型的选工具准确率;{@code maxSequentialToolsInvocations} * 作为循环护栏,杜绝「反复追问同一问题」这类失控 ReAct 循环。 */ public ReActAgent build(AgentIdentity identity, ToolScope scope) { String systemPrompt = systemPromptService.buildPrompt(scope); ErpReadTool readTool = new ErpReadTool(erp, jdbc, resolver, identity); Object[] tools; switch (scope) { case READ: tools = new Object[]{kgQueryTool, skillTool, interactionTool, readTool, new QueryTool(sqlModel, jdbc, audit, identity, resolver)}; break; case WRITE: tools = new Object[]{skillTool, interactionTool, readTool, new ProposeWriteTool(erp, jdbc, ops, mapper, identity, resolver), new FormCollectTool(erp, jdbc, resolver, identity, mapper)}; break; default: // FULL tools = new Object[]{kgQueryTool, skillTool, interactionTool, readTool, new ProposeWriteTool(erp, jdbc, ops, mapper, identity, resolver), new QueryTool(sqlModel, jdbc, audit, identity, resolver), new FormCollectTool(erp, jdbc, resolver, identity, mapper)}; } return AiServices.builder(ReActAgent.class) .streamingChatModel(streamingModel) .tools(tools) .maxSequentialToolsInvocations(8) // 循环护栏:防止 askUser/工具无限自我循环 .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(30) .chatMemoryStore(memoryStore) .build()) .systemMessageProvider(memoryId -> systemPrompt) .build(); } /** 供确定性「新增」路径直接构建 collectForm 表单(不经 LLM 工具选择)。 */ public FormCollectTool formCollectTool(AgentIdentity identity) { return new FormCollectTool(erp, jdbc, resolver, identity, mapper); } /** 供确定性「修改/作废/审核…」路径直接调 proposeWrite(不经 LLM 工具选择)。 */ public ProposeWriteTool proposeWriteTool(AgentIdentity identity) { return new ProposeWriteTool(erp, jdbc, ops, mapper, identity, resolver); } }