Skip to content

Commit d9aea1c

Browse files
committed
feat: data analysis
1 parent 6ed75c2 commit d9aea1c

File tree

16 files changed

+401
-53
lines changed

16 files changed

+401
-53
lines changed

backend/alembic/env.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626

2727
# from apps.system.models.user import SQLModel # noqa
2828
# from apps.settings.models.setting_models import SQLModel
29-
#from apps.chat.models.chat_model import SQLModel
30-
from apps.dashboard.models.dashboard_model import SQLModel
29+
from apps.chat.models.chat_model import SQLModel
30+
# from apps.dashboard.models.dashboard_model import SQLModel
3131
from common.core.config import settings # noqa
3232

3333
target_metadata = SQLModel.metadata

backend/alembic/versions/a3af70d43e98_011_license_ddl.py renamed to backend/alembic/versions/a3af70d43e98_012_license_ddl.py

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

1212
# revision identifiers, used by Alembic.
1313
revision = 'a3af70d43e98'
14-
down_revision = '8dc3b1bdbfef'
14+
down_revision = '941e2355a94d'
1515
branch_labels = None
1616
depends_on = None
1717

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""010_modify_chat
2+
3+
Revision ID: bfa10ce83d73
4+
Revises: 8dc3b1bdbfef
5+
Create Date: 2025-06-18 14:16:39.230619
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'bfa10ce83d73'
15+
down_revision = 'a3af70d43e98'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.add_column('chat_record', sa.Column('analysis', sa.Text(), nullable=True))
23+
op.add_column('chat_record', sa.Column('predict', sa.Text(), nullable=True))
24+
op.add_column('chat_record', sa.Column('full_analysis_message', sa.Text(), nullable=True))
25+
op.add_column('chat_record', sa.Column('full_predict_message', sa.Text(), nullable=True))
26+
# ### end Alembic commands ###
27+
28+
29+
def downgrade():
30+
# ### commands auto generated by Alembic - please adjust! ###
31+
op.drop_column('chat_record', 'full_predict_message')
32+
op.drop_column('chat_record', 'full_analysis_message')
33+
op.drop_column('chat_record', 'predict')
34+
op.drop_column('chat_record', 'analysis')
35+
# ### end Alembic commands ###

backend/apps/chat/api/chat.py

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,20 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que
113113
chart_id=request_question.chat_id)
114114
# get schema
115115
request_question.db_schema = get_table_schema(session=session, ds=ds)
116-
llm_service = LLMService(request_question, history_records, CoreDatasource(**ds.model_dump()), aimodel)
116+
db_user = get_user_info(session=session, user_id=current_user.id)
117+
request_question.lang = db_user.language
117118

118-
llm_service.init_record(session=session, current_user=current_user)
119+
llm_service = LLMService(request_question, aimodel, history_records, CoreDatasource(**ds.model_dump()))
119120

120-
db_user = get_user_info(session=session, user_id=current_user.id)
121+
llm_service.init_record(session=session, current_user=current_user)
121122

122123
def run_task():
123124
try:
124125
# return id
125126
yield orjson.dumps({'type': 'id', 'id': llm_service.get_record().id}).decode() + '\n\n'
126127

127128
# generate sql
128-
sql_res = llm_service.generate_sql(session=session, lang=db_user.language)
129+
sql_res = llm_service.generate_sql(session=session)
129130
full_sql_text = ''
130131
for chunk in sql_res:
131132
full_sql_text += chunk
@@ -144,7 +145,7 @@ def run_task():
144145
yield orjson.dumps({'content': orjson.dumps(result).decode(), 'type': 'sql-data'}).decode() + '\n\n'
145146

146147
# generate chart
147-
chart_res = llm_service.generate_chart(session=session, lang=db_user.language)
148+
chart_res = llm_service.generate_chart(session=session)
148149
full_chart_text = ''
149150
for chunk in chart_res:
150151
full_chart_text += chunk
@@ -166,3 +167,67 @@ def run_task():
166167
yield orjson.dumps({'content': str(e), 'type': 'error'}).decode() + '\n\n'
167168

168169
return StreamingResponse(run_task(), media_type="text/event-stream")
170+
171+
172+
@router.post("/record/{chart_record_id}/analysis")
173+
async def analysis(session: SessionDep, current_user: CurrentUser, chart_record_id: int):
174+
record = session.query(ChatRecord).get(chart_record_id)
175+
if not record:
176+
raise HTTPException(
177+
status_code=400,
178+
detail=f"Chat record with id {chart_record_id} not found"
179+
)
180+
181+
if not record.chart:
182+
raise HTTPException(
183+
status_code=500,
184+
detail=f"Chat record with id {chart_record_id} has not generated chart, do not support to analyze it"
185+
)
186+
187+
chat = session.query(Chat).filter(Chat.id == record.chat_id).first()
188+
if not chat:
189+
raise HTTPException(
190+
status_code=400,
191+
detail=f"Chat with id {record.chart_id} not found"
192+
)
193+
194+
if chat.create_by != current_user.id:
195+
raise HTTPException(
196+
status_code=401,
197+
detail=f"You cannot use the chat with id {record.chart_id}"
198+
)
199+
200+
# Get available AI model
201+
aimodel = session.exec(select(AiModelDetail).where(
202+
AiModelDetail.status == True,
203+
AiModelDetail.api_key.is_not(None)
204+
)).first()
205+
if not aimodel:
206+
raise HTTPException(
207+
status_code=500,
208+
detail="No available AI model configuration found"
209+
)
210+
211+
request_question = ChatQuestion(chat_id=chat.id, question='')
212+
db_user = get_user_info(session=session, user_id=current_user.id)
213+
request_question.lang = db_user.language
214+
215+
llm_service = LLMService(request_question, aimodel)
216+
llm_service.set_record(record)
217+
218+
def run_task():
219+
try:
220+
# generate analysis
221+
analysis_res = llm_service.generate_analysis(session=session)
222+
for chunk in analysis_res:
223+
yield orjson.dumps({'content': chunk, 'type': 'analysis-result'}).decode() + '\n\n'
224+
yield orjson.dumps({'type': 'info', 'msg': 'analysis generated'}).decode() + '\n\n'
225+
226+
yield orjson.dumps({'type': 'analysis_finish'}).decode() + '\n\n'
227+
228+
except Exception as e:
229+
traceback.print_exc()
230+
# llm_service.save_error(session=session, message=str(e))
231+
yield orjson.dumps({'content': str(e), 'type': 'error'}).decode() + '\n\n'
232+
233+
return StreamingResponse(run_task(), media_type="text/event-stream")

backend/apps/chat/curd/chat.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
5959
record_list = session.query(ChatRecord).options(
6060
load_only(ChatRecord.id, ChatRecord.chat_id, ChatRecord.create_time, ChatRecord.finish_time,
6161
ChatRecord.question, ChatRecord.sql_answer, ChatRecord.sql, ChatRecord.data,
62-
ChatRecord.chart_answer, ChatRecord.chart, ChatRecord.finish, ChatRecord.error,
63-
ChatRecord.run_time)).filter(
62+
ChatRecord.chart_answer, ChatRecord.chart, ChatRecord.analysis, ChatRecord.predict, ChatRecord.finish,
63+
ChatRecord.error, ChatRecord.run_time)).filter(
6464
and_(Chat.create_by == current_user.id, ChatRecord.chat_id == chart_id)).order_by(ChatRecord.create_time).all()
6565

