Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
custom-skills
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
eazy-template
custom-skills
Commits
20a64954
Commit
20a64954
authored
Mar 19, 2026
by
yuxiaodi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
init
parent
729cd5bc
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
121 additions
and
84 deletions
+121
-84
.eazy
.eazy
+49
-0
resource_dev.json
.resource/resource_dev.json
+1
-0
Dockerfile
Dockerfile
+5
-3
Makefile
Makefile
+37
-38
app.py
app.py
+29
-11
hello.py
hello.py
+0
-32
No files found.
.eazy
0 → 100644
View file @
20a64954
{
"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
}
}
]
}
.resource/resource_dev.json
0 → 100644
View file @
20a64954
{}
Dockerfile
View file @
20a64954
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
Makefile
View file @
20a64954
# 变量定义
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
...
...
main
.py
→
app
.py
View file @
20a64954
...
...
@@ -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
)
hello.py
deleted
100644 → 0
View file @
729cd5bc
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}"
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment