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
+
+
+ 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)}")