From 4ce91f1f5b1a781a824052d465a82c5ce4283d8d Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Mon, 17 Mar 2025 09:27:45 +0800 Subject: [PATCH] 'commit' --- .../Model/__pycache__/biModel.cpython-310.pyc | Bin 3767 -> 2920 bytes AI/Text2Sql/Model/biModel.py | 187 +++++++----------- AI/Text2Sql/Util/PostgreSQLUtil.py | 86 -------- .../PostgreSQLUtil.cpython-310.pyc | Bin 2531 -> 0 bytes AI/Text2Sql/__pycache__/app.cpython-310.pyc | Bin 5729 -> 6623 bytes AI/Text2Sql/app.py | 187 +++++++++--------- .../1a38e532-5349-4df2-a61b-8e40f7a7261d.xlsx | Bin 0 -> 6441 bytes 7 files changed, 162 insertions(+), 298 deletions(-) delete mode 100644 AI/Text2Sql/Util/PostgreSQLUtil.py delete mode 100644 AI/Text2Sql/Util/__pycache__/PostgreSQLUtil.cpython-310.pyc create mode 100644 AI/Text2Sql/static/1a38e532-5349-4df2-a61b-8e40f7a7261d.xlsx diff --git a/AI/Text2Sql/Model/__pycache__/biModel.cpython-310.pyc b/AI/Text2Sql/Model/__pycache__/biModel.cpython-310.pyc index eeaa4a4c4842f2644a86e699e0d9f03016ce2134..1cf7b09ef6c6ef90016694de9197a8ebc863ef7e 100644 GIT binary patch literal 2920 zcmb7G&uy&4++wSm(1d41rqU|^$Wxt?7q=WS7BXm4E{{xxX7L358n_cax?$$YX!eCE?_HV=Vc zCKP(ulBFAG;%4ibOD&|bDf80e)hni#U$OJ;cbH3;Q;R9n7Up?#^0ZpNLxZSw%PQ79 zE6F7~Ao0!MspVLnm3KFs9U>9$e^1Vx%?Wp@BG+=C+U3$hrCiEgn9pUcTi#4&!^vH# zz%#iOTm4U0>%mZKkM`>^92d7@cQks4O9O5Lqdi;1_qH!wiqk6kNw`tba}_=Jsn3M^ zZAW@g;OWL!9Teu%nbcy|oKI)3_Mvdx7E@;XwNs|+6(CTw*>2vgyPmbKZi^Miv5H=@ zv*TwM7Otf-=JCnXQ|9ChwD+LB5A962MpAH8qU%Ym3#E*KVL5J>T}yh1r$RIAqA(Q< zwWiu04S=x&(}X6m6ndzEz@YJWT=*+=2##R)fEZh3o6M2Bw8?$$4ho(jLffK37m<6! zV?N*F+c1kOwrNunQD3`HgyCsEze`2zFo9A0f!@?T9o9y0txrqzFlj~_Lq53?_4N|< zBR|qb@(pQ2zeL192Ds`O^)D!CMk#3wdNFapkKQK_*j-A=68Rix+Kk?gDv#9NCfy{- zM7oHWwJ&4L}hk*rk(X1$Ghex(i!m_FxJiUr4=o$fw$slleqJzgSn3J7qjMxmsVwc)$)p8?iLaR00jv(B+&D~-$H-fBPD>Sm($0=Ul$bvMR$a2RK5v= zKwEVRr5?$}#GtBRa8H+^3ndrp9*!LDLD``iZ1Y`p4@C%d&+1c9_nNAEE;wpRPu**` z;XDQ%j($bImBi{Zc0q;r5!C~7Y9Pg*TEt&M>~3#S`xD_ zQCXyk9adu4DJ7mjhJ`0UURod;>sDEG@N`eX>Zhn82qn|{*zyic>+L`7TdB$KNC9@AH9Q#O0^4n?WxIf@s05my6X>5WiR{m!ZH>7P;$5^?{_0 ze{Wac;dLYK}FFM~*uJok2$Vcy!bl9UC{s6T^v7<9~aO Blso_c literal 3767 zcmc&%TW=dh6rP#g^(~1VOd3#uvh)HmRDl9Q5K5sCS5%~i)-Hi4U^$+RS>o8q?51ff z8;V*X1Vvm0h|-csfCv%+q#}Vp{0Lr<_7R%UJb|bxE=3jR%z7Pf;uPLkd&fK1J@b9% zd}mzl=!g)wUKBqRHeE)@PgrTbRH)nsxAi;}oN!ts<8+9Qvmr(aXNEG$xWd(A>X3>x zu0e~+13U;VTG$&A;$f%<>fTC z=W)2PEx}Ti{Sm#z)t?+lW|I1z0}t=h?d*t|_3Y9IA50!d>L%Ck(YIMG=gzBwfq?=| zc7g@N&X-Fir^7gG7zbhW_)r#}+?*duicIxlG?gPUOaQKGLWA zwL@QAICbW`mzoE8WPr2u>F%2$P@6}LP3J_x>eH9sJ974;=iC=J-naPr@x>$Gy2FKG zPl-~W?haXc=d;CEKU#X>)x{Uzm;F9sG0riB(r{awpfE|5Jkd7HYLZe;rljkkHs*XBZ%oTiT5R(83w1n$8JT$>| zhnof~aW@g#YL%kY&M7FCzM}?2I7q8Db2mtgSqE5mhHiYz0VsBuM-v6Rk+th$NsY zW$W))A)w$9070TMCJFdEs!0O=1Ol|K3*rVO0Pe=6z>}vh=kA9J5YQYnF-$H1KY_Cg zoNZcY%f>Zs4kz-vBy4Fofj^J|4O#yT?i~jqU+s`{q-M-3t{X}it^=#q_#&#r`o4Gw zrbxv=y@-G(ZoMc_jMv?|=z(7RE7AAg4R4U#Y`I9Zuy!L_+nTgz8?|7))wuIZjDFcu~4i%{0~qXW9a|@ diff --git a/AI/Text2Sql/Model/biModel.py b/AI/Text2Sql/Model/biModel.py index 2f085611..d162583f 100644 --- a/AI/Text2Sql/Model/biModel.py +++ b/AI/Text2Sql/Model/biModel.py @@ -1,134 +1,87 @@ -from Text2Sql.Util.PostgreSQLUtil import PostgreSQLUtil, postgresql_pool - +import asyncpg # 删除数据 -def delete_question(question_id: str): - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - # 删除 t_bi_question 表中的数据 - delete_sql = """ - DELETE FROM t_bi_question WHERE id = %s - """ - db.execute_query(delete_sql, (question_id,)) - +async def delete_question(db: asyncpg.Connection, question_id: str): + delete_sql = """ + DELETE FROM t_bi_question WHERE id = $1 + """ + await db.execute(delete_sql, question_id) # 插入数据 -def insert_question(question_id: str, question: str): - # 向 t_bi_question 表插入数据 - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - insert_sql = """ - INSERT INTO t_bi_question (id,question, state_id, is_system, is_collect) - VALUES (%s,%s, %s, %s, %s) - """ - db.execute_query(insert_sql, (question_id, question, 0, 0, 0)) - +async def insert_question(db: asyncpg.Connection, question_id: str, question: str): + insert_sql = """ + INSERT INTO t_bi_question (id, question, state_id, is_system, is_collect) + VALUES ($1, $2, $3, $4, $5) + """ + await db.execute(insert_sql, question_id, question, 0, 0, 0) # 修改数据 -''' -示例: -# 更新 question 和 state_id 字段 -update_question_by_id(db, question_id=1, question="新的问题描述", state_id=1) - -# 只更新 excel_file_name 字段 -update_question_by_id(db, question_id=1, excel_file_name="new_excel.xlsx") - -# 只更新 is_collect 字段 -update_question_by_id(db, question_id=1, is_collect=1) - -# 不更新任何字段(因为所有参数都是 None) -update_question_by_id(db, question_id=1, question=None, state_id=None) -''' - - -def update_question_by_id(question_id: str, **kwargs): - """ - 根据主键更新 t_bi_question 表,只更新非 None 的字段 - :param db: PostgreSQLUtil 实例 - :param question_id: 主键 id - :param kwargs: 需要更新的字段和值 - :return: 更新是否成功 - """ - # 过滤掉值为 None 的字段 +async def update_question_by_id(db: asyncpg.Connection, question_id: str, **kwargs): update_fields = {k: v for k, v in kwargs.items() if v is not None} if not update_fields: - return False # 没有需要更新的字段 - - # 动态构建 SET 子句 - set_clause = ", ".join([f"{field} = %s" for field in update_fields.keys()]) - - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - # 构建完整 SQL - sql = f""" - UPDATE t_bi_question - SET {set_clause} - WHERE id = %s - """ - # 参数列表 - params = list(update_fields.values()) + [question_id] - - # 执行更新 - try: - db.execute_query(sql, params) - return True - except Exception as e: - print(f"更新失败: {e}") - return False - - -# 根据 问题id 查询 sql -def get_question_by_id(question_id: str): - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - select_sql = """ - select * from t_bi_question where id=%s - """ - _data = db.execute_query(select_sql, (question_id,)) - return _data + return False + set_clause = ", ".join([f"{field} = ${i+1}" for i, field in enumerate(update_fields.keys())]) + sql = f""" + UPDATE t_bi_question + SET {set_clause} + WHERE id = ${len(update_fields) + 1} + """ + params = list(update_fields.values()) + [question_id] + + try: + await db.execute(sql, *params) + return True + except Exception as e: + print(f"更新失败: {e}") + return False + +# 根据问题 ID 查询 SQL +async def get_question_by_id(db: asyncpg.Connection, question_id: str): + select_sql = """ + SELECT * FROM t_bi_question WHERE id = $1 + """ + _data = await db.fetch(select_sql, question_id) + return _data # 保存系统推荐 -def set_system_recommend_questions(question_id: str, flag: str): - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - sql = f""" - UPDATE t_bi_question - SET is_system =%s WHERE id = %s - """ - # 执行更新 - try: - db.execute_query(sql, int(flag), question_id) - return True - except Exception as e: - print(f"更新失败: {e}") - return False +async def set_system_recommend_questions(db: asyncpg.Connection, question_id: str, flag: str): + sql = """ + UPDATE t_bi_question + SET is_system = $1 WHERE id = $2 + """ + try: + await db.execute(sql, int(flag), question_id) + return True + except Exception as e: + print(f"更新失败: {e}") + return False # 设置用户收藏 -def set_user_collect_questions(question_id: str, flag: str): - sql = f""" - UPDATE t_bi_question - SET is_collect =%s WHERE id = %s - """ - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - # 执行更新 - try: - db.execute_query(sql, int(flag), question_id) - return True - except Exception as e: - print(f"更新失败: {e}") - return False - -# 查询有哪些系统推荐问题 -def get_system_recommend_questions(): +async def set_user_collect_questions(db: asyncpg.Connection, question_id: str, flag: str): sql = """ - SELECT * FROM t_bi_question WHERE is_system = 1 + UPDATE t_bi_question + SET is_collect = $1 WHERE id = $2 """ - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - _data = db.execute_query(sql) - return _data + try: + await db.execute(sql, int(flag), question_id) + return True + except Exception as e: + print(f"更新失败: {e}") + return False + +# 查询系统推荐问题 +async def get_system_recommend_questions(db: asyncpg.Connection): + sql = """ + SELECT * FROM t_bi_question WHERE is_system = 1 + """ + _data = await db.fetch(sql) + return _data -# 查询有哪些用户收藏问题 -def get_user_collect_questions(): - # 从t_bi_question表中获取所有is_collect=1的数据 - sql=""" - SELECT * FROM t_bi_question WHERE is_collect = 1 +# 查询用户收藏问题 +async def get_user_collect_questions(db: asyncpg.Connection): + sql = """ + SELECT * FROM t_bi_question WHERE is_collect = 1 """ - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - _data = db.execute_query(sql) - return _data \ No newline at end of file + _data = await db.fetch(sql) + return _data \ No newline at end of file diff --git a/AI/Text2Sql/Util/PostgreSQLUtil.py b/AI/Text2Sql/Util/PostgreSQLUtil.py deleted file mode 100644 index b45ac52e..00000000 --- a/AI/Text2Sql/Util/PostgreSQLUtil.py +++ /dev/null @@ -1,86 +0,0 @@ -import json -from datetime import date, datetime - -import psycopg2 -from psycopg2 import pool -from psycopg2.extras import RealDictCursor -from Config import * - -# 创建连接池 -postgresql_pool = psycopg2.pool.SimpleConnectionPool( - minconn=1, - maxconn=10, - host=PG_HOST, - port=PG_PORT, - dbname=PG_DATABASE, - user=PG_USER, - password=PG_PASSWORD -) - - -class PostgreSQLUtil: - def __init__(self, connection): - self.connection = connection - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.connection.commit() - postgresql_pool.putconn(self.connection) - - def execute_query(self, sql, params=None, return_dict=True): - """执行查询并返回结果""" - try: - with self.connection.cursor( - cursor_factory=RealDictCursor if return_dict else None - ) as cursor: - cursor.execute(sql, params) - - if cursor.description: - columns = [desc[0] for desc in cursor.description] - results = cursor.fetchall() - - # 转换字典格式 - if return_dict: - return results - else: - return [dict(zip(columns, row)) for row in results] - else: - return {"rowcount": cursor.rowcount} - - except Exception as e: - print(f"执行SQL出错: {e}") - self.connection.rollback() - raise - - -def get_db(): - connection = postgresql_pool.getconn() - try: - yield PostgreSQLUtil(connection) - finally: - postgresql_pool.putconn(connection) - - -# 使用示例 -if __name__ == "__main__": - ''' - db_gen = get_db():调用生成器函数,返回生成器对象。 - db = next(db_gen):从生成器中获取 PostgreSQLUtil 实例。 - 生成器函数确保连接在使用后正确归还到连接池。 - ''' - # 从生成器中获取数据库实例 - db_gen = get_db() - db = next(db_gen) - try: - # 示例查询 - result = db.execute_query("SELECT version()") - print("数据库版本:", result) - - # 返回JSON - json_data = db.query_to_json("SELECT * FROM t_base_class LIMIT 2") - print("JSON结果:", json_data) - finally: - # 手动关闭生成器 - db_gen.close() diff --git a/AI/Text2Sql/Util/__pycache__/PostgreSQLUtil.cpython-310.pyc b/AI/Text2Sql/Util/__pycache__/PostgreSQLUtil.cpython-310.pyc deleted file mode 100644 index 75c3c6a6a4b1c26e57232702cf2b8e4286f07903..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2531 zcmaJ@|7#pY6rY*h+uOV3E?AT9^`xc9zM9qC;{1CzZYafAnW5p zD4Qp|e`|P~fDN?rFTbirR{ssS6PdP{)O|PQ(nOjDb#76qH{v*iZ+VWp;bh>Y6HSrC z0wna28x)q^K)+deJ6{hXFODK$+%;sR{&Jk8xPeG*!(WWtI+uDg;X;}XH%V4w;d^-S z?M;KhQRvAi5Ke6B6Px+eXW$0S*L)qmx}Wn6_~yVp=qw7m=PShOmgQMEu_k!s)QRWQ zAoLh)(rL(JpMajA&28Ey4uq^iuF&Z#R2I=_?xjH-p(hd^E{R^yyFx@ieExi5a%{m* z=3{Ys;ZzXSPQ+1dVf^?)m9M3TDl6dvp182vfYHXfG#w|1g4A&a@nE!|NLzdE(Cg@U z=+RC?9=ozw2c`|SiZU?DAd8B=4zC2_9vI4k}h2FPgX()aU=7MW{PeFMYL6# ze9d#x^#+%D0B(IWE(huI{=>9`&K)Ws;wO zv|d2tmuXuQX38RpkUB@-Il7^@b+{O9vu((dw!W;3r!tr!KS?&}S&(k!w#h~g){eGw z^ME3s>O?1-8s=4nyh)ZQKvF+rwsW}RZVlgn-HhNrW*#TW%qqAPe=kCxR~f03G`}8G zvH>-tRZ2}i-!?9i57{PYs*n^gByWK3hViPgN>_* z7<+D*908`Y_$~_#*a%^sAUc(Wg$Gy#SZqBPel%t z5Y_5NvP*d-Xkayw<`Pf6m)$TF51~?FYR%)>Os*kmScKLJ!i^B`Iq++_uMG2yny<0HTKoSmOctB2T6rvMSkN6u79FP^`^^yfZU}DFo9~SEStonrep)@U`WpHPnWz-l{RgTO#}QYVSMAQC-DcH1WnP;>mnF_=;&ve!&- zRW}`G;t7zUFY0a(IZms$GIe5VqB`;l7YRiEfYrnoWaqsvcg}r&_1p8;&c3yC>9euc z12@$k8F^-I=Hy7~EV>DIybxZ+krT&H9`*yof9yb>OdtD|22L!~cR?ZdV*)Lz;&(a2>(eCr<(~3BVU%+zlW$ f0K~Wdj6cd<=0j$IQNzF(6A(B~u1slM diff --git a/AI/Text2Sql/__pycache__/app.cpython-310.pyc b/AI/Text2Sql/__pycache__/app.cpython-310.pyc index 291a307a327cddd337037b39382f8c0391406a89..dd63c21a1ab4900124692cdf2ccb8bb1a5a67904 100644 GIT binary patch literal 6623 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 literal 5729 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 diff --git a/AI/Text2Sql/app.py b/AI/Text2Sql/app.py index c5b9841c..568b9a3a 100644 --- a/AI/Text2Sql/app.py +++ b/AI/Text2Sql/app.py @@ -1,13 +1,17 @@ import json import uuid - -import uvicorn # 导入 uvicorn +from datetime import date, datetime +from asyncpg.pool import Pool from fastapi import FastAPI, Form, Query -from openai import OpenAI +from fastapi.staticfiles import StaticFiles +from contextlib import asynccontextmanager +from fastapi import FastAPI, Depends +import asyncpg +import uvicorn +from openai import AsyncOpenAI from starlette.responses import StreamingResponse -from starlette.staticfiles import StaticFiles -from Config import MODEL_API_KEY, MODEL_API_URL, QWEN_MODEL_NAME +from Config import * from Model.biModel import * from Text2Sql.Util.MarkdownToDocxUtil import markdown_to_docx from Text2Sql.Util.SaveToExcel import save_to_excel @@ -15,22 +19,66 @@ from Text2Sql.Util.VannaUtil import VannaUtil # 初始化 FastAPI app = FastAPI() -# 配置静态文件目录 app.mount("/static", StaticFiles(directory="static"), name="static") -# 初始化一次vanna的类 vn = VannaUtil() +# 初始化 FastAPI 应用 +@asynccontextmanager +async def lifespan(app: FastAPI): + # 启动时初始化连接池 + app.state.pool = await asyncpg.create_pool( + host=PG_HOST, + port=PG_PORT, + database=PG_DATABASE, + user=PG_USER, + password=PG_PASSWORD, + min_size=1, + max_size=10 + ) + yield + # 关闭时释放连接池 + await app.state.pool.close() + +app = FastAPI(lifespan=lifespan) + +# 依赖注入连接池 +async def get_db(): + async with app.state.pool.acquire() as connection: + yield connection + +class PostgreSQLUtil: + def __init__(self, pool: Pool): + self.pool = pool + + async def execute_query(self, sql, params=None): + async with self.pool.acquire() as connection: + result = await connection.fetch(sql, params) + return result + + async def query_to_json(self, sql, params=None): + data = await self.execute_query(sql, params) + return json.dumps(data, default=self.json_serializer) + + @staticmethod + def json_serializer(obj): + """处理JSON无法序列化的类型""" + if isinstance(obj, (date, datetime)): + return obj.isoformat() + raise TypeError(f"Type {type(obj)} not serializable") + +async def create_pool(): + return await asyncpg.create_pool( + host=PG_HOST, + port=PG_PORT, + database=PG_DATABASE, + user=PG_USER, + password=PG_PASSWORD, + min_size=1, + max_size=10 + ) - -@app.get("/") -def read_root(): - return {"message": "Welcome to AI SQL World!"} - - -# 通过语义生成Excel -# http://10.10.21.20:8000/questions/get_excel @app.post("/questions/get_excel") -def get_excel(question_id: str = Form(...), question_str: str = Form(...)): +async def get_excel(question_id: str = Form(...), question_str: str = Form(...), db: asyncpg.Connection = Depends(get_db)): # 只接受guid号 if len(question_id) != 36: return {"success": False, "message": "question_id格式错误"} @@ -43,34 +91,40 @@ def get_excel(question_id: str = Form(...), question_str: str = Form(...)): question = question_str + common_prompt # 先删除后插入,防止重复插入 - delete_question(question_id) - insert_question(question_id, question) + await delete_question(db, question_id) + await insert_question(db, question_id, question) # 获取完整 SQL sql = vn.generate_sql(question) print("生成的查询 SQL:\n", sql) # 更新question_id - update_question_by_id(question_id=question_id, sql=sql, state_id=1) + await update_question_by_id(db, question_id=question_id, sql=sql, state_id=1) # 执行SQL查询 - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - _data = db.execute_query(sql) + _data = await db.fetch(sql) # 在static目录下,生成一个guid号的临时文件 uuid_str = str(uuid.uuid4()) filename = f"static/{uuid_str}.xlsx" save_to_excel(_data, filename) # 更新EXCEL文件名称 - update_question_by_id(question_id, excel_file_name=filename) + await update_question_by_id(db, question_id, excel_file_name=filename) # 返回静态文件URL return {"success": True, "message": "Excel文件生成成功", "download_url": f"/static/{uuid_str}.xlsx"} # http://10.10.21.20:8000/questions/get_docx_stream?question_id_get=af15d834-e7f5-46b4-a0f6-15f1f888f443 +# 初始化 OpenAI 客户端 +client = AsyncOpenAI( + api_key=MODEL_API_KEY, + base_url=MODEL_API_URL, +) + @app.api_route("/questions/get_docx_stream", methods=["POST", "GET"]) async def get_docx_stream( question_id: str = Form(None, description="问题ID(POST请求)"), # POST 请求参数 - question_id_get: str = Query(None, description="问题ID(GET请求)") # GET 请求参数 + question_id_get: str = Query(None, description="问题ID(GET请求)"), # GET 请求参数 + db: asyncpg.Connection = Depends(get_db) ): # 根据请求方式获取 question_id if question_id is not None: # POST 请求 @@ -81,8 +135,9 @@ async def get_docx_stream( return {"success": False, "message": "缺少问题ID参数"} # 根据问题ID获取查询sql - sql = get_question_by_id(question_id)[0]['sql'] - # 4、生成word报告 + sql = (await db.fetch("SELECT * FROM t_bi_question WHERE id = $1", question_id))[0]['sql'] + + # 生成word报告 prompt = ''' 请根据以下 JSON 数据,整理出2000字左右的话描述当前数据情况。要求: 1、以Markdown格式返回,我将直接通过markdown格式生成Word。 @@ -91,19 +146,15 @@ async def get_docx_stream( 4、尽量以条目列出,这样更清晰 5、数据: ''' - with PostgreSQLUtil(postgresql_pool.getconn()) as db: - _data = db.execute_query(sql) - prompt = prompt + json.dumps(_data, ensure_ascii=False) - - # 初始化 OpenAI 客户端 - client = OpenAI( - api_key=MODEL_API_KEY, - base_url=MODEL_API_URL, - ) + _data = await db.fetch(sql) + #print(_data) + # 将 asyncpg.Record 转换为 JSON 格式 + json_data = json.dumps([dict(record) for record in _data], ensure_ascii=False) + print(json_data) # 打印 JSON 数据 + prompt = prompt + json.dumps(json_data, ensure_ascii=False) # 调用 OpenAI API 生成总结(流式输出) - response = client.chat.completions.create( - #model=MODEL_NAME, + response = await client.chat.completions.create( model=QWEN_MODEL_NAME, messages=[ {"role": "system", "content": "你是一个数据分析助手,擅长从 JSON 数据中提取关键信息并生成详细的总结。"}, @@ -122,7 +173,7 @@ async def get_docx_stream( async def generate_stream(): summary = "" try: - for chunk in response: + async for chunk in response: # 使用 async for 处理流式响应 if chunk.choices[0].delta.content: # 检查是否有内容 chunk_content = chunk.choices[0].delta.content # 逐字拆分并返回 @@ -135,7 +186,7 @@ async def get_docx_stream( markdown_to_docx(summary, output_file=filename) # 记录到数据库 - update_question_by_id(question_id, docx_file_name=filename) + await db.execute("UPDATE t_bi_question SET docx_file_name = $1 WHERE id = $2", filename, question_id) except Exception as e: # 如果发生异常,返回错误信息 @@ -149,7 +200,7 @@ async def get_docx_stream( finally: # 确保资源释放 if "response" in locals(): - response.close() + await response.aclose() # 使用 StreamingResponse 返回流式结果 return StreamingResponse( @@ -164,60 +215,6 @@ async def get_docx_stream( } ) - -# 返回生成的Word文件下载地址 -# http://10.10.21.20:8000/questions/get_docx_file?question_id_get=af15d834-e7f5-46b4-a0f6-15f1f888f443 -@app.api_route("/questions/get_docx_file", methods=["POST", "GET"]) -async def get_docx_file( - question_id: str = Form(None, description="问题ID(POST请求)"), # POST 请求参数 - question_id_get: str = Query(None, description="问题ID(GET请求)"), # GET 请求参数 -): - # 根据请求方式获取 question_id - if question_id is not None: # POST 请求 - question_id = question_id - elif question_id_get is not None: # GET 请求 - question_id = question_id_get - else: - return {"success": False, "message": "缺少问题ID参数"} - - # 根据问题ID获取查询docx_file_name - docx_file_name = get_question_by_id(question_id)[0]['docx_file_name'] - - # 返回成功和静态文件的URL - return {"success": True, "message": "Word文件生成成功", "download_url": f"{docx_file_name}"} - - -# 设置问题为系统推荐问题 ,0:取消,1:设置 -@app.post("/questions/set_system_recommend") -def set_system_recommend(question_id: str = Form(...), flag: str = Form(...)): - set_system_recommend_questions(question_id, flag) - # 提示保存成功 - return {"success": True, "message": "保存成功"} - -# 设置问题为用户收藏问题 ,0:取消,1:设置 -@app.post("/questions/set_user_collect") -def set_user_collect(question_id: str = Form(...), flag: str = Form(...)): - set_user_collect_questions(question_id, flag) - # 提示保存成功 - return {"success": True, "message": "保存成功"} - -# 查询有哪些系统推荐问题 -@app.get("/questions/get_system_recommend") -def get_system_recommend(): - # 查询所有系统推荐问题 - system_recommend_questions = get_system_recommend_questions() - # 返回查询结果 - return {"success": True, "data": system_recommend_questions} - -# 查询有哪些用户收藏问题 -@app.get("/questions/get_user_collect") -def get_user_collect(): - # 查询所有用户收藏问题 - user_collect_questions = get_user_collect_questions() - # 返回查询结果 - return {"success": True, "data": user_collect_questions} - - -# 确保直接运行脚本时启动 FastAPI 应用 +# 启动 FastAPI if __name__ == "__main__": - uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True) + uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True) \ No newline at end of file diff --git a/AI/Text2Sql/static/1a38e532-5349-4df2-a61b-8e40f7a7261d.xlsx b/AI/Text2Sql/static/1a38e532-5349-4df2-a61b-8e40f7a7261d.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..45f5e16bfeeaa65614c2b050535aa305872bd2d3 GIT binary patch literal 6441 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