Commit 20a64954 authored by yuxiaodi's avatar yuxiaodi

init

parent 729cd5bc
{
"version": "1.0.0",
"name": "EazyBot自定义技能开发模板",
"tags": ["python", "3.12", "fastapi"],
"description": "EazyBot自定义技能开发模板",
"init_commands": ["make install"],
"start_commands": ["make run"],
"stop_commands": ["make stop"],
"mapping_ports":
{
"APP_PORT_1": 8000,
"APP_PORT_2": 8001,
"APP_PORT_3": 8002,
"APP_PORT_4": 8003
},
"deploy":
[
{
"image_name": "python312-fastapi",
"app_access_entry": true,
"ports":
[
{
"name": "APP_PORT_1",
"main_port": true,
"inner_access_env_key": "API_URL",
"open_access": true,
"open_access_env_key": "API_DOMAIN"
},
{
"name": "APP_PORT_2",
"main_port": false,
"dns_env_key": null,
"open_access": false,
"open_access_env_key": null
}
],
"docker_file_dir": "./",
"deploy_resources":
{
"cpu_limit": 0.5,
"memory_limit": 2,
"storage_limit": 1
}
}
]
}
FROM hb.eazytec-cloud.com/eazydevelop/python:3.12.3-slim-amd64
# 使用官方的 Python 基础镜像
FROM hb.eazytec-cloud.com/eazytec/eazydevelop-python:ubuntu22.04-python3.12
# 设置工作目录
WORKDIR /app
......@@ -8,10 +9,11 @@ COPY . /app
ENV RESOURCE_PATH=/app/.resource/resource_deploy.json
# 安装依赖
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/ && pip install --no-cache-dir -r requirements.txt
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ && \
pip install --no-cache-dir -r requirements.txt
# 暴露端口
EXPOSE $APP_PORT_1
# 启动命令
CMD uvicorn main:app --host 0.0.0.0 --port $APP_PORT_1
CMD python app.py
# 变量定义
APP_NAME = fastapi-hello
APP_NAME = python3.12
PYTHON = python3
PIP = pip3
VENV = venv
APP_PORT_1 ?= 8000
RESOURCE_PATH = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/.resource/resource_dev.json
LOG_FILE = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/app.log
# 检查 uv 是否安装
UV = uv
APP_PORT = $(or $(APP_PORT_1),8000)
RESOURCE_PATH = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/.resource/resource_dev.json
LOG_FILE = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/.logs/app.log
# 安装命令
.PHONY: install
install:
@echo "检查Python版本..."
@echo "检查 Python 版本..."
@$(PYTHON) --version
@echo "创建虚拟环境..."
@$(PYTHON) -m venv $(VENV)
@echo "安装依赖..."
@. $(VENV)/bin/activate && $(PIP) install --upgrade pip
@. $(VENV)/bin/activate && $(PIP) install -r requirements.txt
@echo "✅ 安装完成"
@echo "检查 uv 是否已安装..."
@if ! command -v $(UV) >/dev/null 2>&1; then \
echo "❌ 错误:uv 未安装,请先安装 uv"; \
echo "安装命令:curl -LsSf https://astral.sh/uv/install.sh | sh"; \
exit 1; \
fi
@echo "✅ uv 已安装"
@if [ ! -d ".venv" ]; then \
echo "正在使用 uv 创建虚拟环境..."; \
$(UV) venv .venv; \
else \
echo "虚拟环境已存在,跳过创建步骤。"; \
fi
@echo "正在使用 uv 安装依赖..."
@$(UV) pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
@echo "✅ 安装完成!虚拟环境已创建并激活,依赖已安装"
# 运行命令
.PHONY: run
run:
@echo "启动FastAPI应用..."
@. $(VENV)/bin/activate && APP_PORT_1=$(APP_PORT_1) $(PYTHON) main.py &
@echo "FastAPI服务已启动,端口: $(APP_PORT_1)"
@echo "正在启动 Python 3.12 应用..."
@mkdir -p .logs
RESOURCE_PATH=$(RESOURCE_PATH) $(UV) run python app.py > $(LOG_FILE) 2>&1 &
@echo "✅ Python 3.12 应用已启动"
# 停止命令
.PHONY: stop
stop:
@ss -ltnp | grep ':$(APP_PORT)' | awk '{print $7}' | sed 's/.*pid=\([0-9]*\).*/\1/' | xargs kill
@echo "FastAPI服务已停止"
# 测试命令
.PHONY: test
test:
@echo "正在运行测试..."
@. $(VENV)/bin/activate && python -m pytest tests/ -v
# 清理命令
.PHONY: clean
clean:
@echo "正在清理..."
@rm -rf $(VENV) __pycache__ .pytest_cache
@rm -f $(LOG_FILE)
@echo "✅ 清理完成"
@echo "正在停止 Python 3.12 应用..."
@if command -v lsof >/dev/null 2>&1; then \
echo "使用 lsof 命令停止应用..."; \
lsof -ti:$(APP_PORT) | xargs kill -9 2>/dev/null || echo "没有找到运行在端口 $(APP_PORT) 的进程"; \
elif command -v ss >/dev/null 2>&1; then \
echo "使用 ss 命令停止应用..."; \
ss -ltnp | grep ':$(APP_PORT)' | awk '{print $7}' | sed 's/.*pid=\([0-9]*\).*/\1/' | xargs kill 2>/dev/null || echo "没有找到运行在端口 $(APP_PORT) 的进程"; \
else \
echo "❌ 错误:系统中没有找到 lsof 或 ss 命令"; \
exit 1; \
fi
@echo "✅ Python 3.12 应用已停止"
# 重新安装
.PHONY: reinstall
reinstall: clean install
@echo "✅ 重新安装完成"
# 打包技能:将 `skills/<技能目录>/` 生成 `skills/<技能目录>.skill`
.PHONY: skill
......
......@@ -4,6 +4,7 @@ from pathlib import Path
import os
import re
from typing import Optional
import uvicorn
app = FastAPI()
......@@ -13,11 +14,11 @@ SKILLS_DIR = BASE_DIR / "skills"
def _build_welcome_message(download_url: str) -> str:
return (
"欢迎来到 Eazybot 智能体技能开发模板。\n"
"请在 IDE 中的 `skills/` 目录下开发您的自定义技能;开发完成后,使用 `make skill` 命令打包。\n"
"欢迎来到 Eazybot 智能体技能开发模板。"
"请在 IDE 中的 `skills/` 目录下开发您的自定义技能;开发完成后,使用 `make skill` 命令打包。"
"部署后,你可以访问:"
f"\n{download_url}"
"\n下载打包好的 `.skill` 压缩包;也可以直接把该链接交给 Eazybot,让其自行安装技能。"
f"{download_url}"
"下载打包好的 `.skill` 压缩包;也可以直接把该链接交给 Eazybot,让其自行安装技能。"
)
......@@ -28,24 +29,41 @@ def read_root(request: Request):
@app.get("/download")
def download_skill(skill: Optional[str] = Query(default=None, description="要下载的技能名(例如 weather-query 或 weather-query.skill)")):
def download_skill(
skill: Optional[str] = Query(
default=None,
description="要下载的技能名(例如 weather-query 或 weather-query.skill)",
),
):
if not SKILLS_DIR.exists() or not SKILLS_DIR.is_dir():
raise HTTPException(status_code=404, detail="未找到 `skills/` 目录,请先创建并开发技能后再打包。")
raise HTTPException(
status_code=404,
detail="未找到 `skills/` 目录,请先创建并开发技能后再打包。",
)
if skill is None or not str(skill).strip():
skill_files = sorted(SKILLS_DIR.glob("*.skill"))
if not skill_files:
raise HTTPException(status_code=404, detail="未找到任何 `.skill` 文件,请先在 `skills/` 目录下开发技能并执行 `make skill`。")
raise HTTPException(
status_code=404,
detail="未找到任何 `.skill` 文件,请先在 `skills/` 目录下开发技能并执行 `make skill`。",
)
file_path = skill_files[0]
else:
raw = str(skill).strip()
name = raw[:-6] if raw.endswith(".skill") else raw
if not re.fullmatch(r"[A-Za-z0-9._-]+", name):
raise HTTPException(status_code=400, detail="参数 `skill` 仅允许包含字母、数字、点、下划线和短横线。")
raise HTTPException(
status_code=400,
detail="参数 `skill` 仅允许包含字母、数字、点、下划线和短横线。",
)
file_path = SKILLS_DIR / f"{name}.skill"
if not file_path.exists() or not file_path.is_file():
raise HTTPException(status_code=404, detail=f"未找到技能文件:`{file_path.name}`。")
raise HTTPException(
status_code=404,
detail=f"未找到技能文件:`{file_path.name}`。",
)
return FileResponse(
path=str(file_path),
......@@ -53,7 +71,7 @@ def download_skill(skill: Optional[str] = Query(default=None, description="要
filename=file_path.name,
)
if __name__ == "__main__":
import uvicorn
port = int(os.getenv("APP_PORT_1", 8000))
uvicorn.run("main:app", host="0.0.0.0", port=port, reload=True)
uvicorn.run("app:app", host="0.0.0.0", port=port, reload=True)
from fastapi import APIRouter, Request
from pydantic import BaseModel
router = APIRouter()
@router.get("/")
def hello_world(request: Request):
download_url = f"{str(request.base_url).rstrip('/')}/download"
return {
"message": (
"欢迎来到 Eazybot 智能体技能开发模板。\n"
"请在 IDE 中的 `skills/` 目录下开发您的自定义技能,并使用 `make skill` 命令打包。\n"
f"部署后,你可以访问:\n{download_url}\n"
"下载打包好的 `.skill` 压缩包;或把该链接交给 Eazybot 让其自行安装技能。"
)
}
@router.get("/hello/{name}")
def hello_world_name(name: str):
return {"message": f"Hello {name}"}
@router.get("/hello/")
def hello_world_name_query(name: str):
return {"message": f"Hello {name}"}
class People(BaseModel):
name: str
@router.post("/hello_post/")
def hello_world_name_post(people: People):
return {"message": f"Hello {people.name}"}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment