feat: 初始化工程
This commit is contained in:
68
.gitignore
vendored
Normal file
68
.gitignore
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# 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/
|
||||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
WORKDIR /app
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||||
|
RUN echo 'Asia/Shanghai' > /etc/timezone
|
||||||
|
COPY ./packages /app/packages
|
||||||
|
COPY requirements.txt /app/
|
||||||
|
RUN pip install --no-cache-dir --no-index --find-links=/app/packages -r requirements.txt
|
||||||
|
COPY . /app/
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
# pip download -r requirements.txt -d ./packages --only-binary=:all: --platform manylinux2014_x86_64 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
9
agent/health.py
Normal file
9
agent/health.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from langchain.agents import create_agent
|
||||||
|
|
||||||
|
from agent.model import model
|
||||||
|
from agent.prompt import tip_prompt
|
||||||
|
|
||||||
|
tip_agent = create_agent(
|
||||||
|
model=model,
|
||||||
|
system_prompt=tip_prompt
|
||||||
|
)
|
||||||
21
agent/model.py
Normal file
21
agent/model.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from langchain.chat_models import init_chat_model
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
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')
|
||||||
|
)
|
||||||
|
|
||||||
|
# MODEL_NAME=deepseek-v4-flash
|
||||||
|
# MODEL_BASE_URL=https://api.deepseek.com
|
||||||
|
# MODEL_API_KEY=sk-0b237d41f6bc44fc9732ea66bd7eade0
|
||||||
|
|
||||||
|
# MODEL_NAME="glm-5.2"
|
||||||
|
# MODEL_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||||
|
# MODEL_API_KEY="sk-52bcd98e9c1d45908437c4e8706eefff"
|
||||||
58
agent/prompt.py
Normal file
58
agent/prompt.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
tip_prompt = """
|
||||||
|
你是一个专业的健康科普作者。请一次性生成5篇健康科普文章,分别对应以下5个分类,每个分类各1篇。
|
||||||
|
|
||||||
|
【分类】
|
||||||
|
1. 饮食营养
|
||||||
|
2. 运动健身
|
||||||
|
3. 睡眠作息
|
||||||
|
4. 心理健康
|
||||||
|
5. 常见病预防
|
||||||
|
|
||||||
|
【选题要求】
|
||||||
|
请你从每个分类中自行选择一个具体、实用的细分主题,5篇文章的主题互不重复。选题应贴近日常生活,适合普通大众阅读。
|
||||||
|
|
||||||
|
【每篇文章要求】
|
||||||
|
1. 标题简洁有力,吸引普通读者点击,控制在10字以内
|
||||||
|
2. 概要控制在50字以内,概括文章核心观点
|
||||||
|
3. 正文500字左右,语言通俗易懂,适合大众阅读,避免过多专业术语
|
||||||
|
4. 内容需有科学依据,给出3-5条可操作的实用建议
|
||||||
|
5. 正文结尾附一句总结金句
|
||||||
|
6. 文末附免责声明:"本文仅供科普参考,不构成医疗建议。"
|
||||||
|
7. 不要使用Markdown格式,正文中的换行用\n表示
|
||||||
|
8. 5篇文章的主题和内容互不重复
|
||||||
|
|
||||||
|
请严格按以下JSON数组格式输出,不要输出任何其他内容:
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "文章标题",
|
||||||
|
"summary": "50字以内的概要",
|
||||||
|
"category": "分类名称",
|
||||||
|
"content": "正文内容,500字左右"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "文章标题",
|
||||||
|
"summary": "50字以内的概要",
|
||||||
|
"category": "分类名称",
|
||||||
|
"content": "正文内容,500字左右"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "文章标题",
|
||||||
|
"summary": "50字以内的概要",
|
||||||
|
"category": "分类名称",
|
||||||
|
"content": "正文内容,500字左右"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "文章标题",
|
||||||
|
"summary": "50字以内的概要",
|
||||||
|
"category": "分类名称",
|
||||||
|
"content": "正文内容,500字左右"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "文章标题",
|
||||||
|
"summary": "50字以内的概要",
|
||||||
|
"category": "分类名称",
|
||||||
|
"content": "正文内容,500字左右"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
50
main.py
Normal file
50
main.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from starlette.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
from routers import routers
|
||||||
|
from service import agent_service
|
||||||
|
|
||||||
|
scheduler = BackgroundScheduler()
|
||||||
|
|
||||||
|
|
||||||
|
def job_task():
|
||||||
|
"""定时执行的任务"""
|
||||||
|
try:
|
||||||
|
print(f"定时任务执行: {datetime.now()}")
|
||||||
|
agent_service.generate_tip_agent()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"定时任务异常: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
scheduler.add_job(job_task, trigger="cron", hour=2, minute=0, second=0, id="daily_gen_tips", replace_existing=True)
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
# ========== 服务启动阶段 ==========
|
||||||
|
if not scheduler.running:
|
||||||
|
scheduler.start()
|
||||||
|
print("调度器已启动")
|
||||||
|
|
||||||
|
yield # 此处服务正常运行,接收请求
|
||||||
|
|
||||||
|
# ========== 服务关闭阶段 ==========
|
||||||
|
scheduler.shutdown()
|
||||||
|
print("调度器已关闭")
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(title="AI Health Service", lifespan=lifespan)
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
for router in routers:
|
||||||
|
app.include_router(router)
|
||||||
12
requirements.txt
Normal file
12
requirements.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
fastapi~=0.140.0
|
||||||
|
python-dotenv~=1.2.2
|
||||||
|
requests~=2.34.2
|
||||||
|
langchain~=1.3.14
|
||||||
|
langchain-core~=1.5.1
|
||||||
|
langchain-openai~=1.4.1
|
||||||
|
starlette~=1.3.1
|
||||||
|
pydantic~=2.13.4
|
||||||
|
SQLAlchemy~=2.0.51
|
||||||
|
asyncpg~=0.30.0
|
||||||
|
uvicorn~=0.23.0
|
||||||
|
APScheduler~=3.11.3
|
||||||
5
routers/__init__.py
Normal file
5
routers/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from .agent import router as agent_router
|
||||||
|
|
||||||
|
routers = [
|
||||||
|
agent_router
|
||||||
|
]
|
||||||
15
routers/agent.py
Normal file
15
routers/agent.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from service import agent_service
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/agent", tags=["Agent"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/tip/generate")
|
||||||
|
def generate_tip():
|
||||||
|
return agent_service.generate_tip_agent()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tip/latest")
|
||||||
|
def latest_tip():
|
||||||
|
return agent_service.get_latest_tip()
|
||||||
38
service/agent_service.py
Normal file
38
service/agent_service.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import json
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
from langchain_core.messages import HumanMessage
|
||||||
|
|
||||||
|
from agent.health import tip_agent
|
||||||
|
|
||||||
|
memory_store: Dict[str, Any] = {
|
||||||
|
"articles": []
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_tip_agent():
|
||||||
|
try:
|
||||||
|
print("开始生成今日5篇健康科普文章")
|
||||||
|
resp = tip_agent.invoke({
|
||||||
|
"messages": [HumanMessage(content="请生成今日5篇健康科普文章")]
|
||||||
|
})
|
||||||
|
last_msg = resp["messages"][-1]
|
||||||
|
raw_text = last_msg.content.strip()
|
||||||
|
|
||||||
|
# 解析大模型返回的json字符串
|
||||||
|
articles = json.loads(raw_text)
|
||||||
|
# 写入内存缓存
|
||||||
|
memory_store["articles"] = articles
|
||||||
|
print("结束生成今日5篇健康科普文章")
|
||||||
|
return True
|
||||||
|
except json.JSONDecodeError as je:
|
||||||
|
print(f"[JSON解析错误] {str(je)}")
|
||||||
|
memory_store["articles"] = []
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n[错误]: {str(e)}")
|
||||||
|
memory_store["articles"] = []
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_latest_tip():
|
||||||
|
return memory_store["articles"]
|
||||||
Reference in New Issue
Block a user