Skip to content

Commit 48e7cf1

Browse files
committed
Merge branch 'main' into deploy-oauth
2 parents 4a98b87 + 831329d commit 48e7cf1

37 files changed

+1409
-260
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,7 @@ cython_debug/
196196

197197
**/.nuxt
198198
**/.data
199-
**./output
199+
**./output
200+
201+
*.mp3
202+
*.pcm

config.yaml.full

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ tool:
4646
web_scraper:
4747
endpoint:
4848
api_key: # `token`
49+
# [optional] https://console.volcengine.com/speech/new/experience/tts
50+
text_to_speech:
51+
app_id: # `app_id`
52+
api_key: # `app_secret`
53+
speaker: # `speaker`
4954
# [optional] https://open.larkoffice.com/app
5055
lark:
5156
endpoint: # `app_id`

docs/content/5.tools/3.sandbox-tools.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,73 @@ navigation:
55
icon: i-lucide-codesandbox
66
---
77

8-
提供多种沙箱工具:
8+
## 概述
99

10+
VeADK 提供多种沙箱工具,为 Agent 的执行与操作提供安全、隔离的运行环境。目前支持以下几种类型:
11+
- Code sandbox
1012
- Computer sandbox (TBD)
1113
- Browser sandbox (TBD)
12-
- Code sandbox (TBD)
14+
15+
当前,VeADK 支持两种方式调用 Code Sandbox:
16+
- (推荐)内建工具 [`run_code`](https://www.volcengine.com/docs/86681/1847934):该方式通过 AgentKit tools 调用,配置过程可自定义代码生成模型。通过设置环境变量 `AGENTKIT_TOOL_ID` 或者在 config.yaml 中添加配置即可开始使用:
17+
```yaml
18+
agentkit:
19+
tool:
20+
id: <your_id>
21+
```
22+
- MCP 工具 [`code_sandbox`](https://www.volcengine.com/mcp-marketplace/detail?name=Code-Sandbox%20MCP):该方式通过 MCP(Model Context Protocol) 调用 Code Sandbox 工具,具体步骤为:
23+
- 在 VeFaaS 一键部署 [Code Sandbox Agent 应用](https://www.volcengine.com/docs/6662/1538139),配置过程中可自定义代码生成模型;
24+
- 部署成功后,在部署的 Code sanbox 的应用详情标签页获取**访问地址**,地址形式是:{YOUR_URL}/?token={YOUR_TOKEN};
25+
- 为 Code sandbox [创建 MCP Server](https://www.volcengine.com/mcp-marketplace/detail?name=Code-Sandbox%20MCP) 并配置环境变量:
26+
```bash
27+
SANDBOX_API={YOUR_URL}
28+
AUTH_TOKEN={YOUR_TOKEN}
29+
```
30+
- 最后,将上述 Code sanbox 访问地址设置环境变量 `TOOL_CODE_SANDBOX_URL` 或者在 config.yaml 中添加配置即可开始使用:
31+
```yaml
32+
tool:
33+
code_sandbox:
34+
url: <your_url>
35+
```
36+
37+
38+
## 使用
39+
以下示例展示了在 VeADK 中如何通过两种方式调用沙箱工具来执行代码。
40+
41+
示例一:使用内建工具 `run_code`:
42+
43+
```python [agent.py]
44+
import asyncio
45+
from veadk import Agent, Runner
46+
from veadk.tools.builtin_tools.run_code import run_code
47+
48+
agent = Agent(
49+
instruction="你是一名资深的代码专家。请通过调用 run_code 工具执行代码、调试、并展示运行结果。",
50+
tools=[run_code]
51+
)
52+
53+
runner = Runner(agent=agent)
54+
55+
response = asyncio.run(runner.run(messages="请用Python写一个函数,计算斐波那契数列的前10项,并打印出来。"))
56+
57+
print(response)
58+
```
59+
60+
示例二:使用 MCP 工具 `code_sandbox`
61+
62+
```python [agent.py]
63+
import asyncio
64+
from veadk import Agent, Runner
65+
from veadk.tools.sandbox.code_sandbox import code_sandbox
66+
67+
agent = Agent(
68+
instruction="你是一名资深的代码专家。请通过调用 code_sandbox 工具执行代码、调试、并展示运行结果。",
69+
tools=[code_sandbox]
70+
)
71+
72+
runner = Runner(agent=agent)
73+
74+
response = asyncio.run(runner.run(messages="请用Python写一个函数,计算斐波那契数列的前10项,并打印出来。"))
75+
76+
print(response)
77+
```

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "veadk-python"
3-
version = "0.2.21"
3+
version = "0.2.22"
44
description = "Volcengine agent development kit, integrations with Volcengine cloud services."
55
readme = "README.md"
66
requires-python = ">=3.10"
@@ -54,6 +54,7 @@ database = [
5454
"tos>=2.8.4", # For TOS storage and Viking DB
5555
"mem0ai==0.1.118", # For mem0
5656
]
57+
speech = []
5758
eval = [
5859
"prometheus-client>=0.22.1", # For exporting data to Prometheus pushgateway
5960
"deepeval>=3.2.6", # For DeepEval-based evaluation
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 pytest
16+
from unittest.mock import patch, MagicMock
17+
from veadk.auth.veauth.speech_veauth import get_speech_token
18+
19+
20+
# Test cases
21+
22+
23+
def test_get_speech_token_with_env_vars(monkeypatch):
24+
"""Test when credentials are available in environment variables"""
25+
# Setup
26+
monkeypatch.setenv("VOLCENGINE_ACCESS_KEY", "test_access_key")
27+
monkeypatch.setenv("VOLCENGINE_SECRET_KEY", "test_secret_key")
28+
29+
mock_response = {"Result": {"APIKeys": [{"APIKey": "test_api_key"}]}}
30+
31+
with patch("veadk.auth.veauth.speech_veauth.ve_request") as mock_ve_request:
32+
mock_ve_request.return_value = mock_response
33+
34+
# Execute
35+
result = get_speech_token()
36+
37+
# Verify
38+
assert result == "test_api_key"
39+
mock_ve_request.assert_called_once_with(
40+
request_body={
41+
"ProjectName": "default",
42+
"OnlyAvailable": True,
43+
},
44+
header={"X-Security-Token": ""},
45+
action="ListAPIKeys",
46+
ak="test_access_key",
47+
sk="test_secret_key",
48+
service="speech_saas_prod",
49+
version="2025-05-20",
50+
region="cn-beijing",
51+
host="open.volcengineapi.com",
52+
)
53+
54+
55+
def test_get_speech_token_with_vefaas_iam(monkeypatch):
56+
"""Test when credentials are obtained from vefaas iam"""
57+
# Setup
58+
monkeypatch.delenv("VOLCENGINE_ACCESS_KEY", raising=False)
59+
monkeypatch.delenv("VOLCENGINE_SECRET_KEY", raising=False)
60+
61+
mock_cred = MagicMock()
62+
mock_cred.access_key_id = "vefaas_access_key"
63+
mock_cred.secret_access_key = "vefaas_secret_key"
64+
mock_cred.session_token = "vefaas_session_token"
65+
66+
mock_response = {"Result": {"APIKeys": [{"APIKey": "vefaas_api_key"}]}}
67+
68+
with (
69+
patch(
70+
"veadk.auth.veauth.speech_veauth.get_credential_from_vefaas_iam"
71+
) as mock_get_cred,
72+
patch("veadk.auth.veauth.speech_veauth.ve_request") as mock_ve_request,
73+
):
74+
mock_get_cred.return_value = mock_cred
75+
mock_ve_request.return_value = mock_response
76+
77+
# Execute
78+
result = get_speech_token(region="cn-shanghai")
79+
80+
# Verify
81+
assert result == "vefaas_api_key"
82+
mock_get_cred.assert_called_once()
83+
mock_ve_request.assert_called_once_with(
84+
request_body={
85+
"ProjectName": "default",
86+
"OnlyAvailable": True,
87+
},
88+
header={"X-Security-Token": "vefaas_session_token"},
89+
action="ListAPIKeys",
90+
ak="vefaas_access_key",
91+
sk="vefaas_secret_key",
92+
service="speech_saas_prod",
93+
version="2025-05-20",
94+
region="cn-shanghai",
95+
host="open.volcengineapi.com",
96+
)
97+
98+
99+
def test_get_speech_token_invalid_response():
100+
"""Test when API response is invalid"""
101+
# Setup
102+
monkeypatch = pytest.MonkeyPatch()
103+
monkeypatch.setenv("VOLCENGINE_ACCESS_KEY", "test_access_key")
104+
monkeypatch.setenv("VOLCENGINE_SECRET_KEY", "test_secret_key")
105+
106+
mock_response = {"Error": {"Message": "Invalid request"}}
107+
108+
with patch("veadk.auth.veauth.speech_veauth.ve_request") as mock_ve_request:
109+
mock_ve_request.return_value = mock_response
110+
111+
# Execute & Verify
112+
with pytest.raises(ValueError, match="Failed to get speech api key list"):
113+
get_speech_token()

tests/test_misc.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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 os
16+
import sys
17+
import types
18+
19+
from veadk.utils.misc import get_agents_dir, get_agent_dir
20+
21+
22+
class GetAgentsDirTest:
23+
def test_get_agents_dir_from_main_file(monkeypatch):
24+
"""
25+
Case 1: __main__.__file__ exists (common in CLI or uv run environments)
26+
"""
27+
fake_main = types.SimpleNamespace(__file__="/tmp/project/testapp/agent.py")
28+
monkeypatch.setitem(sys.modules, "__main__", fake_main)
29+
30+
result = get_agents_dir()
31+
assert result == "/tmp/project"
32+
result = get_agent_dir()
33+
assert result == "/tmp/project/testapp"
34+
35+
def test_get_agents_dir_from_sys_argv(monkeypatch):
36+
"""
37+
Case 2: Fallback to sys.argv[0]
38+
"""
39+
fake_main = types.SimpleNamespace()
40+
monkeypatch.setitem(sys.modules, "__main__", fake_main)
41+
monkeypatch.setattr(sys, "argv", ["/tmp/project/testapp/agent.py"])
42+
43+
result = get_agents_dir()
44+
assert result == "/tmp/project"
45+
result = get_agent_dir()
46+
assert result == "/tmp/project/testapp"
47+
48+
def test_get_agents_dir_from_cwd(monkeypatch, tmp_path):
49+
"""
50+
Case 3: Fallback to current working directory (REPL or no file context)
51+
"""
52+
fake_main = types.SimpleNamespace()
53+
monkeypatch.setitem(sys.modules, "__main__", fake_main)
54+
monkeypatch.setattr(sys, "argv", [])
55+
56+
fake_cwd = tmp_path / "some_dir"
57+
fake_cwd.mkdir()
58+
59+
monkeypatch.setattr(os, "getcwd", lambda: str(fake_cwd))
60+
result = get_agents_dir()
61+
62+
# should return the parent of fake_cwd
63+
assert result == str(tmp_path)
64+
result = get_agent_dir()
65+
assert result == str(tmp_path / "some_dir")

tests/test_runtime_data_collecting.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import json
1616
import os
17+
import uuid
1718

1819
import pytest
1920
from utils import generate_events, generate_session
@@ -25,7 +26,7 @@
2526
USER_ID = "user"
2627
SESSION_ID = "session"
2728

28-
EVAL_SET_ID = "temp_unittest"
29+
EVAL_SET_ID = "temp_unittest" + uuid.uuid4().hex
2930

3031

3132
@pytest.mark.asyncio
@@ -46,7 +47,7 @@ async def test_runtime_data_collecting():
4647
recorder = EvalSetRecorder(session_service=session_service, eval_set_id=EVAL_SET_ID)
4748
dump_path = await recorder.dump(APP_NAME, USER_ID, SESSION_ID)
4849

49-
assert dump_path == f"/tmp/{APP_NAME}/{recorder.eval_set_id}.evalset.json"
50+
# assert dump_path == f"/tmp/{APP_NAME}/{recorder.eval_set_id}.evalset.json"
5051
assert os.path.exists(dump_path) and os.path.isfile(dump_path)
5152
assert os.path.getsize(dump_path) > 0
5253

0 commit comments

Comments
 (0)