Skip to content

Commit 3e6aa67

Browse files
authored
Feat/zh036 (#898)
2 parents bef1878 + e6bf853 commit 3e6aa67

60 files changed

Lines changed: 2255 additions & 167 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docker/docker-compose.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ services:
5353

5454
backend:
5555
container_name: bisheng-backend
56-
image: dataelement/bisheng-backend:v0.3.6.dev1
56+
image: dataelement/bisheng-backend:v0.3.7.dev1
5757
ports:
5858
- "7860:7860"
5959
environment:
@@ -92,7 +92,7 @@ services:
9292

9393
frontend:
9494
container_name: bisheng-frontend
95-
image: dataelement/bisheng-frontend:v0.3.6.dev1
95+
image: dataelement/bisheng-frontend:v0.3.7.dev1
9696
ports:
9797
- "3001:3001"
9898
environment:

‎src/backend/None‎

Whitespace-only changes.

‎src/backend/bisheng/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
try:
88
# 通过ci去自动修改
9-
__version__ = '0.3.6.dev1'
9+
__version__ = '0.3.7.dev1'
1010
except metadata.PackageNotFoundError:
1111
# Case where package metadata is not available.
1212
__version__ = ''

‎src/backend/bisheng/api/errcode/knowledge.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,8 @@ class KnowledgeChunkError(BaseErrorCode):
2020
class KnowledgeSimilarError(BaseErrorCode):
2121
Code: int = 10920
2222
Msg: str = '未配置QA知识库相似问模型'
23+
24+
25+
class KnowledgeQAError(BaseErrorCode):
26+
Code: int = 10930
27+
Msg: str = '该问题已被标注过'

‎src/backend/bisheng/api/router.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
finetune_router, flows_router, group_router, knowledge_router,
44
qa_router, report_router, server_router, skillcenter_router,
55
user_router, validate_router, variable_router, audit_router, evaluation_router,
6-
tag_router, llm_router)
6+
tag_router, llm_router,mark_router)
77
from bisheng.api.v2 import chat_router_rpc, knowledge_router_rpc, rpc_router_rpc, flow_router, assistant_router_rpc
88
from fastapi import APIRouter
99

@@ -27,6 +27,7 @@
2727
router.include_router(evaluation_router)
2828
router.include_router(tag_router)
2929
router.include_router(llm_router)
30+
router.include_router(mark_router)
3031

3132
router_rpc = APIRouter(prefix='/api/v2', )
3233
router_rpc.include_router(knowledge_router_rpc)

‎src/backend/bisheng/api/services/chat_imp.py‎

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,82 @@
1+
import asyncio
2+
import json
3+
# 设置 websockets 的日志级别为 NONE
4+
import logging
5+
from collections import defaultdict
6+
from datetime import datetime, timedelta
7+
8+
from bisheng.api.v1.schemas import resp_500
19
from bisheng.database.base import session_getter
210
from bisheng.database.models.message import ChatMessage
11+
from pydantic import BaseModel
12+
from websockets import connect
13+
14+
# 维护一个连接池
15+
connection_pool = defaultdict(asyncio.Queue)
16+
logging.getLogger('websockets').setLevel(logging.ERROR)
17+
18+
expire = 600 # reids 60s 过期
19+
20+
21+
class TimedQueue:
22+
23+
def __init__(self):
24+
self.queue = asyncio.Queue()
25+
self.last_active = datetime.now()
26+
27+
async def put_nowait(self, item):
28+
self.last_active = datetime.now()
29+
await self.queue.put(item)
30+
31+
async def get_nowait(self):
32+
self.last_active = datetime.now()
33+
return await self.queue.get()
34+
35+
def empty(self):
36+
return self.queue.empty()
37+
38+
def qsize(self):
39+
return self.queue.qsize()
40+
41+
42+
async def clean_inactive_queues(queue: defaultdict, timeout_threshold: timedelta):
43+
while True:
44+
current_time = datetime.now()
45+
for key, timed_queue in list(queue.items()):
46+
# 如果队列超过设定的阈值时间没有活跃,则清理队列
47+
if current_time - timed_queue.last_active > timeout_threshold:
48+
while not timed_queue.empty():
49+
timed_queue.get_nowait() # 从队列中移除任务
50+
del queue[key] # 删除队列
51+
await asyncio.sleep(timeout_threshold.total_seconds())
52+
53+
54+
# 维护一个连接池
55+
connection_pool = defaultdict(TimedQueue)
56+
clean_inactive_queues(connection_pool, timedelta(minutes=5))
57+
58+
59+
async def get_connection(uri, identifier):
60+
"""
61+
获取WebSocket连接。如果连接池中有可用的连接,则直接返回;
62+
否则,创建新的连接并添加到连接池。
63+
"""
64+
if connection_pool[identifier].empty():
65+
# 建立新的WebSocket连接
66+
websocket = await connect(uri)
67+
68+
await connection_pool[identifier].put_nowait(websocket)
69+
70+
# 从连接池中获取连接
71+
websocket = await connection_pool[identifier].get_nowait()
72+
return websocket
73+
74+
75+
async def release_connection(identifier, websocket):
76+
"""
77+
释放WebSocket连接,将其放回连接池。
78+
"""
79+
await connection_pool[identifier].put_nowait(websocket)
380

