Commit 3cbc33a7 authored by 徐健's avatar 徐健

看板ai优化

parent 28bcb45b
Pipeline #25194 passed with stages
in 2 minutes and 35 seconds
...@@ -18,14 +18,13 @@ import reactor.core.publisher.Flux; ...@@ -18,14 +18,13 @@ import reactor.core.publisher.Flux;
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/api/workbench/agent") @RequestMapping("/api/workbench/agent")
public class WorkbenchAgentChatController { public class WorkbenchAgentChatController {
private final CareAgentService careAgentService;
private final WorkbenchAIService workbenchAIService; private final WorkbenchAIService workbenchAIService;
/** /**
* 推荐场景的标准 GET 方式的流式聊天入口。 * 推荐场景的标准 GET 方式的流式聊天入口。
*/ */
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamCareAgent(@RequestParam("sessionId") String sessionId, public Flux<ServerSentEvent<String>> streamAgent(@RequestParam("sessionId") String sessionId,
@RequestParam("message") String message) { @RequestParam("message") String message) {
return streamWorkbenchAgentInternal(sessionId, message); return streamWorkbenchAgentInternal(sessionId, message);
} }
......
package com.infoepoch.pms.agent.domain.itworkbench; package com.infoepoch.pms.agent.domain.itworkbench;
import com.fasterxml.jackson.databind.JsonNode;
import com.infoepoch.pms.agent.common.utils.LogHelper; import com.infoepoch.pms.agent.common.utils.LogHelper;
import com.infoepoch.pms.agent.config.JsonUtils; import com.infoepoch.pms.agent.config.JsonUtils;
import com.infoepoch.pms.agent.domain.care.log.CareTraceLogSupport; import com.infoepoch.pms.agent.domain.care.log.CareTraceLogSupport;
import com.infoepoch.pms.agent.domain.itworkbench.enums.WorkbenchIntent; import com.infoepoch.pms.agent.domain.itworkbench.enums.WorkbenchIntent;
import com.infoepoch.pms.agent.domain.itworkbench.model.QueryDto;
import com.infoepoch.pms.agent.domain.itworkbench.model.WorkbenchConversationState; import com.infoepoch.pms.agent.domain.itworkbench.model.WorkbenchConversationState;
import com.infoepoch.pms.agent.domain.itworkbench.model.department.DepartmentWorkDto; import com.infoepoch.pms.agent.domain.itworkbench.model.department.DepartmentWorkDto;
import com.infoepoch.pms.agent.domain.itworkbench.model.kpi.AnnualKpiDto; import com.infoepoch.pms.agent.domain.itworkbench.model.kpi.AnnualKpiDto;
...@@ -12,11 +14,11 @@ import com.infoepoch.pms.agent.domain.itworkbench.remote.WorkbenchRemoteService; ...@@ -12,11 +14,11 @@ import com.infoepoch.pms.agent.domain.itworkbench.remote.WorkbenchRemoteService;
import com.infoepoch.pms.agent.domain.itworkbench.state.WorkbenchConversationStateService; import com.infoepoch.pms.agent.domain.itworkbench.state.WorkbenchConversationStateService;
import com.infoepoch.pms.agent.domain.itworkbench.understanding.WorkbenchIntentRecognizer; import com.infoepoch.pms.agent.domain.itworkbench.understanding.WorkbenchIntentRecognizer;
import com.infoepoch.pms.agent.domain.itworkbench.understanding.WorkbenchQueryUnderstandingService; import com.infoepoch.pms.agent.domain.itworkbench.understanding.WorkbenchQueryUnderstandingService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Service @Service
...@@ -49,55 +51,137 @@ public class WorkbenchAIService { ...@@ -49,55 +51,137 @@ public class WorkbenchAIService {
try { try {
// 1. 意图识别 // 1. 意图识别
WorkbenchIntent intent = intentRecognizer.recognize(traceId, sessionId, message, state); WorkbenchIntent intent = intentRecognizer.recognize(traceId, sessionId, message, state);
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "streamChat",
"消息=[" + message + "] intent=" + intent + " state.lastIntent=" + (state != null ? state.lastIntent() : "null")
));
// ====================== 【终极修复:只改这里】 ====================== // 2. 判断是否为全量查询(重置筛选条件)
// 判断:用户是不是要【重新查新业务】 boolean isFullDataQuery = isFullDataQuery(message);
boolean isNewQuery = isNewBusinessMessage(message);
// 如果是新查询 → 不传状态(清空上下文!!!) // 3. 判断是否为跨类型切换(新业务类型)
WorkbenchConversationState parseState = isNewQuery ? null : state; boolean isCrossTypeSwitch = state != null && state.lastIntent() != null && !state.lastIntent().equals(intent);
// ==================================================================
// 2. 查询数据 // 4. 判断是否包含业务类型关键词(重新查库)
Object data = null; boolean isReQueryWithKeyword = isReQueryWithKeyword(message, intent);
String dataType = ""; LogHelper.info(this, CareTraceLogSupport.format(
Map<String, Object> conditions = new HashMap<>(); traceId, sessionId, "streamChat",
"isFullDataQuery=" + isFullDataQuery + " isCrossTypeSwitch=" + isCrossTypeSwitch + " isReQueryWithKeyword=" + isReQueryWithKeyword
));
// 5. 多轮对话:同类型且无业务关键词时使用历史数据筛选,否则查库
if (state != null && !isCrossTypeSwitch && !isFullDataQuery && !isReQueryWithKeyword) {
WorkbenchConversationState.TypeData typeData = getTypeDataForIntent(state, intent);
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "streamChat",
"typeData=" + (typeData == null ? "null" : typeData.dataType())
));
if (typeData != null && typeData.hasData()) {
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "workbench_multi_turn", "使用上一轮数据筛选,类型:" + typeData.dataType()
));
return Flux.concat(
Flux.just("好的,我基于刚才的数据为你筛选...\n"),
parser.generateAnswerWithHistory(traceId, sessionId, typeData.dataJson(), typeData.dataType(), message),
Flux.just("\n以上就是筛选后的内容啦~")
)
.doOnComplete(()-> LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "workbench_chat_complete", "流式返回完成"
)))
.doOnError(err -> LogHelper.error(this, CareTraceLogSupport.format(
traceId, sessionId, "workbench_error", "异常:" + err.getMessage()
), err));
}
}
// 6. 查库获取数据
// 跨类型切换/全量查询/含业务关键词:传入 null 不继承筛选条件;否则传入 state 继承筛选条件
WorkbenchConversationState parseState = (isCrossTypeSwitch || isFullDataQuery || isReQueryWithKeyword) ? null : state;
Object data;
String dataType;
if (WorkbenchIntent.QUERY_DEPARTMENT_WORK.equals(intent)) { if (WorkbenchIntent.QUERY_DEPARTMENT_WORK.equals(intent)) {
// 这里传入 parseState,不是原来的 state!
DepartmentWorkDto dto = parser.understandWork(traceId, sessionId, message, parseState); DepartmentWorkDto dto = parser.understandWork(traceId, sessionId, message, parseState);
dto.setSessionId(sessionId); QueryDto queryDto = new QueryDto(dto.getWorkGroupContain(), dto.getManagerUser(), sessionId);
JsonNode jsonNode = remoteService.queryValidAuthority(queryDto);
if(jsonNode != null){
int code = jsonNode.path("code").asInt();
String msg = jsonNode.path("msg").asText();
if( code == HttpStatus.FORBIDDEN.value()) {
return Flux.just(msg);
}
}
dto.sessionId = sessionId;
data = remoteService.queryWork(dto); data = remoteService.queryWork(dto);
dataType = "重点工作"; dataType = "重点工作";
conditions = extractWorkConditions(dto);
} }
else if (WorkbenchIntent.QUERY_ANNUAL_KPI.equals(intent)) { else if (WorkbenchIntent.QUERY_ANNUAL_KPI.equals(intent)) {
AnnualKpiDto dto = parser.understandKpi(traceId, sessionId, message, parseState); AnnualKpiDto dto = parser.understandKpi(traceId, sessionId, message, parseState);
dto.setSessionId(sessionId); QueryDto queryDto = new QueryDto(dto.getWorkGroupContain(), dto.getManagerUser(), sessionId);
JsonNode jsonNode = remoteService.queryValidAuthority(queryDto);
if(jsonNode != null){
int code = jsonNode.path("code").asInt();
String msg = jsonNode.path("msg").asText();
if( code == HttpStatus.FORBIDDEN.value()) {
return Flux.just(msg);
}
}
dto.sessionId = sessionId;
data = remoteService.queryKpi(dto); data = remoteService.queryKpi(dto);
dataType = "部门 KPI"; dataType = "部门 KPI";
conditions = extractKpiConditions(dto);
} }
else if (WorkbenchIntent.QUERY_DEPARTMENT_MINUTES.equals(intent)) { else if (WorkbenchIntent.QUERY_DEPARTMENT_MINUTES.equals(intent)) {
DepartmentMinutesDto dto = parser.understandMinutes(traceId, sessionId, message, parseState); DepartmentMinutesDto dto = parser.understandMinutes(traceId, sessionId, message, parseState);
dto.setSessionId(sessionId); QueryDto queryDto = new QueryDto(dto.getWorkGroup(), dto.getManagerName(), sessionId);
JsonNode jsonNode = remoteService.queryValidAuthority(queryDto);
if(jsonNode != null){
int code = jsonNode.path("code").asInt();
String msg = jsonNode.path("msg").asText();
if( code == HttpStatus.FORBIDDEN.value()) {
return Flux.just(msg);
}
}
dto.sessionId = sessionId;
data = remoteService.queryMinutes(dto); data = remoteService.queryMinutes(dto);
dataType = "会议跟踪"; dataType = "会议跟踪";
conditions = extractMinutesConditions(dto);
} }
else { else {
return Flux.just("我是您的员工看板智能助手,目前只能帮您查询重点工作、KPI、会议纪要相关信息哦~"); return Flux.just("我是您的员工看板智能助手,目前只能支持查询重点工作、KPI、会议纪要相关信息哦~\n"
+"我可以例举几个问题提供帮助哦~\n"
+"1.查询本年度重点工作的详细信息\n"
+"2.查询本年度 KPI 的详细信息\n"
+"3.查询本年度会议纪要的详细信息");
} }
// ====================== 【保存状态:简单安全版】 ====================== // 6. 保存状态(包含数据 JSON)
// 新查询 → 全新创建
// 筛选 → 覆盖更新(你没有rewrite,就直接用initial,完全没问题)
conversationStateService.save(
WorkbenchConversationState.initial(sessionId, intent, conditions)
);
// ==================================================================
String dataJson = JsonUtils.objectToJson(data); String dataJson = JsonUtils.objectToJson(data);
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "save_state_before",
"准备保存状态,sessionId=" + sessionId + ", intent=" + intent + ", dataType=" + dataType + ", dataJson 长度=" + (dataJson != null ? dataJson.length() : 0)
));
if (state == null) {
// 首次查询,创建新状态
WorkbenchConversationState newState = WorkbenchConversationState.initial(sessionId, intent, Map.of(), dataType, dataJson);
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "save_state_new",
"创建新状态,newState.sessionId=" + newState.sessionId() + ", lastIntent=" + newState.lastIntent()
));
conversationStateService.save(newState);
} else {
// 更新对应类型的状态
WorkbenchConversationState newState = state.update(intent, Map.of());
// 更新当前类型的数据
newState.saveDataForType(dataType, intent, dataJson, Map.of());
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "save_state_update",
"更新状态,newState.sessionId=" + newState.sessionId() + ", lastIntent=" + newState.lastIntent()
));
conversationStateService.save(newState);
}
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "save_state_after",
"状态保存完成"
));
return Flux.concat( return Flux.concat(
Flux.just("我这就为你查询" + dataType + "信息,请稍候...\n"), Flux.just("我这就为你查询" + dataType + "信息,请稍候...\n"),
...@@ -119,51 +203,54 @@ public class WorkbenchAIService { ...@@ -119,51 +203,54 @@ public class WorkbenchAIService {
} }
} }
// ====================== 【工具方法:判断是否新查询】 ====================== /**
private boolean isNewBusinessMessage(String message) { * 根据意图获取对应的类型数据
if (!org.springframework.util.StringUtils.hasText(message)) return false; */
String m = message.toLowerCase(); private WorkbenchConversationState.TypeData getTypeDataForIntent(WorkbenchConversationState state, WorkbenchIntent intent) {
return m.contains("查") if (state == null) return null;
|| m.contains("kpi")
|| m.contains("绩效") Map<String, WorkbenchConversationState.TypeData> typeDataMap = state.getTypeDataMap();
|| m.contains("指标") if (typeDataMap == null || typeDataMap.isEmpty()) return null;
|| m.contains("工作")
|| m.contains("任务") // 按意图匹配数据类型
|| m.contains("会议") switch (intent) {
|| m.contains("纪要") case QUERY_DEPARTMENT_WORK:
|| m.contains("督办") return typeDataMap.get("重点工作");
|| m.contains("跟踪"); case QUERY_ANNUAL_KPI:
} return typeDataMap.get("部门 KPI");
private Map<String, Object> extractWorkConditions(DepartmentWorkDto dto) { case QUERY_DEPARTMENT_MINUTES:
Map<String, Object> conditions = new HashMap<>(); return typeDataMap.get("会议跟踪");
if (StringUtils.hasText(dto.getYear())) conditions.put("year", dto.getYear()); default:
if (StringUtils.hasText(dto.getMonth())) conditions.put("month", dto.getMonth()); return null;
if (dto.getLevelType() != null) conditions.put("levelType", dto.getLevelType()); }
if (StringUtils.hasText(dto.getCategoryContain())) conditions.put("categoryContain", dto.getCategoryContain());
if (StringUtils.hasText(dto.getWorkGroupContain())) conditions.put("workGroupContain", dto.getWorkGroupContain());
if (StringUtils.hasText(dto.getManagerUser())) conditions.put("managerUser", dto.getManagerUser());
return conditions;
} }
private Map<String, Object> extractKpiConditions(AnnualKpiDto dto) { // ====================== 【工具方法:判断是否全量查询】 ======================
Map<String, Object> conditions = new HashMap<>(); private boolean isFullDataQuery(String message) {
if (StringUtils.hasText(dto.getYear())) conditions.put("year", dto.getYear()); if (!org.springframework.util.StringUtils.hasText(message)) return false;
if (StringUtils.hasText(dto.getMonth())) conditions.put("month", dto.getMonth()); String m = message.toLowerCase();
if (dto.getLevelType() != null) conditions.put("levelType", dto.getLevelType()); return
if (StringUtils.hasText(dto.getCategoryContain())) conditions.put("categoryContain", dto.getCategoryContain()); m.contains("全量")
if (StringUtils.hasText(dto.getWorkGroupContain())) conditions.put("workGroupContain", dto.getWorkGroupContain()); || m.contains("全部")
if (StringUtils.hasText(dto.getManagerUser())) conditions.put("managerUser", dto.getManagerUser()); || m.contains("所有")
return conditions; || m.contains("重置")
|| m.contains("清除")
|| m.contains("不带筛选")
|| m.contains("无条件");
} }
private Map<String, Object> extractMinutesConditions(DepartmentMinutesDto dto) { // ====================== 【工具方法:判断是否包含业务类型关键词(重新查库)】 ======================
Map<String, Object> conditions = new HashMap<>(); private boolean isReQueryWithKeyword(String message, WorkbenchIntent intent) {
if (StringUtils.hasText(dto.getCurrentYear())) conditions.put("currentYear", dto.getCurrentYear()); if (!org.springframework.util.StringUtils.hasText(message)) return false;
if (StringUtils.hasText(dto.getMonth())) conditions.put("month", dto.getMonth()); String m = message.toLowerCase();
if (StringUtils.hasText(dto.getCategoryContain())) conditions.put("categoryContain", dto.getCategoryContain()); return switch (intent) {
if (StringUtils.hasText(dto.getDocumentNumber())) conditions.put("documentNumber", dto.getDocumentNumber()); case QUERY_ANNUAL_KPI ->
if (StringUtils.hasText(dto.getWorkGroup())) conditions.put("workGroup", dto.getWorkGroup()); m.contains("kpi") || m.contains("绩效") || m.contains("指标");
if (StringUtils.hasText(dto.getManagerName())) conditions.put("managerName", dto.getManagerName()); case QUERY_DEPARTMENT_WORK ->
return conditions; m.contains("重点") || m.contains("工作") || m.contains("任务");
case QUERY_DEPARTMENT_MINUTES ->
m.contains("会议") || m.contains("纪要") || m.contains("督办") || m.contains("跟踪");
default -> false;
};
} }
} }
...@@ -21,18 +21,21 @@ public class WorkbenchAgentConfiguration { ...@@ -21,18 +21,21 @@ public class WorkbenchAgentConfiguration {
"workbenchAgent", "workbenchAgent",
chatModel, chatModel,
""" """
你是员工看板智能体。 你是员工看板智能体,专注于基于业务数据提供准确、简洁的查询应答服务。
当用户进行通用对话时,保持员工看板智能体身份,自然回复用户。 核心数据结构说明:
1. 头表为部门(department),每个部门对应12个月的执行进度数据,存储于progress中;
回答要求: 2. progress为月度进度汇总数据,record为对应月份的责任人填报原始数据;
- 推荐类内容只能依据调用方提供的真实数据作答 3. 单月支持多人填报,系统自动将填报数据合并汇总至对应月份的progress中;
- 不能编造不存在的数据 4. 数据支持层级结构,children字段为当前数据的子节点集合。
- 必须使用中文回答
- 保持自然、友好、简洁 应答规则:
- 绝不能提到分页、页码、后台处理中、接口调用、候选集、模型或任何技术实现细节 - 严格依据调用方提供的真实数据作答,严禁编造、虚构数据;
- 当结果不足时,只输出实际合适的结果,并自然说明 - 数据为空时,统一友好提示“查询的数据为空”,不得输出技术类描述;
""" - 必须使用中文回复,语气自然、友好、简洁;
- 输出结果仅展示业务数据结论,不提及分页、接口、模型、后台处理等技术细节;
- 结果不足时,仅输出实际匹配内容并自然说明。
"""
); );
} }
} }
package com.infoepoch.pms.agent.domain.itworkbench.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 查询条件类
* 用于封装年度KPI查询所需的各种条件参数
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class QueryDto {
private String workGroup;
private String managerName;
private String sessionId;
}
...@@ -12,21 +12,50 @@ import java.util.Map; ...@@ -12,21 +12,50 @@ import java.util.Map;
*/ */
public record WorkbenchConversationState( public record WorkbenchConversationState(
String sessionId, String sessionId,
WorkbenchIntent lastIntent, WorkbenchIntent lastIntent,
Map<String, Object> conditions, Map<String, Object> conditions,
LocalDateTime updatedAt LocalDateTime updatedAt,
Map<String, TypeData> typeDataMap
) { ) {
public WorkbenchConversationState { public WorkbenchConversationState {
conditions = sanitize(conditions); conditions = sanitize(conditions);
typeDataMap = typeDataMap == null ? new LinkedHashMap<>() : new LinkedHashMap<>(typeDataMap);
updatedAt = updatedAt == null ? LocalDateTime.now() : updatedAt; updatedAt = updatedAt == null ? LocalDateTime.now() : updatedAt;
} }
public WorkbenchConversationState(String sessionId) {
this(sessionId, null, Collections.emptyMap(), LocalDateTime.now(), new LinkedHashMap<>());
}
public static WorkbenchConversationState initial(String sessionId, WorkbenchIntent intent, Map<String, Object> conditions) { public static WorkbenchConversationState initial(String sessionId, WorkbenchIntent intent, Map<String, Object> conditions) {
return new WorkbenchConversationState(sessionId, intent, conditions, LocalDateTime.now()); return new WorkbenchConversationState(sessionId, intent, conditions, LocalDateTime.now(), new LinkedHashMap<>());
}
public static WorkbenchConversationState initial(String sessionId, WorkbenchIntent intent, Map<String, Object> conditions, String dataType, String dataJson) {
WorkbenchConversationState state = new WorkbenchConversationState(sessionId, intent, conditions, LocalDateTime.now(), new LinkedHashMap<>());
state.getTypeDataMap().put(dataType, new TypeData(dataType, intent, dataJson, Collections.emptyMap()));
return state;
} }
public WorkbenchConversationState update(WorkbenchIntent intent, Map<String, Object> conditions) { public WorkbenchConversationState update(WorkbenchIntent intent, Map<String, Object> conditions) {
return new WorkbenchConversationState(sessionId, intent, conditions, LocalDateTime.now()); return new WorkbenchConversationState(sessionId, intent, conditions, LocalDateTime.now(), typeDataMap);
}
public Map<String, TypeData> getTypeDataMap() {
return typeDataMap;
}
public void saveDataForType(String dataType, WorkbenchIntent intent, String dataJson, Map<String, Object> conditions) {
typeDataMap.put(dataType, new TypeData(dataType, intent, dataJson, sanitize(conditions)));
}
public boolean hasDataForType(String dataType) {
TypeData typeData = typeDataMap.get(dataType);
return typeData != null && typeData.hasData();
}
public TypeData getDataForType(String dataType) {
return typeDataMap.get(dataType);
} }
private static Map<String, Object> sanitize(Map<String, Object> conditions) { private static Map<String, Object> sanitize(Map<String, Object> conditions) {
...@@ -41,4 +70,22 @@ public record WorkbenchConversationState( ...@@ -41,4 +70,22 @@ public record WorkbenchConversationState(
}); });
return Map.copyOf(sanitized); return Map.copyOf(sanitized);
} }
/**
* 类型数据记录
*/
public record TypeData(
String dataType,
WorkbenchIntent intent,
String dataJson,
Map<String, Object> conditions
) {
public TypeData {
conditions = conditions == null ? Collections.emptyMap() : Map.copyOf(conditions);
}
public boolean hasData() {
return dataJson != null && !dataJson.isBlank();
}
}
} }
...@@ -164,4 +164,6 @@ public class DepartmentWork { ...@@ -164,4 +164,6 @@ public class DepartmentWork {
private List<DepartmentWork> children; private List<DepartmentWork> children;
private List<DepartmentWorkProgress> monthChildren;
} }
...@@ -6,6 +6,7 @@ import lombok.NoArgsConstructor; ...@@ -6,6 +6,7 @@ import lombok.NoArgsConstructor;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 部门工作进度 * 部门工作进度
...@@ -117,4 +118,6 @@ public class DepartmentWorkProgress { ...@@ -117,4 +118,6 @@ public class DepartmentWorkProgress {
private Boolean submitFlag; private Boolean submitFlag;
private Integer lastReFillFlag; private Integer lastReFillFlag;
public List<ManagerMonthRecord> managerMonthRecordList;
} }
package com.infoepoch.pms.agent.domain.itworkbench.model.department;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
/**
* 责任人月工作记录表
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ManagerMonthRecord {
/**
* 主键
*/
private String id;
/**
* 月工作ID
*/
private String monthId;
/**
* 责任人ID
*/
private String managerId;
/**
* 责任人名称
*/
private String managerName;
/**
* 目标值
*/
private String target;
/**
* 实际值
*/
private String actual;
/**
* 遗留值
*/
private String remain;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 完成标识
*/
private Boolean finishFlag;
/**
* 截至目前完成进度
*/
private BigDecimal currentProgress;
/**
* 下月工作计划
*/
private String nextMonthGoal;
}
\ No newline at end of file
...@@ -170,4 +170,6 @@ public class AnnualKpi { ...@@ -170,4 +170,6 @@ public class AnnualKpi {
* 月实际值 * 月实际值
*/ */
private String actual; private String actual;
private List<AnnualKpiProgress> progressList;
} }
\ No newline at end of file
...@@ -87,4 +87,7 @@ public class DepartmentMinutesProgress { ...@@ -87,4 +87,7 @@ public class DepartmentMinutesProgress {
private String commentContent; private String commentContent;
List<ManagerMinutesRecord> managerMinutesRecords;
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; ...@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.infoepoch.pms.agent.common.utils.LogHelper; import com.infoepoch.pms.agent.common.utils.LogHelper;
import com.infoepoch.pms.agent.config.JsonUtils; import com.infoepoch.pms.agent.config.JsonUtils;
import com.infoepoch.pms.agent.domain.itworkbench.model.QueryDto;
import com.infoepoch.pms.agent.domain.itworkbench.model.department.DepartmentWork; import com.infoepoch.pms.agent.domain.itworkbench.model.department.DepartmentWork;
import com.infoepoch.pms.agent.domain.itworkbench.model.kpi.AnnualKpi; import com.infoepoch.pms.agent.domain.itworkbench.model.kpi.AnnualKpi;
import com.infoepoch.pms.agent.domain.itworkbench.model.kpi.AnnualKpiDto; import com.infoepoch.pms.agent.domain.itworkbench.model.kpi.AnnualKpiDto;
...@@ -21,6 +22,7 @@ import org.springframework.web.util.UriBuilder; ...@@ -21,6 +22,7 @@ import org.springframework.web.util.UriBuilder;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -175,4 +177,18 @@ public class WorkbenchRemoteService { ...@@ -175,4 +177,18 @@ public class WorkbenchRemoteService {
} }
return uriBuilder.build(); return uriBuilder.build();
} }
public JsonNode queryValidAuthority(QueryDto dto) {
try {
String url = properties.getAuthorityPath();
LogHelper.info(this, "业务接口地址: " + url);
JsonNode root = postForJson(url, null, dto);
if (!root.isObject()) {
throw new IllegalStateException("业务接口返回结构不正确: " + url);
}
return root;
} catch (Exception e) {
return null;
}
}
} }
\ No newline at end of file
...@@ -20,21 +20,17 @@ public class WorkbenchConversationStateService { ...@@ -20,21 +20,17 @@ public class WorkbenchConversationStateService {
private static final String KEY_PREFIX = "workbench:state:"; private static final String KEY_PREFIX = "workbench:state:";
private static final Duration TTL = Duration.ofMinutes(120); private static final Duration TTL = Duration.ofMinutes(120);
private final RedisTemplate<String, Object> redisTemplate; private final RedisTemplate<String, String> redisTemplate;
public Optional<WorkbenchConversationState> load(String sessionId) { public Optional<WorkbenchConversationState> load(String sessionId) {
if (!StringUtils.hasText(sessionId)) { if (!StringUtils.hasText(sessionId)) {
return Optional.empty(); return Optional.empty();
} }
Object cached = redisTemplate.opsForValue().get(KEY_PREFIX + sessionId); String json = redisTemplate.opsForValue().get(KEY_PREFIX + sessionId);
if (cached == null) { if (!StringUtils.hasText(json)) {
return Optional.empty(); return Optional.empty();
} }
try { try {
String json = JsonUtils.objectToJson(cached);
if (!StringUtils.hasText(json)) {
return Optional.empty();
}
return Optional.ofNullable(JsonUtils.jsonToObject(json, WorkbenchConversationState.class)); return Optional.ofNullable(JsonUtils.jsonToObject(json, WorkbenchConversationState.class));
} catch (Exception e) { } catch (Exception e) {
return Optional.empty(); return Optional.empty();
...@@ -45,6 +41,7 @@ public class WorkbenchConversationStateService { ...@@ -45,6 +41,7 @@ public class WorkbenchConversationStateService {
if (state == null || !StringUtils.hasText(state.sessionId())) { if (state == null || !StringUtils.hasText(state.sessionId())) {
return; return;
} }
redisTemplate.opsForValue().set(KEY_PREFIX + state.sessionId(), state, TTL); String json = JsonUtils.objectToJson(state);
redisTemplate.opsForValue().set(KEY_PREFIX + state.sessionId(), json, TTL);
} }
} }
...@@ -28,9 +28,6 @@ public class WorkbenchIntentRecognizer { ...@@ -28,9 +28,6 @@ public class WorkbenchIntentRecognizer {
this.observationSupport = observationSupport; this.observationSupport = observationSupport;
} }
public WorkbenchIntent recognize(String traceId, String sessionId, String message) {
return recognize(traceId, sessionId, message, null);
}
public WorkbenchIntent recognize(String traceId, String sessionId, String message, WorkbenchConversationState state) { public WorkbenchIntent recognize(String traceId, String sessionId, String message, WorkbenchConversationState state) {
String trimMsg = StringUtils.hasText(message) ? message.trim() : ""; String trimMsg = StringUtils.hasText(message) ? message.trim() : "";
...@@ -47,38 +44,48 @@ public class WorkbenchIntentRecognizer { ...@@ -47,38 +44,48 @@ public class WorkbenchIntentRecognizer {
return doClassify(traceId, sessionId, trimMsg); return doClassify(traceId, sessionId, trimMsg);
} }
// ====================== 只有【筛选词】才走继承 ====================== // ====================== 多轮对话:沿用上次意图 ======================
if (state != null && state.lastIntent() != null) { if (state != null && state.lastIntent() != null) {
WorkbenchIntent lastIntent = state.lastIntent(); WorkbenchIntent lastIntent = state.lastIntent();
if (!WorkbenchIntent.GENERAL_CHAT.equals(lastIntent)) { if (!WorkbenchIntent.GENERAL_CHAT.equals(lastIntent)) {
boolean isIrrelevant = isIrrelevantInput(trimMsg); LogHelper.info(this, CareTraceLogSupport.format(
if (!isIrrelevant) { traceId, sessionId, "意图识别 (多轮)",
LogHelper.info(this, CareTraceLogSupport.format( "消息=[" + trimMsg + "] isNewQuery=" + isNewQuery + " 沿用意图:" + lastIntent
traceId, sessionId, "意图识别(多轮)", "筛选条件,沿用意图:" + lastIntent ));
)); return lastIntent;
return lastIntent; } else {
} else { LogHelper.info(this, CareTraceLogSupport.format(
LogHelper.info(this, CareTraceLogSupport.format( traceId, sessionId, "意图识别 (多轮)",
traceId, sessionId, "意图识别(多轮)", "无关输入,不继承" "消息=[" + trimMsg + "] 上次是 GENERAL_CHAT,重新识别"
)); ));
return WorkbenchIntent.GENERAL_CHAT;
}
} }
} }
// 全新会话 // 全新会话 或 上次是 GENERAL_CHAT
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "意图识别",
"消息=[" + trimMsg + "] state=" + (state == null ? "null" : "exist") + " 重新识别"
));
return doClassify(traceId, sessionId, trimMsg); return doClassify(traceId, sessionId, trimMsg);
} }
/** /**
* 【关键判断】 * 【关键判断】
* 只要包含这些词 = 用户要查【新业务】,必须清空上下文! * 只要包含这些词 = 用户要查【新业务】,必须清空上下文!
* 排除全量查询关键词(全量/全部/所有/重置/清除)
*/ */
public boolean isNewBusinessQuery(String message) { public boolean isNewBusinessQuery(String message) {
if (!StringUtils.hasText(message)) return false; if (!StringUtils.hasText(message)) return false;
String m = message.toLowerCase(); String m = message.toLowerCase();
return m.contains("重点工作")
|| m.contains("kpi") // 如果包含全量查询关键词,不视为新业务(避免误判)
if (m.contains("全量") || m.contains("全部") || m.contains("所有")
|| m.contains("重置") || m.contains("清除")) {
return false;
}
return
m.contains("kpi")
|| m.contains("绩效") || m.contains("绩效")
|| m.contains("指标") || m.contains("指标")
|| m.contains("工作") || m.contains("工作")
...@@ -137,21 +144,4 @@ public class WorkbenchIntentRecognizer { ...@@ -137,21 +144,4 @@ public class WorkbenchIntentRecognizer {
return WorkbenchIntent.GENERAL_CHAT; return WorkbenchIntent.GENERAL_CHAT;
} }
} }
}
private boolean isIrrelevantInput(String message) {
if (!StringUtils.hasText(message)) return true;
boolean isPureNumber = message.matches("^\\d+$");
boolean isTooShort = message.length() <= 1;
boolean hasBizKeyword = message.contains("室")
|| message.contains("部门")
|| message.contains("完成")
|| message.contains("填报")
|| message.contains("年度")
|| message.contains("月度")
|| message.contains("重点")
|| message.contains("a")
|| message.contains("b")
|| message.contains("c");
return isPureNumber || isTooShort || !hasBizKeyword;
}
}
\ No newline at end of file
...@@ -50,13 +50,6 @@ public class WorkbenchQueryUnderstandingService { ...@@ -50,13 +50,6 @@ public class WorkbenchQueryUnderstandingService {
this.observationSupport = observationSupport; this.observationSupport = observationSupport;
} }
// ==============================
// 解析 重点工作 → DepartmentWorkDto
// ==============================
public DepartmentWorkDto understandWork(String traceId, String sessionId, String message) {
return understandWork(traceId, sessionId, message, null);
}
public DepartmentWorkDto understandWork(String traceId, String sessionId, String message, WorkbenchConversationState state) { public DepartmentWorkDto understandWork(String traceId, String sessionId, String message, WorkbenchConversationState state) {
LocalDate now = LocalDate.now(); LocalDate now = LocalDate.now();
int currentYear = now.getYear(); int currentYear = now.getYear();
...@@ -68,11 +61,11 @@ public class WorkbenchQueryUnderstandingService { ...@@ -68,11 +61,11 @@ public class WorkbenchQueryUnderstandingService {
你是员工看板重点工作解析器,只输出纯净 JSON,禁止输出任何多余内容! 你是员工看板重点工作解析器,只输出纯净 JSON,禁止输出任何多余内容!
【严格遵守以下规则,不准脑补、不准乱赋值】 【严格遵守以下规则,不准脑补、不准乱赋值】
1. year 和 month 必须有值,不能为 null 1. year 必须有值,不能为 null
- 未提时间 → 默认当前年 %d,当前月 %d - 未提时间 → 默认当前年 %d,当前月 %d
- 本月 → %d年%d月 - 本月 → %d年%d月
- 上月/上个月 → %d年%d月 - 上月/上个月 → %d年%d月
- 只提年份、没提月份或者只提月份没提年份 → 年份默认当前年 %d 月份默认当前月 %d - 只提月份没提年份 → 年份默认当前年 %d
2. levelType 严格规则: 2. levelType 严格规则:
- 明确说 A → 1 - 明确说 A → 1
...@@ -116,8 +109,7 @@ public class WorkbenchQueryUnderstandingService { ...@@ -116,8 +109,7 @@ public class WorkbenchQueryUnderstandingService {
currentYear, currentMonth, currentYear, currentMonth,
currentYear, currentMonth, currentYear, currentMonth,
lastMonthYear, lastMonth, lastMonthYear, lastMonth,
currentYear, currentMonth, currentYear, message
message
); );
AiObservationContext ctx = buildCtx(traceId, sessionId, "重点工作解析"); AiObservationContext ctx = buildCtx(traceId, sessionId, "重点工作解析");
...@@ -126,18 +118,9 @@ public class WorkbenchQueryUnderstandingService { ...@@ -126,18 +118,9 @@ public class WorkbenchQueryUnderstandingService {
DepartmentWorkDto dto = JsonUtils.jsonToObject(clean, DepartmentWorkDto.class); DepartmentWorkDto dto = JsonUtils.jsonToObject(clean, DepartmentWorkDto.class);
if (dto == null) dto = new DepartmentWorkDto(); if (dto == null) dto = new DepartmentWorkDto();
if (state != null && !state.conditions().isEmpty()) {
mergeWorkDtoConditions(dto, state.conditions());
}
return dto; return dto;
} }
// ==============================
// 解析 KPI → AnnualKpiDto
// ==============================
public AnnualKpiDto understandKpi(String traceId, String sessionId, String message) {
return understandKpi(traceId, sessionId, message, null);
}
public AnnualKpiDto understandKpi(String traceId, String sessionId, String message, WorkbenchConversationState state) { public AnnualKpiDto understandKpi(String traceId, String sessionId, String message, WorkbenchConversationState state) {
LocalDate now = LocalDate.now(); LocalDate now = LocalDate.now();
...@@ -150,11 +133,11 @@ public class WorkbenchQueryUnderstandingService { ...@@ -150,11 +133,11 @@ public class WorkbenchQueryUnderstandingService {
你是员工看板 Kpi 解析器,只输出纯净 JSON,禁止输出任何多余内容! 你是员工看板 Kpi 解析器,只输出纯净 JSON,禁止输出任何多余内容!
【严格遵守以下规则,不准脑补、不准乱赋值】 【严格遵守以下规则,不准脑补、不准乱赋值】
1. year 和 month 必须有值,不能为 null 1. year必须有值,不能为 null
- 未提时间 → 默认当前年 %d,当前月 %d - 未提时间 → 默认当前年 %d,当前月 %d
- 本月 → %d年%d月 - 本月 → %d年%d月
- 上月/上个月 → %d年%d月 - 上月/上个月 → %d年%d月
- 只提年份、没提月份或者只提月份没提年份 → 年份默认当前年 %d 月份默认当前月 %d - 只提月份没提年份 → 年份默认当前年 %d
2. levelType 严格规则: 2. levelType 严格规则:
- 明确说 A → 1 - 明确说 A → 1
...@@ -192,7 +175,7 @@ public class WorkbenchQueryUnderstandingService { ...@@ -192,7 +175,7 @@ public class WorkbenchQueryUnderstandingService {
currentYear, currentMonth, currentYear, currentMonth,
currentYear, currentMonth, currentYear, currentMonth,
lastMonthYear, lastMonth, lastMonthYear, lastMonth,
currentYear, currentMonth, currentYear,
message message
); );
...@@ -202,18 +185,9 @@ public class WorkbenchQueryUnderstandingService { ...@@ -202,18 +185,9 @@ public class WorkbenchQueryUnderstandingService {
AnnualKpiDto dto = JsonUtils.jsonToObject(clean, AnnualKpiDto.class); AnnualKpiDto dto = JsonUtils.jsonToObject(clean, AnnualKpiDto.class);
if (dto == null) dto = new AnnualKpiDto(); if (dto == null) dto = new AnnualKpiDto();
if (state != null && !state.conditions().isEmpty()) {
mergeKpiDtoConditions(dto, state.conditions());
}
return dto; return dto;
} }
// ==============================
// 解析 会议纪要 → DepartmentMinutesDto
// ==============================
public DepartmentMinutesDto understandMinutes(String traceId, String sessionId, String message) {
return understandMinutes(traceId, sessionId, message, null);
}
public DepartmentMinutesDto understandMinutes(String traceId, String sessionId, String message, WorkbenchConversationState state) { public DepartmentMinutesDto understandMinutes(String traceId, String sessionId, String message, WorkbenchConversationState state) {
LocalDate now = LocalDate.now(); LocalDate now = LocalDate.now();
...@@ -226,11 +200,11 @@ public class WorkbenchQueryUnderstandingService { ...@@ -226,11 +200,11 @@ public class WorkbenchQueryUnderstandingService {
你是员工看板会议纪要解析器,只输出纯净 JSON,禁止输出任何多余内容! 你是员工看板会议纪要解析器,只输出纯净 JSON,禁止输出任何多余内容!
【严格遵守以下规则,不准脑补、不准乱赋值】 【严格遵守以下规则,不准脑补、不准乱赋值】
1. currentYear 和 month 必须有值,不能为 null 1. currentYear必须有值,不能为 null
- 未提时间 → 默认当前年 %d,当前月 %d - 未提时间 → 默认当前年 %d,当前月 %d
- 本月 → %d年%d月 - 本月 → %d年%d月
- 上月/上个月 → %d年%d月 - 上月/上个月 → %d年%d月
- 只提年份、没提月份或者只提月份没提年份 → 年份默认当前年 %d 月份默认当前月 %d - 只提月份没提年份 → 年份默认当前年 %d
2. categoryContain: 2. categoryContain:
只有用户明确说【会议名称为 XXX】才赋值,否则必须为 null 只有用户明确说【会议名称为 XXX】才赋值,否则必须为 null
...@@ -272,8 +246,7 @@ public class WorkbenchQueryUnderstandingService { ...@@ -272,8 +246,7 @@ public class WorkbenchQueryUnderstandingService {
currentYear, currentMonth, currentYear, currentMonth,
currentYear, currentMonth, currentYear, currentMonth,
lastMonthYear, lastMonth, lastMonthYear, lastMonth,
currentYear, currentMonth, currentYear, message
message
); );
AiObservationContext ctx = buildCtx(traceId, sessionId, "会议纪要解析"); AiObservationContext ctx = buildCtx(traceId, sessionId, "会议纪要解析");
...@@ -282,79 +255,9 @@ public class WorkbenchQueryUnderstandingService { ...@@ -282,79 +255,9 @@ public class WorkbenchQueryUnderstandingService {
DepartmentMinutesDto dto = JsonUtils.jsonToObject(clean, DepartmentMinutesDto.class); DepartmentMinutesDto dto = JsonUtils.jsonToObject(clean, DepartmentMinutesDto.class);
if (dto == null) dto = new DepartmentMinutesDto(); if (dto == null) dto = new DepartmentMinutesDto();
if (state != null && !state.conditions().isEmpty()) {
mergeMinutesDtoConditions(dto, state.conditions());
}
return dto; return dto;
} }
// ==============================
// 条件合并方法
// ==============================
private void mergeWorkDtoConditions(DepartmentWorkDto dto, Map<String, Object> history) {
if (dto.getYear() == null && history.containsKey("year")) {
dto.setYear(String.valueOf(history.get("year")));
}
if (dto.getMonth() == null && history.containsKey("month")) {
dto.setMonth(String.valueOf(history.get("month")));
}
if (dto.getLevelType() == null && history.containsKey("levelType")) {
dto.setLevelType((Integer) history.get("levelType"));
}
if (dto.getCategoryContain() == null && history.containsKey("categoryContain")) {
dto.setCategoryContain((String) history.get("categoryContain"));
}
if (dto.getManagerUser() == null && history.containsKey("managerUser")) {
dto.setManagerUser((String) history.get("managerUser"));
}
if (dto.getWorkGroupContain() == null && history.containsKey("workGroupContain")) {
dto.setWorkGroupContain((String) history.get("workGroupContain"));
}
}
private void mergeKpiDtoConditions(AnnualKpiDto dto, Map<String, Object> history) {
if (dto.getYear() == null && history.containsKey("year")) {
dto.setYear(String.valueOf(history.get("year")));
}
if (dto.getMonth() == null && history.containsKey("month")) {
dto.setMonth(String.valueOf(history.get("month")));
}
if (dto.getLevelType() == null && history.containsKey("levelType")) {
dto.setLevelType((Integer) history.get("levelType"));
}
if (dto.getCategoryContain() == null && history.containsKey("categoryContain")) {
dto.setCategoryContain((String) history.get("categoryContain"));
}
if (dto.getManagerUser() == null && history.containsKey("managerUser")) {
dto.setManagerUser((String) history.get("managerUser"));
}
if (dto.getWorkGroupContain() == null && history.containsKey("workGroupContain")) {
dto.setWorkGroupContain((String) history.get("workGroupContain"));
}
}
private void mergeMinutesDtoConditions(DepartmentMinutesDto dto, Map<String, Object> history) {
if (dto.getCurrentYear() == null && history.containsKey("currentYear")) {
dto.setCurrentYear(String.valueOf(history.get("currentYear")));
}
if (dto.getMonth() == null && history.containsKey("month")) {
dto.setMonth(String.valueOf(history.get("month")));
}
if (dto.getCategoryContain() == null && history.containsKey("categoryContain")) {
dto.setCategoryContain((String) history.get("categoryContain"));
}
if (dto.getDocumentNumber() == null && history.containsKey("documentNumber")) {
dto.setDocumentNumber((String) history.get("documentNumber"));
}
if (dto.getManagerName() == null && history.containsKey("managerName")) {
dto.setManagerName((String) history.get("managerName"));
}
if (dto.getWorkGroup() == null && history.containsKey("workGroup")) {
dto.setWorkGroup((String) history.get("workGroup"));
}
}
public Flux<String> generateAnswer(String traceId, String sessionId, String data) { public Flux<String> generateAnswer(String traceId, String sessionId, String data) {
try { try {
RunnableConfig runnableConfig = buildRunnableConfig(traceId, sessionId, "员工看板数据查询"); RunnableConfig runnableConfig = buildRunnableConfig(traceId, sessionId, "员工看板数据查询");
...@@ -395,6 +298,67 @@ public class WorkbenchQueryUnderstandingService { ...@@ -395,6 +298,67 @@ public class WorkbenchQueryUnderstandingService {
} }
} }
/**
* 基于上一轮历史数据进行多轮筛选(不查库)
* @param traceId 追踪 ID
* @param sessionId 会话 ID
* @param historyDataJson 上一轮查询的原始数据 JSON
* @param dataType 数据类型(重点工作/部门 KPI/会议跟踪)
* @param newQuestion 用户新的筛选问题
* @return 流式响应
*/
public Flux<String> generateAnswerWithHistory(String traceId, String sessionId, String historyDataJson, String dataType, String newQuestion) {
try {
String prompt = """
你是员工看板数据助手。用户之前查询了【%s】,现在基于原有数据提出新的筛选要求:【%s
原始数据如下:
%s
请根据用户的新要求,从原始数据中筛选出符合条件的内容,并以自然语言展示。
如果无法筛选出结果,请友好地告知用户。
只输出结果,不要输出思考过程。
""".formatted(dataType, newQuestion, historyDataJson);
RunnableConfig runnableConfig = buildRunnableConfig(traceId, sessionId, "员工看板多轮筛选");
AtomicInteger chunkIndex = new AtomicInteger(0);
return careAgent.stream(prompt, runnableConfig)
.filter(StreamingOutput.class::isInstance)
.map(StreamingOutput.class::cast)
.filter(output -> output.getOutputType() == OutputType.AGENT_MODEL_STREAMING)
.map(StreamingOutput::message)
.filter(AssistantMessage.class::isInstance)
.map(AssistantMessage.class::cast)
.map(AbstractMessage::getText)
.filter(StringUtils::hasText)
.doOnNext(chunk -> chunkIndex.incrementAndGet())
.switchIfEmpty(Flux.defer(() -> {
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "workbench_multi_turn_fallback", "模型未返回内容"
));
return Flux.just("筛选完成,但未生成有效回答。");
}))
.doOnComplete(() -> {
LogHelper.info(this, CareTraceLogSupport.format(
traceId, sessionId, "workbench_multi_turn_complete", "多轮筛选完成,chunk =" + chunkIndex.get()
));
})
.doOnError(error -> LogHelper.error(this, CareTraceLogSupport.format(
traceId, sessionId, "workbench_multi_turn_error",
"多轮筛选异常:" + error.getMessage() + "chunk =" + chunkIndex.get()
), error));
} catch (Exception e) {
LogHelper.error(this, CareTraceLogSupport.format(
traceId,
sessionId,
"多轮筛选异常",
"多轮筛选失败,异常=" + CareTraceLogSupport.safeText(e.getMessage())
), e);
return Flux.error(new IllegalArgumentException("多轮筛选失败"));
}
}
private RunnableConfig buildRunnableConfig(String traceId, String sessionId, String scene) { private RunnableConfig buildRunnableConfig(String traceId, String sessionId, String scene) {
AiObservationContext currentContext = observationSupport.currentContext(); AiObservationContext currentContext = observationSupport.currentContext();
String requestId = StringUtils.hasText(currentContext.requestId()) ? currentContext.requestId() : traceId; String requestId = StringUtils.hasText(currentContext.requestId()) ? currentContext.requestId() : traceId;
......
...@@ -28,7 +28,7 @@ public class AiModelInvoker { ...@@ -28,7 +28,7 @@ public class AiModelInvoker {
buildRequestFields(prompt, context) buildRequestFields(prompt, context)
); );
try { try {
String response = support.withObservationContext(context, () -> chatModel.call(prompt)); String response = support.withObservationContext(context, () -> chatModel.call(prompt));
String responseStage = AiReadableLogSupport.stageWithScene(context, "模型完整返回"); String responseStage = AiReadableLogSupport.stageWithScene(context, "模型完整返回");
logger.info( logger.info(
AiModelInvoker.class, AiModelInvoker.class,
......
...@@ -15,4 +15,6 @@ public class ITProperties { ...@@ -15,4 +15,6 @@ public class ITProperties {
private String minutesPath; private String minutesPath;
private String workPath; private String workPath;
private String authorityPath;
} }
...@@ -62,6 +62,7 @@ pms: ...@@ -62,6 +62,7 @@ pms:
kpi-path: /it-workbench/api/functionCallTools/queryKpiList kpi-path: /it-workbench/api/functionCallTools/queryKpiList
work-path: /it-workbench/api/functionCallTools/queryWorkList work-path: /it-workbench/api/functionCallTools/queryWorkList
minutes-path: /it-workbench/api/functionCallTools/queryMinutesList minutes-path: /it-workbench/api/functionCallTools/queryMinutesList
authority-path: /it-workbench/api/functionCallTools/queryValidAuthority
sso: sso:
cookie-name: pms_agent_sso_token cookie-name: pms_agent_sso_token
session-cookie-name: pms_agent_session session-cookie-name: pms_agent_session
......
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