Skip to content

Price lookup with a cross-check

Answer "what is the Bitcoin price?" from live data, and check a web search result against a structured API in code.

Requires tool calls

The deployment must allow api.coingecko.com and mcp.exa.ai (GET /health → allow_hosts). Both services work without an API key.

Full request body
{
  "system": "You are a financial assistant. Answer only from the data provided.",
  "context": "User message: \"Bro, what's the bitcoin price right now in dollars?\"",
  "schema": {
    "coin": {
      "enum": [
        "bitcoin",
        "ethereum",
        "solana"
      ],
      "instruction": "Crypto coin the user is asking about."
    },
    "search_result": {
      "type": "call",
      "http": {
        "method": "POST",
        "url": "https://mcp.exa.ai/mcp",
        "headers": {
          "Accept": "application/json, text/event-stream"
        },
        "body": {
          "jsonrpc": "2.0",
          "id": 1,
          "method": "tools/call",
          "params": {
            "name": "web_search_exa",
            "arguments": {
              "query": "{coin} price today in USD",
              "numResults": 3
            }
          }
        },
        "timeout": 30,
        "response": {
          "select": "result.content[0].text"
        }
      },
      "args": {
        "coin": "$coin"
      },
      "expose": true,
      "on_error": "review"
    },
    "api_price": {
      "type": "call",
      "http": {
        "method": "GET",
        "url": "https://api.coingecko.com/api/v3/simple/price",
        "query": {
          "ids": "{coin}",
          "vs_currencies": "usd"
        },
        "timeout": 10,
        "response": {
          "select": "{coin}.usd"
        }
      },
      "args": {
        "coin": "$coin"
      },
      "expose": true,
      "on_error": "reject",
      "on": [
        {
          "when": {
            "eq": null
          },
          "do": "reject"
        }
      ]
    },
    "search_price": {
      "type": "number",
      "nullable": true,
      "depends_on": [
        "search_result"
      ],
      "instruction": "Price in USD according to search_result (number only, no digit separators). null if not found.",
      "on": [
        {
          "when": {
            "gt_expr": "api_price * 1.05"
          },
          "do": "review"
        },
        {
          "when": {
            "lt_expr": "api_price * 0.95"
          },
          "do": "review"
        }
      ]
    },
    "answer": {
      "type": "string",
      "free_form": true,
      "depends_on": [
        "api_price"
      ],
      "instruction": "Answer the user's question in 1 sentence in English using api_price. Copy the price digits EXACTLY as in Known values (do not change or group the digits).",
      "on_reject": "Sorry, the price data is currently unavailable (the API failed or the coin is unknown). Please try again shortly."
    }
  },
  "max_new_tokens": 80
}

What happens

  1. The model picks coin from an enum (a clean argument for the URLs).
  2. Two calls run in parallel: a web search through Exa's public MCP endpoint, and CoinGecko's price API.
  3. The model reads search_price from the search text only. It does not depend on api_price, so the API price is not in its prompt and cannot be copied.
  4. Code compares the two (gt_expr / lt_expr, ±5%) and flags a mismatch.
  5. answer uses the API price. If the price API fails, the request is rejected with an honest message instead of an invented price.

Lessons from testing

  • A search result still showed a stale price (about $64,575 while the API said about $84,500). The model read it correctly with confidence ~1.0: confidence cannot catch a wrong source, a cross-check can.
  • When the price API was rate-limited and the call only used on_error: review, the answer invented a price. Use on_error: reject for data an answer depends on.
  • Asked to format 1514071277 with separators, the model wrote "Rp15.140.712.77". Ask it to copy digits as they are and format numbers in your own code.

The simpler version without search (CoinGecko only) is playground preset 24. Guide: Tool calls.