481

582
def comment_answer(message_id: int, comment: str):
@@ -9,3 +86,68 @@ def comment_answer(message_id: int, comment: str):
986
message.remark = comment[:4096]
1087
session.add(message)
1188
session.commit()
89+
90+
91+
class ContentStreamResp(BaseModel):
92+
role: str
93+
content: str
94+
95+
96+
class ChoiceStreamResp(BaseModel):
97+
index: int = 0
98+
delta: ContentStreamResp = 0
99+
session_id: str
100+
101+
def __str__(self) -> str:
102+
jsonData = '{"index": "%s", "delta": %s, "session_id": "%s"}' % (
103+
self.index, json.dumps(self.delta.dict(), ensure_ascii=False), self.session_id)
104+
return '{"choices":[%s]}\n\n' % (jsonData)
105+
106+
107+
async def event_stream(
108+
webosocket: connect,
109+
message: str,
110+
session_id: str,
111+
model: str,
112+
streaming: bool,
113+
):
114+
115+
payload = {'inputs': message, 'flow_id': model, 'chat_id': session_id}
116+
try:
117+
await webosocket.send(json.dumps(payload, ensure_ascii=False))
118+
except Exception as e:
119+
yield json.dumps(resp_500(message=str(e)).__dict__)
120+
return
121+
sync = ''
122+
while True:
123+
try:
124+
msg = await webosocket.recv()
125+
except Exception as e:
126+
yield json.dumps(resp_500(message=str(e)).__dict__)
127+
break
128+
if msg is None:
129+
continue
130+
# 判断msg 的类型
131+
res = json.loads(msg)
132+
if streaming:
133+
if res.get('type') != 'end' and res.get('message'):
134+
delta = ContentStreamResp(role='assistant', content=res.get('message'))
135+
yield str(ChoiceStreamResp(index=0, session_id=session_id, delta=delta))
136+
else:
137+
# 通过此处控制下面的close是否发送消息
138+
if res.get('type') == 'end':
139+
sync = res.get('message')
140+
141+
if res.get('type') == 'close':
142+
if not streaming and sync:
143+
delta = ContentStreamResp(role='assistant', content=sync)
144+
msg = ChoiceStreamResp(index=0,
145+
session_id=session_id,
146+
delta=delta,
147+
finish_reason='stop')
148+
yield '{"choices":[%s]}' % (json.dumps(msg.dict()))
149+
# 释放连接
150+
elif streaming:
151+
yield 'data: [DONE]'
152+
await release_connection(session_id, webosocket)
153+
break

‎src/backend/bisheng/api/services/finetune.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ def parse_log_data(cls, log_data: str) -> List[Dict[str, str]]:
406406
sub_data = {'step': None, 'loss': None}
407407
elem = elem.strip()
408408
elem_data = json.loads(elem)
409-
if elem_data['loss'] is None:
409+
if elem_data.get('loss', None) is None:
410410
continue
411411
sub_data['step'] = elem_data['current_steps']
412412
sub_data['loss'] = elem_data['loss']

‎src/backend/bisheng/api/v1/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from bisheng.api.v1.evaluation import router as evaluation_router
1818
from bisheng.api.v1.tag import router as tag_router
1919
from bisheng.api.v1.llm import router as llm_router
20+
from bisheng.api.v1.mark_task import router as mark_router
2021

2122
__all__ = [
2223
'chat_router',
@@ -38,4 +39,5 @@
3839
'audit_router',
3940
'tag_router',
4041
'llm_router',
42+
'mark_router',
4143
]

‎src/backend/bisheng/api/v1/callback.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,10 @@ async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
509509
# 从tool cache中获取input信息
510510
input_info = self.tool_cache.get(kwargs.get('run_id').hex)
511511
if input_info:
512+
if not self.chat_id:
513+
# 说明是调试界面,不用持久化数据
514+
self.tool_cache.pop(kwargs.get('run_id').hex)
515+
return
512516
output_info.update(input_info['input'])
513517
intermediate_steps = f'{input_info["steps"]}\n\n{intermediate_steps}'
514518
ChatMessageDao.insert_one(
@@ -541,6 +545,10 @@ async def on_tool_error(self, error: Union[Exception, KeyboardInterrupt],
541545
await self.websocket.send_json(resp.dict())
542546

543547
# 保存工具调用记录
548+
if not self.chat_id:
549+
# 说明是调试界面,不用持久化数据
550+
self.tool_cache.pop(kwargs.get('run_id').hex)
551+
return
544552
tool_name, tool_category = self.parse_tool_category(kwargs.get('name'))
545553
self.tool_cache.pop(kwargs.get('run_id').hex)
546554
ChatMessageDao.insert_one(

0 commit comments

Comments
 (0)