Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions backend/alembic/versions/048_update_chat_record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""empty message

Revision ID: cd23f594ac05
Revises: c1b794a961ce
Create Date: 2025-11-06 14:12:40.384804

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = 'cd23f594ac05'
down_revision = 'c1b794a961ce'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('chat_record', sa.Column('sql_prase', sa.Text(), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('chat_record', 'sql_prase')
# ### end Alembic commands ###
4 changes: 2 additions & 2 deletions backend/apps/chat/api/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,14 @@ async def analysis_or_predict(session: SessionDep, current_user: CurrentUser, ch

stmt = select(ChatRecord.id, ChatRecord.question, ChatRecord.chat_id, ChatRecord.datasource,
ChatRecord.engine_type,
ChatRecord.ai_modal_id, ChatRecord.create_by, ChatRecord.chart, ChatRecord.data).where(
ChatRecord.ai_modal_id, ChatRecord.create_by, ChatRecord.chart, ChatRecord.data,ChatRecord.sql_prase).where(
and_(ChatRecord.id == chat_record_id))
result = session.execute(stmt)
for r in result:
record = ChatRecord(id=r.id, question=r.question, chat_id=r.chat_id, datasource=r.datasource,
engine_type=r.engine_type, ai_modal_id=r.ai_modal_id, create_by=r.create_by,
chart=r.chart,
data=r.data)
data=r.data,sql_prase=r.sql_prase)

if not record:
raise Exception(f"Chat record with id {chat_record_id} not found")
Expand Down
6 changes: 4 additions & 2 deletions backend/apps/chat/curd/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,17 +644,19 @@ def save_chart_answer(session: SessionDep, record_id: int, answer: str) -> ChatR
return record


def save_chart(session: SessionDep, record_id: int, chart: str) -> ChatRecord:
def save_chart(session: SessionDep, record_id: int, chart: str, sql_prase: str) -> ChatRecord:
if not record_id:
raise Exception("Record id cannot be None")
record = get_chat_record_by_id(session, record_id)

record.chart = chart
record.sql_prase = sql_prase

result = ChatRecord(**record.model_dump())

stmt = update(ChatRecord).where(and_(ChatRecord.id == record.id)).values(
chart=record.chart
chart=record.chart,
sql_prase=record.sql_prase
)

session.execute(stmt)
Expand Down
9 changes: 9 additions & 0 deletions backend/apps/chat/models/chat_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from apps.template.generate_guess_question.generator import get_guess_question_template
from apps.template.generate_predict.generator import get_predict_template
from apps.template.generate_sql.generator import get_sql_template, get_sql_example_template
from apps.template.prase_sql.generator import get_prase_sql_template
from apps.template.select_datasource.generator import get_datasource_template


Expand All @@ -40,6 +41,7 @@ class OperationEnum(Enum):
GENERATE_SQL_WITH_PERMISSIONS = '5'
CHOOSE_DATASOURCE = '6'
GENERATE_DYNAMIC_SQL = '7'
PRASE_SQL = '8'


class ChatFinishStep(Enum):
Expand Down Expand Up @@ -109,6 +111,7 @@ class ChatRecord(SQLModel, table=True):
error: str = Field(sa_column=Column(Text, nullable=True))
analysis_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
predict_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
sql_prase: str = Field(sa_column=Column(Text, nullable=True))


class ChatRecordResult(BaseModel):
Expand Down Expand Up @@ -229,6 +232,12 @@ def predict_sys_question(self):
def predict_user_question(self):
return get_predict_template()['user'].format(fields=self.fields, data=self.data)

def prase_sql_sys_question(self):
return get_prase_sql_template()['system'].format(lang=self.lang)

def prase_sql_user_question(self,sql='',chart=''):
return get_prase_sql_template()['user'].format(sql=sql,chart=chart)

def datasource_sys_question(self):
return get_datasource_template()['system'].format(lang=self.lang)

Expand Down
60 changes: 56 additions & 4 deletions backend/apps/chat/task/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ def check_save_sql(self, session: Session, res: str) -> str:

return sql

def check_save_chart(self, session: Session, res: str) -> Dict[str, Any]:
def check_save_chart(self, session: Session, res: str, sql_prase: str) -> Dict[str, Any]:

json_str = extract_nested_json(res)
if json_str is None:
Expand Down Expand Up @@ -814,7 +814,7 @@ def check_save_chart(self, session: Session, res: str) -> Dict[str, Any]:
if error:
raise SingleMessageError(message)

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

return chart

Expand Down Expand Up @@ -989,6 +989,7 @@ def run_task(self, in_chat: bool = True, stream: bool = True,

use_dynamic_ds: bool = self.current_assistant and self.current_assistant.type in dynamic_ds_types
is_page_embedded: bool = self.current_assistant and self.current_assistant.type == 4
is_assistant_embedded: bool = self.current_assistant and self.current_assistant.type == 1
dynamic_sql_result = None
sqlbot_temp_sql_text = None
assistant_dynamic_sql = None
Expand Down Expand Up @@ -1092,7 +1093,16 @@ def run_task(self, in_chat: bool = True, stream: bool = True,

# filter chart
SQLBotLogUtil.info(full_chart_text)
chart = self.check_save_chart(session=_session, res=full_chart_text)

# sql prase
if is_assistant_embedded:
sql_prase = self.generate_sql_paras(_session,real_execute_sql,full_chart_text)
if in_chat:
yield 'data:' + orjson.dumps(
{'content': sql_prase,
'type': 'sql_prase'}).decode() + '\n\n'

chart = self.check_save_chart(session=_session, res=full_chart_text,sql_prase=sql_prase)
SQLBotLogUtil.info(chart)

if not stream:
Expand Down Expand Up @@ -1283,6 +1293,49 @@ def validate_history_ds(self, session: Session):
except Exception as e:
raise SingleMessageError(f"ds is invalid [{str(e)}]")

def generate_sql_paras(self, _session: Session, real_execute_sql: Optional[str] = '',chart: Optional[str] = ''):
# prase sql
prase_sql_msg: List[Union[BaseMessage, dict[str, Any]]] = []
prase_sql_msg.append(SystemMessage(self.chat_question.prase_sql_sys_question()))
prase_sql_msg.append(HumanMessage(self.chat_question.prase_sql_user_question(real_execute_sql,chart)))
self.current_logs[OperationEnum.PRASE_SQL] = start_log(session=_session,
ai_modal_id=self.chat_question.ai_modal_id,
ai_modal_name=self.chat_question.ai_modal_name,
operate=OperationEnum.PRASE_SQL,
record_id=self.record.id,
full_message=[{'type': msg.type,
'content': msg.content}
for
msg in prase_sql_msg])

token_usage = {}
prase_res = process_stream(self.llm.stream(prase_sql_msg), token_usage)
prase_full_thinking_text = ''
prase_full_text = ''
for chunk in prase_res:
if chunk.get('content'):
prase_full_text += chunk.get('content')
if chunk.get('reasoning_content'):
prase_full_thinking_text += chunk.get('reasoning_content')
prase_sql_msg.append(AIMessage(prase_full_text))

self.current_logs[OperationEnum.PRASE_SQL] = end_log(session=_session,
log=self.current_logs[
OperationEnum.PRASE_SQL],
full_message=[
{'type': msg.type,
'content': msg.content}
for msg in prase_sql_msg],
reasoning_content=prase_full_thinking_text,
token_usage=token_usage)

prase_json_str = extract_nested_json(prase_full_text)
return prase_json_str
# if prase_json_str is None:
# raise SingleMessageError(f'Cannot parse datasource from answer: {prase_full_text}')
# ds = orjson.loads(prase_json_str)
# return ds['info']


def execute_sql_with_db(db: SQLDatabase, sql: str) -> str:
"""Execute SQL query using SQLDatabase
Expand Down Expand Up @@ -1455,7 +1508,6 @@ def process_stream(res: Iterator[BaseMessageChunk],
}
get_token_usage(chunk, token_usage)


def get_lang_name(lang: str):
if not lang:
return '简体中文'
Expand Down
1 change: 1 addition & 0 deletions backend/apps/template/generate_sql/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ def get_sql_template():
def get_sql_example_template(db_type: Union[str, DB]):
template = get_base_sql_template(db_type)
return template['template']

Empty file.
10 changes: 10 additions & 0 deletions backend/apps/template/prase_sql/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Union

from apps.db.constant import DB
from apps.template.template import get_base_template, get_sql_template as get_base_sql_template


def get_prase_sql_template():
template = get_base_template()
return template['template']['prase_sql']

39 changes: 39 additions & 0 deletions backend/templates/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,42 @@ template:

### 子查询映射表:
{sub_query}
prase_sql:
system: |
### 请使用语言:{lang} 回答

### 说明:
提供给你一句SQL和根据这个SQL已经生成的图表信息,你需要解析出sql所有的原始字段名称和sql字段别名,并把他们归类为维度、指标,如果是指标需要给出他的聚合函数是什么,同时提取图表标题,图表类型。
图表信息在chart字段内,SQL在sql字段内。
图表对应字段显示名称为name,图表原始字段名称为value,可以在chart.axis,chart.columns等字段内找到对应关系。

你必须遵守以下规则:
- 生成的数据使用JSON格式返回:
{{"success":true,"info":{{"type":"图表类型","title":"图表标题","limit":"sql限制条数","xAxisSource":[{{"sourceName":"sql维度原始字段名称","showName":"图表对应字段显示name"}}],"yAxisSource":[{{"sourceName":"sql指标原始字段名称","showName":"图表对应字段显示name","summary":"聚合函数"}}]}}}}
- 如果不能生成数据,回答:
{{"success":false,"message":"无法生成结果的原因"}}
- 如何SQL存在聚合字段且聚合字段是经过多个sql原始字段运算得到的 如: sum(a*b) as c,则只取第一个原始字段a,同时原始字段a对应的sql字段别名为c

### 以下<example>帮助你理解问题及返回格式的例子,不要将<example>内的表结构用来回答用户的问题
<example>
<chat-examples>
<example>
<input>
<chart>{{"type":"pie","title":"门店销售数量分布","axis":{{"y":{{"name":"销售数量","value":"total_sales"}},"series":{{"name":"门店","value":"shop_name"}}}}}}</chart>
<sql>SELECT `t1`.`shop` AS `shop_name`, COUNT(`t1`.`id`) AS `order_count` FROM `demo_tea_order` `t1` GROUP BY `t1`.`shop` LIMIT 1000</sql>
</input>
<output>
{{"success":true,"info":{{"type":"pie","title":"门店订单分布","limit":1000,"xAxisSource":[{{"sourceName":"shop","showName":"门店"}}],"yAxisSource":[{{"sourceName":"id","showName":"订单数量","summary":"count"}}]}}}}
</output>
</example>
</chat-examples>
<example>

### 响应, 请直接返回JSON结果:
```json

user: |
### chart:
{chart}
### sql:
{sql}
1 change: 1 addition & 0 deletions frontend/src/api/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class ChatRecord {
recommended_question?: string
analysis_record_id?: number
predict_record_id?: number
sql_prase?: string

constructor()
constructor(
Expand Down
1 change: 1 addition & 0 deletions frontend/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@
"excel": "Excel",
"picture": "Image",
"add_to_dashboard": "Add to Dashboard",
"add_to_dataV_en": "Add to Canvas",
"full_screen": "Full Screen",
"exit_full_screen": "Exit Full Screen",
"sql_generation": "Generate SQL",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/i18n/ko-KR.json
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@
"excel": "Excel",
"picture": "이미지",
"add_to_dashboard": "대시보드에 추가",
"add_to_dataV_ko": "캔버스에 추가",
"full_screen": "전체 화면",
"exit_full_screen": "전체 화면 종료",
"sql_generation": "SQL 생성",
Expand Down Expand Up @@ -693,4 +694,4 @@
"setting_successfully": "설정 성공",
"customize_theme_color": "사용자 정의 테마 색상"
}
}
}
3 changes: 2 additions & 1 deletion frontend/src/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@
"excel": "Excel",
"picture": "图片",
"add_to_dashboard": "添加到仪表板",
"add_to_dataV": "添加到画布",
"full_screen": "全屏",
"exit_full_screen": "退出全屏",
"sql_generation": "生成SQL",
Expand Down Expand Up @@ -693,4 +694,4 @@
"setting_successfully": "设置成功",
"customize_theme_color": "自定义主题色"
}
}
}
3 changes: 3 additions & 0 deletions frontend/src/views/chat/answer/ChartAnswer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ const sendMessage = async () => {
case 'sql':
_currentChat.value.records[index.value].sql = data.content
break
case 'sql_prase':
_currentChat.value.records[index.value].sql_prase = data.content
break
case 'sql-data':
getChatData(_currentChat.value.records[index.value].id)
break
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/views/chat/chat-block/ChartBlock.vue
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,18 @@ function showSql() {
sqlShow.value = true
}

function addToResource() {
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
const readyData = {
eventName: 'sqlbot_add_to_resource',
busi: props.message?.record?.sql_prase,
ready: true,
messageId: 100001,
}
console.log('sqlbot-readyData', JSON.stringify(readyData))
window.parent.postMessage(readyData, '*')
}

function addToDashboard() {
const recordeInfo = {
id: '1-1',
Expand Down Expand Up @@ -422,6 +434,15 @@ watch(
</div>
</el-popover>
</div>
<div v-if="message?.record?.sql_prase">
<el-tooltip effect="dark" :content="t('chat.add_to_dataV')" placement="top">
<el-button class="tool-btn" text @click="addToResource">
<el-icon size="16">
<icon_into_item_outlined />
</el-icon>
</el-button>
</el-tooltip>
</div>
<div v-if="message?.record?.chart && !isAssistant">
<el-tooltip effect="dark" :content="t('chat.add_to_dashboard')" placement="top">
<el-button class="tool-btn" text @click="addToDashboard">
Expand Down