union-app-chat-stream 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +16 -0
- package/PROJECT_OVERVIEW.md +187 -0
- package/app/.env +63 -0
- package/app/.env.dev +63 -0
- package/app/.env.prod.bj11 +63 -0
- package/app/.env.prod.sh20 +63 -0
- package/app/.env.prod.sz31 +63 -0
- package/app/.env.test.bj12 +63 -0
- package/app/__init__.py +42 -0
- package/app/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/__pycache__/authenticated_user.cpython-312.pyc +0 -0
- package/app/__pycache__/extensions.cpython-312.pyc +0 -0
- package/app/__pycache__/wsgi.cpython-312.pyc +0 -0
- package/app/authenticated_user.py +77 -0
- package/app/config/__pycache__/config_loader.cpython-312.pyc +0 -0
- package/app/config/__pycache__/env_config.cpython-312.pyc +0 -0
- package/app/config/__pycache__/logger_config.cpython-312.pyc +0 -0
- package/app/config/env_config.py +96 -0
- package/app/config/logger_config.py +46 -0
- package/app/manager/__init__.py +4 -0
- package/app/manager/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/manager/__pycache__/chatstream_manager.cpython-312.pyc +0 -0
- package/app/manager/__pycache__/prompts.cpython-312.pyc +0 -0
- package/app/manager/__pycache__/runtime_manager.cpython-312.pyc +0 -0
- package/app/manager/__pycache__/toolcall_manager.cpython-312.pyc +0 -0
- package/app/manager/chatstream_manager.py +90 -0
- package/app/manager/prompts.py +62 -0
- package/app/manager/runtime_manager.py +552 -0
- package/app/models/__pycache__/schemas.cpython-312.pyc +0 -0
- package/app/models/schemas.py +30 -0
- package/app/service/__init__.py +4 -0
- package/app/service/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/service/__pycache__/chat_service.cpython-312.pyc +0 -0
- package/app/service/__pycache__/llm_service.cpython-312.pyc +0 -0
- package/app/service/__pycache__/rag_service.cpython-312.pyc +0 -0
- package/app/service/__pycache__/tool_call_service.cpython-312.pyc +0 -0
- package/app/service/__pycache__/union_service.cpython-312.pyc +0 -0
- package/app/service/chat_service.py +228 -0
- package/app/service/llm_service.py +214 -0
- package/app/service/rag_service.py +866 -0
- package/app/service/union_service.py +201 -0
- package/app/utils/__init__.py +5 -0
- package/app/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/utils/__pycache__/common_utils.cpython-312.pyc +0 -0
- package/app/utils/__pycache__/debug_context.cpython-312.pyc +0 -0
- package/app/utils/__pycache__/function_utils.cpython-312.pyc +0 -0
- package/app/utils/__pycache__/jwt_utils.cpython-312.pyc +0 -0
- package/app/utils/common_utils.py +169 -0
- package/app/utils/debug_context.py +16 -0
- package/app/utils/function_utils.py +274 -0
- package/app/utils/jwt_utils.py +39 -0
- package/app/views/__init__.py +6 -0
- package/app/views/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/views/__pycache__/view_chatstream.cpython-312.pyc +0 -0
- package/app/views/__pycache__/view_healthcheck.cpython-312.pyc +0 -0
- package/app/views/__pycache__/view_runtime.cpython-312.pyc +0 -0
- package/app/views/view_chatstream.py +53 -0
- package/app/views/view_healthcheck.py +14 -0
- package/app/views/view_runtime.py +72 -0
- package/app/wsgi.py +37 -0
- package/ci.yml +14 -0
- package/deploy/autoconf/templates/env.j2 +25 -0
- package/deploy/autoconf.yml +15 -0
- package/deploy/scripts/healthcheck.sh +0 -0
- package/deploy/scripts/requirements.txt +53 -0
- package/deploy/scripts/start.sh +75 -0
- package/deploy/scripts/stop.sh +31 -0
- package/knowledge/.gitkeep +0 -0
- package/knowledge/000001-biz-offline-85b99bd43b-v1.md +88 -0
- package/knowledge/000002-biz-offline-717e8d823e-v1.md +90 -0
- package/knowledge/000003-biz-offline-c963227cc8-v1.md +84 -0
- package/knowledge/000004-biz-offline-2a5868e7da-v1.md +92 -0
- package/knowledge/000005-biz-offline-f9d9cf1a88-v1.md +79 -0
- package/knowledge/000006-biz-offline-c4fa2df3bd-v1.md +77 -0
- package/knowledge/000007-biz-offline-78304b70ca-v1.md +76 -0
- package/knowledge/000008-biz-offline-987ae67b35-v1.md +75 -0
- package/knowledge/000009-biz-offline-4d656bcea3-v1.md +85 -0
- package/knowledge/000010-sop-offline-a9e1050719-v1.md +100 -0
- package/knowledge/000011-biz-offline-5de0624891-v1.md +86 -0
- package/knowledge/000012-biz-offline-7dfacccba3-v1.md +82 -0
- package/knowledge/000013-biz-offline-5e1d29d2ed-v1.md +81 -0
- package/knowledge/000014-biz-offline-1d0ed8b841-v1.md +68 -0
- package/knowledge/000015-biz-offline-8a1376ee3e-v1.md +78 -0
- package/knowledge/000016-biz-offline-c8bfc2aa08-v1.md +99 -0
- package/knowledge/000017-biz-offline-9dffb28032-v1.md +88 -0
- package/knowledge/000018-biz-offline-f935bc9a6a-v1.md +80 -0
- package/knowledge/000019-biz-offline-858b3ecd89-v1.md +86 -0
- package/knowledge/000020-biz-offline-65cb5c4f40-v1.md +113 -0
- package/knowledge/000021-biz-offline-1bf211639c-v1.md +148 -0
- package/knowledge/000022-biz-offline-8c5a637879-v1.md +140 -0
- package/knowledge/000023-biz-offline-fe872b8712-v1.md +188 -0
- package/knowledge/000024-biz-offline-a85010c500-v1.md +133 -0
- package/knowledge/000025-biz-offline-8af58a3638-v1.md +136 -0
- package/knowledge/000026-biz-offline-6754102e93-v1.md +142 -0
- package/knowledge/000027-biz-offline-ea2e5ca5f9-v1.md +150 -0
- package/knowledge/000028-scenario-offline-dab45cebb4-v1.md +136 -0
- package/knowledge/000029-scenario-offline-5b8ae5ea9f-v1.md +143 -0
- package/knowledge/000030-scenario-offline-9a82d42f3f-v1.md +136 -0
- package/knowledge/000031-scenario-offline-cc2edc0197-v1.md +122 -0
- package/knowledge/000032-scenario-offline-e5f6e5cbfa-v1.md +122 -0
- package/knowledge/000033-scenario-offline-e1955849aa-v1.md +135 -0
- package/knowledge/000034-scenario-offline-3a13d49a3a-v1.md +138 -0
- package/knowledge/000035-scenario-offline-fd5560211f-v1.md +147 -0
- package/knowledge/000036-scenario-offline-function-call-mock-v1.md +134 -0
- package/package.json +18 -0
- package/requirements.txt +53 -0
- package/tools/prompts.yaml +10 -0
- package/tools/tool_definitions.yaml +303 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Generator, Dict, Any, Optional, List
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from app.models.schemas import ChatResponse
|
|
11
|
+
from app.utils import common_utils
|
|
12
|
+
from app.manager.prompts import (
|
|
13
|
+
PROMPT_EXTRACT_TIME_RANGE_SYSTEM,
|
|
14
|
+
PROMPT_EXTRACT_TIME_RANGE_USER,
|
|
15
|
+
PROMPT_QUALITY_DECLINE_MONTHS,
|
|
16
|
+
PROMPT_FAULT_DETAILS_BY_ORG,
|
|
17
|
+
PROMPT_FAILURE_PATTERNS_BY_ORG
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RuntimeManager:
|
|
22
|
+
"""运行时分析业务管理器"""
|
|
23
|
+
|
|
24
|
+
# 常量定义
|
|
25
|
+
LARGE_SCALE_THRESHOLD = 20000000 # 大规模交易量阈值
|
|
26
|
+
MEDIUM_SCALE_THRESHOLD = 5000000 # 中等规模交易量阈值
|
|
27
|
+
TOP_INFLUENCE_COUNT = 10 # 影响度排名前N的数量
|
|
28
|
+
TOP_FAULT_CAUSE_COUNT = 5 # 故障原因前N的数量
|
|
29
|
+
TOP_ORG_COUNT = 3 # 机构前N的数量
|
|
30
|
+
|
|
31
|
+
def __init__(self, union_service, llm_service):
|
|
32
|
+
self._union_service = union_service
|
|
33
|
+
self._llm_service = llm_service
|
|
34
|
+
|
|
35
|
+
def extract_time_range_from_query(self, query_content: str) -> Optional[Dict[str, Any]]:
|
|
36
|
+
"""
|
|
37
|
+
从用户查询中提取时间范围
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
query_content: 用户查询文本
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
包含 queryTimeBegin 和 queryTimeEnd 的字典,失败返回None
|
|
44
|
+
"""
|
|
45
|
+
logger.info("开始从查询中提取时间范围")
|
|
46
|
+
current_time = common_utils.current_time_format_by('%Y-%m-%d')
|
|
47
|
+
system_prompt = PROMPT_EXTRACT_TIME_RANGE_SYSTEM.format(current_time=current_time)
|
|
48
|
+
user_prompt = PROMPT_EXTRACT_TIME_RANGE_USER
|
|
49
|
+
|
|
50
|
+
time_range = self._llm_service.execute_llm(
|
|
51
|
+
system_prompt=system_prompt,
|
|
52
|
+
user_prompt=user_prompt,
|
|
53
|
+
query_text=query_content,
|
|
54
|
+
temperature=0.3,
|
|
55
|
+
use_json_format=True
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if time_range:
|
|
59
|
+
logger.info(f"时间范围提取成功: {time_range}")
|
|
60
|
+
else:
|
|
61
|
+
logger.warning("时间范围提取失败")
|
|
62
|
+
return time_range
|
|
63
|
+
|
|
64
|
+
def query_fulllink_data(
|
|
65
|
+
self,
|
|
66
|
+
query_time_begin: str,
|
|
67
|
+
query_time_end: str,
|
|
68
|
+
jsessionid: str
|
|
69
|
+
) -> tuple[Optional[list[Dict[str, Any]]], str]:
|
|
70
|
+
"""
|
|
71
|
+
查询全链路数据
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
query_time_begin: 查询开始时间(格式:yyyy-MM-dd)
|
|
75
|
+
query_time_end: 查询结束时间(格式:yyyy-MM-dd)
|
|
76
|
+
jsessionid: 登录态ID,用于外部接口鉴权
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
(数据列表, 消息)元组
|
|
80
|
+
"""
|
|
81
|
+
logger.info(f"查询全链路数据: {query_time_begin} ~ {query_time_end}")
|
|
82
|
+
|
|
83
|
+
# 参数校验
|
|
84
|
+
if not query_time_begin and not query_time_end:
|
|
85
|
+
return None, "success"
|
|
86
|
+
|
|
87
|
+
# 解析时间参数
|
|
88
|
+
start_int = common_utils.parse_month(query_time_begin) if query_time_begin else None
|
|
89
|
+
end_int = common_utils.parse_month(query_time_end) if query_time_end else None
|
|
90
|
+
|
|
91
|
+
# 构建查询参数,月度数据ymd为yyyyMM01字符串格式
|
|
92
|
+
params = {
|
|
93
|
+
'startDate': f"{start_int}01",
|
|
94
|
+
'endDate': f"{end_int}01"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# 调用通用大数据查询方法
|
|
98
|
+
data, message = self._union_service.query_bigdata(
|
|
99
|
+
interface_name=self._union_service.BIGDATA_INTERFACE_FULL_LINK,
|
|
100
|
+
params=params,
|
|
101
|
+
jsessionid=jsessionid,
|
|
102
|
+
description="查询全链路数据"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if message == "success":
|
|
106
|
+
logger.info(f"全链路数据查询成功,共 {len(data) if data else 0} 条记录")
|
|
107
|
+
else:
|
|
108
|
+
logger.error(f"全链路数据查询失败: {message}")
|
|
109
|
+
return data, message
|
|
110
|
+
|
|
111
|
+
def analyze_quality_decline_months_stream(
|
|
112
|
+
self,
|
|
113
|
+
query_content: str,
|
|
114
|
+
fulllink_data: list[Dict[str, Any]]
|
|
115
|
+
) -> Generator[str, None, None]:
|
|
116
|
+
"""
|
|
117
|
+
流式分析质量下降月份
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
query_content: 用户查询文本
|
|
121
|
+
fulllink_data: 全链路数据
|
|
122
|
+
|
|
123
|
+
Yields:
|
|
124
|
+
LLM响应的流式内容
|
|
125
|
+
"""
|
|
126
|
+
logger.info("开始流式分析质量下降月份")
|
|
127
|
+
system_prompt = PROMPT_QUALITY_DECLINE_MONTHS.format(data_json=str(fulllink_data))
|
|
128
|
+
yield from self._llm_service.execute_llm_stream(
|
|
129
|
+
system_prompt=system_prompt,
|
|
130
|
+
query_text=query_content
|
|
131
|
+
)
|
|
132
|
+
logger.info("质量下降月份分析完成")
|
|
133
|
+
|
|
134
|
+
def query_bank_running_details(
|
|
135
|
+
self,
|
|
136
|
+
decline_months: str,
|
|
137
|
+
jsessionid: str
|
|
138
|
+
) -> Optional[Dict[str, Any]]:
|
|
139
|
+
"""
|
|
140
|
+
查询银行运行详情(包含业务逻辑处理)
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
decline_months: 下降月份字符串,逗号分隔
|
|
144
|
+
jsessionid: 登录态ID,用于外部接口鉴权
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
处理后的银行运行数据
|
|
148
|
+
"""
|
|
149
|
+
logger.info(f"查询并处理银行运行详情: {decline_months}")
|
|
150
|
+
|
|
151
|
+
# 解析时间数组
|
|
152
|
+
filtered_time_arrays = decline_months.split(",")
|
|
153
|
+
filtered_time = [f"{common_utils.parse_month(item_time)}" for item_time in filtered_time_arrays]
|
|
154
|
+
|
|
155
|
+
# 构建查询参数
|
|
156
|
+
params = {
|
|
157
|
+
'monthList': filtered_time
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# 查询原始数据
|
|
161
|
+
raw_data, message = self._union_service.query_bigdata(
|
|
162
|
+
interface_name=self._union_service.BIGDATA_INTERFACE_BANK_MONTHLY,
|
|
163
|
+
params=params,
|
|
164
|
+
jsessionid=jsessionid,
|
|
165
|
+
description="查询银行月度运行数据"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if not raw_data:
|
|
169
|
+
logger.warning(f"银行运行原始数据查询为空: {message}")
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
# 业务逻辑处理
|
|
173
|
+
sorted_result = self._process_bank_running_data(raw_data, filtered_time_arrays)
|
|
174
|
+
|
|
175
|
+
logger.info("银行运行详情查询和处理完成")
|
|
176
|
+
return sorted_result
|
|
177
|
+
|
|
178
|
+
def _process_bank_running_data(
|
|
179
|
+
self,
|
|
180
|
+
raw_data: List[Dict[str, Any]],
|
|
181
|
+
filtered_time_arrays: List[str]
|
|
182
|
+
) -> Dict[str, Any]:
|
|
183
|
+
"""处理银行运行数据(业务逻辑)"""
|
|
184
|
+
# 分组汇总数据
|
|
185
|
+
grouped_total_data = defaultdict(lambda: {"totalCnt": 0, "failCnt": 0})
|
|
186
|
+
for item in raw_data:
|
|
187
|
+
datetime = item["ym"]
|
|
188
|
+
grouped_total_data[datetime]["totalCnt"] += item["total_cnt"]
|
|
189
|
+
grouped_total_data[datetime]["failCnt"] += item["total_cnt_fail"]
|
|
190
|
+
|
|
191
|
+
# 设置规模级别
|
|
192
|
+
item['scale'] = self._calculate_scale(item['total_cnt'], datetime)
|
|
193
|
+
|
|
194
|
+
# 计算失败率
|
|
195
|
+
filtered_data_dict = {}
|
|
196
|
+
for datetime, cnt in grouped_total_data.items():
|
|
197
|
+
total_cnt = cnt["totalCnt"]
|
|
198
|
+
fail_cnt = cnt["failCnt"]
|
|
199
|
+
fail_rate = self._calculate_fail_rate(fail_cnt, total_cnt)
|
|
200
|
+
filtered_data_dict[datetime] = {
|
|
201
|
+
"failRate": fail_rate,
|
|
202
|
+
"failCnt": fail_cnt,
|
|
203
|
+
"datetime": datetime
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# 计算影响度并分组
|
|
207
|
+
grouped_time_data = defaultdict(list)
|
|
208
|
+
for item in raw_data:
|
|
209
|
+
influence = (
|
|
210
|
+
item['total_cnt_fail'] -
|
|
211
|
+
filtered_data_dict[item['ym']]['failRate'] * item['total_cnt']
|
|
212
|
+
)
|
|
213
|
+
item['influence'] = influence
|
|
214
|
+
grouped_time_data[item['ym']].append(item)
|
|
215
|
+
|
|
216
|
+
# 排序并取前N
|
|
217
|
+
sorted_result = defaultdict(list)
|
|
218
|
+
for time in grouped_time_data:
|
|
219
|
+
sorted_result[time] = sorted(
|
|
220
|
+
grouped_time_data[time],
|
|
221
|
+
key=lambda x: x['influence'],
|
|
222
|
+
reverse=True
|
|
223
|
+
)[:self.TOP_INFLUENCE_COUNT]
|
|
224
|
+
|
|
225
|
+
# 获取故障信息并填充数据
|
|
226
|
+
fault_infos = self._union_service.query_jirainfo(filtered_time_arrays)
|
|
227
|
+
flat_result = [item for group in sorted_result.values() for item in group]
|
|
228
|
+
self._enrich_data_with_fault_info(flat_result, filtered_time_arrays, fault_infos)
|
|
229
|
+
|
|
230
|
+
return sorted_result
|
|
231
|
+
|
|
232
|
+
def _calculate_scale(self, total_cnt: int, datetime: str) -> str:
|
|
233
|
+
"""根据交易量计算规模级别"""
|
|
234
|
+
days = common_utils.get_days_in_month(datetime)
|
|
235
|
+
if total_cnt >= self.LARGE_SCALE_THRESHOLD * days:
|
|
236
|
+
return 'large'
|
|
237
|
+
elif total_cnt < self.MEDIUM_SCALE_THRESHOLD * days:
|
|
238
|
+
return 'small'
|
|
239
|
+
else:
|
|
240
|
+
return 'medium'
|
|
241
|
+
|
|
242
|
+
def _calculate_fail_rate(self, fail_cnt: int, total_cnt: int) -> float:
|
|
243
|
+
"""计算失败率"""
|
|
244
|
+
if total_cnt <= 0:
|
|
245
|
+
return 0.0
|
|
246
|
+
return float(
|
|
247
|
+
(Decimal(fail_cnt) / Decimal(total_cnt)).quantize(Decimal("0.00000001"))
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def _group_fault_info_by_time(
|
|
251
|
+
self,
|
|
252
|
+
fault_info_list: List[Dict[str, Any]]
|
|
253
|
+
) -> Dict[int, Dict[str, Dict[str, int]]]:
|
|
254
|
+
"""按时间和机构分组故障信息"""
|
|
255
|
+
time_fault_group = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
|
|
256
|
+
for item in fault_info_list:
|
|
257
|
+
time = common_utils.parse_month(item['questionTime'])
|
|
258
|
+
org_code = item['orgCode']
|
|
259
|
+
fault_cause = item['faultCause']
|
|
260
|
+
impact_num = item['impactNum']
|
|
261
|
+
time_fault_group[time][org_code][fault_cause] += impact_num
|
|
262
|
+
return time_fault_group
|
|
263
|
+
|
|
264
|
+
def _get_top_fault_causes(
|
|
265
|
+
self,
|
|
266
|
+
org_fault_dict: Dict[str, int]
|
|
267
|
+
) -> List[str]:
|
|
268
|
+
"""获取影响数前N的故障原因"""
|
|
269
|
+
return sorted(
|
|
270
|
+
org_fault_dict.keys(),
|
|
271
|
+
key=lambda x: org_fault_dict[x],
|
|
272
|
+
reverse=True
|
|
273
|
+
)[:self.TOP_ORG_COUNT]
|
|
274
|
+
|
|
275
|
+
def _enrich_data_with_fault_info(
|
|
276
|
+
self,
|
|
277
|
+
data_list: List[Dict[str, Any]],
|
|
278
|
+
filtered_time_arrays: List[str],
|
|
279
|
+
fault_info: List[Dict[str, Any]]
|
|
280
|
+
) -> None:
|
|
281
|
+
"""为数据列表添加故障信息"""
|
|
282
|
+
time_fault_group = self._group_fault_info_by_time(fault_info)
|
|
283
|
+
time_fault_dict = {}
|
|
284
|
+
|
|
285
|
+
for time, org_dict in time_fault_group.items():
|
|
286
|
+
org_code_fault = {}
|
|
287
|
+
for org_code, fault_dict in org_dict.items():
|
|
288
|
+
top_fault_causes = self._get_top_fault_causes(fault_dict)
|
|
289
|
+
org_code_fault[org_code] = ','.join(top_fault_causes)
|
|
290
|
+
time_fault_dict[str(time)] = org_code_fault
|
|
291
|
+
|
|
292
|
+
org_codes = [item['org_code'] for item in data_list]
|
|
293
|
+
name_dict = self._union_service.query_orginfo(org_codes)
|
|
294
|
+
|
|
295
|
+
for item in data_list:
|
|
296
|
+
item['orgName'] = name_dict.get(item['org_code'], item['org_code'])
|
|
297
|
+
time_key = item.get('ym', '')
|
|
298
|
+
item['faultCause'] = time_fault_dict.get(
|
|
299
|
+
time_key, {}
|
|
300
|
+
).get(item['org_code'], "未知原因")
|
|
301
|
+
|
|
302
|
+
def generate_fault_details_by_org_stream(
|
|
303
|
+
self,
|
|
304
|
+
data: Any
|
|
305
|
+
) -> Generator[str, None, None]:
|
|
306
|
+
"""
|
|
307
|
+
流式生成以机构为维度的故障详情
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
data: 数据对象
|
|
311
|
+
|
|
312
|
+
Yields:
|
|
313
|
+
LLM响应的流式内容
|
|
314
|
+
"""
|
|
315
|
+
logger.info(f"开始生成以机构为维度的故障详情,data:{str(data)}")
|
|
316
|
+
system_prompt = PROMPT_FAULT_DETAILS_BY_ORG.format(data_json=str(data))
|
|
317
|
+
yield from self._llm_service.execute_llm_stream(system_prompt=system_prompt)
|
|
318
|
+
logger.info("机构维度故障详情生成完成")
|
|
319
|
+
|
|
320
|
+
def query_fault_details(
|
|
321
|
+
self,
|
|
322
|
+
decline_months: str
|
|
323
|
+
) -> Optional[Dict[str, Any]]:
|
|
324
|
+
"""
|
|
325
|
+
查询故障详情(包含业务逻辑处理)
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
decline_months: 下降月份字符串,逗号分隔
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
按时间分组的故障详情字典
|
|
332
|
+
"""
|
|
333
|
+
logger.info(f"查询并处理故障详情: {decline_months}")
|
|
334
|
+
|
|
335
|
+
filtered_time_arrays = decline_months.split(",")
|
|
336
|
+
fault_infos = self._union_service.query_jirainfo(filtered_time_arrays)
|
|
337
|
+
|
|
338
|
+
if not fault_infos:
|
|
339
|
+
logger.warning("故障信息查询为空")
|
|
340
|
+
return {}
|
|
341
|
+
|
|
342
|
+
# 业务逻辑处理
|
|
343
|
+
time_fault_dict = self._process_fault_details(fault_infos)
|
|
344
|
+
logger.info("故障详情查询和处理完成")
|
|
345
|
+
return time_fault_dict
|
|
346
|
+
|
|
347
|
+
def _process_fault_details(
|
|
348
|
+
self,
|
|
349
|
+
fault_infos: List[Dict[str, Any]]
|
|
350
|
+
) -> Dict[str, Any]:
|
|
351
|
+
"""处理故障详情(业务逻辑)"""
|
|
352
|
+
# 按时间和故障原因分组
|
|
353
|
+
time_fault_group = {}
|
|
354
|
+
all_org_codes = []
|
|
355
|
+
for item in fault_infos:
|
|
356
|
+
time = common_utils.parse_month(item['questionTime'])
|
|
357
|
+
fault_cause = item['faultCause']
|
|
358
|
+
|
|
359
|
+
if time not in time_fault_group:
|
|
360
|
+
time_fault_group[time] = {}
|
|
361
|
+
|
|
362
|
+
if fault_cause not in time_fault_group[time]:
|
|
363
|
+
time_fault_group[time][fault_cause] = {'total': 0, 'orgs': {}}
|
|
364
|
+
|
|
365
|
+
fault_group = time_fault_group[time][fault_cause]
|
|
366
|
+
fault_group['total'] += item['impactNum']
|
|
367
|
+
fault_group['orgs'][item['orgCode']] = (
|
|
368
|
+
fault_group['orgs'].get(item['orgCode'], 0) + item['impactNum']
|
|
369
|
+
)
|
|
370
|
+
all_org_codes.append(item['orgCode'])
|
|
371
|
+
|
|
372
|
+
# 获取机构名称
|
|
373
|
+
name_dict = self._union_service.query_orginfo(all_org_codes)
|
|
374
|
+
|
|
375
|
+
# 构建结果
|
|
376
|
+
time_fault_dict = {}
|
|
377
|
+
for time, fc_dict in time_fault_group.items():
|
|
378
|
+
sorted_fc = sorted(
|
|
379
|
+
fc_dict.items(),
|
|
380
|
+
key=lambda x: x[1]['total'],
|
|
381
|
+
reverse=True
|
|
382
|
+
)[:self.TOP_FAULT_CAUSE_COUNT]
|
|
383
|
+
|
|
384
|
+
fault_orgs_dict = {}
|
|
385
|
+
for fc, info in sorted_fc:
|
|
386
|
+
sorted_orgs = sorted(
|
|
387
|
+
info['orgs'].keys(),
|
|
388
|
+
key=lambda org: info['orgs'][org],
|
|
389
|
+
reverse=True
|
|
390
|
+
)[:self.TOP_ORG_COUNT]
|
|
391
|
+
fault_orgs_dict[fc] = {
|
|
392
|
+
'total': info['total'],
|
|
393
|
+
'orgNames': ','.join(name_dict.get(org, org) for org in sorted_orgs)
|
|
394
|
+
}
|
|
395
|
+
time_fault_dict[str(time)] = fault_orgs_dict
|
|
396
|
+
|
|
397
|
+
return time_fault_dict
|
|
398
|
+
|
|
399
|
+
def analyze_failure_patterns_stream(
|
|
400
|
+
self,
|
|
401
|
+
bankrunning_data: Dict[str, Any]
|
|
402
|
+
) -> Generator[str, None, None]:
|
|
403
|
+
"""
|
|
404
|
+
流式分析机构失败模式
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
bankrunning_data: 银行运行数据
|
|
408
|
+
|
|
409
|
+
Yields:
|
|
410
|
+
LLM响应的流式内容
|
|
411
|
+
"""
|
|
412
|
+
logger.info("开始分析机构失败模式")
|
|
413
|
+
system_prompt = PROMPT_FAILURE_PATTERNS_BY_ORG.format(data_json=str(bankrunning_data))
|
|
414
|
+
yield from self._llm_service.execute_llm_stream(system_prompt=system_prompt)
|
|
415
|
+
logger.info("机构失败模式分析完成")
|
|
416
|
+
|
|
417
|
+
def analyze_runtime_complete_stream(
|
|
418
|
+
self,
|
|
419
|
+
query_content: str,
|
|
420
|
+
conversation_id: str,
|
|
421
|
+
jsessionid: str
|
|
422
|
+
) -> Generator[ChatResponse, None, None]:
|
|
423
|
+
"""
|
|
424
|
+
完整的运行时分析流式处理
|
|
425
|
+
|
|
426
|
+
这是编排了所有业务步骤的高层方法
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
query_content: 用户查询文本
|
|
430
|
+
conversation_id: 对话ID
|
|
431
|
+
jsessionid: 登录态ID,用于调用外部接口
|
|
432
|
+
|
|
433
|
+
Yields:
|
|
434
|
+
ChatResponse 流式消息
|
|
435
|
+
|
|
436
|
+
Raises:
|
|
437
|
+
RuntimeError: 当任何步骤失败时
|
|
438
|
+
"""
|
|
439
|
+
logger.info("=" * 60)
|
|
440
|
+
logger.info("开始完整的运行时分析流程")
|
|
441
|
+
logger.info("=" * 60)
|
|
442
|
+
|
|
443
|
+
# 步骤1:提取时间范围
|
|
444
|
+
time_range = self.extract_time_range_from_query(query_content)
|
|
445
|
+
if not time_range:
|
|
446
|
+
yield self._format_error_message(conversation_id, "提取时间范围失败")
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
# 步骤2:查询全链路数据
|
|
450
|
+
fulllink_data, msg = self.query_fulllink_data(
|
|
451
|
+
time_range['queryTimeBegin'],
|
|
452
|
+
time_range['queryTimeEnd'],
|
|
453
|
+
jsessionid
|
|
454
|
+
)
|
|
455
|
+
if msg != "success":
|
|
456
|
+
yield self._format_error_message(conversation_id, f"查询全链路数据失败: {msg}")
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
# 步骤3:LLM流式分析质量下降月份
|
|
460
|
+
response_accumulator = []
|
|
461
|
+
yield from self._yield_llm_stream_with_accumulation(
|
|
462
|
+
self.analyze_quality_decline_months_stream(query_content, fulllink_data),
|
|
463
|
+
response_accumulator,
|
|
464
|
+
conversation_id,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# 解析下降月份
|
|
468
|
+
decline_months_str = self._parse_decline_months_from_response(response_accumulator)
|
|
469
|
+
if not decline_months_str:
|
|
470
|
+
yield self._format_error_message(conversation_id, "解析下降月份失败")
|
|
471
|
+
return
|
|
472
|
+
|
|
473
|
+
logger.info(f"识别到下降月份: {decline_months_str}")
|
|
474
|
+
|
|
475
|
+
# 步骤4:查询银行运行详情
|
|
476
|
+
bankrunning_data = self.query_bank_running_details(decline_months_str, jsessionid)
|
|
477
|
+
|
|
478
|
+
# 步骤5:生成机构维度故障详情
|
|
479
|
+
if bankrunning_data:
|
|
480
|
+
yield from self._yield_llm_stream(
|
|
481
|
+
self.generate_fault_details_by_org_stream(bankrunning_data),
|
|
482
|
+
conversation_id,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# 步骤6:查询故障详情并生成报告
|
|
486
|
+
fault_data = self.query_fault_details(decline_months_str)
|
|
487
|
+
if fault_data:
|
|
488
|
+
yield from self._yield_llm_stream(
|
|
489
|
+
self.generate_fault_details_by_org_stream(fault_data),
|
|
490
|
+
conversation_id,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
# 步骤7:分析失败模式
|
|
494
|
+
if bankrunning_data:
|
|
495
|
+
yield from self._yield_llm_stream(
|
|
496
|
+
self.analyze_failure_patterns_stream(bankrunning_data),
|
|
497
|
+
conversation_id,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# 发送完成信号
|
|
501
|
+
yield ChatResponse(conversationId=conversation_id, finish_reason="done")
|
|
502
|
+
logger.info("=" * 60)
|
|
503
|
+
logger.info("运行时分析流程完成")
|
|
504
|
+
logger.info("=" * 60)
|
|
505
|
+
|
|
506
|
+
def _format_error_message(self, conversation_id: str, error_msg: str) -> ChatResponse:
|
|
507
|
+
return ChatResponse(conversationId=conversation_id, content=error_msg, finish_reason="error")
|
|
508
|
+
|
|
509
|
+
def _yield_llm_stream_with_accumulation(
|
|
510
|
+
self,
|
|
511
|
+
stream_generator: Generator[str, None, None],
|
|
512
|
+
accumulator: list[str],
|
|
513
|
+
conversation_id: str,
|
|
514
|
+
) -> Generator[ChatResponse, None, None]:
|
|
515
|
+
"""处理LLM流式响应并累积内容"""
|
|
516
|
+
for chunk in stream_generator:
|
|
517
|
+
if chunk.startswith("[ERROR]"):
|
|
518
|
+
yield ChatResponse(conversationId=conversation_id, content=chunk, finish_reason="error")
|
|
519
|
+
return
|
|
520
|
+
else:
|
|
521
|
+
logger.debug(f"LLM响应: {chunk}")
|
|
522
|
+
yield ChatResponse(conversationId=conversation_id, content=chunk)
|
|
523
|
+
accumulator.append(chunk)
|
|
524
|
+
|
|
525
|
+
def _yield_llm_stream(
|
|
526
|
+
self,
|
|
527
|
+
stream_generator: Generator[str, None, None],
|
|
528
|
+
conversation_id: str,
|
|
529
|
+
) -> Generator[ChatResponse, None, None]:
|
|
530
|
+
"""处理LLM流式响应(不累积内容)"""
|
|
531
|
+
for chunk in stream_generator:
|
|
532
|
+
if chunk.startswith("[ERROR]"):
|
|
533
|
+
yield ChatResponse(conversationId=conversation_id, content=chunk, finish_reason="error")
|
|
534
|
+
return
|
|
535
|
+
else:
|
|
536
|
+
logger.debug(f"LLM响应: {chunk}")
|
|
537
|
+
yield ChatResponse(conversationId=conversation_id, content=chunk)
|
|
538
|
+
|
|
539
|
+
def _parse_decline_months_from_response(
|
|
540
|
+
self,
|
|
541
|
+
response_accumulator: list[str]
|
|
542
|
+
) -> Optional[str]:
|
|
543
|
+
"""从响应中解析下降月份"""
|
|
544
|
+
try:
|
|
545
|
+
total_response = ''.join(response_accumulator)
|
|
546
|
+
cleaned_response = common_utils.remove_think_tag(total_response)
|
|
547
|
+
parsed_data = json.loads(cleaned_response)
|
|
548
|
+
decline_months_str = parsed_data.get("declineMonth")
|
|
549
|
+
return decline_months_str
|
|
550
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
551
|
+
logger.error(f"解析下降月份失败: {e}")
|
|
552
|
+
return None
|
|
Binary file
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Literal, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ChatRequest(BaseModel):
|
|
7
|
+
"""聊天请求模型"""
|
|
8
|
+
model_config = ConfigDict(extra="forbid")
|
|
9
|
+
|
|
10
|
+
conversation_id: Optional[str] = Field(
|
|
11
|
+
default=None,
|
|
12
|
+
alias="conversationId",
|
|
13
|
+
description="对话ID,用于多轮对话上下文;为空时后端自动创建",
|
|
14
|
+
)
|
|
15
|
+
question: str = Field(..., min_length=1, max_length=4096, description="用户问题")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ChatResponse(BaseModel):
|
|
19
|
+
"""聊天流式响应模型"""
|
|
20
|
+
model_config = ConfigDict(extra="forbid")
|
|
21
|
+
|
|
22
|
+
conversation_id: str = Field(..., alias="conversationId", description="对话ID")
|
|
23
|
+
content: Optional[str] = Field(default=None, description="模型正式回复内容增量")
|
|
24
|
+
reasoning_content: Optional[str] = Field(default=None, description="模型推理内容增量")
|
|
25
|
+
tool_call: Optional[str] = Field(default=None, description="工具调用信息")
|
|
26
|
+
tool_result: Optional[str] = Field(default=None, description="工具执行结果")
|
|
27
|
+
finish_reason: Optional[Literal["stop", "error", "rejected", "done"]] = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="结束原因;中间流式增量为空",
|
|
30
|
+
)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|