6666
chat_info.records = record_list
@@ -158,6 +158,44 @@ def save_full_sql_message_and_answer(session: SessionDep, record_id: int, answer
158158
return result
159159

160160

161+
def save_full_analysis_message_and_answer(session: SessionDep, record_id: int, answer: str,
162+
full_message: str) -> ChatRecord:
163+
if not record_id:
164+
raise Exception("Record id cannot be None")
165+
record = session.query(ChatRecord).filter(ChatRecord.id == record_id).first()
166+
record.full_analysis_message = full_message
167+
record.analysis = answer
168+
169+
result = ChatRecord(**record.model_dump())
170+
171+
session.add(record)
172+
session.flush()
173+
session.refresh(record)
174+
175+
session.commit()
176+
177+
return result
178+
179+
180+
def save_full_predict_message_and_answer(session: SessionDep, record_id: int, answer: str,
181+
full_message: str) -> ChatRecord:
182+
if not record_id:
183+
raise Exception("Record id cannot be None")
184+
record = session.query(ChatRecord).filter(ChatRecord.id == record_id).first()
185+
record.full_predict_message = full_message
186+
record.predict = answer
187+
188+
result = ChatRecord(**record.model_dump())
189+
190+
session.add(record)
191+
session.flush()
192+
session.refresh(record)
193+
194+
session.commit()
195+
196+
return result
197+
198+
161199
def save_sql(session: SessionDep, record_id: int, sql: str) -> ChatRecord:
162200
if not record_id:
163201
raise Exception("Record id cannot be None")

backend/apps/chat/models/chat_model.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
from sqlalchemy import Column, Text, BigInteger, DateTime, Integer, Identity, Boolean
66
from sqlmodel import SQLModel, Field
77

8+
from apps.template.generate_analysis.generator import get_analysis_template
89
from apps.template.generate_chart.generator import get_chart_template
10+
from apps.template.generate_predict.generator import get_predict_template
911
from apps.template.generate_sql.generator import get_sql_template
1012

1113

@@ -36,8 +38,12 @@ class ChatRecord(SQLModel, table=True):
3638
data: str = Field(sa_column=Column(Text, nullable=True))
3739
chart_answer: str = Field(sa_column=Column(Text, nullable=True))
3840
chart: str = Field(sa_column=Column(Text, nullable=True))
41+
analysis: str = Field(sa_column=Column(Text, nullable=True))
42+
predict: str = Field(sa_column=Column(Text, nullable=True))
3943
full_sql_message: str = Field(sa_column=Column(Text, nullable=True))
4044
full_chart_message: str = Field(sa_column=Column(Text, nullable=True))
45+
full_analysis_message: str = Field(sa_column=Column(Text, nullable=True))
46+
full_predict_message: str = Field(sa_column=Column(Text, nullable=True))
4147
finish: bool = Field(sa_column=Column(Boolean, nullable=True, default=False))
4248
error: str = Field(sa_column=Column(Text, nullable=True))
4349
run_time: float = Field(default=0)
@@ -72,6 +78,8 @@ class AiModelQuestion(BaseModel):
7278
db_schema: str = ""
7379
sql: str = ""
7480
rule: str = ""
81+
fields: str = ""
82+
data: str = ""
7583
lang: str = "zh-CN"
7684

7785
def sql_sys_question(self):
@@ -87,6 +95,18 @@ def chart_sys_question(self):
8795
def chart_user_question(self):
8896
return get_chart_template()['user'].format(sql=self.sql, question=self.question, rule=self.rule, lang=self.lang)
8997

98+
def analysis_sys_question(self):
99+
return get_analysis_template()['system']
100+
101+
def analysis_user_question(self):
102+
return get_analysis_template()['user'].format(fields=self.fields, data=self.data, lang=self.lang)
103+
104+
def predict_sys_question(self):
105+
return get_predict_template()['system']
106+
107+
def predict_user_question(self):
108+
return get_predict_template()['user'].format(fields=self.fields, data=self.data, lang=self.lang)
109+
90110

91111
class ChatQuestion(AiModelQuestion):
92112
question: str

0 commit comments

Comments
 (0)