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
4 changes: 4 additions & 0 deletions examples/skills_with_exec_tool/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Set TRPC_AGENT_API_KEY、TRPC_AGENT_BASE_URL、TRPC_AGENT_MODEL_NAME
TRPC_AGENT_API_KEY=your-api-key
TRPC_AGENT_BASE_URL=your-base-url
TRPC_AGENT_MODEL_NAME=your-model-name
118 changes: 118 additions & 0 deletions examples/skills_with_exec_tool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# SkillExecTool 示例

本示例使用单个 `interactive-report` Skill,验证 `SkillExecTool` 的交互式
stdin、命令执行、输出文件收集和 Artifact 保存。

## 关键特性

- `skill_load` 加载唯一 Skill。
- `skill_exec` 启动交互式 Python 程序,并通过初始 `stdin` 回答两个问题。
- `output_files=["out/report.txt"]` 验证进程结束后的文件收集。
- `InMemoryArtifactService` 验证 `save_as_artifacts` 和 `artifact_files`。

## Agent 层级结构说明

- 根节点:`LlmAgent`(`skill_exec_agent`)。
- 挂载 `SkillToolSet` 和本地技能仓库,无子 Agent。

## 关键代码解释

- `run_agent.py`:要求模型按固定参数调用 `skill_exec`,并打印工具结果。
- `agent/tools.py`:使用本地 Workspace Runtime 构造 `SkillToolSet`。
- `skills/interactive-report/`:唯一 Skill,包含交互脚本和产物说明。

## stdin 如何工作

`run_agent.py` 要求模型在调用 `skill_exec` 时传入:

```json
{
"command": "python3 scripts/create_report.py",
"stdin": "release-1.2.0\n2\n"
}
```

`SkillExecTool` 会把该字符串作为进程启动时的初始 stdin。交互脚本连续调用两次
`input()`:

1. `release-1.2.0` 回答 `Release name`。
2. `2` 回答 `Report mode`,表示生成详细报告。

其效果等价于:

```bash
printf 'release-1.2.0\n2\n' | python3 scripts/create_report.py
```

这里演示的是启动时一次性提供 stdin。对于程序启动后才出现的动态问题,应先通过
`skill_exec` 获取 `session_id`,再使用 `skill_write_stdin` 分次输入,并通过
`skill_poll_session` 获取后续输出和最终产物。

## 环境要求

- Python3.10+,推荐 Python3.12

## 构建步骤

```bash
git clone https://github.com/trpc-group/trpc-agent-python.git
cd trpc-agent-python
./build.sh
source .venv/bin/activate
```

## 运行步骤

### 配置环境变量

通过环境变量或当前目录的 `.env` 配置:

- `TRPC_AGENT_API_KEY`
- `TRPC_AGENT_BASE_URL`
- `TRPC_AGENT_MODEL_NAME`
- 可选:`SKILLS_ROOT` 指向技能根目录

### 运行命令

```bash
cd examples/skills_with_exec_tool
python3 run_agent.py
```

## 预期结果

```txt
[Invoke Tool: skill_load(...)]
[Invoke Tool: skill_exec({
"skill": "interactive-report",
"command": "python3 scripts/create_report.py",
"stdin": "release-1.2.0\n2\n",
"output_files": ["out/report.txt"],
"save_as_artifacts": true
})]
[Tool Result: {
"status": "exited",
"exit_code": 0,
"result": {
"output_files": [{
"name": "out/report.txt",
"content": "Release: release-1.2.0\nMode: detailed\n..."
}],
"artifact_files": [{
"name": "skill-exec-demo/out/report.txt",
"version": 0
}]
}
}]
```

验证通过需要同时满足:

- `status=exited` 且 `exit_code=0`。
- `output_files` 包含非空的 `out/report.txt`。
- `artifact_files` 包含 `skill-exec-demo/out/report.txt`。

## 适用场景建议

- 交互式 CLI、安装向导、选择菜单等需要 stdin/TTY 的 Skill。
- 长时间运行、需要分段输出或最终收集产物的 Skill。
5 changes: 5 additions & 0 deletions examples/skills_with_exec_tool/agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
38 changes: 38 additions & 0 deletions examples/skills_with_exec_tool/agent/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
"""Agent configured for the interactive skill execution example."""

from trpc_agent_sdk.agents import LlmAgent
from trpc_agent_sdk.models import LLMModel
from trpc_agent_sdk.models import OpenAIModel

from .config import get_model_config
from .prompts import INSTRUCTION
from .tools import create_skill_tool_set


def _create_model() -> LLMModel:
"""Create the configured model."""
api_key, url, model_name = get_model_config()
model = OpenAIModel(model_name=model_name, api_key=api_key, base_url=url)
return model


def create_agent() -> LlmAgent:
"""Create an agent with the skill execution toolset."""
skill_tool_set, skill_repository = create_skill_tool_set()

return LlmAgent(
name="skill_exec_agent",
description="An assistant demonstrating interactive Agent Skill execution.",
model=_create_model(),
instruction=INSTRUCTION,
tools=[skill_tool_set],
skill_repository=skill_repository,
)


root_agent = create_agent()
19 changes: 19 additions & 0 deletions examples/skills_with_exec_tool/agent/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
""" Agent config module"""

import os


