ZeroClaw — Tool 시스템
Layer 2 학습. 실제 소스 코드 기반. 파일:
src/tools/traits.rs,src/tools/mod.rs,src/agent/prompt.rs
전체 구조
Tool trait (계약 정의)
↓
개별 Tool 구현체 (ShellTool, FileReadTool, MemoryRecallTool, ...)
↓
all_tools_with_runtime() (레지스트리 조립)
↓
Agent::build() (agent에 주입)
↓
SystemPromptBuilder::ToolsSection (LLM에 노출)
↓
ToolDispatcher (tool call 파싱 & 실행)
Tool trait — 계약
// src/tools/traits.rs
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters_schema(&self) -> serde_json::Value;
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult>;
// default method: 위 3개로 자동 구성
fn spec(&self) -> ToolSpec {
ToolSpec {
name: self.name().to_string(),
description: self.description().to_string(),
parameters: self.parameters_schema(),
}
}
}
pub struct ToolResult {
pub success: bool,
pub output: String, // LLM에 돌려줄 텍스트
pub error: Option<String>,
}
pub struct ToolSpec {
pub name: String,
pub description: String,
pub parameters: serde_json::Value, // JSON Schema
}
커스텀 Tool 구현 예시
// 학교 LMS 크롤러 tool
struct FetchAssignmentTool {
db: Arc<Db>,
security: Arc<SecurityPolicy>,
}
#[async_trait]
impl Tool for FetchAssignmentTool {
fn name(&self) -> &str { "fetch_assignments" }
fn description(&self) -> &str {
"학교 LMS에서 미제출 과제 목록을 조회한다."
}
fn parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"course": {
"type": "string",
"description": "과목명 (생략하면 전체 조회)"
}
}
})
}
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
let course = args.get("course").and_then(|v| v.as_str());
let assignments = crawl_lms(course).await?;
Ok(ToolResult {
success: true,
output: serde_json::to_string(&assignments)?,
error: None,
})
}
}
이것만 구현하면 ZeroClaw agent가 자동으로 LLM에 노출하고 호출한다.
레지스트리 조립 — all_tools_with_runtime()
config에 따라 활성화된 tool들을 Vec<Box<dyn Tool>>로 조립한다.
기본 내장 도구 (항상 등록):
ShellTool shell 명령 실행 (allowlist, timeout 포함)
FileReadTool 파일 읽기 (경로 검증)
FileWriteTool 파일 쓰기
FileEditTool 파일 라인 편집
GlobSearchTool 파일 glob 검색
ContentSearchTool 파일 내용 검색
CronAddTool cron 작업 추가
MemoryStoreTool 기억 저장
MemoryRecallTool 기억 조회
MemoryForgetTool 기억 삭제
ScheduleTool 일정 관리
GitOperationsTool git 작업
CalculatorTool 계산기
WeatherTool 날씨 조회
조건부 등록 (config에 따라):
BrowserTool browser.enabled = true
HttpRequestTool http_request.enabled = true
WebSearchTool web_search.enabled = true
JiraTool jira.enabled = true
NotionTool notion.enabled = true
ComposioTool composio_key 있을 때
DelegateTool agents 설정이 있을 때
SecurityPolicy가 생성 시 주입된다
Arc::new(ShellTool::new_with_sandbox(
security.clone(), // ← 보안 정책 주입
runtime,
sandbox,
)),
ShellTool은 실행 전 security.is_allowed_command(cmd)로 명령어 검증, security.is_rate_limited()로 비율 제한을 체크한다.
System Prompt 주입 — ToolsSection
agent가 초기화되면 build_system_prompt()가 호출되고, ToolsSection이 모든 tool을 LLM에게 설명하는 텍스트를 생성한다.
// src/agent/prompt.rs
impl PromptSection for ToolsSection {
fn build(&self, ctx: &PromptContext<'_>) -> Result<String> {
let mut out = String::from("## Tools\n\n");
for tool in ctx.tools {
writeln!(
out,
"- **{}**: {}\n Parameters: `{}`",
tool.name(),
tool.description(), // ← description()이 LLM에게 노출
tool.parameters_schema() // ← schema가 파라미터 설명으로
);
}
// XmlToolDispatcher면 여기에 <tool_call> 사용법도 추가
out.push_str(ctx.dispatcher_instructions);
Ok(out)
}
}
결과적으로 LLM이 받는 system prompt에는:
## Tools
- **shell**: 쉘 명령을 실행한다.
Parameters: `{"type":"object","properties":{"command":{"type":"string"}}}`
- **fetch_assignments**: 학교 LMS에서 미제출 과제 목록을 조회한다.
Parameters: `{"type":"object","properties":{"course":{...}}}`
...
전체 데이터 흐름
① Config → all_tools_with_runtime()
→ Vec<Box<dyn Tool>> 조립 (Security 주입)
② Agent::build()
→ tool_specs = tools.iter().map(|t| t.spec()).collect()
→ tools와 tool_specs 모두 보관
③ turn() 첫 호출
→ build_system_prompt()
→ ToolsSection.build() → tool 목록 텍스트 생성
→ history에 system message로 push
④ turn() 루프
→ ChatRequest { messages, tools: Some(&tool_specs) }
→ provider.chat(...) 호출
├─ NativeDispatcher: tool_specs를 API tools 배열로 전달
└─ XmlDispatcher: tool 목록이 이미 system prompt 텍스트에 있음
⑤ LLM 응답 → tool call 파싱
→ executor.execute_tool_call(call)
→ tools.iter().find(|t| t.name() == call.name)
→ tool.execute(args).await
→ ToolResult { success, output, error }
ArcToolRef 패턴
MCP tool 등 나중에 동적으로 추가되는 tool은 Arc<dyn Tool>로 관리된다. Box<dyn Tool>과 함께 쓰기 위해 ArcToolRef 래퍼를 사용한다.
pub struct ArcToolRef(pub Arc<dyn Tool>);
#[async_trait]
impl Tool for ArcToolRef {
fn name(&self) -> &str { self.0.name() }
fn description(&self) -> &str { self.0.description() }
fn parameters_schema(&self) -> serde_json::Value { self.0.parameters_schema() }
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
self.0.execute(args).await
}
}
커스텀 tool 추가 방법
1. tool 구현 (새 파일)
// src/tools/my_tool.rs
pub struct MyTool { ... }
#[async_trait]
impl Tool for MyTool { ... }
2. all_tools_with_runtime()에 등록
// src/tools/mod.rs - all_tools_with_runtime() 함수 내
tool_arcs.push(Arc::new(MyTool::new(security.clone())));
끝이야. system prompt 주입, LLM 노출, 파싱, 실행까지 전부 자동으로 처리된다.
관련
- trait-system — Tool trait 원형
- agent-loop — tool call 실행 흐름 (turn() 내부)
- provider-implementations — Provider trait
- overview — ZeroClaw 학습 지도