LLM 에이전트는 수천 개의 tool 정의를 컨텍스트에 담을 수 없다.
벡터 검색은 유사한 tool은 찾지만, 그들이 속한 워크플로는 놓친다.
graph-tool-call은 tool 그래프를 구축하고 — 단일 매칭이 아닌 — 올바른 체인을 찾아온다.
| Retrieval 없음 | graph-tool-call | |
|---|---|---|
| 248 tools (K8s API) | 12% 정확도 | 82% 정확도 |
| 1068 tools (GitHub full API) | context overflow | 78% Recall@5 |
| 토큰 사용량 | 8,192 tok | 1,699 tok (79% ↓) |
qwen3:4b (4-bit) 기준 — 전체 벤치마크
목차
LLM 에이전트는 tool이 필요하다. 하지만 tool 개수가 늘면 두 가지가 무너진다.
- 컨텍스트 오버플로 — Kubernetes API 248개 엔드포인트 = tool 정의 8,192 토큰. LLM이 막히고 정확도는 **12%**까지 떨어진다.
- 벡터 검색은 워크플로를 놓친다 — *"내 주문 취소해줘"*를 검색하면
cancelOrder가 나오지만, 실제 흐름은listOrders → getOrder → cancelOrder → processRefund다. 벡터 검색은 tool 하나만 돌려주고, 정작 필요한 건 체인이다.
graph-tool-call은 둘 다 해결한다. tool 관계를 그래프로 모델링하고, 하이브리드 검색(BM25 + 그래프 탐색 + 임베딩 + MCP annotation)으로 멀티스텝 워크플로를 검색하며, 토큰 사용량을 64–91% 줄이면서도 정확도를 유지하거나 개선한다.
| 시나리오 | Vector-only | graph-tool-call |
|---|---|---|
| "내 주문 취소" | cancelOrder 반환 |
listOrders → getOrder → cancelOrder → processRefund |
| "파일 읽고 저장" | read_file 반환 |
read_file + write_file (COMPLEMENTARY 관계) |
| "오래된 레코드 삭제" | "delete" 매칭 tool 아무거나 | MCP annotation으로 destructive tool 우선 |
| "이제 그거 취소해" (이전에 listing 함) | history 컨텍스트 없음 | 사용한 tool 강등, 다음 단계 tool 부스트 |
| 여러 Swagger spec에 중복 tool | 결과에 중복 노출 | cross-source 자동 dedupe |
| 1,200 API 엔드포인트 | 느리고 노이즈 많음 | 카테고리화 + 그래프 탐색으로 정밀 검색 |
OpenAPI / MCP / Python 함수 → Ingest → Tool 그래프 빌드 → Hybrid 검색 → Agent
예시 — 사용자가 *"내 주문 취소하고 환불 처리해줘"*라고 한다.
벡터 검색은 cancelOrder를 찾는다. 하지만 실제 워크플로는 이렇다:
┌──────────┐
PRECEDES │listOrders│ PRECEDES
┌─────────┤ ├──────────┐
▼ └──────────┘ ▼
┌──────────┐ ┌───────────┐
│ getOrder │ │cancelOrder│
└──────────┘ └─────┬─────┘
│ COMPLEMENTARY
▼
┌──────────────┐
│processRefund │
└──────────────┘
graph-tool-call은 tool 하나가 아니라 체인 전체를 반환한다. 검색은 **weighted Reciprocal Rank Fusion (wRRF)**로 4가지 신호를 결합한다.
- BM25 — 키워드 매칭
- 그래프 탐색 — 관계 기반 확장 (PRECEDES, REQUIRES, COMPLEMENTARY)
- 임베딩 유사도 — semantic 검색 (선택, 어떤 provider든)
- MCP annotation — read-only / destructive / idempotent 힌트
코어 패키지는 의존성 0 — Python 표준 라이브러리만 사용. 필요한 것만 골라 설치하면 된다.
pip install graph-tool-call # core (BM25 + graph) — 의존성 없음
pip install graph-tool-call[embedding] # + 임베딩, cross-encoder reranker
pip install graph-tool-call[openapi] # + OpenAPI YAML 지원
pip install graph-tool-call[mcp] # + MCP server / proxy 모드
pip install graph-tool-call[all] # 전부전체 extras
| Extra | 설치 | 사용 시기 |
|---|---|---|
openapi |
pyyaml | YAML OpenAPI spec |
embedding |
numpy | semantic 검색 (Ollama/OpenAI/vLLM 연동) |
embedding-local |
numpy, sentence-transformers | 로컬 sentence-transformers 모델 |
similarity |
rapidfuzz | 중복 탐지 |
langchain |
langchain-core | LangChain 통합 |
visualization |
pyvis, networkx | HTML graph export, GraphML |
dashboard |
dash, dash-cytoscape | 인터랙티브 대시보드 |
lint |
ai-api-lint | 망가진 API spec 자동 수정 |
mcp |
mcp | MCP server / proxy 모드 |
uvx graph-tool-call search "user authentication" \
--source https://petstore.swagger.io/v2/swagger.jsonQuery: "user authentication"
Source: https://petstore.swagger.io/v2/swagger.json (19 tools)
Results (5):
1. getUserByName — Get user by user name
2. deleteUser — Delete user
3. createUser — Create user
4. loginUser — Logs user into the system
5. updateUser — Updated user
from graph_tool_call import ToolGraph
# Petstore API에서 tool 그래프 빌드
tg = ToolGraph.from_url(
"https://petstore3.swagger.io/api/v3/openapi.json",
cache="petstore.json",
)
print(tg)
# → ToolGraph(tools=19, nodes=22, edges=100)
# Tool 검색
tools = tg.retrieve("create a new pet", top_k=5)
for t in tools:
print(f"{t.name}: {t.description}")
# 워크플로 가이드 포함 검색
results = tg.retrieve_with_scores("process an order", top_k=5)
for r in results:
print(f"{r.tool.name} [{r.confidence}]")
for rel in r.relations:
print(f" → {rel.hint}")
# OpenAPI tool 직접 실행
result = tg.execute(
"addPet", {"name": "Buddy", "status": "available"},
base_url="https://petstore3.swagger.io/api/v3",
)plan_workflow()은 prerequisite을 포함한 순서 있는 실행 체인을 반환 — 에이전트 round-trip을 3-4회에서 1회로 줄인다.
plan = tg.plan_workflow("process a refund")
for step in plan.steps:
print(f"{step.order}. {step.tool.name} — {step.reason}")
# 1. getOrder — prerequisite for requestRefund
# 2. requestRefund — primary action
plan.save("refund_workflow.json")워크플로 편집, 파라미터 매핑, 시각화 → Direct API 가이드 참조.
# MCP server에서 (HTTP JSON-RPC tools/list)
tg.ingest_mcp_server("https://mcp.example.com/mcp")
# MCP tool 리스트에서 (annotation 보존)
tg.ingest_mcp_tools(mcp_tools, server_name="filesystem")
# Python 콜러블에서 (type hint + docstring)
tg.ingest_functions([read_file, write_file])MCP annotation (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)은 검색 신호로 활용된다 — query intent가 자동 분류되어, read 쿼리는 read-only tool을, delete 쿼리는 destructive tool을 우선한다.
graph-tool-call은 여러 통합 패턴을 제공한다. 본인 스택에 맞는 걸 골라 쓰면 된다.
| 사용 환경 | 패턴 | 토큰 절감 | 가이드 |
|---|---|---|---|
| Claude Code / Cursor / Windsurf | MCP Proxy (N개 MCP server → 3개 meta-tool) | ~1,200 tok/turn | docs/integrations/mcp-proxy.md |
| MCP 호환 클라이언트 | MCP Server (단일 소스를 MCP로) | 다양 | docs/integrations/mcp-server.md |
| LangChain / LangGraph (50+ tools) | Gateway tools (N tool → 2 meta-tool) | 92% | docs/integrations/langchain.md |
| OpenAI / Anthropic SDK (기존 코드) | Middleware (1줄 monkey-patch) | 76–91% | docs/integrations/middleware.md |
| 검색 직접 제어 | Python API (retrieve() + 포맷 어댑터) |
다양 | docs/integrations/direct-api.md |
여러 MCP server를 쓰면 tool 이름이 매 LLM turn마다 쌓인다. 하나의 server 뒤로 묶어버리자: 172 tools → 3 meta-tools.
# 1. ~/backends.json에 MCP server 목록 작성
# 2. Claude Code에 proxy 등록
claude mcp add -s user tool-proxy -- \
uvx "graph-tool-call[mcp]" proxy --config ~/backends.json전체 셋업, passthrough 모드, remote transport → MCP Proxy 가이드.
from graph_tool_call.langchain import create_gateway_tools
# Slack, GitHub, Jira, MS365... 62 tools
gateway = create_gateway_tools(all_tools, top_k=10)
# → [search_tools, call_tool] — 컨텍스트엔 2개 tool만
agent = create_react_agent(model=llm, tools=gateway)62개 tool을 다 바인딩하는 것 대비 92% 토큰 절감. auto-filter, manual 패턴은 LangChain 가이드 참조.
from graph_tool_call.middleware import patch_openai
patch_openai(client, graph=tg, top_k=5) # ← 이 한 줄만 추가
# 기존 코드 그대로 — 248개 tool이 들어가지만, 관련된 5개만 전송됨
response = client.chat.completions.create(
model="gpt-4o",
tools=all_248_tools,
messages=messages,
)Anthropic도 patch_anthropic으로 동일하게 동작. Middleware 가이드 참조.
두 가지 질문: (1) 검색된 부분만 LLM에 줘도 정확하게 tool을 고르는가? (2) Retriever 자체가 top K 안에 정답 tool을 넣는가?
| Dataset | Tools | Baseline acc | graph-tool-call | 토큰 절감 |
|---|---|---|---|---|
| Petstore | 19 | 100% | 95% (k=5) | 64% |
| GitHub | 50 | 100% | 88% (k=5) | 88% |
| Mixed MCP | 38 | 97% | 90% (k=5) | 83% |
| Kubernetes core/v1 | 248 | 12% | 82% (k=5 + ontology) | 79% |
핵심 발견 — 248 tool에서 baseline은 컨텍스트 오버플로로 12%까지 무너지지만 graph-tool-call은 82%까지 회복한다. 작은 규모에서는 baseline 자체가 강하므로, graph-tool-call의 가치는 정확도 손실 없이 토큰 절감.
→ 전체 결과 (pipeline / retrieval-only / competitive / 1068-scale / 200 tool LangChain agent — GPT, Claude 시리즈): docs/benchmarks.md
# 재현
python -m benchmarks.run_benchmark # retrieval만
python -m benchmarks.run_benchmark --mode pipeline -m qwen3:4b # 전체 파이프라인BM25 + 그래프 위에 semantic 검색을 추가. 무거운 의존성 없이 외부 임베딩 server에 연결 가능.
tg.enable_embedding("ollama/qwen3-embedding:0.6b") # Ollama (권장)
tg.enable_embedding("openai/text-embedding-3-large") # OpenAI
tg.enable_embedding("vllm/Qwen/Qwen3-Embedding-0.6B") # vLLM
tg.enable_embedding("sentence-transformers/all-MiniLM-L6-v2") # 로컬
tg.enable_embedding(lambda texts: my_embed_fn(texts)) # custom callable가중치는 자동 재조정. 모든 provider 형식은 API 레퍼런스 참조.
tg.enable_reranker() # cross-encoder rerank
tg.enable_diversity(lambda_=0.7) # MMR diversity
tg.set_weights(keyword=0.2, graph=0.5, embedding=0.3, annotation=0.2)이전에 호출한 tool을 넘겨주면 강등시키고 다음 단계 후보를 부스트한다.
tools = tg.retrieve("now cancel it", history=["listOrders", "getOrder"])
# → [cancelOrder, processRefund, ...]tg.save("my_graph.json")
tg = ToolGraph.load("my_graph.json")
# 또는 from_url의 cache= 사용
tg = ToolGraph.from_url(url, cache="my_graph.json")tg.auto_organize(llm="ollama/qwen2.5:7b")
tg.auto_organize(llm="litellm/claude-sonnet-4-20250514")
tg.auto_organize(llm=openai.OpenAI())더 풍부한 카테고리, 관계, 검색 키워드를 빌드. Ollama, OpenAI client, litellm, 그리고 callable을 지원. API 레퍼런스 참조.
| 기능 | API | 문서 |
|---|---|---|
| Spec 간 중복 탐지 | find_duplicates / merge_duplicates |
API ref |
| 충돌 탐지 | apply_conflicts |
API ref |
| 운영 분석 | analyze |
API ref |
| 인터랙티브 대시보드 | dashboard() |
API ref |
| HTML / GraphML / Cypher export | export_html / export_graphml / export_cypher |
API ref |
| 망가진 OpenAPI spec 자동 수정 | from_url(url, lint=True) |
ai-api-lint |
| 문서 | 설명 |
|---|---|
| CLI 레퍼런스 | 모든 graph-tool-call CLI 명령 |
| Python API 레퍼런스 | ToolGraph 메서드, helper, middleware, LangChain |
| 통합 가이드 | MCP server / proxy, LangChain, middleware, direct API |
| 벤치마크 결과 | 전체 pipeline / retrieval / competitive / scale 표 |
| Architecture | 시스템 개요, 파이프라인 레이어, 데이터 모델 |
| Design notes | 알고리즘 설계 — normalization, 의존성 탐지, ontology |
| Research | 경쟁 분석, API 스케일 데이터 |
| Release checklist | 릴리스 프로세스, changelog 흐름 |
기여를 환영한다.
git clone https://github.com/SonAIengine/graph-tool-call.git
cd graph-tool-call
pip install poetry pre-commit
poetry install --with dev --all-extras
pre-commit install # 매 commit 전 ruff 자동 실행
# Test, lint, benchmark
poetry run pytest -v
poetry run ruff check . && poetry run ruff format --check .
python -m benchmarks.run_benchmark -v