Metadata-Version: 2.5
Name: agent-gantry
Version: 0.16.0
Summary: Universal Tool Orchestration Platform - Intelligent, secure tool orchestration for LLM-based agent systems
Project-URL: Homepage, https://github.com/CodeHalwell/Agent-Gantry
Project-URL: Repository, https://github.com/CodeHalwell/Agent-Gantry
Author: Agent-Gantry Contributors
License-Expression: MIT
License-File: LICENSE
Keywords: agents,llm,mcp,orchestration,semantic-routing,tools
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: <3.14,>=3.10
Requires-Dist: jsonschema>=4.18.0
Requires-Dist: numpy>=1.24.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pyyaml>=6.0.0
Provides-Extra: a2a
Requires-Dist: fastapi>=0.104.0; extra == 'a2a'
Requires-Dist: httpx>=0.24.0; extra == 'a2a'
Requires-Dist: uvicorn>=0.24.0; extra == 'a2a'
Provides-Extra: agent-frameworks
Requires-Dist: agent-framework<2.0.0,>=1.5.0; extra == 'agent-frameworks'
Requires-Dist: crewai>=1.15.0; extra == 'agent-frameworks'
Requires-Dist: google-adk>=2.6.1; extra == 'agent-frameworks'
Requires-Dist: langchain-openai>=1.4.1; extra == 'agent-frameworks'
Requires-Dist: langchain>=1.3.14; extra == 'agent-frameworks'
Requires-Dist: langgraph>=1.2.10; extra == 'agent-frameworks'
Requires-Dist: llama-index-core>=0.14.23; extra == 'agent-frameworks'
Requires-Dist: llama-index-llms-openai>=0.7.10; extra == 'agent-frameworks'
Provides-Extra: all
Requires-Dist: agent-framework<2.0.0,>=1.5.0; extra == 'all'
Requires-Dist: anthropic>=0.120.2; extra == 'all'
Requires-Dist: asyncpg>=0.29.0; extra == 'all'
Requires-Dist: beautifulsoup4>=4.12.0; extra == 'all'
Requires-Dist: chromadb>=0.4.0; extra == 'all'
Requires-Dist: cohere>=7.0.8; extra == 'all'
Requires-Dist: crewai>=1.15.0; extra == 'all'
Requires-Dist: cryptography>=41.0.0; extra == 'all'
Requires-Dist: einops>=0.8.1; extra == 'all'
Requires-Dist: fastapi>=0.104.0; extra == 'all'
Requires-Dist: google-adk>=2.6.1; extra == 'all'
Requires-Dist: google-cloud-aiplatform>=1.70.0; extra == 'all'
Requires-Dist: google-genai>=1.75.0; extra == 'all'
Requires-Dist: groq>=1.6.0; extra == 'all'
Requires-Dist: httpx>=0.24.0; extra == 'all'
Requires-Dist: huggingface-hub[hf-xet]>=0.36.0; extra == 'all'
Requires-Dist: lancedb>=0.4.0; extra == 'all'
Requires-Dist: langchain-openai>=1.4.1; extra == 'all'
Requires-Dist: langchain>=1.3.14; extra == 'all'
Requires-Dist: langgraph>=1.2.10; extra == 'all'
Requires-Dist: llama-index-core>=0.14.23; extra == 'all'
Requires-Dist: llama-index-llms-openai>=0.7.10; extra == 'all'
Requires-Dist: matplotlib>=3.7.0; extra == 'all'
Requires-Dist: mcp<3,>=1.27.2; extra == 'all'
Requires-Dist: numpy>=1.24.0; extra == 'all'
Requires-Dist: openai>=2.45.0; extra == 'all'
Requires-Dist: pandas>=2.0.0; extra == 'all'
Requires-Dist: pillow>=10.0.0; extra == 'all'
Requires-Dist: pint>=0.24.4; extra == 'all'
Requires-Dist: psutil>=5.9.0; extra == 'all'
Requires-Dist: pubchempy>=1.0.5; extra == 'all'
Requires-Dist: pyarrow>=14.0.1; extra == 'all'
Requires-Dist: pypdf>=3.0.0; extra == 'all'
Requires-Dist: pytest-asyncio<1.0.0,>=0.21.0; extra == 'all'
Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
Requires-Dist: pytest>=7.0.0; extra == 'all'
Requires-Dist: python-barcode>=0.15.0; extra == 'all'
Requires-Dist: python-docx>=1.0.0; extra == 'all'
Requires-Dist: python-dotenv>=1.0.0; extra == 'all'
Requires-Dist: python-dotenv>=1.1.1; extra == 'all'
Requires-Dist: pytz>=2023.3; extra == 'all'
Requires-Dist: qdrant-client>=1.7.0; extra == 'all'
Requires-Dist: qrcode>=7.4.0; extra == 'all'
Requires-Dist: rdkit>=2025.9.3; extra == 'all'
Requires-Dist: requests>=2.31.0; extra == 'all'
Requires-Dist: ruff>=0.1.0; extra == 'all'
Requires-Dist: scikit-learn>=1.3.0; extra == 'all'
Requires-Dist: sentence-transformers>=2.2.0; extra == 'all'
Requires-Dist: sympy>=1.14.0; extra == 'all'
Requires-Dist: textblob>=0.18.0; extra == 'all'
Requires-Dist: typesafe-sdk>=0.7.0; extra == 'all'
Requires-Dist: uvicorn>=0.24.0; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.120.2; extra == 'anthropic'
Provides-Extra: chroma
Requires-Dist: chromadb>=0.4.0; extra == 'chroma'
Provides-Extra: cohere
Requires-Dist: cohere>=7.0.8; extra == 'cohere'
Provides-Extra: dev
Requires-Dist: pytest-asyncio<1.0.0,>=0.21.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: python-dotenv>=1.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Requires-Dist: typesafe-sdk>=0.7.0; extra == 'dev'
Provides-Extra: embeddings
Requires-Dist: numpy>=1.24.0; extra == 'embeddings'
Requires-Dist: sentence-transformers>=2.2.0; extra == 'embeddings'
Provides-Extra: example-tools
Requires-Dist: beautifulsoup4>=4.12.0; extra == 'example-tools'
Requires-Dist: cryptography>=41.0.0; extra == 'example-tools'
Requires-Dist: einops>=0.8.1; extra == 'example-tools'
Requires-Dist: huggingface-hub[hf-xet]>=0.36.0; extra == 'example-tools'
Requires-Dist: matplotlib>=3.7.0; extra == 'example-tools'
Requires-Dist: pandas>=2.0.0; extra == 'example-tools'
Requires-Dist: pillow>=10.0.0; extra == 'example-tools'
Requires-Dist: pint>=0.24.4; extra == 'example-tools'
Requires-Dist: psutil>=5.9.0; extra == 'example-tools'
Requires-Dist: pubchempy>=1.0.5; extra == 'example-tools'
Requires-Dist: pypdf>=3.0.0; extra == 'example-tools'
Requires-Dist: python-barcode>=0.15.0; extra == 'example-tools'
Requires-Dist: python-docx>=1.0.0; extra == 'example-tools'
Requires-Dist: python-dotenv>=1.1.1; extra == 'example-tools'
Requires-Dist: pytz>=2023.3; extra == 'example-tools'
Requires-Dist: qrcode>=7.4.0; extra == 'example-tools'
Requires-Dist: rdkit>=2025.9.3; extra == 'example-tools'
Requires-Dist: requests>=2.31.0; extra == 'example-tools'
Requires-Dist: scikit-learn>=1.3.0; extra == 'example-tools'
Requires-Dist: sympy>=1.14.0; extra == 'example-tools'
Requires-Dist: textblob>=0.18.0; extra == 'example-tools'
Provides-Extra: google-genai
Requires-Dist: google-genai>=1.75.0; extra == 'google-genai'
Provides-Extra: google-vertexai
Requires-Dist: google-cloud-aiplatform>=1.70.0; extra == 'google-vertexai'
Provides-Extra: groq
Requires-Dist: groq>=1.6.0; extra == 'groq'
Provides-Extra: jev
Requires-Dist: typesafe-sdk>=0.7.0; extra == 'jev'
Provides-Extra: lancedb
Requires-Dist: lancedb>=0.4.0; extra == 'lancedb'
Requires-Dist: pyarrow>=14.0.1; extra == 'lancedb'
Provides-Extra: llm-providers
Requires-Dist: anthropic>=0.120.2; extra == 'llm-providers'
Requires-Dist: google-cloud-aiplatform>=1.70.0; extra == 'llm-providers'
Requires-Dist: google-genai>=1.75.0; extra == 'llm-providers'
Requires-Dist: groq>=1.6.0; extra == 'llm-providers'
Requires-Dist: openai>=2.45.0; extra == 'llm-providers'
Provides-Extra: mcp
Requires-Dist: mcp<3,>=1.27.2; extra == 'mcp'
Provides-Extra: mistral
Requires-Dist: openai>=2.45.0; extra == 'mistral'
Provides-Extra: nomic
Requires-Dist: einops>=0.8.1; extra == 'nomic'
Requires-Dist: numpy>=1.24.0; extra == 'nomic'
Requires-Dist: sentence-transformers>=2.2.0; extra == 'nomic'
Provides-Extra: openai
Requires-Dist: openai>=2.45.0; extra == 'openai'
Provides-Extra: pgvector
Requires-Dist: asyncpg>=0.29.0; extra == 'pgvector'
Provides-Extra: qdrant
Requires-Dist: qdrant-client>=1.7.0; extra == 'qdrant'
Provides-Extra: vector-stores
Requires-Dist: asyncpg>=0.29.0; extra == 'vector-stores'
Requires-Dist: chromadb>=0.4.0; extra == 'vector-stores'
Requires-Dist: qdrant-client>=1.7.0; extra == 'vector-stores'
Description-Content-Type: text/markdown

