From 9245864314506309a3502bf2a12561a83cb6b054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Tue, 7 Oct 2025 17:33:35 +0800 Subject: [PATCH] add docker --- .dockerignore | 56 ++++++++++++++++++++ Dockerfile | 44 ++++++++++++++++ __pycache__/gbase_agent.cpython-312.pyc | Bin 6870 -> 4908 bytes docker-compose.yml | 24 +++++++++ fastapi_app.py | 59 +++++++++++---------- gbase_agent.py | 65 ------------------------ requirements.txt | 13 +++++ 7 files changed, 168 insertions(+), 93 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 requirements.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a2e4e89 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,56 @@ +# Git +.git +.gitignore + +# Python +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +env +pip-log.txt +pip-delete-this-directory.txt +.tox +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.log +.git +.mypy_cache +.pytest_cache +.hypothesis + +# Virtual environments +venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Project specific +logs/ +projects/_cache/ +*.zip + +# Docker +Dockerfile +docker-compose.yml +.dockerignore \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..acee6f8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +# 使用Python 3.12官方镜像作为基础镜像 +FROM python:3.12-slim + +# 设置工作目录 +WORKDIR /app + +# 设置环境变量 +ENV PYTHONPATH=/app +ENV PYTHONUNBUFFERED=1 +ENV AGENT_POOL_SIZE=1 + +# 安装系统依赖 +RUN sed -i 's|http://deb.debian.org|http://mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources && \ + apt-get update && apt-get install -y \ + curl \ + wget \ + git \ + nodejs \ + npm \ + ripgrep \ + && rm -rf /var/lib/apt/lists/* + +# 复制requirements文件并安装Python依赖 +COPY requirements.txt . +RUN pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt + +# 复制应用代码 +COPY . . + +# 创建必要的目录 +RUN mkdir -p /app/projects + +# 设置权限 +RUN chmod +x /app/mcp/json_reader_server.py + +# 暴露端口 +EXPOSE 8000 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/ || exit 1 + +# 启动命令 +CMD ["python", "fastapi_app.py"] diff --git a/__pycache__/gbase_agent.cpython-312.pyc b/__pycache__/gbase_agent.cpython-312.pyc index 53f128139a3613a33c8d3a51d88df5c069cdd88b..e2649d8c776873208e26afdec8af0b890d223eb1 100644 GIT binary patch delta 1081 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 F`+rAtw}1cu diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2b48421 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3.8" + +services: + qwen-agent: + build: . + container_name: qwen-agent-api + ports: + - "8000:8000" + environment: + # 应用配置 + - PYTHONPATH=/app + - PYTHONUNBUFFERED=1 + - AGENT_POOL_SIZE=2 + volumes: + # 挂载项目数据目录 + - ./projects:/app/projects + restart: unless-stopped + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s diff --git a/fastapi_app.py b/fastapi_app.py index 691e6e1..2d87f2e 100644 --- a/fastapi_app.py +++ b/fastapi_app.py @@ -1,6 +1,7 @@ import json import os from typing import AsyncGenerator, Dict, List, Optional, Union +from contextlib import asynccontextmanager import uvicorn from fastapi import BackgroundTasks, FastAPI, HTTPException @@ -42,12 +43,40 @@ from agent_pool import (get_agent_from_pool, init_global_agent_pool, from gbase_agent import init_agent_service_universal, update_agent_llm from zip_project_handler import zip_handler -app = FastAPI(title="Database Assistant API", version="1.0.0") - # 全局助手实例池,在应用启动时初始化 agent_pool_size = int(os.getenv("AGENT_POOL_SIZE", "1")) +@asynccontextmanager +async def lifespan(app: FastAPI): + """应用生命周期管理""" + # 启动时初始化助手实例池 + print(f"正在启动FastAPI应用,初始化助手实例池(大小: {agent_pool_size})...") + + try: + def agent_factory(): + return init_agent_service_universal() + + await init_global_agent_pool(pool_size=agent_pool_size, agent_factory=agent_factory) + print("助手实例池初始化完成!") + yield + except Exception as e: + print(f"助手实例池初始化失败: {e}") + raise + + # 关闭时清理实例池 + print("正在关闭应用,清理助手实例池...") + + from agent_pool import get_agent_pool + pool = get_agent_pool() + if pool: + await pool.shutdown() + print("助手实例池清理完成!") + + +app = FastAPI(title="Database Assistant API", version="1.0.0", lifespan=lifespan) + + class Message(BaseModel): role: str content: str @@ -279,32 +308,6 @@ async def cleanup_cache(): raise HTTPException(status_code=500, detail=f"缓存清理失败: {str(e)}") -@app.on_event("startup") -async def startup_event(): - """应用启动时初始化助手实例池""" - print(f"正在启动FastAPI应用,初始化助手实例池(大小: {agent_pool_size})...") - - try: - def agent_factory(): - return init_agent_service_universal() - - await init_global_agent_pool(pool_size=agent_pool_size, agent_factory=agent_factory) - print("助手实例池初始化完成!") - except Exception as e: - print(f"助手实例池初始化失败: {e}") - raise - - -@app.on_event("shutdown") -async def shutdown_event(): - """应用关闭时清理实例池""" - print("正在关闭应用,清理助手实例池...") - - from agent_pool import get_agent_pool - pool = get_agent_pool() - if pool: - await pool.shutdown() - print("助手实例池清理完成!") if __name__ == "__main__": diff --git a/gbase_agent.py b/gbase_agent.py index 53ecc9d..678ed35 100644 --- a/gbase_agent.py +++ b/gbase_agent.py @@ -21,7 +21,6 @@ import os from typing import Dict, List, Optional, Union from qwen_agent.agents import Assistant -from qwen_agent.gui import WebUI from qwen_agent.llm.oai import TextChatAtOAI from qwen_agent.llm.schema import ASSISTANT, FUNCTION, Message from qwen_agent.utils.output_beautify import typewriter_print @@ -169,67 +168,3 @@ def test(query="数据库里有几张表"): final_response = responses[-1][-1] # 取最后一个响应作为最终结果 print("Answer:", final_response["content"]) - -def app_tui(): - # Define the agent - 使用通用初始化 - bot = init_agent_service_universal() - - # Chat - messages = [] - while True: - # Query example: 数据库里有几张表 - query = input("user question: ") - # File example: resource/poem.pdf - file = input("file url (press enter if no file): ").strip() - if not query: - print("user question cannot be empty!") - continue - if not file: - messages.append({"role": "user", "content": query}) - else: - messages.append( - {"role": "user", "content": [{"text": query}, {"file": file}]} - ) - - response = [] - for response in bot.run(messages): - print("bot response:", response) - messages.extend(response) - - -def app_gui(): - # Define the agent - 使用通用初始化 - bot = init_agent_service_universal() - chatbot_config = { - "prompt.suggestions": [ - "数据库里有几张表", - "创建一个学生表包括学生的姓名、年龄", - "增加一个学生名字叫韩梅梅,今年6岁", - ] - } - WebUI( - bot, - chatbot_config=chatbot_config, - ).run() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="数据库助手") - parser.add_argument( - "--query", type=str, default="数据库里有几张表", help="用户问题" - ) - parser.add_argument( - "--mode", - type=str, - choices=["test", "tui", "gui"], - default="test", - help="运行模式", - ) - args = parser.parse_args() - - if args.mode == "test": - test(args.query) - elif args.mode == "tui": - app_tui() - elif args.mode == "gui": - app_gui() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..752b45e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +# FastAPI和Web服务器 +fastapi==0.116.1 +uvicorn==0.35.0 + +# HTTP客户端 +requests==2.32.5 + +# Qwen Agent框架 +qwen-agent[mcp]==0.0.29 + +# 数据处理 +pydantic==2.10.5 +python-dateutil==2.8.2