Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
pms-agent
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
姜耀祖
pms-agent
Commits
3cbc33a7
Commit
3cbc33a7
authored
Apr 29, 2026
by
徐健
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
看板ai优化
parent
28bcb45b
Pipeline
#25194
passed with stages
in 2 minutes and 35 seconds
Changes
17
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
444 additions
and
239 deletions
+444
-239
WorkbenchAgentChatController.java
...ch/pms/agent/controller/WorkbenchAgentChatController.java
+1
-2
WorkbenchAIService.java
...poch/pms/agent/domain/itworkbench/WorkbenchAIService.java
+156
-69
WorkbenchAgentConfiguration.java
...main/itworkbench/aiModel/WorkbenchAgentConfiguration.java
+15
-12
QueryDto.java
...nfoepoch/pms/agent/domain/itworkbench/model/QueryDto.java
+18
-0
WorkbenchConversationState.java
.../domain/itworkbench/model/WorkbenchConversationState.java
+51
-4
DepartmentWork.java
...t/domain/itworkbench/model/department/DepartmentWork.java
+2
-0
DepartmentWorkProgress.java
.../itworkbench/model/department/DepartmentWorkProgress.java
+3
-0
ManagerMonthRecord.java
...main/itworkbench/model/department/ManagerMonthRecord.java
+71
-0
AnnualKpi.java
...och/pms/agent/domain/itworkbench/model/kpi/AnnualKpi.java
+2
-0
DepartmentMinutesProgress.java
.../itworkbench/model/minutes/DepartmentMinutesProgress.java
+3
-0
WorkbenchRemoteService.java
...ent/domain/itworkbench/remote/WorkbenchRemoteService.java
+16
-0
WorkbenchConversationStateService.java
.../itworkbench/state/WorkbenchConversationStateService.java
+5
-8
WorkbenchIntentRecognizer.java
.../itworkbench/understanding/WorkbenchIntentRecognizer.java
+27
-37
WorkbenchQueryUnderstandingService.java
...nch/understanding/WorkbenchQueryUnderstandingService.java
+70
-106
AiModelInvoker.java
.../infoepoch/pms/agent/observability/ai/AiModelInvoker.java
+1
-1
ITProperties.java
...java/com/infoepoch/pms/agent/properties/ITProperties.java
+2
-0
application.yml
src/main/resources/application.yml
+1
-0
No files found.
src/main/java/com/infoepoch/pms/agent/controller/WorkbenchAgentChatController.java
View file @
3cbc33a7
...
...
@@ -18,14 +18,13 @@ import reactor.core.publisher.Flux;
@RequiredArgsConstructor
@RequestMapping
(
"/api/workbench/agent"
)
public
class
WorkbenchAgentChatController
{
private
final
CareAgentService
careAgentService
;
private
final
WorkbenchAIService
workbenchAIService
;
/**
* 推荐场景的标准 GET 方式的流式聊天入口。
*/
@GetMapping
(
value
=
"/stream"
,
produces
=
MediaType
.
TEXT_EVENT_STREAM_VALUE
)
public
Flux
<
ServerSentEvent
<
String
>>
stream
Care
Agent
(
@RequestParam
(
"sessionId"
)
String
sessionId
,
public
Flux
<
ServerSentEvent
<
String
>>
streamAgent
(
@RequestParam
(
"sessionId"
)
String
sessionId
,
@RequestParam
(
"message"
)
String
message
)
{
return
streamWorkbenchAgentInternal
(
sessionId
,
message
);
}
...
...
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/WorkbenchAIService.java
View file @
3cbc33a7
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.config.JsonUtils
;
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.model.QueryDto
;
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.kpi.AnnualKpiDto
;
...
...
@@ -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.understanding.WorkbenchIntentRecognizer
;
import
com.infoepoch.pms.agent.domain.itworkbench.understanding.WorkbenchQueryUnderstandingService
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.StringUtils
;
import
reactor.core.publisher.Flux
;
import
java.util.HashMap
;
import
java.util.Map
;
@Service
...
...
@@ -49,55 +51,137 @@ public class WorkbenchAIService {
try
{
// 1. 意图识别
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"
)
));
// ====================== 【终极修复:只改这里】 ======================
// 判断:用户是不是要【重新查新业务】
boolean
isNewQuery
=
isNewBusinessMessage
(
message
);
// 2. 判断是否为全量查询(重置筛选条件)
boolean
isFullDataQuery
=
isFullDataQuery
(
message
);
// 如果是新查询 → 不传状态(清空上下文!!!)
WorkbenchConversationState
parseState
=
isNewQuery
?
null
:
state
;
// ==================================================================
// 3. 判断是否为跨类型切换(新业务类型)
boolean
isCrossTypeSwitch
=
state
!=
null
&&
state
.
lastIntent
()
!=
null
&&
!
state
.
lastIntent
().
equals
(
intent
);
// 2. 查询数据
Object
data
=
null
;
String
dataType
=
""
;
Map
<
String
,
Object
>
conditions
=
new
HashMap
<>();
// 4. 判断是否包含业务类型关键词(重新查库)
boolean
isReQueryWithKeyword
=
isReQueryWithKeyword
(
message
,
intent
);
LogHelper
.
info
(
this
,
CareTraceLogSupport
.
format
(
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
))
{
// 这里传入 parseState,不是原来的 state!
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
);
dataType
=
"重点工作"
;
conditions
=
extractWorkConditions
(
dto
);
}
else
if
(
WorkbenchIntent
.
QUERY_ANNUAL_KPI
.
equals
(
intent
))
{
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
);
dataType
=
"部门 KPI"
;
conditions
=
extractKpiConditions
(
dto
);
}
else
if
(
WorkbenchIntent
.
QUERY_DEPARTMENT_MINUTES
.
equals
(
intent
))
{
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
);
dataType
=
"会议跟踪"
;
conditions
=
extractMinutesConditions
(
dto
);
}
else
{
return
Flux
.
just
(
"我是您的员工看板智能助手,目前只能帮您查询重点工作、KPI、会议纪要相关信息哦~"
);
return
Flux
.
just
(
"我是您的员工看板智能助手,目前只能支持查询重点工作、KPI、会议纪要相关信息哦~\n"
+
"我可以例举几个问题提供帮助哦~\n"
+
"1.查询本年度重点工作的详细信息\n"
+
"2.查询本年度 KPI 的详细信息\n"
+
"3.查询本年度会议纪要的详细信息"
);
}
// ====================== 【保存状态:简单安全版】 ======================
// 新查询 → 全新创建
// 筛选 → 覆盖更新(你没有rewrite,就直接用initial,完全没问题)
conversationStateService
.
save
(
WorkbenchConversationState
.
initial
(
sessionId
,
intent
,
conditions
)
);
// ==================================================================
// 6. 保存状态(包含数据 JSON)
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
(
Flux
.
just
(
"我这就为你查询"
+
dataType
+
"信息,请稍候...\n"
),
...
...
@@ -119,51 +203,54 @@ public class WorkbenchAIService {
}
}
// ====================== 【工具方法:判断是否新查询】 ======================
private
boolean
isNewBusinessMessage
(
String
message
)
{
/**
* 根据意图获取对应的类型数据
*/
private
WorkbenchConversationState
.
TypeData
getTypeDataForIntent
(
WorkbenchConversationState
state
,
WorkbenchIntent
intent
)
{
if
(
state
==
null
)
return
null
;
Map
<
String
,
WorkbenchConversationState
.
TypeData
>
typeDataMap
=
state
.
getTypeDataMap
();
if
(
typeDataMap
==
null
||
typeDataMap
.
isEmpty
())
return
null
;
// 按意图匹配数据类型
switch
(
intent
)
{
case
QUERY_DEPARTMENT_WORK:
return
typeDataMap
.
get
(
"重点工作"
);
case
QUERY_ANNUAL_KPI:
return
typeDataMap
.
get
(
"部门 KPI"
);
case
QUERY_DEPARTMENT_MINUTES:
return
typeDataMap
.
get
(
"会议跟踪"
);
default
:
return
null
;
}
}
// ====================== 【工具方法:判断是否全量查询】 ======================
private
boolean
isFullDataQuery
(
String
message
)
{
if
(!
org
.
springframework
.
util
.
StringUtils
.
hasText
(
message
))
return
false
;
String
m
=
message
.
toLowerCase
();
return
m
.
contains
(
"全量"
)
||
m
.
contains
(
"全部"
)
||
m
.
contains
(
"所有"
)
||
m
.
contains
(
"重置"
)
||
m
.
contains
(
"清除"
)
||
m
.
contains
(
"不带筛选"
)
||
m
.
contains
(
"无条件"
);
}
// ====================== 【工具方法:判断是否包含业务类型关键词(重新查库)】 ======================
private
boolean
isReQueryWithKeyword
(
String
message
,
WorkbenchIntent
intent
)
{
if
(!
org
.
springframework
.
util
.
StringUtils
.
hasText
(
message
))
return
false
;
String
m
=
message
.
toLowerCase
();
return
m
.
contains
(
"查"
)
||
m
.
contains
(
"kpi"
)
||
m
.
contains
(
"绩效"
)
||
m
.
contains
(
"指标"
)
||
m
.
contains
(
"工作"
)
||
m
.
contains
(
"任务"
)
||
m
.
contains
(
"会议"
)
||
m
.
contains
(
"纪要"
)
||
m
.
contains
(
"督办"
)
||
m
.
contains
(
"跟踪"
);
}
private
Map
<
String
,
Object
>
extractWorkConditions
(
DepartmentWorkDto
dto
)
{
Map
<
String
,
Object
>
conditions
=
new
HashMap
<>();
if
(
StringUtils
.
hasText
(
dto
.
getYear
()))
conditions
.
put
(
"year"
,
dto
.
getYear
());
if
(
StringUtils
.
hasText
(
dto
.
getMonth
()))
conditions
.
put
(
"month"
,
dto
.
getMonth
());
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
<>();
if
(
StringUtils
.
hasText
(
dto
.
getYear
()))
conditions
.
put
(
"year"
,
dto
.
getYear
());
if
(
StringUtils
.
hasText
(
dto
.
getMonth
()))
conditions
.
put
(
"month"
,
dto
.
getMonth
());
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
>
extractMinutesConditions
(
DepartmentMinutesDto
dto
)
{
Map
<
String
,
Object
>
conditions
=
new
HashMap
<>();
if
(
StringUtils
.
hasText
(
dto
.
getCurrentYear
()))
conditions
.
put
(
"currentYear"
,
dto
.
getCurrentYear
());
if
(
StringUtils
.
hasText
(
dto
.
getMonth
()))
conditions
.
put
(
"month"
,
dto
.
getMonth
());
if
(
StringUtils
.
hasText
(
dto
.
getCategoryContain
()))
conditions
.
put
(
"categoryContain"
,
dto
.
getCategoryContain
());
if
(
StringUtils
.
hasText
(
dto
.
getDocumentNumber
()))
conditions
.
put
(
"documentNumber"
,
dto
.
getDocumentNumber
());
if
(
StringUtils
.
hasText
(
dto
.
getWorkGroup
()))
conditions
.
put
(
"workGroup"
,
dto
.
getWorkGroup
());
if
(
StringUtils
.
hasText
(
dto
.
getManagerName
()))
conditions
.
put
(
"managerName"
,
dto
.
getManagerName
());
return
conditions
;
return
switch
(
intent
)
{
case
QUERY_ANNUAL_KPI
->
m
.
contains
(
"kpi"
)
||
m
.
contains
(
"绩效"
)
||
m
.
contains
(
"指标"
);
case
QUERY_DEPARTMENT_WORK
->
m
.
contains
(
"重点"
)
||
m
.
contains
(
"工作"
)
||
m
.
contains
(
"任务"
);
case
QUERY_DEPARTMENT_MINUTES
->
m
.
contains
(
"会议"
)
||
m
.
contains
(
"纪要"
)
||
m
.
contains
(
"督办"
)
||
m
.
contains
(
"跟踪"
);
default
->
false
;
};
}
}
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/aiModel/WorkbenchAgentConfiguration.java
View file @
3cbc33a7
...
...
@@ -21,17 +21,20 @@ public class WorkbenchAgentConfiguration {
"workbenchAgent"
,
chatModel
,
"""
你是员工看板智能体
。
你是员工看板智能体,专注于基于业务数据提供准确、简洁的查询应答服务
。
当用户进行通用对话时,保持员工看板智能体身份,自然回复用户。
核心数据结构说明:
1. 头表为部门(department),每个部门对应12个月的执行进度数据,存储于progress中;
2. progress为月度进度汇总数据,record为对应月份的责任人填报原始数据;
3. 单月支持多人填报,系统自动将填报数据合并汇总至对应月份的progress中;
4. 数据支持层级结构,children字段为当前数据的子节点集合。
回答要求:
- 推荐类内容只能依据调用方提供的真实数据作答
- 不能编造不存在的数据
- 必须使用中文回答
- 保持自然、友好、简洁
- 绝不能提到分页、页码、后台处理中、接口调用、候选集、模型或任何技术实现细节
- 当结果不足时,只输出实际合适的结果,并自然说明
应答规则:
- 严格依据调用方提供的真实数据作答,严禁编造、虚构数据;
- 数据为空时,统一友好提示“查询的数据为空”,不得输出技术类描述;
- 必须使用中文回复,语气自然、友好、简洁;
- 输出结果仅展示业务数据结论,不提及分页、接口、模型、后台处理等技术细节;
- 结果不足时,仅输出实际匹配内容并自然说明。
"""
);
}
...
...
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/model/QueryDto.java
0 → 100644
View file @
3cbc33a7
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
;
}
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/model/WorkbenchConversationState.java
View file @
3cbc33a7
...
...
@@ -14,19 +14,48 @@ public record WorkbenchConversationState(
String
sessionId
,
WorkbenchIntent
lastIntent
,
Map
<
String
,
Object
>
conditions
,
LocalDateTime
updatedAt
LocalDateTime
updatedAt
,
Map
<
String
,
TypeData
>
typeDataMap
)
{
public
WorkbenchConversationState
{
conditions
=
sanitize
(
conditions
);
typeDataMap
=
typeDataMap
==
null
?
new
LinkedHashMap
<>()
:
new
LinkedHashMap
<>(
typeDataMap
);
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
)
{
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
)
{
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
)
{
...
...
@@ -41,4 +70,22 @@ public record WorkbenchConversationState(
});
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
();
}
}
}
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/model/department/DepartmentWork.java
View file @
3cbc33a7
...
...
@@ -164,4 +164,6 @@ public class DepartmentWork {
private
List
<
DepartmentWork
>
children
;
private
List
<
DepartmentWorkProgress
>
monthChildren
;
}
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/model/department/DepartmentWorkProgress.java
View file @
3cbc33a7
...
...
@@ -6,6 +6,7 @@ import lombok.NoArgsConstructor;
import
java.math.BigDecimal
;
import
java.util.Date
;
import
java.util.List
;
/**
* 部门工作进度
...
...
@@ -117,4 +118,6 @@ public class DepartmentWorkProgress {
private
Boolean
submitFlag
;
private
Integer
lastReFillFlag
;
public
List
<
ManagerMonthRecord
>
managerMonthRecordList
;
}
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/model/department/ManagerMonthRecord.java
0 → 100644
View file @
3cbc33a7
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
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/model/kpi/AnnualKpi.java
View file @
3cbc33a7
...
...
@@ -170,4 +170,6 @@ public class AnnualKpi {
* 月实际值
*/
private
String
actual
;
private
List
<
AnnualKpiProgress
>
progressList
;
}
\ No newline at end of file
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/model/minutes/DepartmentMinutesProgress.java
View file @
3cbc33a7
...
...
@@ -87,4 +87,7 @@ public class DepartmentMinutesProgress {
private
String
commentContent
;
List
<
ManagerMinutesRecord
>
managerMinutesRecords
;
}
\ No newline at end of file
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/remote/WorkbenchRemoteService.java
View file @
3cbc33a7
...
...
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.infoepoch.pms.agent.common.utils.LogHelper
;
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.kpi.AnnualKpi
;
import
com.infoepoch.pms.agent.domain.itworkbench.model.kpi.AnnualKpiDto
;
...
...
@@ -21,6 +22,7 @@ import org.springframework.web.util.UriBuilder;
import
java.net.URI
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
...
...
@@ -175,4 +177,18 @@ public class WorkbenchRemoteService {
}
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
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/state/WorkbenchConversationStateService.java
View file @
3cbc33a7
...
...
@@ -20,21 +20,17 @@ public class WorkbenchConversationStateService {
private
static
final
String
KEY_PREFIX
=
"workbench:state:"
;
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
)
{
if
(!
StringUtils
.
hasText
(
sessionId
))
{
return
Optional
.
empty
();
}
Object
cached
=
redisTemplate
.
opsForValue
().
get
(
KEY_PREFIX
+
sessionId
);
if
(
cached
==
null
)
{
return
Optional
.
empty
();
}
try
{
String
json
=
JsonUtils
.
objectToJson
(
cached
);
String
json
=
redisTemplate
.
opsForValue
().
get
(
KEY_PREFIX
+
sessionId
);
if
(!
StringUtils
.
hasText
(
json
))
{
return
Optional
.
empty
();
}
try
{
return
Optional
.
ofNullable
(
JsonUtils
.
jsonToObject
(
json
,
WorkbenchConversationState
.
class
));
}
catch
(
Exception
e
)
{
return
Optional
.
empty
();
...
...
@@ -45,6 +41,7 @@ public class WorkbenchConversationStateService {
if
(
state
==
null
||
!
StringUtils
.
hasText
(
state
.
sessionId
()))
{
return
;
}
redisTemplate
.
opsForValue
().
set
(
KEY_PREFIX
+
state
.
sessionId
(),
state
,
TTL
);
String
json
=
JsonUtils
.
objectToJson
(
state
);
redisTemplate
.
opsForValue
().
set
(
KEY_PREFIX
+
state
.
sessionId
(),
json
,
TTL
);
}
}
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/understanding/WorkbenchIntentRecognizer.java
View file @
3cbc33a7
...
...
@@ -28,9 +28,6 @@ public class WorkbenchIntentRecognizer {
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
)
{
String
trimMsg
=
StringUtils
.
hasText
(
message
)
?
message
.
trim
()
:
""
;
...
...
@@ -47,38 +44,48 @@ public class WorkbenchIntentRecognizer {
return
doClassify
(
traceId
,
sessionId
,
trimMsg
);
}
// ======================
只有【筛选词】才走继承
======================
// ======================
多轮对话:沿用上次意图
======================
if
(
state
!=
null
&&
state
.
lastIntent
()
!=
null
)
{
WorkbenchIntent
lastIntent
=
state
.
lastIntent
();
if
(!
WorkbenchIntent
.
GENERAL_CHAT
.
equals
(
lastIntent
))
{
boolean
isIrrelevant
=
isIrrelevantInput
(
trimMsg
);
if
(!
isIrrelevant
)
{
LogHelper
.
info
(
this
,
CareTraceLogSupport
.
format
(
traceId
,
sessionId
,
"意图识别(多轮)"
,
"筛选条件,沿用意图:"
+
lastIntent
traceId
,
sessionId
,
"意图识别 (多轮)"
,
"消息=["
+
trimMsg
+
"] isNewQuery="
+
isNewQuery
+
" 沿用意图:"
+
lastIntent
));
return
lastIntent
;
}
else
{
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
);
}
/**
* 【关键判断】
* 只要包含这些词 = 用户要查【新业务】,必须清空上下文!
* 排除全量查询关键词(全量/全部/所有/重置/清除)
*/
public
boolean
isNewBusinessQuery
(
String
message
)
{
if
(!
StringUtils
.
hasText
(
message
))
return
false
;
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
(
"工作"
)
...
...
@@ -137,21 +144,4 @@ public class WorkbenchIntentRecognizer {
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
;
}
}
src/main/java/com/infoepoch/pms/agent/domain/itworkbench/understanding/WorkbenchQueryUnderstandingService.java
View file @
3cbc33a7
...
...
@@ -50,13 +50,6 @@ public class WorkbenchQueryUnderstandingService {
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
)
{
LocalDate
now
=
LocalDate
.
now
();
int
currentYear
=
now
.
getYear
();
...
...
@@ -68,11 +61,11 @@ public class WorkbenchQueryUnderstandingService {
你是员工看板重点工作解析器,只输出纯净 JSON,禁止输出任何多余内容!
【严格遵守以下规则,不准脑补、不准乱赋值】
1. year
和 month
必须有值,不能为 null
1. year 必须有值,不能为 null
- 未提时间 → 默认当前年 %d,当前月 %d
- 本月 → %d年%d月
- 上月/上个月 → %d年%d月
- 只提
年份、没提月份或者只提月份没提年份 → 年份默认当前年 %d 月份默认当前月 %d
- 只提
月份没提年份 → 年份默认当前年 %d
2. levelType 严格规则:
- 明确说 A → 1
...
...
@@ -116,8 +109,7 @@ public class WorkbenchQueryUnderstandingService {
currentYear
,
currentMonth
,
currentYear
,
currentMonth
,
lastMonthYear
,
lastMonth
,
currentYear
,
currentMonth
,
message
currentYear
,
message
);
AiObservationContext
ctx
=
buildCtx
(
traceId
,
sessionId
,
"重点工作解析"
);
...
...
@@ -126,18 +118,9 @@ public class WorkbenchQueryUnderstandingService {
DepartmentWorkDto
dto
=
JsonUtils
.
jsonToObject
(
clean
,
DepartmentWorkDto
.
class
);
if
(
dto
==
null
)
dto
=
new
DepartmentWorkDto
();
if
(
state
!=
null
&&
!
state
.
conditions
().
isEmpty
())
{
mergeWorkDtoConditions
(
dto
,
state
.
conditions
());
}
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
)
{
LocalDate
now
=
LocalDate
.
now
();
...
...
@@ -150,11 +133,11 @@ public class WorkbenchQueryUnderstandingService {
你是员工看板 Kpi 解析器,只输出纯净 JSON,禁止输出任何多余内容!
【严格遵守以下规则,不准脑补、不准乱赋值】
1. year
和 month
必须有值,不能为 null
1. year必须有值,不能为 null
- 未提时间 → 默认当前年 %d,当前月 %d
- 本月 → %d年%d月
- 上月/上个月 → %d年%d月
- 只提
年份、没提月份或者只提月份没提年份 → 年份默认当前年 %d 月份默认当前月 %d
- 只提
月份没提年份 → 年份默认当前年 %d
2. levelType 严格规则:
- 明确说 A → 1
...
...
@@ -192,7 +175,7 @@ public class WorkbenchQueryUnderstandingService {
currentYear
,
currentMonth
,
currentYear
,
currentMonth
,
lastMonthYear
,
lastMonth
,
currentYear
,
currentMonth
,
currentYear
,
message
);
...
...
@@ -202,18 +185,9 @@ public class WorkbenchQueryUnderstandingService {
AnnualKpiDto
dto
=
JsonUtils
.
jsonToObject
(
clean
,
AnnualKpiDto
.
class
);
if
(
dto
==
null
)
dto
=
new
AnnualKpiDto
();
if
(
state
!=
null
&&
!
state
.
conditions
().
isEmpty
())
{
mergeKpiDtoConditions
(
dto
,
state
.
conditions
());
}
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
)
{
LocalDate
now
=
LocalDate
.
now
();
...
...
@@ -226,11 +200,11 @@ public class WorkbenchQueryUnderstandingService {
你是员工看板会议纪要解析器,只输出纯净 JSON,禁止输出任何多余内容!
【严格遵守以下规则,不准脑补、不准乱赋值】
1. currentYear
和 month
必须有值,不能为 null
1. currentYear必须有值,不能为 null
- 未提时间 → 默认当前年 %d,当前月 %d
- 本月 → %d年%d月
- 上月/上个月 → %d年%d月
- 只提
年份、没提月份或者只提月份没提年份 → 年份默认当前年 %d 月份默认当前月 %d
- 只提
月份没提年份 → 年份默认当前年 %d
2. categoryContain:
只有用户明确说【会议名称为 XXX】才赋值,否则必须为 null
...
...
@@ -272,8 +246,7 @@ public class WorkbenchQueryUnderstandingService {
currentYear, currentMonth,
currentYear, currentMonth,
lastMonthYear, lastMonth,
currentYear, currentMonth,
message
currentYear, message
);
AiObservationContext ctx = buildCtx(traceId, sessionId, "
会议纪要解析
");
...
...
@@ -282,79 +255,9 @@ public class WorkbenchQueryUnderstandingService {
DepartmentMinutesDto dto = JsonUtils.jsonToObject(clean, DepartmentMinutesDto.class);
if (dto == null) dto = new DepartmentMinutesDto();
if (state != null && !state.conditions().isEmpty()) {
mergeMinutesDtoConditions(dto, state.conditions());
}
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) {
try {
RunnableConfig runnableConfig = buildRunnableConfig(traceId, sessionId, "
员工看板数据查询
");
...
...
@@ -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) {
AiObservationContext currentContext = observationSupport.currentContext();
String requestId = StringUtils.hasText(currentContext.requestId()) ? currentContext.requestId() : traceId;
...
...
src/main/java/com/infoepoch/pms/agent/observability/ai/AiModelInvoker.java
View file @
3cbc33a7
src/main/java/com/infoepoch/pms/agent/properties/ITProperties.java
View file @
3cbc33a7
...
...
@@ -15,4 +15,6 @@ public class ITProperties {
private
String
minutesPath
;
private
String
workPath
;
private
String
authorityPath
;
}
src/main/resources/application.yml
View file @
3cbc33a7
...
...
@@ -62,6 +62,7 @@ pms:
kpi-path
:
/it-workbench/api/functionCallTools/queryKpiList
work-path
:
/it-workbench/api/functionCallTools/queryWorkList
minutes-path
:
/it-workbench/api/functionCallTools/queryMinutesList
authority-path
:
/it-workbench/api/functionCallTools/queryValidAuthority
sso
:
cookie-name
:
pms_agent_sso_token
session-cookie-name
:
pms_agent_session
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment