Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/content/90.cli/1.overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ VeADK 提供如下命令便捷您的操作:
| 命令 | 描述 | 说明 |
| :-- | :-- | :-- |
| `veadk init` | 生成可在 VeFaaS 中部署的项目脚手架 | 将会在您的目录中新增 `deploy.yaml` 文件 |
| `veadk create` | 在当前目录中创建一个新的智能体 | 将会生成智能体项目脚手架,包括 `config.yaml`, `agent.py` 等 |
| `veadk deploy` | 将某个项目部署到 VeFaaS 中 | |
| `veadk prompt` | 优化智能体系统提示词 | 借助火山引擎 PromptPilot 产品 |
| `veadk web` | 支持长短期记忆、知识库的前端调试界面 | 兼容 Google ADK web |
Expand Down
25 changes: 25 additions & 0 deletions docs/content/90.cli/2.commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ navigation:
icon: i-lucide-terminal
---

## 创建智能体

使用 `veadk create` 命令在当前目录中创建一个新的智能体项目脚手架。

```bash
veadk create [OPTIONS]
```

选项包括:

- `--agent-name`:智能体的名称。如果未提供,将会提示您输入。
- `--ark-api-key`:ARK API 密钥。如果未提供,将会提示您输入或稍后在 `config.yaml` 文件中配置。

执行 `veadk create` 命令后,它会:

1. 提示您输入智能体名称(如果未通过 `--agent-name` 提供)。
2. 提示您输入 ARK API 密钥(如果未通过 `--ark-api-key` 提供),您可以选择立即输入或稍后在生成的 `config.yaml` 文件中手动配置。
3. 在当前目录下创建一个以智能体名称命名的新目录。
4. 在新目录中生成以下文件:
* `config.yaml`: 包含模型配置和日志级别的配置文件。
* `{agent_name}/__init__.py`: 包初始化文件。
* `{agent_name}/agent.py`: 智能体的主要逻辑文件,包含一个基本的 `Agent` 定义。

如果目标目录已存在且不为空,它会提示您是否要覆盖它。


## 提示词优化

Expand Down
128 changes: 128 additions & 0 deletions tests/cli/test_cli_create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from click.testing import CliRunner
from pathlib import Path
from veadk.cli.cli_create import create, _generate_files


def test_create_agent_with_options():
runner = CliRunner()
with runner.isolated_filesystem() as temp_dir:
result = runner.invoke(
create, ["--agent-name", "test-agent", "--ark-api-key", "test-key"]
)
assert result.exit_code == 0

agent_folder = Path(temp_dir) / "test-agent"
assert agent_folder.exists()

config_path = agent_folder / "config.yaml"
assert config_path.exists()
config_content = config_path.read_text()
assert "api_key: test-key" in config_content

agent_init_path = agent_folder / "test-agent" / "__init__.py"
assert agent_init_path.exists()

agent_py_path = agent_folder / "test-agent" / "agent.py"
assert agent_py_path.exists()


def test_create_agent_overwrite_existing_directory():
runner = CliRunner()
with runner.isolated_filesystem() as temp_dir:
# First, create the agent
runner.invoke(
create, ["--agent-name", "test-agent", "--ark-api-key", "test-key"]
)

# Attempt to create it again, but cancel the overwrite
result = runner.invoke(
create,
["--agent-name", "test-agent", "--ark-api-key", "test-key"],
input="n\n",
)
assert "Operation cancelled" in result.output

# Attempt to create it again, and confirm the overwrite
result = runner.invoke(
create,
["--agent-name", "test-agent", "--ark-api-key", "new-key"],
input="y\n",
)
assert result.exit_code == 0
agent_folder = Path(temp_dir) / "test-agent"
config_path = agent_folder / "config.yaml"
config_content = config_path.read_text()
assert "api_key: new-key" in config_content


def test_generate_files(tmp_path: Path):
agent_name = "test-agent"
api_key = "test-key"
target_dir = tmp_path / agent_name

_generate_files(agent_name, api_key, target_dir)

agent_code_dir = target_dir / agent_name
assert agent_code_dir.is_dir()

config_file = target_dir / "config.yaml"
assert config_file.is_file()
content = config_file.read_text()
assert f"api_key: {api_key}" in content

init_file = agent_code_dir / "__init__.py"
assert init_file.is_file()

agent_file = agent_code_dir / "agent.py"
assert agent_file.is_file()


def test_prompt_for_ark_api_key_enter_now():
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(create, input="test-agent\n1\nmy-secret-key\n")
assert result.exit_code == 0
assert "my-secret-key" in (Path("test-agent") / "config.yaml").read_text()


def test_prompt_for_ark_api_key_configure_later():
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(create, input="test-agent\n2\n")
assert result.exit_code == 0
assert "api_key: " in (Path("test-agent") / "config.yaml").read_text()


