Agent recipe
Brief yourself on a DeFi protocol
Pull a protocol's total value locked and its latest news, so an agent can write a one-page brief with figures and sources.
Before you cover a DeFi protocol or build on it, you want two things: how much value it secures today, and what has happened to it lately. This recipe pulls the protocol's total value locked from an on-chain data tool and its recent coverage from web search, so an agent can draft a one-page brief with figures and sources.
Input fields can differ from one source to the next, so read a tool's schema with GET /v1/tools/{id} before you call it, or hand the tools to a model as functions and let it fill them in.
Steps
Read its total value locked
Protocol TVL · DeFi
Fetch the protocol's current total value locked, the headline figure for its size.
Example input { "protocol": "aave" }No tool in the catalog serves this step right now. The rest of the recipe still runs; check back for this one.
Collect recent news
Web search · Web search
Search the web for the protocol's latest announcements, audits and incidents, and keep the sources.
Example input { "query": "Aave protocol news" }No tool in the catalog serves this step right now. The rest of the recipe still runs; check back for this one.
Run the whole recipe
# Needs curl and jq. Step 1 is shown; step 2 works the same way.
TOOL_ID=$(curl -s https://api.orcarouter.ai/v1/tools/discover \
-H "Authorization: Bearer $ORCAROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"q": "defi protocol tvl", "limit": 1}' | jq -r '.data.results[0].id')
# The inputs the tool takes
curl -s "https://api.orcarouter.ai/v1/tools/$TOOL_ID" \
-H "Authorization: Bearer $ORCAROUTER_API_KEY" | jq '.data.input_schema'
# Run it with those inputs
curl -s "https://api.orcarouter.ai/v1/tools/$TOOL_ID/run" \
-H "Authorization: Bearer $ORCAROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": {"protocol": "aave"}}'# pip install requests openai
import json
import os
import requests
from openai import OpenAI
BASE = "https://api.orcarouter.ai"
KEY = os.environ["ORCAROUTER_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}
# One discover query per step of the recipe.
STEPS = ["defi protocol tvl", "web search"]
TASK = (
"Write a short brief on the Aave protocol: its current total value locked "
"and the three most important news items this month, with links."
)
def discover(query, limit=2):
r = requests.post(f"{BASE}/v1/tools/discover", json={"q": query, "limit": limit}, headers=HEADERS, timeout=30)
r.raise_for_status()
return [tool["id"] for tool in r.json()["data"]["results"]]
def run(name, tool_input):
r = requests.post(f"{BASE}/v1/tools/{name}/run", json={"input": tool_input}, headers=HEADERS, timeout=120)
return r.text # the tool's answer, or its error, goes back to the model
ids = [tool_id for query in STEPS for tool_id in discover(query)]
fns = requests.get(
f"{BASE}/v1/tools/functions",
params={"ids": ",".join(ids), "format": "openai"},
headers=HEADERS,
timeout=30,
)
fns.raise_for_status()
tools = fns.json()["data"]["tools"]
llm = OpenAI(base_url=f"{BASE}/v1", api_key=KEY)
messages = [{"role": "user", "content": TASK}]
for _ in range(10):
msg = llm.chat.completions.create(model="openai/gpt-5-mini", messages=messages, tools=tools).choices[0].message
messages.append(msg)
if not msg.tool_calls:
print(msg.content)
break
for call in msg.tool_calls:
result = run(call.function.name, json.loads(call.function.arguments or "{}"))
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})// npm install openai
import OpenAI from "openai";
import type { ChatCompletionMessageParam } from "openai/resources/chat/completions";
const BASE = "https://api.orcarouter.ai";
const KEY = process.env.ORCAROUTER_API_KEY!;
const headers = { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" };
// One discover query per step of the recipe.
const STEPS = ["defi protocol tvl", "web search"];
const TASK =
"Write a short brief on the Aave protocol: its current total value locked " +
"and the three most important news items this month, with links.";
async function discover(q: string, limit = 2): Promise<string[]> {
const res = await fetch(`${BASE}/v1/tools/discover`, {
method: "POST",
headers,
body: JSON.stringify({ q, limit }),
});
if (!res.ok) throw new Error(`discover: HTTP ${res.status}`);
const { data } = await res.json();
return data.results.map((tool: { id: string }) => tool.id);
}
async function run(name: string, input: unknown): Promise<string> {
const res = await fetch(`${BASE}/v1/tools/${name}/run`, {
method: "POST",
headers,
body: JSON.stringify({ input }),
});
return res.text(); // the tool's answer, or its error, goes back to the model
}
async function main() {
const ids = (await Promise.all(STEPS.map((q) => discover(q)))).flat();
const fns = await fetch(
`${BASE}/v1/tools/functions?format=openai&ids=${encodeURIComponent(ids.join(","))}`,
{ headers },
);
if (!fns.ok) throw new Error(`functions: HTTP ${fns.status}`);
const { data } = await fns.json();
const llm = new OpenAI({ baseURL: `${BASE}/v1`, apiKey: KEY });
const messages: ChatCompletionMessageParam[] = [{ role: "user", content: TASK }];
for (let turn = 0; turn < 10; turn++) {
const reply = await llm.chat.completions.create({ model: "openai/gpt-5-mini", messages, tools: data.tools });
const msg = reply.choices[0].message;
messages.push(msg);
if (!msg.tool_calls?.length) {
console.log(msg.content);
return;
}
for (const call of msg.tool_calls) {
const fn = call.type === "function" ? call.function : undefined;
const result = fn ? await run(fn.name, JSON.parse(fn.arguments || "{}")) : "unsupported tool call";
messages.push({ role: "tool", tool_call_id: call.id, content: result });
}
}
}
main();More recipes
- Monitor brand mentions on the web and TikTokSearch the web and TikTok for your brand, then read the comments under the posts that mention it: a daily brand check an agent can run on its own.
- Enrich a lead listTurn a list of company domains into firmographics, a contact's work email and a recent news hook, with one API key.
- Find contact emails for a list of domainsGo from a bare domain to the right person's work email: confirm the company, find who holds the role and look up the address.
