AgentFactory.java
5 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
package com.xly.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xly.agent.AgentIdentity;
import com.xly.agent.EventLogChatMemory;
import com.xly.agent.ReActAgent;
import com.xly.service.ErpClient;
import com.xly.service.EventProjectionService;
import com.xly.service.FormResolverService;
import com.xly.service.LedgerService;
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.UseSkillTool;
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/FormCollectTool),从根上保证鉴权与 token 正确。
*
* <p>工具集**固定 6 个**(findForms/readFormData/lookupRecord/collectForm/proposeWrite/askUser),
* 不再按意图分范围:n=50 基准显示分范围收不到准确率收益,反而破坏 KV-cache 前缀稳定性。
* 无状态、全局的工具({@link KgQueryTool}、{@link InteractionTool})是单例、跨请求复用。
*/
@Component
public class AgentFactory {
private final StreamingChatModel streamingModel;
private final LedgerService ledger;
private final EventProjectionService projection;
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 KgQueryTool kgQueryTool;
private final InteractionTool interactionTool;
private final SkillService skillService;
public AgentFactory(@Qualifier("agentStreamingModel") StreamingChatModel streamingModel,
LedgerService ledger, EventProjectionService projection,
SystemPromptService systemPromptService,
ErpClient erp, JdbcTemplate jdbc, FormResolverService resolver, OpService ops,
ObjectMapper mapper, KgQueryTool kgQueryTool, InteractionTool interactionTool,
SkillService skillService) {
this.streamingModel = streamingModel;
this.ledger = ledger;
this.projection = projection;
this.systemPromptService = systemPromptService;
this.erp = erp;
this.jdbc = jdbc;
this.resolver = resolver;
this.ops = ops;
this.mapper = mapper;
this.kgQueryTool = kgQueryTool;
this.interactionTool = interactionTool;
this.skillService = skillService;
}
/**
* 组装 ReAct agent:固定 7 工具(useSkill + 6)+ 唯一 system prompt。
* {@code maxSequentialToolsInvocations} 作为循环护栏,杜绝失控 ReAct 循环。
*
* @param convId 会话 id(useSkill 落 skill_active 事件需要)
* @param internalUserTurn true=本次的用户消息是系统注入话术(护栏重试),落账时带 internal 标记,
* 前端历史不显示
*/
public ReActAgent build(AgentIdentity identity, String convId, boolean internalUserTurn) {
Object[] tools = new Object[]{kgQueryTool, interactionTool,
new UseSkillTool(skillService, ledger, convId),
new ErpReadTool(erp, resolver, identity),
new ProposeWriteTool(erp, jdbc, ops, mapper, identity, resolver),
new FormCollectTool(jdbc, resolver, identity, mapper)};
return AiServices.builder(ReActAgent.class)
.streamingChatModel(streamingModel)
.tools(tools)
.maxSequentialToolsInvocations(8) // 循环护栏:防止 askUser/工具无限自我循环
// 事件日志记忆:逐条 append 原子落账,读取为四段式投影(见 EventLogChatMemory)
.chatMemoryProvider(memoryId -> new EventLogChatMemory(
String.valueOf(memoryId), ledger, projection, 6000, internalUserTurn))
.systemMessageProvider(memoryId -> systemPromptService.prompt())
.build();
}
/** 供确定性「表单提交」端点直接调 proposeWrite(action=create)(按钮路径,不经 LLM)。 */
public ProposeWriteTool proposeWriteTool(AgentIdentity identity) {
return new ProposeWriteTool(erp, jdbc, ops, mapper, identity, resolver);
}
}