Skip to content

Commit 094bc9e

Browse files
committed
Merge branch 'main' of https://github.com/dataease/SQLBot
2 parents 284576e + 3071646 commit 094bc9e

File tree

6 files changed

+162
-61
lines changed

6 files changed

+162
-61
lines changed

backend/apps/chat/task/llm.py

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from apps.system.schemas.system_schema import AssistantOutDsSchema
3939
from common.core.config import settings
4040
from common.core.deps import CurrentAssistant, CurrentUser
41+
from common.error import SingleMessageError
4142
from common.utils.utils import SQLBotLogUtil, extract_nested_json, prepare_for_orjson
4243

4344
warnings.filterwarnings("ignore")
@@ -78,7 +79,7 @@ def __init__(self, current_user: CurrentUser, chat_question: ChatQuestion,
7879
chat_id = chat_question.chat_id
7980
chat: Chat | None = self.session.get(Chat, chat_id)
8081
if not chat:
81-
raise Exception(f"Chat with id {chat_id} not found")
82+
raise SingleMessageError(f"Chat with id {chat_id} not found")
8283
ds: CoreDatasource | AssistantOutDsSchema | None = None
8384
if chat.datasource:
8485
# Get available datasource
@@ -87,13 +88,13 @@ def __init__(self, current_user: CurrentUser, chat_question: ChatQuestion,
8788
self.out_ds_instance = AssistantOutDsFactory.get_instance(current_assistant)
8889
ds = self.out_ds_instance.get_ds(chat.datasource)
8990
if not ds:
90-
raise Exception("No available datasource configuration found")
91+
raise SingleMessageError("No available datasource configuration found")
9192
chat_question.engine = ds.type
9293
chat_question.db_schema = self.out_ds_instance.get_db_schema(ds.id)
9394
else:
9495
ds = self.session.get(CoreDatasource, chat.datasource)
9596
if not ds:
96-
raise Exception("No available datasource configuration found")
97+
raise SingleMessageError("No available datasource configuration found")
9798
chat_question.engine = ds.type_name if ds.type != 'excel' else 'PostgreSQL'
9899
chat_question.db_schema = get_table_schema(session=self.session, current_user=current_user, ds=ds)
99100

@@ -446,10 +447,11 @@ def select_datasource(self):
446447
_ds = self.session.get(CoreDatasource, _datasource)
447448
if not _ds:
448449
_datasource = None
449-
raise Exception(f"Datasource configuration with id {_datasource} not found")
450+
raise SingleMessageError(f"Datasource configuration with id {_datasource} not found")
450451
self.ds = CoreDatasource(**_ds.model_dump())
451452
self.chat_question.engine = _ds.type_name if _ds.type != 'excel' else 'PostgreSQL'
452-
self.chat_question.db_schema = get_table_schema(session=self.session, current_user=self.current_user, ds=self.ds)
453+
self.chat_question.db_schema = get_table_schema(session=self.session,
454+
current_user=self.current_user, ds=self.ds)
453455
_engine_type = self.chat_question.engine
454456
_chat.engine_type = _ds.type_name
455457
# save chat
@@ -459,9 +461,9 @@ def select_datasource(self):
459461
self.session.commit()
460462

461463
elif data['fail']:
462-
raise Exception(data['fail'])
464+
raise SingleMessageError(data['fail'])
463465
else:
464-
raise Exception('No available datasource configuration found')
466+
raise SingleMessageError('No available datasource configuration found')
465467

466468
except Exception as e:
467469
_error = e
@@ -542,7 +544,7 @@ def generate_with_sub_sql(self, sql, sub_mappings: list):
542544

543545
SQLBotLogUtil.info(full_dynamic_text)
544546
return full_dynamic_text
545-
547+
546548
def generate_assistant_dynamic_sql(self, sql, tables: List):
547549
ds: AssistantOutDsSchema = self.ds
548550
sub_query = []
@@ -552,7 +554,7 @@ def generate_assistant_dynamic_sql(self, sql, tables: List):
552554
if not sub_query:
553555
return None
554556
return self.generate_with_sub_sql(sql=sql, sub_mappings=sub_query)
555-
557+
556558
def build_table_filter(self, sql: str, filters: list):
557559
filter = json.dumps(filters, ensure_ascii=False)
558560
self.chat_question.sql = sql
@@ -672,23 +674,25 @@ def generate_chart(self):
672674
full_message=orjson.dumps(
673675
[{'type': msg.type, 'content': msg.content} for msg in
674676
self.chart_message]).decode())
677+
675678
def check_sql(self, res: str) -> tuple[any]:
676679
json_str = extract_nested_json(res)
677680
if json_str is None:
678-
raise Exception("Cannot parse sql from answer")
681+
raise SingleMessageError(orjson.dumps({'message': 'Cannot parse sql from answer',
682+
'traceback': "Cannot parse sql from answer:\n" + res}).decode())
679683
data: dict = orjson.loads(json_str)
680684
sql = ''
681685
message = ''
682686
if data['success']:
683687
sql = data['sql']
684688
else:
685689
message = data['message']
686-
raise Exception(message)
687-
690+
raise SingleMessageError(message)
691+
688692
if sql.strip() == '':
689-
raise Exception("SQL query is empty")
693+
raise SingleMessageError("SQL query is empty")
690694
return sql, data.get('tables')
691-
695+
692696
def check_save_sql(self, res: str) -> str:
693697
sql, *_ = self.check_sql(res=res)
694698
save_sql(session=self.session, sql=sql, record_id=self.record.id)
@@ -701,7 +705,8 @@ def check_save_chart(self, res: str) -> Dict[str, Any]:
701705

702706
json_str = extract_nested_json(res)
703707
if json_str is None:
704-
raise Exception("Cannot parse chart config from answer")
708+
raise SingleMessageError(orjson.dumps({'message': 'Cannot parse chart config from answer',
709+
'traceback': "Cannot parse chart config from answer:\n" + res}).decode())
705710
data = orjson.loads(json_str)
706711

707712
chart: Dict[str, Any] = {}
@@ -729,7 +734,7 @@ def check_save_chart(self, res: str) -> Dict[str, Any]:
729734
error = True
730735

731736
if error:
732-
raise Exception(message)
737+
raise SingleMessageError(message)
733738

734739
save_chart(session=self.session, chart=orjson.dumps(chart).decode(), record_id=self.record.id)
735740

@@ -865,7 +870,7 @@ def run_task(self, in_chat: bool = True):
865870
if dynamic_sql_result:
866871
SQLBotLogUtil.info(dynamic_sql_result)
867872
sql, *_ = self.check_sql(res=dynamic_sql_result)
868-
873+
869874
sql_result = self.generate_assistant_filter(sql, tables)
870875
else:
871876
sql_result = self.generate_filter(sql, tables) # maybe no sql and tables
@@ -951,12 +956,16 @@ def run_task(self, in_chat: bool = True):
951956
SQLBotLogUtil.info(image_url)
952957
yield f'![{chart["type"]}]({image_url})'
953958
except Exception as e:
954-
traceback.print_exc()
955-
self.save_error(message=str(e))
959+
error_msg: str
960+
if isinstance(e, SingleMessageError):
961+
error_msg = str(e)
962+
else:
963+
error_msg = orjson.dumps({'message': str(e), 'traceback': traceback.format_exc(limit=1)}).decode()
964+
self.save_error(message=error_msg)
956965
if in_chat:
957-
yield orjson.dumps({'content': str(e), 'type': 'error'}).decode() + '\n\n'
966+
yield orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
958967
else:
959-
yield f'> ❌ **ERROR**\n\n> \n\n> {str(e)}。'
968+
yield f'> ❌ **ERROR**\n\n> \n\n> {error_msg}。'
960969

961970
def run_recommend_questions_task_async(self):
962971
self.future = executor.submit(self.run_recommend_questions_task_cache)
@@ -1022,31 +1031,37 @@ def run_analysis_or_predict_task(self, action_type: str):
10221031

10231032
self.finish()
10241033
except Exception as e:
1025-
traceback.print_exc()
1026-
self.save_error(message=str(e))
1027-
yield orjson.dumps({'content': str(e), 'type': 'error'}).decode() + '\n\n'
1034+
error_msg: str
1035+
if isinstance(e, SingleMessageError):
1036+
error_msg = str(e)
1037+
else:
1038+
error_msg = orjson.dumps({'message': str(e), 'traceback': traceback.format_exc(limit=1)}).decode()
1039+
self.save_error(message=error_msg)
1040+
yield orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n'
10281041
finally:
10291042
# end
10301043
pass
1031-
1044+
10321045
def validate_history_ds(self):
10331046
_ds = self.ds
10341047
if not self.current_assistant:
1035-
current_ds = self.session.get(CoreDatasource, _ds.id)
1036-
if not current_ds:
1037-
raise Exception('ds is invalid')
1048+
try:
1049+
current_ds = self.session.get(CoreDatasource, _ds.id)
1050+
if not current_ds:
1051+
raise SingleMessageError('chat.ds_is_invalid')
1052+
except Exception as e:
1053+
raise SingleMessageError("chat.ds_is_invalid")
10381054
else:
10391055
try:
10401056
_ds_list: list[dict] = get_assistant_ds(session=self.session, llm_service=self)
10411057
match_ds = any(item.get("id") == _ds.id for item in _ds_list)
10421058
if not match_ds:
10431059
type = self.current_assistant.type
10441060
msg = f"ds is invalid [please check ds list and public ds list]" if type == 0 else f"ds is invalid [please check ds api]"
1045-
raise Exception(msg)
1061+
raise SingleMessageError(msg)
10461062
except Exception as e:
1047-
raise Exception(f"ds is invalid [{str(e)}]")
1048-
1049-
1063+
raise SingleMessageError(f"ds is invalid [{str(e)}]")
1064+
10501065

10511066
def execute_sql_with_db(db: SQLDatabase, sql: str) -> str:
10521067
"""Execute SQL query using SQLDatabase

backend/common/error.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class SingleMessageError(Exception):
2+
def __init__(self, message):
3+
super().__init__(message)
4+
self.message = message
5+
6+
def __str__(self):
7+
return self.message

frontend/src/i18n/en.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,8 @@
391391
"add_rule_group": "Add rule group",
392392
"permission_rule": "Permission rules",
393393
"restricted_user": "Restricted user",
394-
"set_rule": "Setup rule",
395-
"set_user": "Setup user",
394+
"set_rule": "Set rule",
395+
"set_user": "Set user",
396396
"2": "{msg}",
397397
"238_people": "{msg} people",
398398
"no_permission_rule": "No permission rule",
@@ -525,7 +525,10 @@
525525
"inference_process": "Thought Process",
526526
"thinking": "Thinking",
527527
"data_analysis": "Data Analysis",
528-
"data_predict": "Data Prediction"
528+
"data_predict": "Data Prediction",
529+
"ds_is_invalid": "Datasource is invalid",
530+
"error": "Error",
531+
"show_error_detail": "Show error info"
529532
},
530533
"about": {
531534
"title": "About",
@@ -548,4 +551,4 @@
548551
"back_community": "Restore to Community Edition",
549552
"confirm_tips": "Are you sure to restore to Community Edition?"
550553
}
551-
}
554+
}

frontend/src/i18n/zh-CN.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,10 @@
525525
"inference_process": "思考过程",
526526
"thinking": "思考中",
527527
"data_analysis": "数据分析",
528-
"data_predict": "数据预测"
528+
"data_predict": "数据预测",
529+
"ds_is_invalid": "数据源无效",
530+
"error": "错误",
531+
"show_error_detail": "查看具体信息"
529532
},
530533
"about": {
531534
"title": "关于",
@@ -548,4 +551,4 @@
548551
"back_community": "还原至社区版",
549552
"confirm_tips": "确定还原至社区版?"
550553
}
551-
}
554+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<script setup lang="ts">
2+
import { ref, computed } from 'vue'
3+
import { useI18n } from 'vue-i18n'
4+
import { useAssistantStore } from '@/stores/assistant.ts'
5+
6+
const props = defineProps<{
7+
error?: string
8+
}>()
9+
10+
const { t } = useI18n()
11+
12+
const assistantStore = useAssistantStore()
13+
const isAssistant = computed(() => assistantStore.getAssistant)
14+
15+
const showBlock = computed(() => {
16+
return props.error && props.error?.trim().length > 0
17+
})
18+
19+
const errorMessage = computed(() => {
20+
const obj = { message: props.error, showMore: false, traceback: '' }
21+
if (showBlock.value && props.error?.trim().startsWith('{') && props.error?.trim().endsWith('}')) {
22+
try {
23+
const json = JSON.parse(props.error?.trim())
24+
obj.message = json['message']
25+
obj.traceback = json['traceback']
26+
if (obj.traceback?.trim().length > 0) {
27+
obj.showMore = true
28+
}
29+
} catch (e) {
30+
console.error(e)
31+
}
32+
}
33+
return obj
34+
})
35+
36+
const show = ref(false)
37+
38+
function showTraceBack() {
39+
show.value = true
40+
}
41+
</script>
42+
43+
<template>
44+
<div v-if="showBlock">
45+
<div
46+
v-if="!errorMessage.showMore"
47+
v-dompurify-html="errorMessage.message"
48+
class="error-container"
49+
></div>
50+
<div v-else class="error-container row">
51+
{{ t('chat.error') }}
52+
<el-button text @click="showTraceBack">{{ t('chat.show_error_detail') }}</el-button>
53+
</div>
54+
55+
<el-drawer
56+
v-model="show"
57+
:size="isAssistant ? '100%' : '600px'"
58+
:title="t('chat.error')"
59+
direction="rtl"
60+
body-class="chart-sql-error-body"
61+
>
62+
<el-main>
63+
<div v-dompurify-html="errorMessage.traceback" class="error-container open"></div>
64+
</el-main>
65+
</el-drawer>
66+
</div>
67+
</template>
68+
69+
<style lang="less">
70+
.chart-sql-error-body {
71+
padding: 0;
72+
}
73+
</style>
74+
<style scoped lang="less">
75+
.error-container {
76+
font-weight: 400;
77+
font-size: 16px;
78+
line-height: 24px;
79+
color: rgba(31, 35, 41, 1);
80+
white-space: pre-wrap;
81+
82+
&.row {
83+
display: flex;
84+
flex-direction: row;
85+
align-items: center;
86+
}
87+
&.open {
88+
font-size: 14px;
89+
line-height: 20px;
90+
}
91+
}
92+
</style>

0 commit comments

Comments
 (0)