package com.xly.tool;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xly.agent.AgentIdentity;
import com.xly.service.FormRenderService;
import com.xly.service.FormResolverService;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* FormCollect 工具(LLM 层的「收集信息」工具)——在对话里渲染一张 ERP 表单,让用户一次填齐 N 个参数,
* 而不是逐字段追问。
*
*
表单骨架来自 {@link FormRenderService#formSkeleton}(与 previewChange 预览共用同一实现层);
* 本类负责预填:用户已说的值(含尺寸表达的确定性拆分)对齐到表单字段。
*
*
工具返回结构化 schema(marker {@code type=form_collect});{@code AgentChatController} 侦测到后推
* SSE {@code form_collect} 事件,前端渲染表单;用户填完点【保存】→ 确定性端点
* {@code /api/agent/form/submit} 校验并写 ai_op_queue(AI 侧唯一写入口),不再经过 LLM。
*
*
由 {@code AgentFactory} 按请求身份新建(非 @Component):携带 {@link AgentIdentity} 做表单级授权。
*/
public class FormCollectTool {
// 尺寸拆分正则(实测模型拆不对「长和宽50cm,高5cm」这类表达,必须由代码确定性处理)
// 标注式:"长和宽50"、"高5cm"、"长:50"
private static final Pattern DIM_LABELED =
Pattern.compile("([长宽高厚深](?:[和与、,,及/][长宽高厚深])*)\\s*[::是为]?\\s*(\\d+(?:\\.\\d+)?)");
// 位置式:"50*30*5"、"50x30x5"、"50×30×5"
private static final Pattern DIM_POS =
Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*[*xX×]\\s*(\\d+(?:\\.\\d+)?)(?:\\s*[*xX×]\\s*(\\d+(?:\\.\\d+)?))?");
private final FormRenderService render;
private final FormResolverService resolver;
private final AgentIdentity identity;
private final ObjectMapper mapper;
public FormCollectTool(FormRenderService render, FormResolverService resolver,
AgentIdentity identity, ObjectMapper mapper) {
this.render = render;
this.resolver = resolver;
this.identity = identity;
this.mapper = mapper;
}
@Tool("在对话里弹出一张 ERP 表单让用户一次性填齐多个字段(而不是逐个追问、更不是自己猜)。用于字段较多的新建/录入场景"
+ "(尤其**新建报价**)。entityKeyword = 单据类型(如 报价 / 客户);knownFieldsJson = 用户已说的字段(中文名->值)用于预填"
+ "(如 {\"产品名称\":\"纸盒\",\"数量\":\"1000\",\"长(L)\":\"50\"})。客户/产品/物料会渲染成下拉让用户从真实数据里选。"
+ "用户填完点【保存】后,系统自动校验并提交待办——你**无需**再做任何写操作,也绝不能说已保存。")
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);
expandDimensions(known);
Map form = resolver.resolveMasterForm(entityKeyword.trim());
if (form == null) {
return err("找不到「" + entityKeyword + "」对应的可新建表单。");
}
String formId = String.valueOf(form.get("sFormId"));
String moduleId = String.valueOf(form.get("sModuleId"));
if (!identity.canAccessModule(moduleId)) {
return err("你没有新建「" + entityKeyword + "」的权限。");
}
String table = String.valueOf(form.get("sDataSource"));
List