Commit 6ef4a3cd authored by yuxiaodi's avatar yuxiaodi

init

parent 9a9de2fd
FROM hb.eazytec-cloud.com/eazydevelop/python:3.12.3-slim-amd64
# 设置工作目录
WORKDIR /app
# 复制当前目录下的所有文件到容器中
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
# 暴露端口
EXPOSE $APP_PORT_1
# 启动命令
CMD uvicorn main:app --host 0.0.0.0 --port $APP_PORT_1
# 变量定义
APP_NAME = fastapi-hello
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
# 安装命令
.PHONY: install
install:
@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 "✅ 安装完成"
# 运行命令
.PHONY: run
run:
@echo "启动FastAPI应用..."
@. $(VENV)/bin/activate && APP_PORT_1=$(APP_PORT_1) $(PYTHON) main.py &
@echo "FastAPI服务已启动,端口: $(APP_PORT_1)"
# 停止命令
.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 "✅ 清理完成"
# 重新安装
.PHONY: reinstall
reinstall: clean install
@echo "✅ 重新安装完成"
# 打包技能:将 `skills/<技能目录>/` 生成 `skills/<技能目录>.skill`
.PHONY: skill
SKILLS_DIR = skills
SKILL_DIR ?=
skill:
@if [ ! -d "$(SKILLS_DIR)" ]; then \
echo "❌ 错误:找不到 '$(SKILLS_DIR)' 目录,请先创建并在其中开发技能。"; \
exit 1; \
fi; \
if [ -z "$(SKILL_DIR)" ]; then \
SELECTED_DIR="$$(ls -1d "$(SKILLS_DIR)"/*/ 2>/dev/null | head -n 1 | xargs -n1 basename)"; \
else \
SELECTED_DIR="$(SKILL_DIR)"; \
fi; \
if [ -z "$$SELECTED_DIR" ]; then \
echo "❌ 错误:在 '$(SKILLS_DIR)' 下未找到任何技能子目录。"; \
exit 1; \
fi; \
if [ ! -d "$(SKILLS_DIR)/$$SELECTED_DIR" ]; then \
echo "❌ 错误:指定的技能目录不存在:'$(SKILLS_DIR)/$$SELECTED_DIR'"; \
exit 1; \
fi; \
rm -f "$(SKILLS_DIR)/$$SELECTED_DIR.zip" "$(SKILLS_DIR)/$$SELECTED_DIR.skill"; \
echo "正在打包技能:$$SELECTED_DIR"; \
zip -r -q "$(SKILLS_DIR)/$$SELECTED_DIR.zip" "$(SKILLS_DIR)/$$SELECTED_DIR"; \
mv -f "$(SKILLS_DIR)/$$SELECTED_DIR.zip" "$(SKILLS_DIR)/$$SELECTED_DIR.skill"; \
echo "✅ 已生成:'$(SKILLS_DIR)/$$SELECTED_DIR.skill'"
\ No newline at end of file
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}"}
from fastapi import FastAPI, HTTPException, Query, Request
from fastapi.responses import FileResponse
from pathlib import Path
import os
import re
from typing import Optional
app = FastAPI()
BASE_DIR = Path(__file__).resolve().parent
SKILLS_DIR = BASE_DIR / "skills"
def _build_welcome_message(download_url: str) -> str:
return (
"欢迎来到 Eazybot 智能体技能开发模板。\n"
"请在 IDE 中的 `skills/` 目录下开发您的自定义技能;开发完成后,使用 `make skill` 命令打包。\n"
"部署后,你可以访问:"
f"\n{download_url}"
"\n下载打包好的 `.skill` 压缩包;也可以直接把该链接交给 Eazybot,让其自行安装技能。"
)
@app.get("/")
def read_root(request: Request):
download_url = f"{str(request.base_url).rstrip('/')}/download"
return {"message": _build_welcome_message(download_url)}
@app.get("/download")
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/` 目录,请先创建并开发技能后再打包。")
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`。")
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` 仅允许包含字母、数字、点、下划线和短横线。")
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}`。")
return FileResponse(
path=str(file_path),
media_type="application/octet-stream",
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)
fastapi==0.109.2
uvicorn==0.27.1
\ No newline at end of file
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