From c66d3a871efaa6b735f13c201fd6d4dc292e2f1f Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Tue, 9 Jun 2026 13:51:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=88=9D=E5=A7=8B=E5=8C=96=E5=B7=A5?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 5 + .gitignore | 71 +++++++++++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/modules.xml | 8 ++ .idea/sweet-hut-agent.iml | 10 ++ .idea/vcs.xml | 6 + .idea/workspace.xml | 118 ++++++++++++++++++ agent.py | 11 ++ config/db.py | 31 +++++ main.py | 22 ++++ model.py | 5 + prompt/sql.py | 18 +++ prompt/system.py | 23 ++++ prompt/table.py | 9 ++ requirements.txt | 12 ++ router.py | 11 ++ services/agent_service.py | 44 +++++++ 17 files changed, 410 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/sweet-hut-agent.iml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 agent.py create mode 100644 config/db.py create mode 100644 main.py create mode 100644 model.py create mode 100644 prompt/sql.py create mode 100644 prompt/system.py create mode 100644 prompt/table.py create mode 100644 requirements.txt create mode 100644 router.py create mode 100644 services/agent_service.py diff --git a/.env b/.env new file mode 100644 index 0000000..2511459 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +SQLITE_DB_PATH="sweet-hut.db" +DB_URL="mysql+pymysql://root:estun%40medical@localhost/sweet_hut?charset=utf8mb4" +MODEL_NAME="qwen3.6-plus" +MODEL_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" +MODEL_API_KEY="sk-52bcd98e9c1d45908437c4e8706eefff" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eba1255 --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +# Python 字节码文件 +__pycache__/ +*.py[cod] +*$py.class + +# C 扩展 +*.so + +# 分发/打包 +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# 虚拟环境 +venv/ +env/ +ENV/ +.env +.venv + +# 测试 +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Django 相关 +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +media/ + +# PyCharm IDE +.idea/ +*.iml +*.iws +*.ipr + +# VS Code +.vscode/ +*.code-workspace +.history/ + +# 其他 +.DS_Store + +logs/ +packages/ +*.db +*.db-shm +*.db-wal \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..3cb4b77 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sweet-hut-agent.iml b/.idea/sweet-hut-agent.iml new file mode 100644 index 0000000..3d8e3d3 --- /dev/null +++ b/.idea/sweet-hut-agent.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..b3b372e --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "associatedIndex": 0 +} + + + + + + + + + + + + + + + + + + + + + + + + + + 1780474023959 + + + + + + + + + \ No newline at end of file diff --git a/agent.py b/agent.py new file mode 100644 index 0000000..229299d --- /dev/null +++ b/agent.py @@ -0,0 +1,11 @@ +from langchain.chat_models import init_chat_model + +import os + +chat_model = init_chat_model( + model=os.getenv("MODEL_NAME"), + model_provider="openai", + base_url=os.getenv("MODEL_BASE_URL"), + api_key=os.getenv("MODEL_API_KEY"), + temperature=1.5, +) diff --git a/config/db.py b/config/db.py new file mode 100644 index 0000000..f80b8b9 --- /dev/null +++ b/config/db.py @@ -0,0 +1,31 @@ +import os + +from dotenv import load_dotenv +from sqlalchemy import create_engine +from sqlalchemy import text + +load_dotenv() + +engine = create_engine( + os.getenv("DB_URL"), + pool_pre_ping=True, +) + +conn = engine.connect() + + +def is_select(sql: str) -> bool: + return sql.lower().lstrip().startswith("select") + + +def execute_sql(sql: str): + if not is_select(sql): + raise ValueError("只允许 SELECT 查询") + + result = conn.execute(text(sql)) + rows = result.fetchall() + columns = result.keys() + return { + "columns": list(columns), + "rows": rows + } diff --git a/main.py b/main.py new file mode 100644 index 0000000..d9e1fbc --- /dev/null +++ b/main.py @@ -0,0 +1,22 @@ +from dotenv import load_dotenv +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from router import router + +# 初始化FastAPI +app = FastAPI( + title="Sweet Hut Agent", + description="智能体", + version="0.1.0" +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], +) + +load_dotenv() + +app.include_router(router) diff --git a/model.py b/model.py new file mode 100644 index 0000000..1ee745d --- /dev/null +++ b/model.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + +class ChatRequest(BaseModel): + type: str + message: str diff --git a/prompt/sql.py b/prompt/sql.py new file mode 100644 index 0000000..56eb12f --- /dev/null +++ b/prompt/sql.py @@ -0,0 +1,18 @@ +from langchain_core.prompts import PromptTemplate + +SQL_PROMPT = PromptTemplate.from_template( + """ +你是一个 MySQL 专家。 +请根据用户问题生成一条可执行的 MySQL 查询语句。 +只返回 SQL,不要解释,不要加 ```。 + +数据库结构: +{schema} + +数据库补充: +{option} + +用户问题: +{question} +""" +) \ No newline at end of file diff --git a/prompt/system.py b/prompt/system.py new file mode 100644 index 0000000..4a12529 --- /dev/null +++ b/prompt/system.py @@ -0,0 +1,23 @@ +from langchain_core.prompts import PromptTemplate + +BILL_PROMPT = PromptTemplate.from_template( + """ +你是一位家庭账单助手,负责用清晰、专业、易懂的中文回答用户的问题。 + +用户问题: +{question} + +查询到的数据: +{result} + +回答要求: +1. 仅基于上方数据进行回答,不要编造内容。 +2. 如果数据为空,直接回答:「没有找到相关数据」。 +3. 金额统一保留两位小数,并带上“元”。 +4. 优先给出结论和总结,再选择性补充关键明细。 +5. 不要输出 SQL、字段名、JSON 或技术细节。 +6. 语气自然,像在和家人沟通。 + +现在请根据上述内容回答问题。 +""" +) diff --git a/prompt/table.py b/prompt/table.py new file mode 100644 index 0000000..b257c3a --- /dev/null +++ b/prompt/table.py @@ -0,0 +1,9 @@ +BILL_TABLE_SCHEMA = """ +【表关系】 +bill_record.book_id → bill_book.id +bill_record.category_id → bill_category.id +bill_record.pay_id → bill_pay.id + +【字段说明】 +bill_record.type / bill_category.type 取值:'expensive'=支出,'income'=收入 +""" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5b70968 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +fastapi~=0.136.3 +uvicorn~=0.48.0 +langchain~=1.3.2 +langchain-openai~=1.2.2 +langchain-tavily~=0.2.18 +langgraph~=1.2.2 +langgraph-checkpoint-sqlite~=3.1.0 +pydantic~=2.13.4 +langchain-core~=1.4.0 +python-dotenv~=1.2.2 +langchain-community~=0.4.2 +pymysql~=1.2.0 diff --git a/router.py b/router.py new file mode 100644 index 0000000..1df5fcb --- /dev/null +++ b/router.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter + +from model import ChatRequest +from services.agent_service import query + +router = APIRouter() + + +@router.post("/chat") +async def chat_endpoint(request: ChatRequest): + return query(request) diff --git a/services/agent_service.py b/services/agent_service.py new file mode 100644 index 0000000..3a178de --- /dev/null +++ b/services/agent_service.py @@ -0,0 +1,44 @@ +import os + +from langchain.agents import create_agent +from langchain_community.utilities import SQLDatabase +from langchain_core.messages import HumanMessage + +from agent import chat_model +from config.db import execute_sql +from model import ChatRequest +from prompt.sql import SQL_PROMPT +from prompt.system import BILL_PROMPT +from prompt.table import BILL_TABLE_SCHEMA + +tables = { + "bill": ["bill_book", "bill_category", "bill_pay", "bill_record"] +} + + +def query(request: ChatRequest): + try: + db = SQLDatabase.from_uri(os.getenv("DB_URL"), include_tables=tables[request.type]) + table_info = db.get_table_info() + + sql = chat_model.invoke( + SQL_PROMPT.format(schema=table_info, option=BILL_TABLE_SCHEMA, question=request.message) + ).content.strip() + + print("sql: ", sql) + + result = execute_sql(sql) + + print("result: ", result) + + agent = create_agent( + model=chat_model, + system_prompt=BILL_PROMPT.format(question=request.message, result=result), + ) + + response = agent.invoke({"message": HumanMessage(content=request.message)}) + print("response: ", response) + + return response["messages"][-1].content + except Exception as e: + print(f"\n[错误]: {str(e)}")