diff --git a/src/main/java/com/xly/service/SystemPromptService.java b/src/main/java/com/xly/service/SystemPromptService.java index 9301565..5f051fa 100644 --- a/src/main/java/com/xly/service/SystemPromptService.java +++ b/src/main/java/com/xly/service/SystemPromptService.java @@ -48,9 +48,12 @@ public class SystemPromptService { - queryData(question):用**只读 SQL** 回答没有现成表单能直接答的临时统计 / 分析(跨表汇总、计数、排名、分组);\ 仅当 readFormData / lookupRecord 都答不了时才用。 - askUser(question, options?):意图不明或需二选一时,向用户提一个带选项的小问题;问完即等用户回答。 - - collectForm(entityKeyword):新增字段较多的单据(如报价)时,弹一张表单让用户一次填齐;提交后你再用 proposeCreate。 + - collectForm(entityKeyword, knownFieldsJson?):新增字段较多的单据(尤其**报价**)时,弹一张表单让用户一次填齐。\ + 把用户已说的信息作为 knownFieldsJson 预填(字段中文名->值,如 {"产品名称":"纸盒","数量":"1000","长(L)":"50"}),\ + 客户/产品/物料等会渲染成下拉让用户从真实数据里选。**新增报价一律先用它**,提交后你再用 proposeCreate。 - proposeCreate(entityKeyword, fieldsJson):**提议新增**一条记录(写操作,只提议、暂不执行;用户确认后才新增)。\ - fieldsJson 是已知字段的 JSON(字段中文名->值),如 {"客户名称":"常州测试公司"};主键与必填项会自动补齐。 + fieldsJson 是已知字段的 JSON(字段中文名->值),如 {"客户名称":"常州测试公司"};主键与必填项会自动补齐。\ + **仅当字段(尤其客户/产品)都由用户明确给出、无需猜测时才直接用它**;否则先 collectForm。 - proposeUpdate(entityKeyword, recordKeyword, fieldChinese, newValue):**提议**修改某条记录的某个字段(写操作,只提议、暂不执行)。 - proposeDelete(entityKeyword, recordKeyword):**提议删除**某条记录(写操作,只提议、暂不执行)。删除不可恢复,慎用。 - proposeExamine(entityKeyword, recordKeyword):**提议审核/过账**某张单据(写操作,只提议、暂不执行)。审核有业务后果,慎用。 @@ -67,6 +70,9 @@ public class SystemPromptService { 它们都**只生成待确认提议、不立即执行**;真正的写入要用户在对话内点【确认】才发生,你**绝不能声称已经完成**。 5. 用户问某类数据的数量 / 概况 / 某条记录时,**直接用工具读取并如实汇报**,不要无谓反问;\ 只有确实缺少关键参数(如不知道要查哪张单据)时才用 askUser 提问。 + 6. **新增报价**("报价X"/"给X报价"/"新增报价")**一律先 collectForm**,把用户说的产品/数量/尺寸作为 knownFieldsJson 预填,\ + 客户/产品让用户在下拉里选真实数据。**绝不要把产品名、盒型、规格猜成客户名称**——"报价纸盒"里"纸盒"是产品不是客户;\ + 客户未知就交给表单让用户选,不要自己编、也不要因为找不到就提议新建客户。 """.formatted(renderDomainMap(), renderSkills()); } diff --git a/src/main/java/com/xly/tool/FormCollectTool.java b/src/main/java/com/xly/tool/FormCollectTool.java index c457978..6c90986 100644 --- a/src/main/java/com/xly/tool/FormCollectTool.java +++ b/src/main/java/com/xly/tool/FormCollectTool.java @@ -9,6 +9,8 @@ import dev.langchain4j.agent.tool.Tool; import org.springframework.jdbc.core.JdbcTemplate; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -42,12 +44,18 @@ public class FormCollectTool { this.mapper = mapper; } - @Tool("在对话里弹出一张 ERP 表单让用户一次性填齐多个字段(而不是逐个追问)。用于字段较多的新建/录入场景" - + "(如新建报价、新建客户)。入参 = 实体/单据类型(如 报价 / 客户)。用户填完提交后,你再用 proposeCreate 生成待确认的新增。") - public String collectForm(@P("要新建的实体/单据类型,如 报价 / 客户 / 物料") String entityKeyword) { + @Tool("在对话里弹出一张 ERP 表单让用户一次性填齐多个字段(而不是逐个追问、更不是自己猜)。用于字段较多的新建/录入场景" + + "(尤其**新建报价**)。entityKeyword = 单据类型(如 报价 / 客户);knownFieldsJson = 用户已说的字段(中文名->值)用于预填" + + "(如 {\"产品名称\":\"纸盒\",\"数量\":\"1000\",\"长(L)\":\"50\"})。客户/产品/物料会渲染成下拉让用户从真实数据里选。" + + "用户填完提交后,你再用 proposeCreate 生成待确认的新增。") + public String collectForm( + @P("要新建的实体/单据类型,如 报价 / 客户 / 物料") String entityKeyword, + @P(value = "可选:用户已说的字段 JSON(中文名->值),用于预填表单,如 {\"产品名称\":\"纸盒\",\"数量\":\"1000\"}", + required = false) String knownFieldsJson) { if (entityKeyword == null || entityKeyword.isBlank()) { return err("缺少实体类型。"); } + Map known = parseKnown(knownFieldsJson); Map form = resolver.resolveMasterForm(entityKeyword.trim()); if (form == null) { return err("找不到「" + entityKeyword + "」对应的可新建表单。"); @@ -93,6 +101,10 @@ public class FormCollectTool { if (hint != null && !String.valueOf(hint).isBlank()) { f.put("hint", String.valueOf(hint)); } + String pre = known.get(normLabel(String.valueOf(f.get("label")))); + if (pre != null && !pre.isBlank()) { + f.put("default", pre); // 预填用户已说的值 + } fields.add(f); } @@ -204,6 +216,34 @@ public class FormCollectTool { return o == null ? null : o.toString(); } + /** 解析预填 JSON(中文名->值),键归一化(去括号/空白)。 */ + private Map parseKnown(String json) { + Map m = new HashMap<>(); + if (json == null || json.isBlank()) { + return m; + } + try { + com.fasterxml.jackson.databind.JsonNode n = mapper.readTree(json.trim()); + Iterator> it = n.fields(); + while (it.hasNext()) { + Map.Entry e = it.next(); + String v = e.getValue().asText(""); + if (v != null && !v.isBlank()) { + m.put(normLabel(e.getKey()), v); + } + } + } catch (Exception ignore) { + } + return m; + } + + private static String normLabel(String s) { + if (s == null) { + return ""; + } + return s.replaceAll("[((].*?[))]", "").replaceAll("\\s+", "").trim(); + } + private String err(String msg) { Map m = new LinkedHashMap<>(); m.put("error", msg); diff --git a/src/main/java/com/xly/tool/ProposeWriteTool.java b/src/main/java/com/xly/tool/ProposeWriteTool.java index 84a77c8..fd95160 100644 --- a/src/main/java/com/xly/tool/ProposeWriteTool.java +++ b/src/main/java/com/xly/tool/ProposeWriteTool.java @@ -252,7 +252,9 @@ public class ProposeWriteTool { if (fk != null && !fk.isBlank()) { String id = resolver.resolveFk(fk, v); // 外键:名称 -> id if (id == null) { - return err("找不到名为「" + v + "」的" + zh + ",请确认该" + zh + "是否已存在。"); + String ent = zh.replace("名称", ""); + return err("「" + v + "」不是系统里已有的" + ent + "。请让用户从下拉里选真实存在的" + + ent + ";**不要**因此新建" + ent + "。"); } col.put(colName, id); } else { @@ -421,7 +423,9 @@ public class ProposeWriteTool { if (fk != null && !fk.isBlank()) { String id = resolver.resolveFk(fk, v); if (id == null) { - return err("找不到名为「" + v + "」的" + zh + ",请确认是否已存在。"); + String ent = zh.replace("名称", ""); + return err("「" + v + "」不是系统里已有的" + ent + "。请用 collectForm 让用户从下拉里选真实存在的" + + ent + ";**不要**因此新建" + ent + ",也不要把产品/规格当成客户。"); } value = id; if ("sCustomerId".equals(colName)) {