From 825e0979897fb416bcacbf89d2e6d71b77e38fd6 Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Mon, 31 Aug 2026 15:22:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=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 --- .gitignore | 68 ++++++++++++++++++++++++++++++++++++++++ Dockerfile | 11 +++++++ agent/health.py | 9 ++++++ agent/model.py | 21 +++++++++++++ agent/prompt.py | 58 ++++++++++++++++++++++++++++++++++ main.py | 50 +++++++++++++++++++++++++++++ requirements.txt | 12 +++++++ routers/__init__.py | 5 +++ routers/agent.py | 15 +++++++++ service/agent_service.py | 38 ++++++++++++++++++++++ 10 files changed, 287 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 agent/health.py create mode 100644 agent/model.py create mode 100644 agent/prompt.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 routers/__init__.py create mode 100644 routers/agent.py create mode 100644 service/agent_service.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ddac70 --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0295eb2 --- /dev/null +++ b/Dockerfile @@ -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 diff --git a/agent/health.py b/agent/health.py new file mode 100644 index 0000000..985de6b --- /dev/null +++ b/agent/health.py @@ -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 +) diff --git a/agent/model.py b/agent/model.py new file mode 100644 index 0000000..da55a09 --- /dev/null +++ b/agent/model.py @@ -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" \ No newline at end of file diff --git a/agent/prompt.py b/agent/prompt.py new file mode 100644 index 0000000..4e6f512 --- /dev/null +++ b/agent/prompt.py @@ -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字左右" + } +] +""" diff --git a/main.py b/main.py new file mode 100644 index 0000000..cbb959d --- /dev/null +++ b/main.py @@ -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) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b5a9b21 --- /dev/null +++ b/requirements.txt @@ -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 \ No newline at end of file diff --git a/routers/__init__.py b/routers/__init__.py new file mode 100644 index 0000000..18dacad --- /dev/null +++ b/routers/__init__.py @@ -0,0 +1,5 @@ +from .agent import router as agent_router + +routers = [ + agent_router +] diff --git a/routers/agent.py b/routers/agent.py new file mode 100644 index 0000000..40e8b61 --- /dev/null +++ b/routers/agent.py @@ -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() diff --git a/service/agent_service.py b/service/agent_service.py new file mode 100644 index 0000000..cbbde28 --- /dev/null +++ b/service/agent_service.py @@ -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"] \ No newline at end of file