Skip to content

Commit a51aa93

Browse files
authored
feat(cli): add 'veadk create' for veadk cli (#256)
1 parent 60d53be commit a51aa93

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

docs/content/90.cli/1.overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ VeADK 提供如下命令便捷您的操作:
1010
| 命令 | 描述 | 说明 |
1111
| :-- | :-- | :-- |
1212
| `veadk init` | 生成可在 VeFaaS 中部署的项目脚手架 | 将会在您的目录中新增 `deploy.yaml` 文件 |
13+
| `veadk create` | 在当前目录中创建一个新的智能体 | 将会生成智能体项目脚手架,包括 `config.yaml`, `agent.py`|
1314
| `veadk deploy` | 将某个项目部署到 VeFaaS 中 | |
1415
| `veadk prompt` | 优化智能体系统提示词 | 借助火山引擎 PromptPilot 产品 |
1516
| `veadk web` | 支持长短期记忆、知识库的前端调试界面 | 兼容 Google ADK web |

docs/content/90.cli/2.commands.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@ navigation:
55
icon: i-lucide-terminal
66
---
77

8+
## 创建智能体
9+
10+
使用 `veadk create` 命令在当前目录中创建一个新的智能体项目脚手架。
11+
12+
```bash
13+
veadk create [OPTIONS]
14+
```
15+
16+
选项包括:
17+
18+
- `--agent-name`:智能体的名称。如果未提供,将会提示您输入。
19+
- `--ark-api-key`:ARK API 密钥。如果未提供,将会提示您输入或稍后在 `config.yaml` 文件中配置。
20+
21+
执行 `veadk create` 命令后,它会:
22+
23+
1. 提示您输入智能体名称(如果未通过 `--agent-name` 提供)。
24+
2. 提示您输入 ARK API 密钥(如果未通过 `--ark-api-key` 提供),您可以选择立即输入或稍后在生成的 `config.yaml` 文件中手动配置。
25+
3. 在当前目录下创建一个以智能体名称命名的新目录。
26+
4. 在新目录中生成以下文件:
27+
* `config.yaml`: 包含模型配置和日志级别的配置文件。
28+
* `{agent_name}/__init__.py`: 包初始化文件。
29+
* `{agent_name}/agent.py`: 智能体的主要逻辑文件,包含一个基本的 `Agent` 定义。
30+
31+
如果目标目录已存在且不为空,它会提示您是否要覆盖它。
32+
833

934
## 提示词优化
1035

tests/cli/test_cli_create.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from click.testing import CliRunner
16+
from pathlib import Path
17+
from veadk.cli.cli_create import create, _generate_files
18+
19+
20+
def test_create_agent_with_options():
21+
runner = CliRunner()
22+
with runner.isolated_filesystem() as temp_dir:
23+
result = runner.invoke(
24+
create, ["--agent-name", "test-agent", "--ark-api-key", "test-key"]
25+
)
26+
assert result.exit_code == 0
27+
28+
agent_folder = Path(temp_dir) / "test-agent"
29+
assert agent_folder.exists()
30+
31+
config_path = agent_folder / "config.yaml"
32+
assert config_path.exists()
33+
config_content = config_path.read_text()
34+
assert "api_key: test-key" in config_content
35+
36+
agent_init_path = agent_folder / "test-agent" / "__init__.py"
37+
assert agent_init_path.exists()
38+
39+
agent_py_path = agent_folder / "test-agent" / "agent.py"
40+
assert agent_py_path.exists()
41+
42+
43+
def test_create_agent_overwrite_existing_directory():
44+
runner = CliRunner()
45+
with runner.isolated_filesystem() as temp_dir:
46+
# First, create the agent
47+
runner.invoke(
48+
create, ["--agent-name", "test-agent", "--ark-api-key", "test-key"]
49+
)
50+
51+
# Attempt to create it again, but cancel the overwrite
52+
result = runner.invoke(
53+
create,
54+
["--agent-name", "test-agent", "--ark-api-key", "test-key"],
55+
input="n\n",
56+
)
57+
assert "Operation cancelled" in result.output
58+
59+
# Attempt to create it again, and confirm the overwrite
60+
result = runner.invoke(
61+
create,
62+
["--agent-name", "test-agent", "--ark-api-key", "new-key"],
63+
input="y\n",
64+
)
65+
assert result.exit_code == 0
66+
agent_folder = Path(temp_dir) / "test-agent"
67+
config_path = agent_folder / "config.yaml"
68+
config_content = config_path.read_text()
69+
assert "api_key: new-key" in config_content
70+
71+
72+
def test_generate_files(tmp_path: Path):
73+
agent_name = "test-agent"
74+
api_key = "test-key"
75+
target_dir = tmp_path / agent_name
76+
77+
_generate_files(agent_name, api_key, target_dir)
78+
79+
agent_code_dir = target_dir / agent_name
80+
assert agent_code_dir.is_dir()
81+
82+
config_file = target_dir / "config.yaml"
83+
assert config_file.is_file()
84+
content = config_file.read_text()
85+
assert f"api_key: {api_key}" in content
86+
87+
init_file = agent_code_dir / "__init__.py"
88+
assert init_file.is_file()
89+
90+
agent_file = agent_code_dir / "agent.py"
91+
assert agent_file.is_file()
92+
93+
94+
def test_prompt_for_ark_api_key_enter_now():
95+
runner = CliRunner()
96+
with runner.isolated_filesystem():
97+
result = runner.invoke(create, input="test-agent\n1\nmy-secret-key\n")
98+
assert result.exit_code == 0
99+
assert "my-secret-key" in (Path("test-agent") / "config.yaml").read_text()
100+
101+
102+
def test_prompt_for_ark_api_key_configure_later():
103+
runner = CliRunner()
104+
with runner.isolated_filesystem():
105+
result = runner.invoke(create, input="test-agent\n2\n")
106+
assert result.exit_code == 0
107+
assert "api_key: " in (Path("test-agent") / "config.yaml").read_text()
108+
109+
110+
def test_create_agent_with_prompts():
111+
runner = CliRunner()
112+
with runner.isolated_filesystem() as temp_dir:
113+
result = runner.invoke(create, input="test-agent\n1\ntest-key\n")
114+
assert result.exit_code == 0
115+
116+
agent_folder = Path(temp_dir) / "test-agent"
117+
assert agent_folder.exists()
118+
119+
config_path = agent_folder / "config.yaml"
120+
assert config_path.exists()
121+
config_content = config_path.read_text()
122+
assert "api_key: test-key" in config_content
123+
124+
agent_init_path = agent_folder / "test-agent" / "__init__.py"
125+
assert agent_init_path.exists()
126+
127+
agent_py_path = agent_folder / "test-agent" / "agent.py"
128+
assert agent_py_path.exists()

veadk/cli/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from veadk.cli.cli_deploy import deploy
1919
from veadk.cli.cli_eval import eval
2020
from veadk.cli.cli_init import init
21+
from veadk.cli.cli_create import create
2122
from veadk.cli.cli_kb import kb
2223
from veadk.cli.cli_pipeline import pipeline
2324
from veadk.cli.cli_prompt import prompt
@@ -37,6 +38,7 @@ def veadk():
3738

3839
veadk.add_command(deploy)
3940
veadk.add_command(init)
41+
veadk.add_command(create)
4042
veadk.add_command(prompt)
4143
veadk.add_command(web)
4244
veadk.add_command(pipeline)

veadk/cli/cli_create.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import click
16+
import shutil
17+
from pathlib import Path
18+
19+
_CONFIG_YAML_TEMPLATE = """\
20+
model:
21+
agent:
22+
name: doubao-seed-1-6-251015
23+
api_key: {ark_api_key}
24+
video:
25+
name: doubao-seedance-1-0-pro-250528
26+
# if you want to use different api_key, just uncomment following line and complete api_key
27+
# api_key:
28+
image:
29+
name: doubao-seedream-4-0-250828
30+
# if you want to use different api_key, just uncomment following line and complete api_key
31+
# api_key:
32+
33+
logging:
34+
# ERROR
35+
# WARNING
36+
# INFO
37+
# DEBUG
38+
level: DEBUG
39+
"""
40+
41+
_INIT_PY_TEMPLATE = """\
42+
from . import agent
43+
"""
44+
45+
_AGENT_PY_TEMPLATE = """\
46+
from veadk import Agent
47+
48+
root_agent = Agent(
49+
name="root_agent",
50+
description="A helpful assistant for user questions.",
51+
instruction="Answer user questions to the best of your knowledge",
52+
)
53+
"""
54+
55+
_SUCCESS_MSG = """\
56+
Agent '{agent_name}' created successfully at '{agent_folder}':
57+
- config.yaml
58+
- {agent_name}/__init__.py
59+
- {agent_name}/agent.py
60+
61+
You can run the agent by executing: cd {agent_name} && veadk web
62+
"""
63+
64+
65+
def _prompt_for_ark_api_key() -> str:
66+
click.secho(
67+
"An API key is required to run the agent. See https://www.volcengine.com/docs/82379/1541594 for details.",
68+
fg="green",
69+
)
70+
click.echo("You have two options:")
71+
click.echo(" 1. Enter the API key now.")
72+
click.echo(" 2. Configure it later in the generated config.yaml file.")
73+
choice = click.prompt("Please select an option", type=click.Choice(["1", "2"]))
74+
if choice == "1":
75+
return click.prompt("Please enter your ARK API key")
76+
else:
77+
click.secho(
78+
"You can set the `api_key` in the config.yaml file later.", fg="yellow"
79+
)
80+
return ""
81+
82+
83+
def _generate_files(agent_name: str, ark_api_key: str, target_dir_path: Path) -> None:
84+
agent_dir_path = target_dir_path / agent_name
85+
agent_dir_path.mkdir(parents=True, exist_ok=True)
86+
config_yaml_path = target_dir_path / "config.yaml"
87+
init_file_path = agent_dir_path / "__init__.py"
88+
agent_file_path = agent_dir_path / "agent.py"
89+
90+
config_yaml_content = _CONFIG_YAML_TEMPLATE.format(ark_api_key=ark_api_key)
91+
config_yaml_path.write_text(config_yaml_content)
92+
init_file_path.write_text(_INIT_PY_TEMPLATE)
93+
agent_file_path.write_text(_AGENT_PY_TEMPLATE)
94+
95+
click.secho(
96+
_SUCCESS_MSG.format(agent_name=agent_name, agent_folder=target_dir_path),
97+
fg="green",
98+
)
99+
100+
101+
@click.command()
102+
@click.option("--agent-name", help="The name of the agent.")
103+
@click.option("--ark-api-key", help="The ARK API key.")
104+
def create(agent_name: str, ark_api_key: str) -> None:
105+
"""Creates a new agent in the current folder with prepopulated agent template."""
106+
if not agent_name:
107+
agent_name = click.prompt("Enter the agent name")
108+
if not ark_api_key:
109+
ark_api_key = _prompt_for_ark_api_key()
110+
111+
cwd = Path.cwd()
112+
target_dir_path = cwd / agent_name
113+
114+
if target_dir_path.exists() and any(target_dir_path.iterdir()):
115+
if not click.confirm(
116+
f"Directory '{target_dir_path}' already exists and is not empty. Do you want to overwrite it?"
117+
):
118+
click.secho("Operation cancelled.", fg="red")
119+
return
120+
shutil.rmtree(target_dir_path)
121+
122+
_generate_files(agent_name, ark_api_key, target_dir_path)

0 commit comments

Comments
 (0)