Skip to content

Commit 2585526

Browse files
committed
fix: parse JSON error during typing answer in chat
1 parent 1967ea1 commit 2585526

File tree

5 files changed

+213
-222
lines changed

5 files changed

+213
-222
lines changed

backend/apps/chat/task/llm.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ def run_task(self, in_chat: bool = True):
805805
try:
806806
# return id
807807
if in_chat:
808-
yield orjson.dumps({'type': 'id', 'id': self.get_record().id}).decode() + '\n\n'
808+
yield 'data:' + orjson.dumps({'type': 'id', 'id': self.get_record().id}).decode() + '\n\n'
809809

810810
# return title
811811
if self.change_title:
@@ -814,7 +814,7 @@ def run_task(self, in_chat: bool = True):
814814
rename_object=RenameChat(id=self.get_record().chat_id,
815815
brief=self.chat_question.question.strip()[:20]))
816816
if in_chat:
817-
yield orjson.dumps({'type': 'brief', 'brief': brief}).decode() + '\n\n'
817+
yield 'data:' + orjson.dumps({'type': 'brief', 'brief': brief}).decode() + '\n\n'
818818

819819
# select datasource if datasource is none
820820
if not self.ds:
@@ -823,11 +823,11 @@ def run_task(self, in_chat: bool = True):
823823
for chunk in ds_res:
824824
SQLBotLogUtil.info(chunk)
825825
if in_chat:
826-
yield orjson.dumps(
826+
yield 'data:' + orjson.dumps(
827827
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
828828
'type': 'datasource-result'}).decode() + '\n\n'
829829
if in_chat:
830-
yield orjson.dumps({'id': self.ds.id, 'datasource_name': self.ds.name,
830+
yield 'data:' + orjson.dumps({'id': self.ds.id, 'datasource_name': self.ds.name,
831831
'engine_type': self.ds.type_name or self.ds.type,
832832
'type': 'datasource'}).decode() + '\n\n'
833833

@@ -843,11 +843,11 @@ def run_task(self, in_chat: bool = True):
843843
for chunk in sql_res:
844844
full_sql_text += chunk.get('content')
845845
if in_chat:
846-
yield orjson.dumps(
846+
yield 'data:' + orjson.dumps(
847847
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
848848
'type': 'sql-result'}).decode() + '\n\n'
849849
if in_chat:
850-
yield orjson.dumps({'type': 'info', 'msg': 'sql generated'}).decode() + '\n\n'
850+
yield 'data:' + orjson.dumps({'type': 'info', 'msg': 'sql generated'}).decode() + '\n\n'
851851

852852
# filter sql
853853
SQLBotLogUtil.info(full_sql_text)
@@ -881,34 +881,34 @@ def run_task(self, in_chat: bool = True):
881881
SQLBotLogUtil.info(sql)
882882
format_sql = sqlparse.format(sql, reindent=True)
883883
if in_chat:
884-
yield orjson.dumps({'content': format_sql, 'type': 'sql'}).decode() + '\n\n'
884+
yield 'data:' + orjson.dumps({'content': format_sql, 'type': 'sql'}).decode() + '\n\n'
885885
else:
886886
yield f'```sql\n{format_sql}\n```\n\n'
887887

888888
# execute sql
889889
result = self.execute_sql(sql=sql)
890890
self.save_sql_data(data_obj=result)
891891
if in_chat:
892-
yield orjson.dumps({'content': 'execute-success', 'type': 'sql-data'}).decode() + '\n\n'
892+
yield 'data:' + orjson.dumps({'content': 'execute-success', 'type': 'sql-data'}).decode() + '\n\n'
893893

894894
# generate chart
895895
chart_res = self.generate_chart()
896896
full_chart_text = ''
897897
for chunk in chart_res:
898898
full_chart_text += chunk.get('content')
899899
if in_chat:
900-
yield orjson.dumps(
900+
yield 'data:' + orjson.dumps(
901901
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
902902
'type': 'chart-result'}).decode() + '\n\n'
903903
if in_chat:
904-
yield orjson.dumps({'type': 'info', 'msg': 'chart generated'}).decode() + '\n\n'
904+
yield 'data:' + orjson.dumps({'type': 'info', 'msg': 'chart generated'}).decode() + '\n\n'
905905

906906
# filter chart
907907
SQLBotLogUtil.info(full_chart_text)
908908
chart = self.check_save_chart(res=full_chart_text)
909909
SQLBotLogUtil.info(chart)
910910
if in_chat:
911-
yield orjson.dumps({'content': orjson.dumps(chart).decode(), 'type': 'chart'}).decode() + '\n\n'
911+
yield 'data:' + orjson.dumps({'content': orjson.dumps(chart).decode(), 'type': 'chart'}).decode() + '\n\n'
912912
else:
913913
data = []
914914
_fields = {}
@@ -940,7 +940,7 @@ def run_task(self, in_chat: bool = True):
940940

941941
record = self.finish()
942942
if in_chat:
943-
yield orjson.dumps({'type': 'finish'}).decode() + '\n\n'
943+
yield 'data:' + orjson.dumps({'type': 'finish'}).decode() + '\n\n'
944944
else:
945945
# todo generate picture
946946
if chart['type'] != 'table':
@@ -956,7 +956,7 @@ def run_task(self, in_chat: bool = True):
956956
error_msg = orjson.dumps({'message': str(e), 'traceback': traceback.format_exc(limit=1)}).decode()
957957
self.save_error(message=error_msg)
958958
if in_chat:
959-
yield orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
959+
yield 'data:' + orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
960960
else:
961961
yield f'> ❌ **ERROR**\n\n> \n\n> {error_msg}。'
962962

@@ -990,37 +990,37 @@ def run_analysis_or_predict_task_cache(self, action_type: str):
990990
def run_analysis_or_predict_task(self, action_type: str):
991991
try:
992992

993-
yield orjson.dumps({'type': 'id', 'id': self.get_record().id}).decode() + '\n\n'
993+
yield 'data:' + orjson.dumps({'type': 'id', 'id': self.get_record().id}).decode() + '\n\n'
994994

995995
if action_type == 'analysis':
996996
# generate analysis
997997
analysis_res = self.generate_analysis()
998998
for chunk in analysis_res:
999-
yield orjson.dumps(
999+
yield 'data:' + orjson.dumps(
10001000
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
10011001
'type': 'analysis-result'}).decode() + '\n\n'
1002-
yield orjson.dumps({'type': 'info', 'msg': 'analysis generated'}).decode() + '\n\n'
1002+
yield 'data:' + orjson.dumps({'type': 'info', 'msg': 'analysis generated'}).decode() + '\n\n'
10031003

1004-
yield orjson.dumps({'type': 'analysis_finish'}).decode() + '\n\n'
1004+
yield 'data:' + orjson.dumps({'type': 'analysis_finish'}).decode() + '\n\n'
10051005

10061006
elif action_type == 'predict':
10071007
# generate predict
10081008
analysis_res = self.generate_predict()
10091009
full_text = ''
10101010
for chunk in analysis_res:
1011-
yield orjson.dumps(
1011+
yield 'data:' + orjson.dumps(
10121012
{'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'),
10131013
'type': 'predict-result'}).decode() + '\n\n'
10141014
full_text += chunk.get('content')
1015-
yield orjson.dumps({'type': 'info', 'msg': 'predict generated'}).decode() + '\n\n'
1015+
yield 'data:' + orjson.dumps({'type': 'info', 'msg': 'predict generated'}).decode() + '\n\n'
10161016

10171017
_data = self.check_save_predict_data(res=full_text)
10181018
if _data:
1019-
yield orjson.dumps({'type': 'predict-success'}).decode() + '\n\n'
1019+
yield 'data:' + orjson.dumps({'type': 'predict-success'}).decode() + '\n\n'
10201020
else:
1021-
yield orjson.dumps({'type': 'predict-failed'}).decode() + '\n\n'
1021+
yield 'data:' + orjson.dumps({'type': 'predict-failed'}).decode() + '\n\n'
10221022

1023-
yield orjson.dumps({'type': 'predict_finish'}).decode() + '\n\n'
1023+
yield 'data:' + orjson.dumps({'type': 'predict_finish'}).decode() + '\n\n'
10241024

10251025
self.finish()
10261026
except Exception as e:
@@ -1030,7 +1030,7 @@ def run_analysis_or_predict_task(self, action_type: str):
10301030
else:
10311031
error_msg = orjson.dumps({'message': str(e), 'traceback': traceback.format_exc(limit=1)}).decode()
10321032
self.save_error(message=error_msg)
1033-
yield orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
1033+
yield 'data:' + orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
10341034
finally:
10351035
# end
10361036
pass

frontend/src/views/chat/answer/AnalysisAnswer.vue

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,13 @@ const sendMessage = async () => {
9898
const controller: AbortController = new AbortController()
9999
const response = await chatApi.analysis(currentRecord.analysis_record_id, controller)
100100
const reader = response.body.getReader()
101-
const decoder = new TextDecoder()
101+
const decoder = new TextDecoder('utf-8')
102102
103103
let analysis_answer = ''
104104
let analysis_answer_thinking = ''
105105
106+
let tempResult = ''
107+
106108
while (true) {
107109
if (stopFlag.value) {
108110
controller.abort()
@@ -116,65 +118,61 @@ const sendMessage = async () => {
116118
break
117119
}
118120
119-
const chunk = decoder.decode(value)
120-
121-
let _list = [chunk]
122-
123-
const lines = chunk.trim().split('}\n\n{')
124-
if (lines.length > 1) {
125-
_list = []
126-
for (let line of lines) {
127-
if (!line.trim().startsWith('{')) {
128-
line = '{' + line.trim()
129-
}
130-
if (!line.trim().endsWith('}')) {
131-
line = line.trim() + '}'
132-
}
133-
_list.push(line)
134-
}
121+
let chunk = decoder.decode(value, { stream: true })
122+
tempResult += chunk
123+
const split = tempResult.match(/data:.*}\n\n/g)
124+
if (split) {
125+
chunk = split.join('')
126+
tempResult = tempResult.replace(chunk, '')
127+
} else {
128+
continue
135129
}
136-
for (const str of _list) {
137-
let data
138-
try {
139-
data = JSON.parse(str)
140-
} catch (err) {
141-
console.error('JSON string:', str)
142-
throw err
143-
}
144-
145-
if (data.code && data.code !== 200) {
146-
ElMessage({
147-
message: data.msg,
148-
type: 'error',
149-
showClose: true,
150-
})
151-
_loading.value = false
152-
return
153-
}
154-
155-
switch (data.type) {
156-
case 'id':
157-
currentRecord.id = data.id
158-
_currentChat.value.records[index.value].id = data.id
159-
break
160-
case 'info':
161-
console.info(data.msg)
162-
break
163-
case 'error':
164-
currentRecord.error = data.content
165-
emits('error')
166-
break
167-
case 'analysis-result':
168-
analysis_answer += data.content
169-
analysis_answer_thinking += data.reasoning_content
170-
_currentChat.value.records[index.value].analysis = analysis_answer
171-
_currentChat.value.records[index.value].analysis_thinking = analysis_answer_thinking
172-
break
173-
case 'analysis_finish':
174-
emits('finish', currentRecord.id)
175-
break
130+
if (chunk && chunk.startsWith('data:{')) {
131+
if (split) {
132+
for (const str of split) {
133+
let data
134+
try {
135+
data = JSON.parse(str.replace('data:{', '{'))
136+
} catch (err) {
137+
console.error('JSON string:', str)
138+
throw err
139+
}
140+
141+
if (data.code && data.code !== 200) {
142+
ElMessage({
143+
message: data.msg,
144+
type: 'error',
145+
showClose: true,
146+
})
147+
_loading.value = false
148+
return
149+
}
150+
151+
switch (data.type) {
152+
case 'id':
153+
currentRecord.id = data.id
154+
_currentChat.value.records[index.value].id = data.id
155+
break
156+
case 'info':
157+
console.info(data.msg)
158+
break
159+
case 'error':
160+
currentRecord.error = data.content
161+
emits('error')
162+
break
163+
case 'analysis-result':
164+
analysis_answer += data.content
165+
analysis_answer_thinking += data.reasoning_content
166+
_currentChat.value.records[index.value].analysis = analysis_answer
167+
_currentChat.value.records[index.value].analysis_thinking = analysis_answer_thinking
168+
break
169+
case 'analysis_finish':
170+
emits('finish', currentRecord.id)
171+
break
172+
}
173+
await nextTick()
174+
}
176175
}
177-
await nextTick()
178176
}
179177
}
180178
} catch (error) {

0 commit comments

Comments
 (0)