mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
feat: split analysis & predict in chat
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
"""024_modify_chat_record
|
||||
|
||||
Revision ID: 806bc67ff45f
|
||||
Revises: f535d09946f6
|
||||
Create Date: 2025-07-11 18:09:52.417628
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '806bc67ff45f'
|
||||
down_revision = 'f535d09946f6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('chat_record', sa.Column('analysis_record_id', sa.BigInteger(), nullable=True))
|
||||
op.add_column('chat_record', sa.Column('predict_record_id', sa.BigInteger(), nullable=True))
|
||||
op.drop_column('chat_record', 'run_time')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('chat_record', sa.Column('run_time', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=False))
|
||||
op.drop_column('chat_record', 'predict_record_id')
|
||||
op.drop_column('chat_record', 'analysis_record_id')
|
||||
# ### end Alembic commands ###
|
||||
@@ -129,7 +129,7 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que
|
||||
detail=str(e)
|
||||
)
|
||||
|
||||
return StreamingResponse(run_task(llm_service, session), media_type="text/event-stream")
|
||||
return StreamingResponse(run_task(llm_service), media_type="text/event-stream")
|
||||
|
||||
|
||||
@router.post("/record/{chat_record_id}/{action_type}")
|
||||
@@ -157,7 +157,6 @@ async def analysis_or_predict(session: SessionDep, current_user: CurrentUser, ch
|
||||
|
||||
try:
|
||||
llm_service = LLMService(session, current_user, request_question)
|
||||
llm_service.set_record(record)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise HTTPException(
|
||||
@@ -165,5 +164,5 @@ async def analysis_or_predict(session: SessionDep, current_user: CurrentUser, ch
|
||||
detail=str(e)
|
||||
)
|
||||
|
||||
return StreamingResponse(run_analysis_or_predict_task(llm_service, action_type),
|
||||
return StreamingResponse(run_analysis_or_predict_task(llm_service, action_type, record),
|
||||
media_type="text/event-stream")
|
||||
|
||||
@@ -82,9 +82,9 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
|
||||
load_only(ChatRecord.id, ChatRecord.chat_id, ChatRecord.create_time, ChatRecord.finish_time,
|
||||
ChatRecord.question, ChatRecord.sql_answer, ChatRecord.sql, ChatRecord.data,
|
||||
ChatRecord.chart_answer, ChatRecord.chart, ChatRecord.analysis, ChatRecord.predict,
|
||||
ChatRecord.datasource_select_answer,
|
||||
ChatRecord.datasource_select_answer, ChatRecord.analysis_record_id, ChatRecord.predict_record_id,
|
||||
ChatRecord.recommended_question, ChatRecord.first_chat,
|
||||
ChatRecord.predict_data, ChatRecord.finish, ChatRecord.error, ChatRecord.run_time)).filter(
|
||||
ChatRecord.predict_data, ChatRecord.finish, ChatRecord.error)).filter(
|
||||
and_(Chat.create_by == current_user.id, ChatRecord.chat_id == chart_id)).order_by(ChatRecord.create_time).all()
|
||||
|
||||
result = list(map(format_record, record_list))
|
||||
@@ -130,9 +130,11 @@ def format_record(record: ChatRecord):
|
||||
return _dict
|
||||
|
||||
|
||||
def list_records(session: SessionDep, chart_id: int, current_user: CurrentUser) -> List[ChatRecord]:
|
||||
def list_base_records(session: SessionDep, chart_id: int, current_user: CurrentUser) -> List[ChatRecord]:
|
||||
record_list = session.query(ChatRecord).filter(
|
||||
and_(Chat.create_by == current_user.id, ChatRecord.chat_id == chart_id)).order_by(ChatRecord.create_time).all()
|
||||
and_(Chat.create_by == current_user.id, ChatRecord.chat_id == chart_id,
|
||||
ChatRecord.analysis_record_id is None, ChatRecord.predict_record_id is None)).order_by(
|
||||
ChatRecord.create_time).all()
|
||||
return record_list
|
||||
|
||||
|
||||
@@ -225,6 +227,34 @@ def save_question(session: SessionDep, current_user: CurrentUser, question: Chat
|
||||
return result
|
||||
|
||||
|
||||
def save_analysis_predict_record(session: SessionDep, base_record: ChatRecord, action_type: str) -> ChatRecord:
|
||||
record = ChatRecord()
|
||||
record.question = base_record.question
|
||||
record.chat_id = base_record.chat_id
|
||||
record.datasource = base_record.datasource
|
||||
record.engine_type = base_record.engine_type
|
||||
record.ai_modal_id = base_record.ai_modal_id
|
||||
record.create_time = datetime.datetime.now()
|
||||
record.create_by = base_record.id
|
||||
record.chart = base_record.chart
|
||||
record.data = base_record.data
|
||||
|
||||
if action_type == 'analysis':
|
||||
record.analysis_record_id = base_record.id
|
||||
elif action_type == 'predict':
|
||||
record.predict_record_id = base_record.id
|
||||
|
||||
result = ChatRecord(**record.model_dump())
|
||||
|
||||
session.add(record)
|
||||
session.flush()
|
||||
session.refresh(record)
|
||||
result.id = record.id
|
||||
session.commit()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def save_full_sql_message(session: SessionDep, record_id: int, full_message: str) -> ChatRecord:
|
||||
return save_full_sql_message_and_answer(session=session, record_id=record_id, full_message=full_message, answer='')
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ class ChatRecord(SQLModel, table=True):
|
||||
token_select_datasource_question: str = Field(max_length=256, nullable=True)
|
||||
finish: bool = Field(sa_column=Column(Boolean, nullable=True, default=False))
|
||||
error: str = Field(sa_column=Column(Text, nullable=True))
|
||||
run_time: float = Field(default=0)
|
||||
|
||||
analysis_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
predict_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
|
||||
class CreateChat(BaseModel):
|
||||
id: int = None
|
||||
|
||||
@@ -17,8 +17,8 @@ from apps.ai_model.model_factory import LLMConfig, LLMFactory, get_default_confi
|
||||
from apps.chat.curd.chat import save_question, save_full_sql_message, save_full_sql_message_and_answer, save_sql, \
|
||||
save_error_message, save_sql_exec_data, save_full_chart_message, save_full_chart_message_and_answer, save_chart, \
|
||||
finish_record, save_full_analysis_message_and_answer, save_full_predict_message_and_answer, save_predict_data, \
|
||||
save_full_select_datasource_message_and_answer, list_records, save_full_recommend_question_message_and_answer, \
|
||||
get_old_questions
|
||||
save_full_select_datasource_message_and_answer, save_full_recommend_question_message_and_answer, \
|
||||
get_old_questions, save_analysis_predict_record, list_base_records
|
||||
from apps.chat.models.chat_model import ChatQuestion, ChatRecord, Chat
|
||||
from apps.datasource.crud.datasource import get_table_schema
|
||||
from apps.datasource.models.datasource import CoreDatasource
|
||||
@@ -63,9 +63,9 @@ class LLMService:
|
||||
|
||||
history_records: List[ChatRecord] = list(
|
||||
map(lambda x: ChatRecord(**x.model_dump()), filter(lambda r: True if r.first_chat != True else False,
|
||||
list_records(session=self.session,
|
||||
current_user=current_user,
|
||||
chart_id=chat_question.chat_id))))
|
||||
list_base_records(session=self.session,
|
||||
current_user=current_user,
|
||||
chart_id=chat_question.chat_id))))
|
||||
# get schema
|
||||
if ds:
|
||||
chat_question.db_schema = get_table_schema(session=self.session, ds=ds)
|
||||
@@ -606,7 +606,7 @@ def execute_sql_with_db(db: SQLDatabase, sql: str) -> str:
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
|
||||
def run_task(llm_service: LLMService, session: SessionDep, in_chat: bool = True):
|
||||
def run_task(llm_service: LLMService, in_chat: bool = True):
|
||||
try:
|
||||
# return id
|
||||
if in_chat:
|
||||
@@ -626,7 +626,7 @@ def run_task(llm_service: LLMService, session: SessionDep, in_chat: bool = True)
|
||||
yield orjson.dumps({'id': llm_service.ds.id, 'datasource_name': llm_service.ds.name,
|
||||
'engine_type': llm_service.ds.type_name, 'type': 'datasource'}).decode() + '\n\n'
|
||||
|
||||
llm_service.chat_question.db_schema = get_table_schema(session=session, ds=llm_service.ds)
|
||||
llm_service.chat_question.db_schema = get_table_schema(session=llm_service.session, ds=llm_service.ds)
|
||||
|
||||
# generate sql
|
||||
sql_res = llm_service.generate_sql()
|
||||
@@ -720,8 +720,10 @@ def run_task(llm_service: LLMService, session: SessionDep, in_chat: bool = True)
|
||||
yield f'> ❌ **ERROR**\n\n> \n\n> {str(e)}。'
|
||||
|
||||
|
||||
def run_analysis_or_predict_task(llm_service: LLMService, action_type: str):
|
||||
def run_analysis_or_predict_task(llm_service: LLMService, action_type: str, base_record: ChatRecord):
|
||||
try:
|
||||
llm_service.set_record(save_analysis_predict_record(llm_service.session, base_record, action_type))
|
||||
|
||||
if action_type == 'analysis':
|
||||
# generate analysis
|
||||
analysis_res = llm_service.generate_analysis()
|
||||
@@ -752,10 +754,10 @@ def run_analysis_or_predict_task(llm_service: LLMService, action_type: str):
|
||||
|
||||
yield orjson.dumps({'type': 'predict_finish'}).decode() + '\n\n'
|
||||
|
||||
|
||||
llm_service.finish()
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
# llm_service.save_error(session=session, message=str(e))
|
||||
llm_service.save_error(message=str(e))
|
||||
yield orjson.dumps({'content': str(e), 'type': 'error'}).decode() + '\n\n'
|
||||
finally:
|
||||
# end
|
||||
|
||||
@@ -66,4 +66,4 @@ async def mcp_question(session: SessionDep, chat: ChatMcp):
|
||||
llm_service = LLMService(session, user, chat)
|
||||
llm_service.init_record()
|
||||
|
||||
return StreamingResponse(run_task(llm_service, session, False), media_type="text/event-stream")
|
||||
return StreamingResponse(run_task(llm_service, False), media_type="text/event-stream")
|
||||
|
||||
@@ -47,6 +47,8 @@ export class ChatRecord {
|
||||
run_time: number = 0
|
||||
first_chat: boolean = false
|
||||
recommended_question?: string
|
||||
analysis_record_id?: number
|
||||
predict_record_id?: number
|
||||
|
||||
constructor()
|
||||
constructor(
|
||||
@@ -69,7 +71,9 @@ export class ChatRecord {
|
||||
error: string | undefined,
|
||||
run_time: number,
|
||||
first_chat: boolean,
|
||||
recommended_question: string | undefined
|
||||
recommended_question: string | undefined,
|
||||
analysis_record_id: number | undefined,
|
||||
predict_record_id: number | undefined
|
||||
)
|
||||
constructor(
|
||||
id?: number,
|
||||
@@ -91,7 +95,9 @@ export class ChatRecord {
|
||||
error?: string,
|
||||
run_time?: number,
|
||||
first_chat?: boolean,
|
||||
recommended_question?: string
|
||||
recommended_question?: string,
|
||||
analysis_record_id?: number,
|
||||
predict_record_id?: number
|
||||
) {
|
||||
this.id = id
|
||||
this.chat_id = chat_id
|
||||
@@ -113,6 +119,8 @@ export class ChatRecord {
|
||||
this.run_time = run_time ?? 0
|
||||
this.first_chat = !!first_chat
|
||||
this.recommended_question = recommended_question
|
||||
this.analysis_record_id = analysis_record_id
|
||||
this.predict_record_id = predict_record_id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +243,9 @@ const toChatRecord = (data?: any): ChatRecord | undefined => {
|
||||
data.error,
|
||||
data.run_time,
|
||||
data.first_chat,
|
||||
data.recommended_question
|
||||
data.recommended_question,
|
||||
data.analysis_record_id,
|
||||
data.predict_record_id
|
||||
)
|
||||
}
|
||||
const toChatRecordList = (list: any = []): ChatRecord[] => {
|
||||
|
||||
@@ -46,7 +46,13 @@
|
||||
<template v-for="(message, _index) in computedMessages" :key="_index">
|
||||
<ChatRow :current-chat="currentChat" :msg="message">
|
||||
<template v-if="message.role === 'assistant'">
|
||||
<ChatAnswer v-slot="{ data }" :message="message">
|
||||
<ChatAnswer
|
||||
v-if="
|
||||
message?.record?.analysis_record_id == undefined &&
|
||||
message?.record?.predict_record_id == undefined
|
||||
"
|
||||
:message="message"
|
||||
>
|
||||
<template v-if="message.record?.chart">
|
||||
<div style="padding: 0 22px; display: flex; justify-content: flex-end">
|
||||
<el-button
|
||||
@@ -66,31 +72,38 @@
|
||||
{{ t('chat.data_predict') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="analysis-container">
|
||||
<MdComponent
|
||||
v-if="message.record?.analysis || isAnalysisTyping"
|
||||
:message="message.record?.analysis"
|
||||
/>
|
||||
<el-divider
|
||||
v-if="
|
||||
(message.record?.analysis || isAnalysisTyping) &&
|
||||
(message.record?.predict || isPredictTyping)
|
||||
"
|
||||
/>
|
||||
<template v-if="message.record?.predict || isPredictTyping">
|
||||
<MdComponent :message="message.record?.predict" />
|
||||
<MdComponent :message="message.record?.predict_content" />
|
||||
<PredictChartBlock
|
||||
:id="message.record?.id + '-predict'"
|
||||
:data="message.record?.predict_data ?? '[]'"
|
||||
:message="message"
|
||||
:chart-type="data.chartType"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</ChatAnswer>
|
||||
</template>
|
||||
<div
|
||||
v-if="
|
||||
message?.record?.analysis_record_id != undefined ||
|
||||
message?.record?.predict_record_id != undefined
|
||||
"
|
||||
class="analysis-container"
|
||||
>
|
||||
<template v-if="message.record?.analysis || isAnalysisTyping">
|
||||
<MdComponent :message="message.record?.analysis_thinking" />
|
||||
<MdComponent :message="message.record?.analysis" />
|
||||
</template>
|
||||
|
||||
<el-divider
|
||||
v-if="
|
||||
(message.record?.analysis || isAnalysisTyping) &&
|
||||
(message.record?.predict || isPredictTyping)
|
||||
"
|
||||
/>
|
||||
<template v-if="message.record?.predict || isPredictTyping">
|
||||
<MdComponent :message="message.record?.predict" />
|
||||
<MdComponent :message="message.record?.predict_content" />
|
||||
<!-- <PredictChartBlock-->
|
||||
<!-- :id="message.record?.id + '-predict'"-->
|
||||
<!-- :data="message.record?.predict_data ?? '[]'"-->
|
||||
<!-- :message="message"-->
|
||||
<!-- :chart-type="data.chartType"-->
|
||||
<!-- />-->
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="message.role === 'assistant'" #footer>
|
||||
<RecommendQuestion
|
||||
:questions="message.recommended_question"
|
||||
@@ -146,7 +159,7 @@ import ChatList from './ChatList.vue'
|
||||
import ChatRow from './ChatRow.vue'
|
||||
import ChatAnswer from './ChatAnswer.vue'
|
||||
import MdComponent from './component/MdComponent.vue'
|
||||
import PredictChartBlock from './component/PredictChartBlock.vue'
|
||||
// import PredictChartBlock from './component/PredictChartBlock.vue'
|
||||
import RecommendQuestion from './RecommendQuestion.vue'
|
||||
import ChatCreator from './ChatCreator.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -528,17 +541,23 @@ function getChatPredictData(recordId?: number) {
|
||||
}
|
||||
|
||||
async function clickAnalysis(id?: number) {
|
||||
let _index = -1
|
||||
const currentRecord = find(currentChat.value.records, (value, index) => {
|
||||
if (id === value.id) {
|
||||
_index = index
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (currentRecord == undefined) {
|
||||
const baseRecord = find(currentChat.value.records, (value) => id === value.id)
|
||||
if (baseRecord == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentRecord = new ChatRecord()
|
||||
currentRecord.create_time = new Date()
|
||||
currentRecord.chat_id = baseRecord.chat_id
|
||||
currentRecord.question = baseRecord.question
|
||||
currentRecord.chart = baseRecord.chart
|
||||
currentRecord.data = baseRecord.data
|
||||
currentRecord.analysis_record_id = id
|
||||
|
||||
currentChat.value.records.push(currentRecord)
|
||||
|
||||
let _index = currentChat.value.records.length - 1
|
||||
|
||||
isAnalysisTyping.value = true
|
||||
currentChat.value.records[_index].analysis = ''
|
||||
|
||||
@@ -627,17 +646,23 @@ async function clickAnalysis(id?: number) {
|
||||
}
|
||||
|
||||
async function clickPredict(id?: number) {
|
||||
let _index = -1
|
||||
const currentRecord = find(currentChat.value.records, (value, index) => {
|
||||
if (id === value.id) {
|
||||
_index = index
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (currentRecord == undefined) {
|
||||
const baseRecord = find(currentChat.value.records, (value) => id === value.id)
|
||||
if (baseRecord == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentRecord = new ChatRecord()
|
||||
currentRecord.create_time = new Date()
|
||||
currentRecord.chat_id = baseRecord.chat_id
|
||||
currentRecord.question = baseRecord.question
|
||||
currentRecord.chart = baseRecord.chart
|
||||
currentRecord.data = baseRecord.data
|
||||
currentRecord.predict_record_id = id
|
||||
|
||||
currentChat.value.records.push(currentRecord)
|
||||
|
||||
let _index = currentChat.value.records.length - 1
|
||||
|
||||
isPredictTyping.value = true
|
||||
currentChat.value.records[_index].predict = ''
|
||||
currentChat.value.records[_index].predict_data = ''
|
||||
|
||||
Reference in New Issue
Block a user