diff --git a/src/main/java/com/xly/agent/ReActAgent.java b/src/main/java/com/xly/agent/ReActAgent.java
new file mode 100644
index 0000000..0918971
--- /dev/null
+++ b/src/main/java/com/xly/agent/ReActAgent.java
@@ -0,0 +1,18 @@
+package com.xly.agent;
+
+import dev.langchain4j.service.MemoryId;
+import dev.langchain4j.service.TokenStream;
+import dev.langchain4j.service.UserMessage;
+
+/**
+ * 单一 ReAct 智能体。
+ *
+ *
LangChain4j 的 {@code AiServices} 原生工具调用循环即 ReAct:模型自行决定是否调用工具、
+ * 调用哪个工具、以及何时给出最终答复。不再有旧的 8 场景 SceneSelector 路由——由 agent 自路由。
+ *
+ *
返回 {@link TokenStream} 以支持流式输出;{@link MemoryId} 绑定每个会话独立的对话记忆。
+ */
+public interface ReActAgent {
+
+ TokenStream chat(@MemoryId String conversationId, @UserMessage String userInput);
+}
diff --git a/src/main/java/com/xly/config/AgentConfig.java b/src/main/java/com/xly/config/AgentConfig.java
new file mode 100644
index 0000000..51479fd
--- /dev/null
+++ b/src/main/java/com/xly/config/AgentConfig.java
@@ -0,0 +1,53 @@
+package com.xly.config;
+
+import com.xly.agent.ReActAgent;
+import com.xly.service.SystemPromptService;
+import com.xly.tool.KgQueryTool;
+import dev.langchain4j.memory.chat.MessageWindowChatMemory;
+import dev.langchain4j.model.ollama.OllamaStreamingChatModel;
+import dev.langchain4j.service.AiServices;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.time.Duration;
+
+/**
+ * 组装单一 ReAct agent(M1)。
+ *
+ *
= 流式 Ollama 模型 + 通用工具(当前只有 {@link KgQueryTool})+ 每会话对话记忆
+ * + 注入 L1 域图的 system prompt。取代旧的「每表单一个 ToolMeta 工具 + 8 场景路由」。
+ */
+@Configuration
+public class AgentConfig {
+
+ @Value("${langchain4j.ollama.base-url}")
+ private String ollamaUrl;
+
+ @Value("${langchain4j.ollama.chat-model-name}")
+ private String chatModelName;
+
+ /** 专供 agent 的流式模型:低温度利于稳定的工具调用,较大 numPredict 避免答复被截断。 */
+ @Bean("agentStreamingModel")
+ public OllamaStreamingChatModel agentStreamingModel() {
+ return OllamaStreamingChatModel.builder()
+ .baseUrl(ollamaUrl)
+ .modelName(chatModelName)
+ .temperature(0.1)
+ .topP(0.9)
+ .numPredict(2048)
+ .timeout(Duration.ofSeconds(180))
+ .build();
+ }
+
+ @Bean
+ public ReActAgent reActAgent(SystemPromptService systemPromptService, KgQueryTool kgQueryTool) {
+ String systemPrompt = systemPromptService.buildSystemPrompt();
+ return AiServices.builder(ReActAgent.class)
+ .streamingChatModel(agentStreamingModel())
+ .tools(kgQueryTool)
+ .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(20))
+ .systemMessageProvider(memoryId -> systemPrompt)
+ .build();
+ }
+}
diff --git a/src/main/java/com/xly/service/SystemPromptService.java b/src/main/java/com/xly/service/SystemPromptService.java
new file mode 100644
index 0000000..33091a8
--- /dev/null
+++ b/src/main/java/com/xly/service/SystemPromptService.java
@@ -0,0 +1,81 @@
+package com.xly.service;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 构建单 agent 的 system prompt。
+ *
+ *
核心是把 L1 业务域地图({@code viw_kg_domain},11 个域 + 上下游流转 + 对应智能体)渲染进
+ * system prompt,作为常驻的「路由地图」——让单 agent 先判断问题属于哪个业务域、涉及哪些单据,
+ * 再决定调用哪个工具。L1 体量小且永远相关,适合常驻 prompt;L2/L3 大而稀疏,走工具按需查。
+ */
+@Service
+public class SystemPromptService {
+
+ private final JdbcTemplate jdbc;
+
+ public SystemPromptService(JdbcTemplate jdbc) {
+ this.jdbc = jdbc;
+ }
+
+ public String buildSystemPrompt() {
+ return """
+ 你是「小羚羊」,小羚羊印刷 ERP 的智能助手。服务对象是印刷 / 包装行业的企业用户,\
+ 帮助他们查询和(未来)操作 ERP 里的业务单据。
+
+ 【业务域地图(L1 路由)】
+ 下面是本 ERP 的业务域、单据规模及其上下游流转关系。回答前先据此判断用户的问题属于哪个域、\
+ 可能涉及哪些单据,再决定怎么做:
+ %s
+ 【可用工具】
+ - findForms(keyword):按关键词检索业务表单目录,用来把用户口中的「单据 / 报表」定位到具体是哪一张表单\
+ (拿到 formId / moduleId)。当你不确定用户指的到底是哪张表单时,先调用它确认,不要自己编表单名。
+
+ 【行为准则】
+ 1. 凡是能用工具确认的事实(表单、单据、数据),一律调用工具,绝不凭空编造表单名、单据号或数据。
+ 2. 始终用**简体中文**、简洁、面向业务人员回答;不要暴露内部字段名或技术细节,除非用户明确要求。
+ 3. **直接给出最终答复**:不要复述你正在调用哪个工具、不要输出思考过程或任何过程性文字。
+ 4. 你**当前只有只读的「表单目录检索」能力**。凡涉及新增 / 修改 / 删除 / 审核等写操作,\
+ 如实告诉用户「该能力正在开发中」,绝不假装已经执行或已经生成单据。
+ 5. 信息不足时主动向用户提问,不要猜测。
+ """.formatted(renderDomainMap());
+ }
+
+ private String renderDomainMap() {
+ List