Commit 03518821 authored by alex yao's avatar alex yao

feat:新增AI+财务接口

parent f064f4b6
...@@ -14,6 +14,8 @@ public enum AiDialoguesTypeEnum { ...@@ -14,6 +14,8 @@ public enum AiDialoguesTypeEnum {
AI_BI("ai_bi", "智能问数"), AI_BI("ai_bi", "智能问数"),
AI_FINANCE("ai_finance", "AI财务"),
; ;
private final String type; private final String type;
......
package cn.com.poc.ai_finance.aggregate;
/**
* AI财务
*
* @author alex.yao
* @date 2025/6/20
*/
public interface AiFinanceService {
/**
* 调用AI财务服务
*
* @param dialoguesId 对话id
* @param input 问题
* @param fileUrl 文件地址
* @param knowledgeIds 知识库ids
* @param databaseIds 数据库ids
* @param userId 用户id
*/
void call(String dialoguesId, String input, String fileUrl, Integer[] knowledgeIds, Integer[] databaseIds, Long userId) throws Exception;
}
package cn.com.poc.ai_finance.aggregate.impl;
import cn.com.poc.agent_application.aggregate.AgentApplicationService;
import cn.com.poc.agent_application.constant.AgentApplicationDialoguesRecordConstants;
import cn.com.poc.agent_application.entity.*;
import cn.com.poc.agent_application.service.BizAgentApplicationDialoguesRecordService;
import cn.com.poc.agent_application.service.BizAgentApplicationPublishService;
import cn.com.poc.agent_application.utils.AgentApplicationTools;
import cn.com.poc.ai_dialogues.entity.BizAiDialoguesEntity;
import cn.com.poc.ai_dialogues.service.BizAiDialoguesService;
import cn.com.poc.ai_finance.aggregate.AiFinanceService;
import cn.com.poc.common.constant.CommonConstant;
import cn.com.poc.common.utils.StringUtils;
import cn.com.poc.knowledge.aggregate.KnowledgeService;
import cn.com.poc.thirdparty.resource.demand.ai.constants.KnowledgeSearchTypeEnum;
import cn.com.poc.thirdparty.resource.demand.ai.entity.dialogue.Message;
import cn.com.poc.thirdparty.resource.demand.ai.entity.dialogue.Tool;
import cn.com.poc.thirdparty.resource.demand.ai.entity.function.FunctionCallResult;
import cn.com.yict.framemax.core.exception.BusinessException;
import cn.com.yict.framemax.frame.service.FmxParamConfigService;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
/**
* @author alex.yao
* @date 2025/6/20
*/
@Service
public class AiFinanceServiceImpl implements AiFinanceService {
private final Logger logger = LoggerFactory.getLogger(AiFinanceServiceImpl.class);
private final String FMX_PARAM_AGENT_ID_CODE = "ai_finance.agent_id";
@Resource
private FmxParamConfigService fmxParamConfigService;
@Resource
private BizAiDialoguesService bizAiDialoguesService;
@Resource
private AgentApplicationService agentApplicationService;
@Resource
private KnowledgeService knowledgeService;
@Resource
private BizAgentApplicationPublishService bizAgentApplicationPublishService;
@Resource
private BizAgentApplicationDialoguesRecordService bizAgentApplicationDialoguesRecordService;
@Override
public void call(String dialoguesId, String input, String fileUrl, Integer[] knowledgeIds, Integer[] databaseIds, Long userId) throws Exception {
String agentId = fmxParamConfigService.getParam(FMX_PARAM_AGENT_ID_CODE);
if (StringUtils.isBlank(agentId)) {
logger.error("获取AI财务对话agentId失败");
throw new BusinessException("对话异常");
}
// 获取Agent基本信息
BizAgentApplicationPublishEntity infoEntity = bizAgentApplicationPublishService.getByAgentId(agentId);
if (infoEntity == null) {
logger.error("获取AI财务对话agent信息失败");
throw new BusinessException("对话异常");
}
// 保存对话基础信息
BizAiDialoguesEntity bizAiDialoguesEntity = new BizAiDialoguesEntity();
bizAiDialoguesEntity.setMemberId(userId);
bizAiDialoguesEntity.setDialoguesId(dialoguesId);
bizAiDialoguesEntity.setIsDeleted(CommonConstant.IsDeleted.N);
List<BizAiDialoguesEntity> bizAiDialoguesEntities = bizAiDialoguesService.findByExample(bizAiDialoguesEntity, null);
if (CollectionUtils.isEmpty(bizAiDialoguesEntities)) {
logger.error("dialogues id 不存在,请重新创建");
throw new BusinessException("对话异常");
}
// 保存标题信息
if (StringUtils.isBlank(bizAiDialoguesEntities.get(0).getTitle())) {
BizAiDialoguesEntity portalDialoguesEntity = bizAiDialoguesEntities.get(0);
portalDialoguesEntity.setTitle(input);
bizAiDialoguesService.update(portalDialoguesEntity);
}
// 获取对话配置
String largeModel = infoEntity.getLargeModel();//获取模型
String agentSystem = infoEntity.getAgentSystem();//获取Agent角色词
List<Integer> kdIds = knowledgeService.getKdIdsByKnowledgeInfoIds(knowledgeIds);//获取知识库ids
List<Message> messages = AgentApplicationTools.buildMessage(dialoguesId, agentId, userId, input);//构建消息
List<Tool> tools = AgentApplicationTools.buildFunctionConfig(infoEntity.getVariableStructure(), infoEntity.getIsLongMemory(), dialoguesId, agentId, infoEntity.getUnitIds(), infoEntity.getIsDocumentParsing());//构建函数配置
List<String> fileUrls = new ArrayList<>();//构建文件链接
if (StringUtils.isNotBlank(fileUrl)) {
fileUrls.add(fileUrl);
}
CheckPluginUseEntity checkPluginUseEntity = AgentApplicationTools.checkPluginUse(messages, tools, fileUrls);//检查插件使用
FunctionCallResult functionCallResult = checkPluginUseEntity.getFunctionCallResult();//函数调用结果
boolean stream = true;//是否流式输出
KnowledgeSearchTypeEnum knowledgeSearchTypeEnum = KnowledgeSearchTypeEnum.valueOf(infoEntity.getKnowledgeSearchType());//知识库检索方式
KnowledgeSuperclassProblemConfig knowledgeSuperclassProblemConfig = new KnowledgeSuperclassProblemConfig();//知识库超纲配置
knowledgeSuperclassProblemConfig.setKnowledgeResponseType(infoEntity.getKnowledgeResponseType());//知识库回复类型
knowledgeSuperclassProblemConfig.setKnowledgeCustomResponse(infoEntity.getKnowledgeCustomResponse());//知识库自定义回复
// 记录用户输入时间戳
Long inputTimestamp = System.currentTimeMillis();
// 调用对话Agent
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = servletRequestAttributes.getResponse();
AgentResultEntity agentResultEntity = agentApplicationService.callAgentApplication(agentId,
dialoguesId,
largeModel,
agentSystem,
kdIds.toArray(new Integer[0]),
databaseIds,
infoEntity.getCommunicationTurn(),
infoEntity.getTopP(),
infoEntity.getTemperature(),
messages,
tools,
functionCallResult,
stream,
infoEntity.getKnowledgeSimilarity(),
infoEntity.getKnowledgeNResult(),
knowledgeSearchTypeEnum,
knowledgeSuperclassProblemConfig,
response
);
//保存对话记录
//用户输入记录
BizAgentApplicationDialoguesRecordEntity inputRecord = new BizAgentApplicationDialoguesRecordEntity();
inputRecord.setAgentId(agentId);
inputRecord.setMemberId(userId);
inputRecord.setContent(input);
inputRecord.setDialogsId(dialoguesId);
inputRecord.setRole(AgentApplicationDialoguesRecordConstants.ROLE.USER);
inputRecord.setTimestamp(inputTimestamp);
bizAgentApplicationDialoguesRecordService.save(inputRecord);
//AI输出记录
BizAgentApplicationDialoguesRecordEntity outputRecord = new BizAgentApplicationDialoguesRecordEntity();
outputRecord.setRole(AgentApplicationDialoguesRecordConstants.ROLE.ASSISTANT);
outputRecord.setAgentId(infoEntity.getAgentId());
outputRecord.setDialogsId(dialoguesId);
outputRecord.setMemberId(userId);
outputRecord.setTimestamp(System.currentTimeMillis());
outputRecord.setContent(agentResultEntity.getMessage());
outputRecord.setReasoningContent(agentResultEntity.getReasoningContent());
bizAgentApplicationDialoguesRecordService.save(outputRecord);
}
}
package cn.com.poc.ai_finance.dto;
/**
* @author alex.yao
* @date 2025/6/20
*/
public class AiFinanceDialoguesDto {
/**
* 对话id
*/
private String dialoguesId;
/**
* 问题
*/
private String input;
/**
* 文件地址
*/
private String fileUrl;
/**
* 知识库ids
*/
private Integer[] knowledgeIds;
/**
* 数据库ids
*/
private Integer[] databaseIds;
public String getDialoguesId() {
return dialoguesId;
}
public void setDialoguesId(String dialoguesId) {
this.dialoguesId = dialoguesId;
}
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public String getFileUrl() {
return fileUrl;
}
public void setFileUrl(String fileUrl) {
this.fileUrl = fileUrl;
}
public Integer[] getKnowledgeIds() {
return knowledgeIds;
}
public void setKnowledgeIds(Integer[] knowledgeIds) {
this.knowledgeIds = knowledgeIds;
}
public Integer[] getDatabaseIds() {
return databaseIds;
}
public void setDatabaseIds(Integer[] databaseIds) {
this.databaseIds = databaseIds;
}
}
package cn.com.poc.ai_finance.rest;
import cn.com.poc.ai_finance.dto.AiFinanceDialoguesDto;
import cn.com.yict.framemax.core.rest.BaseRest;
import cn.com.yict.framemax.web.permission.Access;
import cn.com.yict.framemax.web.permission.Permission;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @author alex.yao
* @date 2025/6/24
*/
@Permission(Access.Safety)
public interface AiFinanceRest extends BaseRest {
void call(@RequestBody AiFinanceDialoguesDto dto) throws Exception;
}
package cn.com.poc.ai_finance.rest.impl;
import cn.com.poc.ai_finance.aggregate.AiFinanceService;
import cn.com.poc.ai_finance.dto.AiFinanceDialoguesDto;
import cn.com.poc.ai_finance.rest.AiFinanceRest;
import cn.com.poc.common.utils.BlContext;
import cn.com.poc.support.security.oauth.entity.UserBaseEntity;
import cn.com.yict.framemax.core.exception.BusinessException;
import cn.hutool.core.lang.Assert;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author alex.yao
* @date 2025/6/24
*/
@Component
public class AiFinanceRestImpl implements AiFinanceRest {
@Resource
private AiFinanceService aiFinanceService;
@Override
public void call(AiFinanceDialoguesDto dto) throws Exception {
Assert.notNull(dto.getDialoguesId(), "对话id不能为空");
Assert.notNull(dto.getInput(), "问题不能为空");
UserBaseEntity userBaseEntity = BlContext.getCurrentUserNotException();
if (userBaseEntity == null) {
throw new BusinessException("用户未登录");
}
aiFinanceService.call(dto.getDialoguesId(), dto.getInput(), dto.getFileUrl(), dto.getKnowledgeIds(), dto.getDatabaseIds(), userBaseEntity.getUserId());
}
}
...@@ -13,6 +13,7 @@ import cn.com.poc.thirdparty.resource.demand.ai.function.long_document_reader.Lo ...@@ -13,6 +13,7 @@ import cn.com.poc.thirdparty.resource.demand.ai.function.long_document_reader.Lo
import cn.com.poc.thirdparty.resource.demand.ai.function.long_memory.SetLongMemoryFunction; import cn.com.poc.thirdparty.resource.demand.ai.function.long_memory.SetLongMemoryFunction;
import cn.com.poc.thirdparty.resource.demand.ai.function.memory_variable_writer.MemoryVariableWriterFunction; import cn.com.poc.thirdparty.resource.demand.ai.function.memory_variable_writer.MemoryVariableWriterFunction;
import cn.com.poc.thirdparty.resource.demand.ai.function.notification_reminder.NotificationReminderFunction; import cn.com.poc.thirdparty.resource.demand.ai.function.notification_reminder.NotificationReminderFunction;
import cn.com.poc.thirdparty.resource.demand.ai.function.oa_form.TravelFormV2Function;
import cn.com.poc.thirdparty.resource.demand.ai.function.text_in_pdf2md.PdfToMDFunction; import cn.com.poc.thirdparty.resource.demand.ai.function.text_in_pdf2md.PdfToMDFunction;
import cn.com.poc.thirdparty.resource.demand.ai.function.oa_form.TraveReimbursementlFormFunction; import cn.com.poc.thirdparty.resource.demand.ai.function.oa_form.TraveReimbursementlFormFunction;
import cn.com.poc.thirdparty.resource.demand.ai.function.oa_form.TravelFormFunction; import cn.com.poc.thirdparty.resource.demand.ai.function.oa_form.TravelFormFunction;
...@@ -55,6 +56,8 @@ public enum LargeModelFunctionEnum { ...@@ -55,6 +56,8 @@ public enum LargeModelFunctionEnum {
csv_data_analysis(CSVFunction.class), csv_data_analysis(CSVFunction.class),
completion_of_travel_v2_form(TravelFormV2Function.class),
; ;
private Class<? extends AbstractLargeModelFunction> function; private Class<? extends AbstractLargeModelFunction> function;
......
package cn.com.poc.thirdparty.resource.demand.ai.function.oa_form;
import cn.com.poc.agent_application.entity.KnowledgeContentResult;
import cn.com.poc.agent_application.entity.Variable;
import cn.com.poc.common.utils.DateUtils;
import cn.com.poc.common.utils.JsonUtils;
import cn.com.poc.common.utils.StringUtils;
import cn.com.poc.thirdparty.resource.demand.ai.entity.dbchain.DBChainResult;
import cn.com.poc.thirdparty.resource.demand.ai.entity.dialogue.Message;
import cn.com.poc.thirdparty.resource.demand.ai.entity.largemodel.LargeModelDemandResult;
import cn.com.poc.thirdparty.resource.demand.ai.entity.largemodel.LargeModelResponse;
import cn.com.poc.thirdparty.resource.demand.ai.function.AbstractFunctionResult;
import cn.com.poc.thirdparty.resource.demand.ai.function.AbstractLargeModelFunction;
import cn.com.poc.thirdparty.resource.demand.ai.function.entity.FunctionLLMConfig;
import cn.com.poc.thirdparty.resource.demand.ai.function.entity.Parameters;
import cn.com.poc.thirdparty.resource.demand.ai.function.entity.Properties;
import cn.com.poc.thirdparty.service.LLMService;
import cn.hutool.core.collection.ListUtil;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
/**
* @author alex.yao
* @date 2025/6/24
*/
@Component
public class TravelFormV2Function extends AbstractLargeModelFunction {
@Resource
private LLMService llmService;
private final String DESC = "该方法执行差旅表单申请";
// private final String MODEL = "ERNIE-3.5-8K-0205";
private final String MODEL = "qwen-long";
private final FunctionLLMConfig functionLLMConfig = new FunctionLLMConfig.FunctionLLMConfigBuilder()
.name("completion_of_travel_v2_form")
.parameters(new Parameters("object")
.addProperties("query", new Properties("string", "获取用户输入")))
.description(DESC)
.build();
private final String TEMPLATE = "# 角色:\n" +
"关键词提取助手\n" +
"\n" +
"## 工作规范:\n" +
"1.严格遵守提取规则:\n" +
"-仅从我给你的数据中提取关键词,不要添加任何外部信息\n" +
"\n" +
"2.精准匹配流程:\n" +
"①熟知以下字段名称:出差目的(purpose)(选择:客户拜访、会议、培训)、出差地点(place)、出发日期和时间(departureDate)、返回日期和时间(returnDate)、" +
"去路交通工具(vehicle)、去路预计交通费用(transportationFee)、回程交通工具(returnVehicle)、去路预计交通费用(returnTransportationFee)、" +
"住宿地点(accommodation)、预计住宿费用(accommodationCost)、预支金额(advancePaymentAmount)、总预算(totalBudget)\n" +
"②查询系统今天的日期,今天日期是:${nowday}" +
"③遍历我给你的数据,从数据中提取关键词,将与字段匹配的关键词填充到字段名称后,例如:我要到香港出差,则将“香港”提取出来填充到出差地点里。\n" +
"注意:\n" +
"(1)用户有提到如“今天、明天、后天、昨天”这类,你要把它们提取出来根据给的今天日期转换为具体日期和时间。例如:我今天要去香港出差五天,则你要提取出今天和五天,把今天和五天转换为具体日期,填充到出发日期和返回日期里\n" +
"(2)匹配要使用英文字段,不能用中文\n" +
"(3)数据中有明确与字段匹配的关键词再输出字段,否则不要输出多余字段,不要按照自己想法输出,尤其是出差目的\n" +
"(4)若数据中有出差目的匹配的关键词,要从三个选择中选择一个\n" +
"③使用JSON格式将有内容的字段名称输出\n" +
"\n" +
"3.输出示例:\n" +
"数据:我今天要到香港出差\n" +
"正确输出:\n" +
"{\"place\":\"香港\",\n" +
"\"departureDate\":\"2025-4-30 12:00:00\"}\n" +
"## 限制:\n" +
"-数据中有与字段匹配的关键词再输出字段,否则不要输出\n" +
"-输出必须用JSON格式,不能用其他格式输出\n" +
"-全程只能做关键词提取工作,不做其他工作\n" +
"-输出里不能有提示语任何相关内容\n" +
"\n" +
"## 输入数据:\n" +
"数据:【${content}】";
/**
* 执行函数
*
* @param content 入参
* @param identifier 唯一标识
* @param dbChainResults
* @param knowledgeContentResults
* @return
*/
@Override
public AbstractFunctionResult<String> doFunction(String content, String identifier, List<DBChainResult> dbChainResults, List<KnowledgeContentResult> knowledgeContentResults) {
// content = "我今天要去北京出差,主要是去客户那边交流一下";
AbstractFunctionResult<String> result = new AbstractFunctionResult<>();
if (StringUtils.isBlank(content)) {
result.setFunctionResult(StringUtils.EMPTY);
result.setPromptContent(StringUtils.EMPTY);
return result;
}
Message message = new Message();
message.setRole("user");
message.setContent(TEMPLATE.replace("${content}", content).replace("${nowday}", DateUtils.dateToString(new Date())));
LargeModelResponse largeModelResponse = new LargeModelResponse();
largeModelResponse.setModel(MODEL);
largeModelResponse.setMessages(new Message[]{message});
largeModelResponse.setStream(false);
largeModelResponse.setUser("travelFormFunction");
LargeModelDemandResult largeModelDemandResult = llmService.chat(largeModelResponse);
if (largeModelDemandResult == null) {
result.setFunctionResult(StringUtils.EMPTY);
result.setPromptContent(StringUtils.EMPTY);
return result;
}
String functionResult = largeModelDemandResult.getMessage().replace("`", "").replace("json", "").replace("\n", "");
functionResult = functionResult.replace("客户拜访", "CustomerVisits").replace("会议", " Meetings").replace("培训", "Training");
result.setFunctionResult(functionResult);
result.setPromptContent(functionResult);
return result;
}
@Override
public String getDesc() {
return DESC;
}
@Override
public List<String> getLLMConfig() {
return ListUtil.toList(JsonUtils.serialize(functionLLMConfig));
}
@Override
public List<String> getLLMConfig(List<Variable> variableStructure) {
return getLLMConfig();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment