Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.emergence.ai/llms.txt

Use this file to discover all available pages before exploring further.

This tutorial builds the same agent three times — once for each capability layer — so you can stop at whichever level you need. By the end you have a registered CRAFT agent that lists your project’s data connections and is discoverable by other platform services. What you’ll build: A data-assistant agent that:
  1. Responds to prompts using CRAFT’s managed LLM gateway
  2. Calls the CRAFT Assets API to list real data connections
  3. Is registered in the CRAFT agent registry
Time: ~5 min per step. Steps are independent — come back later for the next one. Prerequisites:
Obtain a CRAFT project access token via your deployment’s OIDC token endpoint (client credentials grant) and export the connection settings:
export CRAFT_TOKEN="$(curl -s -X POST "${OIDC_TOKEN_URL}" \
  -d "grant_type=client_credentials&client_id=${OIDC_CLIENT_ID}&client_secret=${OIDC_CLIENT_SECRET}" \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')"
export CRAFT_GATEWAY_URL="<your-llm-gateway-url>"    # LiteLLM sidecar or gateway
export CRAFT_ASSETS_URL="<your-assets-api-url>"      # CRAFT Assets API
export CRAFT_PROJECT_ID="<your-project-uuid>"
For local dev, run docker-compose up from the solution starter template — it pre-configures OIDC_TOKEN_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, and the local URLs.

Step 1 — Connect to CRAFT’s LLM gateway

The only difference between a standalone agent and a CRAFT-native agent is where it routes its LLM calls. CRAFT runs a LiteLLM gateway that handles model selection, project billing, and rate limits. All four frameworks reach it through an OpenAI-compatible endpoint. Pick your framework:
pip install google-adk litellm
agent.py
import os
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types

# Point ADK at CRAFT's LiteLLM gateway via the LiteLlm wrapper.
# The "openai/" prefix tells LiteLLM to use the OpenAI-compatible endpoint.
model = LiteLlm(
    model="openai/claude-sonnet-4-6",
    api_base=os.environ["CRAFT_GATEWAY_URL"],
    api_key=os.environ["CRAFT_TOKEN"],
)

agent = Agent(
    name="data_assistant",
    model=model,
    description="Helps users understand the data available in their CRAFT project.",
    instruction="You are a helpful data assistant. Answer questions about the user's data concisely.",
)

async def main():
    session_service = InMemorySessionService()
    session = await session_service.create_session(
        app_name="data_assistant", user_id="dev", session_id="s1"
    )
    runner = Runner(agent=agent, app_name="data_assistant", session_service=session_service)

    async for event in runner.run_async(
        user_id="dev", session_id="s1",
        new_message=types.Content(role="user", parts=[types.Part(text="What can you help me with?")]),
    ):
        if event.is_final_response():
            print(event.content.parts[0].text)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())
For production multi-agent patterns with A2A, see the multi-agent patterns guide — it shows how an orchestrator uses ADK to chain a text-to-SQL agent and an insights agent over the A2A protocol.
That’s it. Your agent is running on CRAFT’s managed LLM infrastructure.

Routing to specific backends

The CRAFT gateway uses LiteLLM under the hood, so any model string it recognises works in the model= field — you don’t need to change any client code. Ask your platform team which models are in the project’s allowlist.
# Model string format: vertex_ai/<model-id>
# LiteLLM uses Workload Identity or GOOGLE_APPLICATION_CREDENTIALS automatically.
model = "vertex_ai/gemini-2.0-flash"
# Model string format: bedrock/<model-id>
# LiteLLM uses AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY or instance profile.
model = "bedrock/anthropic.claude-sonnet-4-6-20251204-v1:0"
# Model string format: azure/<deployment-name>
# LiteLLM reads AZURE_API_KEY and AZURE_API_BASE from the gateway config.
model = "azure/gpt-4o"
# Nebius exposes an OpenAI-compatible endpoint; prefix with openai/.
# LiteLLM reads NEBIUS_API_KEY from the gateway config.
model = "openai/Qwen/Qwen3-30B-A3B"

Step 2 — Add a CRAFT tool

Tools let your agent take action. The simplest CRAFT tool calls the Assets API to list the data connections registered in your project — real databases and warehouses the platform knows about.
agent.py
import os
import httpx
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types

# ── CRAFT tool ─────────────────────────────────────────────────────────
def list_data_connections() -> str:
    """List the data connections available in the current CRAFT project."""
    resp = httpx.get(
        f"{os.environ['CRAFT_ASSETS_URL']}/assets/data",
        headers={
            "Authorization": f"Bearer {os.environ['CRAFT_TOKEN']}",
            "X-Project-ID": os.environ["CRAFT_PROJECT_ID"],
        },
    )
    resp.raise_for_status()
    items = resp.json().get("data", [])
    if not items:
        return "No data connections found in this project."
    return "\n".join(f"- {c['name']} ({c['connection_type']})" for c in items)

# ── Agent ──────────────────────────────────────────────────────────────
model = LiteLlm(
    model="openai/claude-sonnet-4-6",
    api_base=os.environ["CRAFT_GATEWAY_URL"],
    api_key=os.environ["CRAFT_TOKEN"],
)

agent = Agent(
    name="data_assistant",
    model=model,
    description="Helps users understand the data available in their CRAFT project.",
    instruction="You are a helpful data assistant. Use list_data_connections to answer questions about available data.",
    tools=[list_data_connections],
)

async def main():
    session_service = InMemorySessionService()
    session = await session_service.create_session(
        app_name="data_assistant", user_id="dev", session_id="s1"
    )
    runner = Runner(agent=agent, app_name="data_assistant", session_service=session_service)

    async for event in runner.run_async(
        user_id="dev", session_id="s1",
        new_message=types.Content(role="user", parts=[types.Part(text="What data connections do I have?")]),
    ):
        if event.is_final_response():
            print(event.content.parts[0].text)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())
Your agent now answers questions using real platform data. Every other CRAFT capability — agents, files, models, artifacts — is one more httpx.get() away.

Step 3 — Register your agent

A registered agent is discoverable by other platform services, the UI, and other agents via the A2A protocol. Registration is one POST to the Assets API.
register.py
import os
import httpx

# POST /assets/agents expects the full Agent Card wrapped in "agent_card".
# "name" and "version" are required; "url" points to your running service's
# /.well-known/agent-card.json endpoint so other agents can fetch full details.
registration = {
    "agent_card": {
        "name": "data-assistant",
        "version": "1.0.0",
        "description": "Answers questions about data connections in a CRAFT project.",
        "url": "http://your-service-host/.well-known/agent-card.json",
        "capabilities": {
            "streaming": False,
            "push_notifications": False,
        },
    },
    "tags": ["data", "assistant"],
}

resp = httpx.post(
    f"{os.environ['CRAFT_ASSETS_URL']}/assets/agents",
    json=registration,
    headers={
        "Authorization": f"Bearer {os.environ['CRAFT_TOKEN']}",
        "X-Project-ID": os.environ["CRAFT_PROJECT_ID"],
    },
)
resp.raise_for_status()
agent_id = resp.json()["resource_uri"]
print(f"Registered: {agent_id}")
The agent_card_url points to a /.well-known/agent-card.json endpoint your service exposes — it describes your agent’s skills so other agents can call it. See the A2A protocol primer for the Agent Card schema, and multi-agent patterns for wiring A2A delegation.
Registration is idempotent — PUT /assets/agents/{resource_uri} updates an existing registration. Automate this in your deploy pipeline so the registry stays in sync with your running service.

What’s next

Add more tools

Function tools, MCP tools via FastMCPToolset, schema discipline.

Multi-agent patterns

A2A delegation, parallel fan-out, supervisor agents.

A2A protocol primer

Agent Cards, JSON-RPC over SSE, task lifecycle.

Eval harness

Golden traces, Langfuse evaluators, regression suites.