---
id: ai-tool-composition-deep
title: Tool Composition — agent 가 tool 사용 / chain
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [ai, agents, tools, vibe-coding]
tech_stack: { language: "TS / Python", applicable_to: ["AI"] }
applied_in: []
aliases: [tool composition, function calling, tool use, ReAct, MCP, agent tools]
---

# Tool Composition

> Agent 의 능력 = tool. **잘 design 된 tool = clear name, type, doc, error**. Tool chain (output → input). MCP 가 표준 protocol.

## 📖 핵심 개념
- Tool = 함수 + 명시적 schema.
- LLM 가 정확 tool + arg 호출.
- Composition: tool A → output → tool B.
- ReAct: reason + action loop.

## 💻 코드 패턴

### 단순 tool (Anthropic)
```ts
const tools = [
  {
    name: 'get_weather',
    description: 'Get current weather for a city',
    input_schema: {
      type: 'object',
      properties: {
        city: { type: 'string', description: 'City name, e.g. "Seoul"' },
        unit: { type: 'string', enum: ['c', 'f'], default: 'c' },
      },
      required: ['city'],
    },
  },
];

const r = await client.messages.create({
  model: 'claude-opus-4-7',
  tools,
  messages: [{ role: 'user', content: 'Weather in Seoul?' }],
});

if (r.stop_reason === 'tool_use') {
  const toolUse = r.content.find(c => c.type === 'tool_use');
  const result = await executeWeather(toolUse.input);
  
  // Tool result 반환
  const r2 = await client.messages.create({
    messages: [
      { role: 'user', content: 'Weather in Seoul?' },
      { role: 'assistant', content: r.content },
      { role: 'user', content: [{ type: 'tool_result', tool_use_id: toolUse.id, content: result }] },
    ],
  });
}
```

### Tool 정의 가이드
```
Name: snake_case, 동작 (get_, list_, create_).
Description: 이 tool 가 언제 / 무엇 / 다른 tool 와 차이.
Input schema: required 명시, default 명시.
Return: 명확.
```

```ts
// ❌ 모호
{
  name: 'data',
  description: 'Get data',
  input_schema: { ... }
}

// ✅
{
  name: 'list_users',
  description: 'List users in the org. Use for finding existing users; for creating new users, use create_user.',
  input_schema: {
    type: 'object',
    properties: {
      limit: { type: 'integer', default: 50, description: 'Max users to return' },
      filter: { type: 'string', description: 'Optional name/email filter' },
    },
  },
}
```

### Tool chain
```ts
// User: "Send Alice the latest sales report"

// Step 1: LLM 가 find_user(name='Alice')
// Step 2: get_sales_report() → URL
// Step 3: send_email(to=alice.email, attachment=url)

// Agent loop
while (true) {
  const r = await llm.call(messages);
  if (r.stop_reason === 'end_turn') break;
  if (r.stop_reason === 'tool_use') {
    const result = await executeTool(r.tool_use);
    messages.push({ role: 'tool', content: result });
  }
}
```

### Parallel tools
```ts
// Anthropic / OpenAI 가 1 turn 에 여러 tool 호출 가능
const tools = r.content.filter(c => c.type === 'tool_use');
const results = await Promise.all(tools.map(t => execute(t)));

messages.push({
  role: 'user',
  content: results.map((r, i) => ({ type: 'tool_result', tool_use_id: tools[i].id, content: r })),
});
```

→ "Get weather in Seoul AND Tokyo" → 2 tool 동시.

### ReAct (reason + act)
```
Thought: I need user's email first.
Action: find_user(name='Alice')
Observation: { email: 'alice@x.com', ... }
Thought: Now I get the report.
Action: get_sales_report()
Observation: { url: 'https://...' }
Thought: Send email.
Action: send_email(to='alice@x.com', attachment='https://...')
Observation: { sent: true }
Thought: Task complete.
```

→ LLM 가 think → act → observe.

### Tool error handling
```ts
async function executeTool(toolUse) {
  try {
    return await tools[toolUse.name](toolUse.input);
  } catch (e) {
    return {
      type: 'tool_result',
      tool_use_id: toolUse.id,
      is_error: true,
      content: `Error: ${e.message}. Try with different input.`,
    };
  }
}
```

→ LLM 가 retry / 다른 input.

### 권한 / 안전
```ts
const ALLOWED_TOOLS = {
  read_file: ['*.md', '!secret/*'],
  write_file: ['draft/*'],
  shell: false,  // disabled
};

function isAllowed(tool, args) {
  if (tool === 'write_file') {
    return micromatch.isMatch(args.path, ALLOWED_TOOLS.write_file);
  }
  // ...
}
```

→ Tool 가 user data destruction 방지.

### Confirmation (destructive)
```ts
async function executeTool(toolUse) {
  if (DESTRUCTIVE.includes(toolUse.name)) {
    const ok = await confirmUser(`${toolUse.name}(${JSON.stringify(toolUse.input)})?`);
    if (!ok) return { is_error: true, content: 'User cancelled' };
  }
  return tools[toolUse.name](toolUse.input);
}
```

