Lyber
Request access
DocsBuild an agent

Agents

Markdown

Build an agent

A tool-using agent loop on Lyber, by hand with the OpenAI SDK or with an agent framework.

The loop

An agent is a loop: call the model with tools, run the calls it asks for, send back the results, and stop when it answers in text. This one also caps each step's cost and waits when the model is busy.

main.py
import json
import os
import time

from openai import OpenAI, RateLimitError

client = OpenAI(
    base_url="https://lyber-paid-acceptance-gateway-production.up.railway.app/v1",
    api_key=os.environ["LYBER_API_KEY"],
    max_retries=0,
)

def read_file(path: str) -> str:
    with open(path) as file:
        return file.read()[:20_000]

TOOLS = [{
    "type": "function",
    "function": {
        "name": "read_file",
        "description": "Read a text file from the project.",
        "parameters": {
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"],
        },
    },
}]
RUN = {"read_file": read_file}

def step(messages):
    for _ in range(6):
        try:
            return client.chat.completions.create(
                model="minimax-m3.1-flash-abliterated",
                messages=messages,
                tools=TOOLS,
                max_tokens=4096,
                extra_body={"max_cost_micro_usdc": 500_000},  # at most $0.50 per step
            )
        except RateLimitError as error:
            if error.response.headers.get("x-lyber-admission") != "not-admitted":
                raise
            time.sleep(int(error.response.headers.get("retry-after", "5")))
    raise RuntimeError("The model stayed busy")

messages = [
    {"role": "system", "content": "Read files with the tool before you answer."},
    {"role": "user", "content": "What does README.md say about setup?"},
]
for _ in range(10):
    reply = step(messages).choices[0].message
    messages.append(reply)
    if not reply.tool_calls:
        print(reply.content)
        break
    for call in reply.tool_calls:
        result = RUN[call.function.name](**json.loads(call.function.arguments))
        messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
  • max_retries=0 keeps the SDK from resending a request that may already have run.
  • A 429 carrying x-lyber-admission: not-admitted never started, so resending it is safe.
  • The step limit and max_cost_micro_usdc bound what one run can spend.

OpenAI Agents SDK

Use OpenAIChatCompletionsModel with a client pointed at Lyber. Turn off tracing, which otherwise sends traces to OpenAI.

terminal
pip install openai-agents
main.py
import asyncio
import os

from openai import AsyncOpenAI
from agents import Agent, ModelSettings, OpenAIChatCompletionsModel, Runner, function_tool, set_tracing_disabled

set_tracing_disabled(True)
client = AsyncOpenAI(base_url="https://lyber-paid-acceptance-gateway-production.up.railway.app/v1", api_key=os.environ["LYBER_API_KEY"], max_retries=0)

@function_tool
def get_weather(city: str) -> str:
    """Current weather for a city."""
    return f"21 °C and clear in {city}"

agent = Agent(
    name="Assistant",
    instructions="Answer briefly. Use tools when they help.",
    model=OpenAIChatCompletionsModel(model="minimax-m3.1-flash-abliterated", openai_client=client),
    model_settings=ModelSettings(max_tokens=2048),
    tools=[get_weather],
)

async def main():
    result = await Runner.run(agent, "What's the weather in Buenos Aires?", max_turns=6)
    print(result.final_output)

asyncio.run(main())

Vercel AI SDK

generateText runs the tool loop for you; stopWhen bounds the number of steps.

terminal
npm install ai @ai-sdk/openai-compatible zod
main.ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { generateText, stepCountIs, tool } from "ai";
import { z } from "zod";

const lyber = createOpenAICompatible({
  name: "lyber",
  baseURL: "https://lyber-paid-acceptance-gateway-production.up.railway.app/v1",
  apiKey: process.env.LYBER_API_KEY,
});

const result = await generateText({
  model: lyber("minimax-m3.1-flash-abliterated"),
  tools: {
    getWeather: tool({
      description: "Current weather for a city.",
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => ({ city, temperatureC: 21 }),
    }),
  },
  stopWhen: stepCountIs(6),
  maxOutputTokens: 2048,
  maxRetries: 0,
  prompt: "What's the weather in Buenos Aires?",
});
console.log(result.text);

Guardrails

The models do what they're asked, so put the checks in your code:

  • Validate tool arguments before running them, and give tools the least access that works.
  • Ask a person before anything destructive or irreversible: deleting files, sending messages, spending money.
  • Treat tool output, web pages and files as data, not instructions.