feat:初始化工程

This commit is contained in:
2026-06-09 13:51:12 +08:00
commit c66d3a871e
17 changed files with 410 additions and 0 deletions

5
.env Normal file
View File

@@ -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"

71
.gitignore vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/sweet-hut-agent.iml" filepath="$PROJECT_DIR$/.idea/sweet-hut-agent.iml" />
</modules>
</component>
</project>

10
.idea/sweet-hut-agent.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (sweet-hut-agent) (2)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

118
.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="fca82301-8db7-46f2-bd02-1f76c1dc566b" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/.env" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/sweet-hut-agent.iml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/agent.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/config/db.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/main.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/model.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/prompt/sql.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/prompt/system.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/prompt/table.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/requirements.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/router.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/services/agent_service.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="FastAPI main" />
<option value="FastAPI test_main" />
<option value="Python Script" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 0
}</component>
<component name="ProjectId" id="3EcNiHw4Lf7c97fEacOE86P9DSi" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"FastAPI.sweet-hut-agent.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "master",
"last_opened_file_path": "D:/Cxx/PythonProjects/sweet-hut-agent/prompt",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="D:\Cxx\PythonProjects\sweet-hut-agent\prompt" />
<recent name="D:\Cxx\PythonProjects\sweet-hut-agent" />
</key>
</component>
<component name="RunManager">
<configuration name="sweet-hut-agent" type="Python.FastAPI">
<option name="file" value="$PROJECT_DIR$/main.py" />
<module name="sweet-hut-agent" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="launchJavascriptDebuger" value="false" />
<method v="2" />
</configuration>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-1632447f56bf-JavaScript-PY-243.26053.29" />
<option value="bundled-python-sdk-b1dbf8ef85a6-4df51de95216-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-243.26053.29" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="fca82301-8db7-46f2-bd02-1f76c1dc566b" name="Changes" comment="" />
<created>1780474023959</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1780474023959</updated>
<workItem from="1780474024998" duration="4337000" />
<workItem from="1780488022005" duration="10000" />
<workItem from="1780971608037" duration="7293000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/sweet_hut_agent$sweet_hut_agent.coverage" NAME="sweet-hut-agent Coverage Results" MODIFIED="1780976147915" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
</component>
</project>

11
agent.py Normal file
View File

@@ -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,
)

31
config/db.py Normal file
View File

@@ -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
}

22
main.py Normal file
View File

@@ -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)

5
model.py Normal file
View File

@@ -0,0 +1,5 @@
from pydantic import BaseModel
class ChatRequest(BaseModel):
type: str
message: str

18
prompt/sql.py Normal file
View File

@@ -0,0 +1,18 @@
from langchain_core.prompts import PromptTemplate
SQL_PROMPT = PromptTemplate.from_template(
"""
你是一个 MySQL 专家。
请根据用户问题生成一条可执行的 MySQL 查询语句。
只返回 SQL不要解释不要加 ```。
数据库结构:
{schema}
数据库补充:
{option}
用户问题:
{question}
"""
)

23
prompt/system.py Normal file
View File

@@ -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. 语气自然,像在和家人沟通。
现在请根据上述内容回答问题。
"""
)

9
prompt/table.py Normal file
View File

@@ -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'=收入
"""

12
requirements.txt Normal file
View File

@@ -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

11
router.py Normal file
View File

@@ -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)

44
services/agent_service.py Normal file
View File

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