### MCP (Model Context Protocol)
```ts
// MCP server (Anthropic 표준)
import { Server } from '@modelcontextprotocol/sdk/server/index.js';

const server = new Server({ name: 'my-tools', version: '1.0.0' });

server.setRequestHandler(ListToolsRequestSchema, () => ({
  tools: [
    { name: 'get_user', description: '...', inputSchema: { ... } },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  if (req.params.name === 'get_user') {
    return { content: [{ type: 'text', text: JSON.stringify(await getUser(...)) }] };
  }
});
```

→ 표준 protocol — Claude Desktop, Claude Code 가 native 지원.

### Tool registry / discovery
```ts
class ToolRegistry {
  private tools = new Map<string, Tool>();
  
  register(tool: Tool) { this.tools.set(tool.name, tool); }
  list(): Tool[] { return [...this.tools.values()]; }
  
  // 동적 — context 에 맞는 tool 만
  filter(context: string): Tool[] {
    return this.list().filter(t => t.relevantTo(context));
  }
}
```

→ 100+ tool = context window 넘김. 필요한 것만.

### Sub-agent (delegation)
```ts
const subAgent = {
  name: 'research_subagent',
  description: 'Delegate complex research task. Returns summary.',
  input_schema: { type: 'object', properties: { task: { type: 'string' } }, required: ['task'] },
};

async function execute(toolUse) {
  if (toolUse.name === 'research_subagent') {
    return await runSubAgent(toolUse.input.task);
  }
}
```

→ Tool 가 sub-LLM 호출.

### 결과 형식
```ts
// 텍스트
return { content: 'Result text' };

// JSON (LLM 가 parse)
return { content: JSON.stringify({ count: 5, items: [...] }) };

// 큰 결과 → reference
const fileId = await saveResult(big);
return { content: `Saved to file ${fileId}. Use read_file to access.` };
```

### Tool 의 idempotency
```
동작 가 idempotent = 안전하게 retry.

GET (read): idempotent.
POST (create): 안 됨 — 중복.
DELETE: idempotent (이미 없음 = 같음).

→ Idempotency key:
"create_user with id 'abc'" 가 두 번 호출 = 1 user 만.
```

### Streaming tool result
```ts
// 큰 결과 = streaming
async function* streamSearch(query) {
  for (const doc of await search(query)) {
    yield { type: 'text', text: JSON.stringify(doc) };
  }
}
```

→ LLM 가 chunk 별 처리.

### Composition: Output → Input
```ts
// Schema-aware 변환
const userResult = await tools.find_user('Alice');
// → { id: 'u1', email: 'alice@x.com' }

const emailResult = await tools.send_email({ 
  to: userResult.email,  // 직접 전달
  body: '...' 
});
```

→ LLM 가 자연 — 하지만 schema 일치 검증.

### Eval
```ts
// Tool selection eval
const cases = [
  { input: 'Email Alice the report', expected: ['find_user', 'get_report', 'send_email'] },
  { input: 'Whats 2+2', expected: [] },  // tool 호출 X
];

for (const c of cases) {
  const r = await agent.run(c.input);
  const tools = r.tool_calls.map(t => t.name);
  assert(deepEqual(tools, c.expected));
}
```

### Caching tool results
```ts
const cache = new Map();
async function cachedTool(name, input) {
  const key = `${name}:${JSON.stringify(input)}`;
  if (cache.has(key)) return cache.get(key);
  const r = await tools[name](input);
  cache.set(key, r);
  return r;
}
```

→ 같은 tool + 같은 input = 1번만.

### Tool naming pitfall
```
❌ search_v2 (옛 search 와 혼동)
❌ doSomething (모호)
❌ user_handler (handler X)

✅ list_users
✅ create_order
✅ delete_file (destructive 명확)
```

### Production debugging
```
Tool log:
- name + input + output
- Latency
- Error
- LLM 가 호출 한 reasoning

→ Bad output = tool error 인지 LLM error 인지.
```

## 🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| Simple tool | Inline schema |
| Many tools | MCP server |
| Destructive | Confirmation |
| Big result | File / streaming |
| Parallel | Promise.all in agent loop |
| Permission | Whitelist + path filter |
| Sub-task | Sub-agent tool |
| Tracing | LangSmith / Langfuse |

## ❌ 안티패턴
- **Tool 너무 많음**: context 폭발, LLM 헷갈림.
- **Schema 모호**: LLM 가 잘못 호출.
- **Permissions 없음**: rm -rf 가능.
- **Error 가 string only**: LLM 가 retry 못 함.
- **No idempotency**: 중복 effect.
- **Tool 가 큰 output**: token 폭발.
- **Confirmation 안 함 (destructive)**: 사고.

## 🤖 LLM 활용 힌트
- Tool name + description 가 가장 큰 lever.
- MCP 가 표준 — Claude / Cursor 가 native.
- Permission whitelist 필수.
- ReAct loop 가 default agent 패턴.

## 🔗 관련 문서
- [[AI_Function_Calling_Deep]]
- [[AI_MCP_Server_Building]]
- [[AI_Multi_Agent_Coordination]]