def get_model_config() -> tuple[str, str, str]:
"""Get model config from environment variables"""
api_key = os.getenv('TRPC_AGENT_API_KEY', '')
url = os.getenv('TRPC_AGENT_BASE_URL', '')
model_name = os.getenv('TRPC_AGENT_MODEL_NAME', '')
if not api_key or not url or not model_name:
raise ValueError('''TRPC_AGENT_API_KEY, TRPC_AGENT_BASE_URL,
and TRPC_AGENT_MODEL_NAME must be set in environment variables''')
return api_key, url, model_name
23 changes: 23 additions & 0 deletions examples/skills_with_exec_tool/agent/prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
"""Instructions for the skill execution example."""

INSTRUCTION = """
You demonstrate interactive Agent Skill execution.

There is one skill: interactive-report.
Always call skill_load before executing it.
Use skill_exec, never skill_run, for the demonstration.
Pass the requested stdin, output_files, save_as_artifacts, and artifact_prefix
arguments unchanged. After execution, report:

- process status and exit code
- collected output_files, including report content and workspace ref
- persisted artifact_files

Do not replace the interactive script with shell redirection or another
command.
"""
40 changes: 40 additions & 0 deletions examples/skills_with_exec_tool/agent/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
"""Build the skill toolset used by the example."""

import os
from pathlib import Path

from trpc_agent_sdk.code_executors import create_local_workspace_runtime
from trpc_agent_sdk.skills import ENV_SKILLS_ROOT
from trpc_agent_sdk.skills import SkillToolSet
from trpc_agent_sdk.skills import create_default_skill_repository
from trpc_agent_sdk.skills.tools import LinkSkillStager


def _get_skill_paths() -> str:
"""Get the skill paths."""
skills_root = os.getenv(ENV_SKILLS_ROOT)
if skills_root:
return skills_root
current_path = Path(__file__).parent
return str(current_path.parent / "skills")


def create_skill_tool_set():
"""Create a local skill repository with interactive execution enabled."""
workspace_runtime = create_local_workspace_runtime()
skill_paths = _get_skill_paths()
repository = create_default_skill_repository(
skill_paths,
workspace_runtime=workspace_runtime,
use_cached_repository=True,
)
skill_toolset = SkillToolSet(
repository=repository,
skill_stager=LinkSkillStager(),
)
return skill_toolset, repository
89 changes: 89 additions & 0 deletions examples/skills_with_exec_tool/run_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python3

# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
"""Demonstrate interactive skill execution and output collection."""

import asyncio
import json
import uuid

from dotenv import load_dotenv
from trpc_agent_sdk.artifacts import InMemoryArtifactService
from trpc_agent_sdk.runners import Runner
from trpc_agent_sdk.sessions import InMemorySessionService
from trpc_agent_sdk.types import Content
from trpc_agent_sdk.types import Part

load_dotenv()


async def run_skill_exec_demo() -> None:
"""Run one interactive skill and collect its output artifact."""
from agent.agent import root_agent

session_service = InMemorySessionService()
runner = Runner(
app_name="skill_exec_agent_demo",
agent=root_agent,
session_service=session_service,
artifact_service=InMemoryArtifactService(),
)
session_id = str(uuid.uuid4())
query = """
Use only the interactive-report skill for this task.

1. Load the skill documentation with skill_load.
2. Call skill_exec, not skill_run, with exactly these important arguments:
- skill: interactive-report
- command: python3 scripts/create_report.py
- stdin: release-1.2.0\\n2\\n
- yield_time_ms: 3000
- output_files: ["out/report.txt"]
- save_as_artifacts: true
- artifact_prefix: "skill-exec-demo/"
3. Report the collected output_files and artifact_files from the final
skill_exec result.
"""

print(f"Session ID: {session_id}")
print(f"User: {query}")
print("Assistant: ", end="", flush=True)
try:
async for event in runner.run_async(
user_id="demo_user",
session_id=session_id,
new_message=Content(parts=[Part.from_text(text=query)]),
):
if not event.content or not event.content.parts:
continue

if event.partial:
for part in event.content.parts:
if part.text:
print(part.text, end="", flush=True)
continue

for part in event.content.parts:
if part.thought:
continue
if part.function_call:
args = json.dumps(part.function_call.args, ensure_ascii=False)
print(f"\n[Invoke Tool: {part.function_call.name}({args})]")
elif part.function_response:
response = json.dumps(
part.function_response.response,
ensure_ascii=False,
default=str,
)
print(f"[Tool Result: {response}]")
print()
finally:
await runner.close()


if __name__ == "__main__":
asyncio.run(run_skill_exec_demo())
33 changes: 33 additions & 0 deletions examples/skills_with_exec_tool/skills/interactive-report/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: interactive-report
description: Generate a release report through an interactive command.
---

# Interactive release report

Use this skill to demonstrate `skill_exec` stdin handling and output
collection.

Run:

```bash
python3 scripts/create_report.py
```

The program asks for:

1. A release name.
2. Report mode `1` (compact) or `2` (detailed).

Use `skill_exec` with newline-separated initial `stdin` when the answers are
already known. Collect the generated file with:

```json
{
"output_files": ["out/report.txt"],
"save_as_artifacts": true,
"artifact_prefix": "skill-exec-demo/"
}
```

Do not use `skill_run` for this demonstration.
Loading
Loading