Loop
How it worksThe loopWhy LoopPricingDocs
guide LlamaIndex · FunctionAgent

Use Loop with LlamaIndex.

Wrap Loop's REST endpoints as FunctionTool objects and pass them to FunctionAgent — or use AgentWorkflow.from_tools_or_functions with plain Python functions. The agent searches, verifies, and reports outcomes automatically. No key required in the free tier.

option 1 FunctionTool + FunctionAgent (recommended for GPT-4o / Claude)

FunctionAgent uses native function calling — the right choice for OpenAI and Anthropic models. Wrap each Loop REST call with FunctionTool.from_defaults(fn=); LlamaIndex builds the JSON schema from type hints and docstrings. Use ReActAgentfrom the same module for LLMs that don't support function calling.

Install: pip install llama-index llama-index-llms-openai requests
loop_llamaindex.py
import asyncio
import requests
from llama_index.core.tools import FunctionTool
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.llms.openai import OpenAI

BASE = "https://api.stayinloop.dev/v1"

def search_loop(query: str, location: str = "Kreuzberg, Berlin") -> list:
    """Search for local businesses using Loop.

    Args:
        query: Natural-language description, e.g. 'vegan outdoor seating table for 4'.
        location: Area hint, default Kreuzberg, Berlin.

    Returns:
        List of matching business results with id, name, confidence, and availability.
    """
    return requests.get(f"{BASE}/search", params={"q": query, "location": location}).json()["results"]


def get_loop_details(result_id: str) -> dict:
    """Get full details for a Loop search result, including a result_token for report().

    Args:
        result_id: id from a search result.

    Returns:
        Full record with attrs, restaurant sub-object, confidence, and result_token.
    """
    return requests.get(f"{BASE}/details/{result_id}").json()


def verify_loop(result_id: str, claim: str) -> dict:
    """Re-check a specific claim against live sources before acting.

    Args:
        result_id: id from a search result.
        claim: What to verify, e.g. 'is_open_now', 'has_outdoor_seating'.

    Returns:
        Observation with verified, confidence, and checked_at fields.
    """
    return requests.get(f"{BASE}/verify/{result_id}", params={"claim": claim}).json()


def report_loop(result_token: str, outcome: str) -> dict:
    """Report what happened after acting on a Loop result. Improves data for everyone.

    Args:
        result_token: From get_loop_details(), valid 30 minutes.
        outcome: One of correct, wrong, booked, closed, other.

    Returns:
        Confirmation with new_confidence score.
    """
    return requests.post(
        f"{BASE}/report", json={"result_token": result_token, "outcome": outcome}
    ).json()


tools = [FunctionTool.from_defaults(fn=f) for f in [search_loop, get_loop_details, verify_loop, report_loop]]
agent = FunctionAgent(llm=OpenAI(model="gpt-4o"), tools=tools)

async def main():
    result = await agent.run(user_msg="Find a vegan restaurant with outdoor seating in Kreuzberg")
    print(str(result))

asyncio.run(main())
option 2 AgentWorkflow.from_tools_or_functions (plain functions, no wrapping)

AgentWorkflow.from_tools_or_functions accepts plain callables alongside BaseTool objects. It wraps functions in FunctionTool automatically and selects FunctionAgent or ReActAgent based on whether the LLM supports function calling.

Install: pip install llama-index llama-index-llms-openai requests
loop_llamaindex_workflow.py
import asyncio
import requests
from llama_index.core.agent.workflow import AgentWorkflow
from llama_index.llms.openai import OpenAI

BASE = "https://api.stayinloop.dev/v1"

# Plain functions — AgentWorkflow wraps them in FunctionTool automatically
def search_loop(query: str, location: str = "Kreuzberg, Berlin") -> list:
    """Search for local businesses using Loop."""
    return requests.get(f"{BASE}/search", params={"q": query, "location": location}).json()["results"]


def get_loop_details(result_id: str) -> dict:
    """Get full details for a Loop search result, including a result_token for report()."""
    return requests.get(f"{BASE}/details/{result_id}").json()


def verify_loop(result_id: str, claim: str) -> dict:
    """Re-check a specific claim against live sources before acting."""
    return requests.get(f"{BASE}/verify/{result_id}", params={"claim": claim}).json()


def report_loop(result_token: str, outcome: str) -> dict:
    """Report what happened after acting on a Loop result. outcome: correct, wrong, booked, closed, other."""
    return requests.post(
        f"{BASE}/report", json={"result_token": result_token, "outcome": outcome}
    ).json()


agent = AgentWorkflow.from_tools_or_functions(
    [search_loop, get_loop_details, verify_loop, report_loop],
    llm=OpenAI(model="gpt-4o"),
    system_prompt="Use Loop tools to find and verify local businesses in Kreuzberg, Berlin.",
)

async def main():
    result = await agent.run(user_msg="Outdoor vegan table for 4 in Kreuzberg — verify it is open now")
    print(str(result))

asyncio.run(main())
Prefer MCP? If your LlamaIndex setup supports MCP clients, point it at https://stayinloop.dev/mcp. The four tools load automatically with no schema wrappers. See the Claude guide for the MCP connector path.
Free tier: 30 reads/min, no key required. To get 60 reads/min and appear named on the signals board, pass Authorization: Bearer sk_live_… as a header or append ?key=sk_live_… to any URL.
← Full API referenceWatch your calls on the signals board →