Skip to content

Commit 1a81bf6

Browse files
committed
feature: update stream chat
1 parent 5705ad2 commit 1a81bf6

File tree

14 files changed

+233
-105
lines changed

14 files changed

+233
-105
lines changed

frontend/.umirc.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ export default defineConfig({
1313
default: 'zh-CN',
1414
antd: true,
1515
},
16-
proxy: {
17-
'/api': {
18-
// target: 'https://nexa-api-pre.alipay.com',
19-
// target: 'http://30.230.0.179:8080',
20-
target: 'http://runtime:8080',
21-
// target: 'http://30.98.121.212:8083',
22-
changeOrigin: true,
23-
},
24-
},
16+
// proxy: {
17+
// '/api': {
18+
// // target: 'https://nexa-api-pre.alipay.com',
19+
// // target: 'http://30.230.0.179:8080',
20+
// target: 'http://runtime:8080',
21+
// // target: 'http://30.98.121.212:8083',
22+
// changeOrigin: true,
23+
// },
24+
// },
2525
routes: [
2626
{
2727
path: '/',

frontend/src/app.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { createGlobalStyle } from '@umijs/max';
2-
// 运行时配置
2+
import type { RequestConfig } from '@umijs/max';
33

4+
// ps: 推master要改成localhost
5+
const API_PREFIX = 'http://localhost:8080';
6+
// const API_PREFIX = 'http://30.183.176.221:8080';
7+
8+
export const request: RequestConfig = {
9+
requestInterceptors: [
10+
(url, options) => ({ url: `${API_PREFIX}${url}`, options })
11+
]
12+
13+
}
414
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
515
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
616
// export async function getInitialState(): Promise<{ name: string }> {

frontend/src/pages/EKG/Common/Chat/ChatContent.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { dataToJson, jsonToData } from '../../utils/format';
22
import { getMyEmpId } from '../../utils/userStore';
33
import { Avatar, Button, Space } from 'antd';
4-
import React, { useContext, useState } from 'react';
4+
import React, { useContext, useState, useRef, useEffect } from 'react';
55
import {
66
CheckCircleFilled,
77
CloseCircleFilled,
@@ -17,6 +17,7 @@ const ChatContent = () => {
1717
const { msgList, selectedAgent, setHeaderType } = useContext(
1818
CommonContext,
1919
) as CommonContextType;
20+
const contentRef = useRef(null);
2021

2122
const formatMsgContent = (dataItem: NEX_MAIN_API.EKGChatJSONMsg) => {
2223
const { content, type } = dataItem;
@@ -58,8 +59,20 @@ const ChatContent = () => {
5859
}
5960
};
6061

62+
useEffect(() => {
63+
const contentElement = contentRef.current;
64+
const scrollToBottom = () => {
65+
contentElement.scrollTop = contentElement.scrollHeight;
66+
};
67+
const observer = new MutationObserver(scrollToBottom);
68+
observer.observe(contentElement, { childList: true, subtree: true });
69+
scrollToBottom();
70+
return () => observer.disconnect();
71+
}, []);
72+
73+
6174
return (
62-
<ContentWrapper>
75+
<ContentWrapper className='contentClass' ref={contentRef}>
6376
{!dataToJson(msgList).flag && <AgentPrologue agent={selectedAgent} />}
6477
{msgList?.length > 0 && (
6578
<>

frontend/src/pages/EKG/Common/Chat/ChatFooter.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,10 @@ const ChatFooter = () => {
154154
},
155155
];
156156
});
157+
// ps: 推master要改成localhost
157158
ssePost(
158-
'/api/portal/conversation/chat',
159+
'http://localhost:8080/api/portal/conversation/chat',
160+
// 'http://30.183.176.221:8080/api/portal/conversation/chat',
159161
{
160162
sessionId: props_sessionId,
161163
agentId: selectedAgent?.agentId,
@@ -227,9 +229,6 @@ const ChatFooter = () => {
227229
>
228230
<Form form={form} style={{ width: '100%', height: '100%' }}>
229231
<div className="InputMainWrapper">
230-
{/* <div style={{ width: '100%', height: '40px' }}>
231-
{renderHeader()}
232-
</div> */}
233232
<div
234233
className="InputWrapper"
235234
style={{

frontend/src/pages/EKG/Common/Chat/TemplateCode/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const TemplateCode = ({ dataSource }: TemplateIprops) => {
4949
return dataSource;
5050
}
5151
});
52-
}, 50); // 调整这个值以改变打字速度
52+
}, 20); // 调整这个值以改变打字速度
5353

5454
return () => {
5555
clearInterval(interval);

frontend/src/pages/EKG/Common/Chat/sseport.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const ssePost = async (
3838
fetchEventSource(`${url}`, {
3939
method: 'POST',
4040
headers,
41+
mode: 'cors',
4142
body: JSON.stringify(paramValues),
4243
openWhenHidden: true,
4344
signal: ctrl.signal,
@@ -104,10 +105,34 @@ export const ssePost = async (
104105
}
105106
},
106107
onmessage: async (msg) => {
107-
108108
if (msg.data !== 'start' && msg.data !== 'end') {
109109
if (msg.data && jsonToData(msg.data).data[0].type === "role_response") {
110-
currentMsg.push(jsonToData(msg.data).data[0]);
110+
const data = jsonToData(msg.data).data[0];
111+
propsObj?.setMsgList((previousList: any) => {
112+
const lastMessage = previousList[previousList.length - 1];
113+
if (lastMessage && lastMessage.content.output === '输出中') {
114+
return [
115+
...previousList.slice(0, -1),
116+
data,
117+
{
118+
type: 'json',
119+
msgId: Date.now(),
120+
content: {
121+
output: '输出中',
122+
input: '',
123+
role: 'assistant',
124+
messageType: 'json',
125+
conversationId: Date.now(),
126+
status: 'EXECUTING',
127+
},
128+
traceId: Date.now(),
129+
chatResultTypeCode: 'cover',
130+
streamingDisplay: false,
131+
},
132+
];
133+
}
134+
return previousList;
135+
});
111136
} else {
112137
currentMsg.push(jsonToData(msg.data).data[0]);
113138
}

muagent/base_configs/env_config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,7 @@
9090
SEARCH_ENGINE_TOP_K = os.environ.get("SEARCH_ENGINE_TOP_K") or 5
9191

9292
# 代码引擎匹配结题数量
93-
CODE_SEARCH_TOP_K = os.environ.get("CODE_SEARCH_TOP_K") or 1
93+
CODE_SEARCH_TOP_K = os.environ.get("CODE_SEARCH_TOP_K") or 1
94+
95+
# (Jieba) 自定义词库文件路径
96+
EXTRA_KEYWORDS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'extra_keywords.txt')
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
花呗
2+
借呗
3+
余额宝
Lines changed: 99 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Union, Optional
1+
from typing import Union, Optional, Sequence
2+
from dataclasses import dataclass
23

34

45
RECOGNIZE_INTENTION_PROMPT = """你是一个任务决策助手,能够将理解用户意图并决策采取最合适的行动,尽可能地以有帮助和准确的方式回应人类,
@@ -64,87 +65,119 @@
6465
'''
6566

6667

68+
GENERAL_INTENTION_PROMPT = """## 背景
69+
作为智能助手,您需要根据用户询问判断其主要意图,以确定接下来的行动。
70+
71+
## 任务
72+
找出最相关的意图,包括以下{num}种:
73+
{intention}
74+
75+
## 输出格式
76+
最相关意图对应的数字(第一个意图对应数字1){extra}。
77+
{example}
78+
## 用户询问
79+
"""
80+
81+
82+
@dataclass
83+
class IntentionInfo:
84+
description: str
85+
name: Optional[str] = None
86+
tag: Optional[str] = None
87+
88+
def __post_init__(self):
89+
marks = '.。,,;;::??\t\n '
90+
self.description = self.description.replace('{', '{{').replace('}', '}}').strip(marks)
91+
92+
if self.name:
93+
self.name = self.name.replace('{', '{{').replace('}', '}}').strip(marks)
94+
95+
def __str__(self):
96+
if self.name:
97+
return f'{self.name}{self.description}'
98+
return self.description
99+
100+
67101
def get_intention_prompt(
68-
background: str, intentions: Union[list, tuple], examples: Optional[dict]=None,
102+
intentions: Sequence[IntentionInfo],
103+
examples: Optional[dict[str, Union[IntentionInfo, Sequence[IntentionInfo]]]]=None,
69104
allow_multiple_choice=False
70105
) -> str:
71106
nums_zh = ('一', '两', '三', '四', '五', '六', '七', '八', '九', '十')
72-
marks = '.。,,;;::??\t\n'
73107

74108
intention_num = len(intentions)
75-
num_zh = nums_zh[intention_num - 1] if intention_num <= 10 else intention_num
76-
prompt = f'##背景##\n{background}\n\n##任务##\n找出最相关的意图,包括以下{num_zh}种:\n'
77-
78-
for i, val in enumerate(intentions):
79-
if isinstance(val, (list, tuple)):
80-
k, v = val
81-
cur_intention = '{}. {}:{};\n'.format(i + 1, k, v.strip(marks))
82-
else:
83-
cur_intention = '{}. {};\n'.format(i + 1, val.strip(marks))
84-
prompt += cur_intention
109+
intention_num_zh = nums_zh[intention_num - 1] if intention_num <= len(nums_zh) else intention_num
110+
111+
intentions_str, intention2idx = '', dict()
112+
for i, intention_info in enumerate(intentions):
113+
end_mark = '。' if i == intention_num - 1 else ';'
114+
intentions_str += '{}. {}{}\n'.format(i + 1, intention_info, end_mark)
115+
intention2idx[str(intention_info)] = i + 1
85116

86-
temp = ',若存在多个意图都是最相关的,请用","分开' if allow_multiple_choice else ''
87-
prompt += f'\n##输出格式##\n最相关意图对应的数字(第一个意图对应数字1){temp}\n\n'
117+
extra = ',若存在多个意图都是最相关的,请用","分开' if allow_multiple_choice else ''
88118

119+
example_str = ''
89120
if examples:
90-
prompt += '##示例##\n'
91-
intention_idx_map = {k[0]: idx + 1 for idx, k in enumerate(intentions)}
92-
for query, ans in examples.items():
93-
if not isinstance(ans, (list, tuple)):
94-
ans = [ans]
95-
ans = ','.join([str(intention_idx_map[x]) for x in ans])
96-
prompt += f'问题:{query}\n回答:{ans}\n\n'
121+
example_str += '\n## 示例\n'
122+
for query, label in examples.items():
123+
if isinstance(label, IntentionInfo):
124+
label = [label]
125+
label = ','.join([str(intention2idx[str(x)]) for x in label])
126+
example_str += f'问题:{query}\n回答:{label}\n'
97127

98-
prompt += '##用户询问##\n问题:{query}\n回答:'
128+
prompt = GENERAL_INTENTION_PROMPT.format(
129+
num=intention_num_zh,
130+
intention=intentions_str[:-1],
131+
extra=extra,
132+
example=example_str
133+
)
134+
135+
prompt += '问题:{query}\n回答:'
99136
return prompt
100137

101138

102-
INTENTIONS_CONSULT_WHICH = [
103-
('整体计划查询', '用户想要获取某个问题的答案,或某个解决方案的完整流程(步骤)。'),
104-
('下一步任务查询', '用户询问某个问题或方案的特定步骤,通常会提及“下一步”、“具体操作”等'),
105-
('闲聊', '用户询问的内容与当前的技术问题或解决方案无关,更多是出于兴趣或社交性质的交流。')
106-
]
107-
INTENTIONS_CONSULT_WHICH_CODE = {
108-
'整体计划查询': 'allPlan',
109-
'下一步任务查询': 'nextStep',
110-
'闲聊': 'justChat'
111-
}
112-
CONSULT_WHICH_PROMPT = get_intention_prompt(
113-
'作为智能助手,您的职责是根据用户询问的内容,精准判断其背后的意图,以便提供最恰当的服务和支持。',
114-
INTENTIONS_CONSULT_WHICH,
115-
{
116-
'如何组织一次活动?': '整体计划查询',
117-
'系统升级的整个流程是怎样的?': '整体计划查询',
118-
'为什么我没有收到红包?请告诉我方案': '整体计划查询',
119-
'听说你们采用了新工具,能讲讲它的特点吗?': '闲聊'
120-
}
139+
INTENTION_ALLPLAN = IntentionInfo(
140+
description='用户想要获取某个问题的答案,或某个解决方案的完整流程(步骤)。',
141+
name='整体计划查询', tag='allPlan'
142+
)
143+
INTENTION_NEXTSTEP = IntentionInfo(
144+
description='用户询问某个问题或方案的特定步骤,通常会提及“下一步”、“具体操作”等。',
145+
name='下一步任务查询', tag='nextStep'
146+
)
147+
INTENTION_CHAT = IntentionInfo(
148+
description='用户询问的内容与当前的技术问题或解决方案无关,更多是出于兴趣或社交性质的交流。',
149+
name='闲聊', tag='justChat'
150+
)
151+
INTENTION_EXECUTE = IntentionInfo(
152+
description='用户在使用某平台、服务、产品或功能时遇到了问题,或者要求完成某一需要执行的任务,或者明确声明要执行某一流程或游戏',
153+
name='执行'
154+
)
155+
INTENTION_QUERY = IntentionInfo(
156+
description='用户的主要目的是获取信息而非执行任务(比如:怎么向银行申请贷款),或只是简单闲聊。',
157+
name='询问'
158+
)
159+
INTENTION_NOMATCH = IntentionInfo(
160+
description='与上述意图都不匹配,属于其他类型的询问意图。'
121161
)
122162

123-
INTENTIONS_WHETHER_EXEC = [
124-
('执行', '用户在使用某平台、服务、产品或功能时遇到了问题,或者明确声明要执行某一流程或游戏。'),
125-
('询问', '用户的主要目的是获取信息,或只是简单闲聊。')
126-
]
163+
INTENTIONS_WHETHER_EXEC = (INTENTION_EXECUTE, INTENTION_QUERY)
127164
WHETHER_EXECUTE_PROMPT = get_intention_prompt(
128-
'作为智能助手,您需要根据用户询问判断其主要意图,以便提供最恰当的服务和支持。',
129-
INTENTIONS_WHETHER_EXEC,
130-
{
131-
'为什么我的优惠券使用失败?': '执行',
132-
'公司团建怎么申请?': '询问',
133-
'开始玩游戏。': '执行'
165+
intentions=INTENTIONS_WHETHER_EXEC,
166+
examples={
167+
'为什么我的优惠券使用失败?': INTENTION_EXECUTE,
168+
'如何向银行贷款?': INTENTION_QUERY,
169+
'开始玩游戏。': INTENTION_EXECUTE,
170+
'帮我看一下今天的天气怎么样': INTENTION_EXECUTE
134171
}
135172
)
136173

137-
DIRECT_CHAT_PROMPT = """##背景##
138-
作为智能助手,您的职责是根据自身专业知识回答用户询问,以便提供最恰当的服务和支持。
139-
140-
##任务##
141-
基于您所掌握的领域知识,对用户的提问进行回答。
142-
143-
##注意事项##
144-
1. 请尽量从客观的角度来回答问题,内容符合事实、有理有据。
145-
2. 内容尽量简洁。
146-
147-
##用户询问##
148-
询问:{query}
149-
回答:
150-
"""
174+
INTENTIONS_CONSULT_WHICH = (INTENTION_ALLPLAN, INTENTION_NEXTSTEP, INTENTION_CHAT)
175+
CONSULT_WHICH_PROMPT = get_intention_prompt(
176+
intentions=INTENTIONS_CONSULT_WHICH,
177+
examples={
178+
'如何组织一次活动?': INTENTION_ALLPLAN,
179+
'系统升级的整个流程是怎样的?': INTENTION_ALLPLAN,
180+
'为什么我没有收到红包?请告诉我方案': INTENTION_ALLPLAN,
181+
'听说你们采用了新工具,能讲讲它的特点吗?': INTENTION_CHAT
182+
}
183+
)

muagent/service/ekg_construct/ekg_construct_base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from loguru import logger
22
import re
3+
import os
34
import json
45
from typing import List, Dict, Optional, Tuple, Literal
56

@@ -15,12 +16,14 @@
1516
from jieba.analyse import extract_tags
1617

1718
import time
19+
import jieba
1820

1921
from muagent.schemas.ekg import *
2022
from muagent.schemas.db import *
2123
from muagent.schemas.common import *
2224
from muagent.db_handler import *
2325
from muagent.orm import table_init
26+
from muagent.base_configs.env_config import EXTRA_KEYWORDS_PATH
2427

2528
from muagent.connector.configs.generate_prompt import *
2629

@@ -81,6 +84,9 @@ def __init__(
8184
# init db handler
8285
self.initialize_space = initialize_space
8386
self.init_handler()
87+
# load custom keywords
88+
if os.path.exists(EXTRA_KEYWORDS_PATH):
89+
jieba.load_userdict(EXTRA_KEYWORDS_PATH)
8490

8591
def init_handler(self, ):
8692
"""Initializes Database VectorBase GraphDB TbaseDB"""

0 commit comments

Comments
 (0)