# Agent-Gantry

**Universal Tool Orchestration Platform for LLM-Based Agent Systems**

*Context is precious. Execution is sacred. Trust is earned.*

Agent-Gantry is a Python library (**v0.16.0**) for building agents that can discover, select, and execute the right tools without flooding every prompt with every schema your organization owns. It combines semantic retrieval, provider schema conversion, secure execution, framework bridges, MCP/A2A interoperability, persistence adapters, and observability into one tool orchestration layer.

## Documentation

The project documentation is now an **Astro + React + TypeScript** site with an implementation journey, interactive tool lifecycle walkthrough, integration matrix, and production operations guidance.

```bash
npm install
npm run dev      # local docs server
npm run build    # type-check and static build
npm run preview  # verify the generated site styling
```

Start with the rich docs in [`src/pages/index.astro`](src/pages/index.astro). The published site lives at [codehalwell.github.io/Agent-Gantry](https://codehalwell.github.io/Agent-Gantry).

## Install

```bash
uv add agent-gantry
# or
pip install agent-gantry
```

Useful extras:

```bash
uv add "agent-gantry[openai]"
uv add "agent-gantry[anthropic]"
uv add "agent-gantry[google-genai]"
uv add "agent-gantry[lancedb,nomic]"
uv add "agent-gantry[mcp,a2a]"
uv add "agent-gantry[agent-frameworks]"
uv add "agent-gantry[all]"
```

## Quick start

```python
from openai import AsyncOpenAI
from agent_gantry import AgentGantry, set_default_gantry, with_semantic_tools

client = AsyncOpenAI()
gantry = AgentGantry()
set_default_gantry(gantry)

@gantry.register(tags=["weather"])
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"The weather in {city} is 72°F and sunny."

@with_semantic_tools(limit=3, dialect="openai")
async def ask_llm(prompt: str, *, tools=None):
    return await client.chat.completions.create(
        model="gpt-5.5",
        messages=[{"role": "user", "content": prompt}],
        tools=tools,
    )

await ask_llm("What's the weather in San Francisco?")
```

Agent-Gantry automatically fingerprints registered tools, syncs definitions to the configured vector store, retrieves semantically relevant tools, and converts schemas to the requested provider dialect.

## Core capabilities

- **Semantic tool routing:** reduce prompt context by retrieving top-k relevant tools instead of injecting every tool.
- **Register once, run anywhere:** emit schemas for OpenAI-compatible APIs, Anthropic, Gemini, framework adapters, MCP, and A2A paths.
- **Secure execution:** run tools through policies, capabilities, timeouts, retries, rate limits, circuit breakers, callbacks, and telemetry.
- **Persistence and retrieval:** use in-memory defaults, LanceDB, Qdrant, Chroma, pgvector, OpenAI/Nomic/sentence-transformers embeddings, and rerankers.
- **Framework coverage:** Microsoft Agent Framework plus LangChain, LangGraph, LlamaIndex, CrewAI, Google ADK, Pydantic AI, OpenAI Agents SDK, Haystack, Agno, Strands Agents, and DSPy.
- **MCP both ways:** consume local (stdio) and remote (Streamable HTTP / SSE) MCP servers, and serve your registry to Claude Desktop, Claude Code or any remote client with `gantry.serve_mcp()` / `agent-gantry serve-mcp --module my_app.tools` — two meta-tools instead of the whole tool list.
- **Skills, retrieved by meaning:** load any Agent Skills (`SKILL.md`) directory with `gantry.add_skills_from_directory(...)` and inject only the skills relevant to each prompt.
- **Bundled Claude Skill:** install with `agent-gantry install-skill --claude` or target a project-local skills directory.
- **Selection without embeddings:** point a decision model at the catalogue instead of a vector store. `JevSelector` replaces embed-then-search for tools, skills and MCP servers; `JevReranker` refines the shortlist when the catalogue is too large to send. Both fail open to semantic routing.

## Selecting tools without a vector store

Semantic routing embeds the query and searches. A *selector* asks a decision
model directly, which needs no embedder, no vector store and no sync — and can
return nothing when nothing fits, which top-k cannot express.

```python
from agent_gantry import AgentGantry, JevSelector

gantry = AgentGantry(selector=JevSelector(threshold=0.3))  # reads TYPESAFE_API_KEY

@gantry.register(tags=["email"], examples=["show my messages", "any new mail"])
def list_inbox(limit: int = 10) -> list[str]:
    """List the most recent messages sitting in the inbox."""
    return []

@gantry.register(tags=["email"], examples=["email Bob about the meeting"])
def send_email(to: str, body: str) -> str:
    """Send an email message to a named recipient."""
    return "sent"

# With a key, this honours the negation and returns list_inbox alone.
# Without one, selection fails open and semantic routing answers instead.
tools = await gantry.retrieve_tools("show my messages, but do not send anything")
```

The same selector covers `retrieve_skills()` and `retrieve_mcp_servers()`. Above
a few hundred entries, keep semantic search and refine it instead:

```python
from agent_gantry import AgentGantry, JevReranker

gantry = AgentGantry(reranker=JevReranker())   # reorders the vector-search shortlist
```

Write `examples=[...]` on your tools before reaching for either: on our own
benchmark that moved the default embedder from 1/5 to 5/5, more than any model
change did. See `agent_gantry/adapters/selectors/README.md` for the measured
trade-offs.

## Manual retrieval and execution

```python
from agent_gantry import AgentGantry
from agent_gantry.schema.execution import ToolCall

gantry = AgentGantry()

@gantry.register(tags=["finance"])
def calculate_tax(amount: float) -> float:
    """Calculate US sales tax for an amount."""
    return amount * 0.08

tools = await gantry.retrieve_tools("What is the tax on $100?", limit=5)
result = await gantry.execute(ToolCall(
    tool_name="calculate_tax",
    arguments={"amount": 100.0},
))
```

## Development

```bash
uv sync --all-extras
uv run pytest
npm install
npm run build
```

## License

MIT
