Files
2nd/10_Wiki/Topics/Coding/AI_Tool_Composition_Deep.md
T
2026-05-10 22:08:15 +09:00

9.2 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
ai-tool-composition-deep Tool Composition — agent 가 tool 사용 / chain Coding draft B conceptual 2026-05-09 2026-05-09
ai
agents
tools
vibe-coding
language applicable_to
TS / Python
AI
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)

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: 명확.
// ❌ 모호
{
  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

// 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

// 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

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.

권한 / 안전

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)

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)

// 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

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)

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 호출.

결과 형식

// 텍스트
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

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

→ LLM 가 chunk 별 처리.

Composition: Output → Input

// 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

// 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

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 패턴.

🔗 관련 문서