AgentFactory.java
6.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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)。
*
* <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 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。
*
* <p>收窄可见工具(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);
}
}