def test_create_agent_with_prompts():
runner = CliRunner()
with runner.isolated_filesystem() as temp_dir:
result = runner.invoke(create, input="test-agent\n1\ntest-key\n")
assert result.exit_code == 0

agent_folder = Path(temp_dir) / "test-agent"
assert agent_folder.exists()

config_path = agent_folder / "config.yaml"
assert config_path.exists()
config_content = config_path.read_text()
assert "api_key: test-key" in config_content

agent_init_path = agent_folder / "test-agent" / "__init__.py"
assert agent_init_path.exists()

agent_py_path = agent_folder / "test-agent" / "agent.py"
assert agent_py_path.exists()
2 changes: 2 additions & 0 deletions veadk/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from veadk.cli.cli_deploy import deploy
from veadk.cli.cli_eval import eval
from veadk.cli.cli_init import init
from veadk.cli.cli_create import create
from veadk.cli.cli_kb import kb
from veadk.cli.cli_pipeline import pipeline
from veadk.cli.cli_prompt import prompt
Expand All @@ -37,6 +38,7 @@ def veadk():

veadk.add_command(deploy)
veadk.add_command(init)
veadk.add_command(create)
veadk.add_command(prompt)
veadk.add_command(web)
veadk.add_command(pipeline)
Expand Down
122 changes: 122 additions & 0 deletions veadk/cli/cli_create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import click
import shutil
from pathlib import Path

_CONFIG_YAML_TEMPLATE = """\
model:
agent:
name: doubao-seed-1-6-251015
api_key: {ark_api_key}
video:
name: doubao-seedance-1-0-pro-250528
# if you want to use different api_key, just uncomment following line and complete api_key
# api_key:
image:
name: doubao-seedream-4-0-250828
# if you want to use different api_key, just uncomment following line and complete api_key
# api_key:

logging:
# ERROR
# WARNING
# INFO
# DEBUG
level: DEBUG
"""

_INIT_PY_TEMPLATE = """\
from . import agent
"""

_AGENT_PY_TEMPLATE = """\
from veadk import Agent

root_agent = Agent(
name="root_agent",
description="A helpful assistant for user questions.",
instruction="Answer user questions to the best of your knowledge",
)
"""

_SUCCESS_MSG = """\
Agent '{agent_name}' created successfully at '{agent_folder}':
- config.yaml
- {agent_name}/__init__.py
- {agent_name}/agent.py

You can run the agent by executing: cd {agent_name} && veadk web
"""


def _prompt_for_ark_api_key() -> str:
click.secho(
"An API key is required to run the agent. See https://www.volcengine.com/docs/82379/1541594 for details.",
fg="green",
)
click.echo("You have two options:")
click.echo(" 1. Enter the API key now.")
click.echo(" 2. Configure it later in the generated config.yaml file.")
choice = click.prompt("Please select an option", type=click.Choice(["1", "2"]))
if choice == "1":
return click.prompt("Please enter your ARK API key")
else:
click.secho(
"You can set the `api_key` in the config.yaml file later.", fg="yellow"
)
return ""


def _generate_files(agent_name: str, ark_api_key: str, target_dir_path: Path) -> None:
agent_dir_path = target_dir_path / agent_name
agent_dir_path.mkdir(parents=True, exist_ok=True)
config_yaml_path = target_dir_path / "config.yaml"
init_file_path = agent_dir_path / "__init__.py"
agent_file_path = agent_dir_path / "agent.py"

config_yaml_content = _CONFIG_YAML_TEMPLATE.format(ark_api_key=ark_api_key)
config_yaml_path.write_text(config_yaml_content)
init_file_path.write_text(_INIT_PY_TEMPLATE)
agent_file_path.write_text(_AGENT_PY_TEMPLATE)

click.secho(
_SUCCESS_MSG.format(agent_name=agent_name, agent_folder=target_dir_path),
fg="green",
)


@click.command()
@click.option("--agent-name", help="The name of the agent.")
@click.option("--ark-api-key", help="The ARK API key.")
def create(agent_name: str, ark_api_key: str) -> None:
"""Creates a new agent in the current folder with prepopulated agent template."""
if not agent_name:
agent_name = click.prompt("Enter the agent name")
if not ark_api_key:
ark_api_key = _prompt_for_ark_api_key()

cwd = Path.cwd()
target_dir_path = cwd / agent_name

if target_dir_path.exists() and any(target_dir_path.iterdir()):
if not click.confirm(
f"Directory '{target_dir_path}' already exists and is not empty. Do you want to overwrite it?"
):
click.secho("Operation cancelled.", fg="red")
return
shutil.rmtree(target_dir_path)

_generate_files(agent_name, ark_api_key, target_dir_path)