AgentFactory.java 5 KB
package com.xly.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xly.agent.AgentIdentity;
import com.xly.agent.ReActAgent;
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.ollama.OllamaChatModel;
import dev.langchain4j.model.ollama.OllamaStreamingChatModel;
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)。
 *
 * <p>为什么按请求新建:LangChain4j 流式工具执行发生在模型 HTTP 回调线程,而非控制器工作线程,
 * ThreadLocal 不可靠。于是把 {@link AgentIdentity}(透传 token + 权限集)直接注入到**每请求新建**的
 * 工具实例里(ErpReadTool/ProposeWriteTool/QueryTool/FormCollectTool),从根上保证鉴权与 token 正确。
 *
 * <p>无状态、全局的工具({@link KgQueryTool} 表单目录/KG、{@link SkillTool} Skill 加载、
 * {@link InteractionTool} AskUser)是单例、跨请求复用。模型、记忆存储、system prompt 也复用。
 */
@Component
public class AgentFactory {

    private final OllamaStreamingChatModel streamingModel;
    private final OllamaChatModel 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;

    private volatile String cachedSystemPrompt;

    public AgentFactory(@Qualifier("agentStreamingModel") OllamaStreamingChatModel streamingModel,
                        @Qualifier("sqlChatModel") OllamaChatModel 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;
    }

    /** system prompt 是全局的(L1 域图 + Skill 摘要),构建一次后缓存。 */
    private String systemPrompt() {
        String p = cachedSystemPrompt;
        if (p == null) {
            synchronized (this) {
                if (cachedSystemPrompt == null) {
                    cachedSystemPrompt = systemPromptService.buildSystemPrompt();
                }
                p = cachedSystemPrompt;
            }
        }
        return p;
    }

    /** 用给定身份组装一个 ReAct agent(工具实例携带该身份的 token 与权限边界)。 */
    public ReActAgent build(AgentIdentity identity) {
        String systemPrompt = systemPrompt();
        return AiServices.builder(ReActAgent.class)
                .streamingChatModel(streamingModel)
                .tools(
                        kgQueryTool,
                        skillTool,
                        interactionTool,
                        new ErpReadTool(erp, jdbc, resolver, identity),
                        new ProposeWriteTool(erp, jdbc, ops, mapper, identity),
                        new QueryTool(sqlModel, jdbc, audit, identity),
                        new FormCollectTool(erp, jdbc, resolver, identity, mapper))
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder()
                        .id(memoryId)
                        .maxMessages(30)
                        .chatMemoryStore(memoryStore)
                        .build())
                .systemMessageProvider(memoryId -> systemPrompt)
                .build();
    }
}