Liquid

Conecte seu agente a qualquer API HTTP em tempo real - descobre e mapeia qualquer API REST uma vez, depois busca dados tipados de forma determinística.

Documentação

Liquid

Conecte seu agente de IA a qualquer coisa — sem precisar escrever ou manter conectores.

Aponte o Liquid para uma URL ou um banco de dados e ele descobre a interface para você: identifica a estrutura, mapeia para os campos que você pediu e lida com autenticação, paginação e normalização — registros tipados, sem código de cliente. Quando a fonte muda, ele re-mapeia e continua funcionando. A mesma API enxuta — fetch · query · write · sense — alcança APIs web, bancos de dados, outros agentes (MCP/A2A), e-mail e até sistemas de IoT e industriais (MQTT, Modbus, OPC UA, BACnet). Um LLM faz o aprendizado na configuração (e em mudanças); o caminho dos dados em si não faz nenhuma chamada de modelo.

PyPI License Python


O que um agente pode alcançar através do Liquid

Uma API voltada para agentes (fetch · query · write · sense) sobre tudo que um agente possa precisar tocar — o Liquid descobre como falar com isso para que o agente não precise. São os sentidos e as mãos do agente: fetch/query sondam, sense percebe um fluxo de eventos ao vivo, write age no mundo.

  • APIs web e mensageria — REST/JSON, GraphQL, SOAP/WSDL, gRPC, WebSocket, streams SSE/NDJSON, MQTT (pub/sub IoT — assine para sentir, publique para agir)
  • E-mail — IMAP/SMTP (qualquer provedor, senha de app ou OAuth2 XOAUTH2) e a API do Gmail (OAuth2): leia uma caixa de entrada, sense novos e-mails conforme chegam e envie
  • Industrial / OT — Modbus (CLPs, sensores) e OPC UA (nós da Indústria 4.0, assinaturas nativas) para o chão de fábrica; BACnet para edifícios (HVAC/BMS) — leia, escreva e sinta
  • Dispositivos Android — celulares, TV boxes, quiosques via ADB: sinta logcat, leia shell, aja com input/am
  • Outros agentes e ferramentas — qualquer servidor MCP, agentes A2A, manifestos de plugins do ChatGPT
  • Bancos de dados — Postgres (+ pgvector), MySQL/MariaDB, SQLite, DuckDB, SQL Server, Neo4j (grafo), MongoDB (documentos), Redis (chave-valor)
  • Pessoas, lugares e coisas — um humano, uma casa ou um carro como nó via connectors: Telegram (perceba mensagens, send respostas), Home Assistant (perceba mudanças de estado de uma casa inteligente inteira, aja via call_service — luzes, fechaduras, mídia) e Smartcar (perceba um veículo conectado em ~30 marcas — localização/bateria/combustível — e aja: lock/unlock, carregue)

Aponte para um endpoint https://…, um DSN postgres://… / mongodb://… / redis://…, um alvo grpc://… ou outro servidor MCP — a descoberta identifica a interface, aprende sua estrutura e entrega registros tipados ao seu agente. O mesmo fetch/query/write funciona independentemente do que está por baixo. Sem conector por serviço para escrever manualmente; a integração se mantém sozinha quando a fonte muda.

# A web API it has never seen — no spec, no connector, no auth
adapter = await liquid.get_or_create(
    "https://api.openbrewerydb.org/v1/breweries",
    target_model={"name": "str", "city": "str", "country": "str"},
    auto_approve=True,
)
breweries = await liquid.fetch(adapter)            # typed records

# A database is just another interface — same API, and it writes too
db = await liquid.get_or_create("postgresql://reader@host/shop",
                                target_model={"id": "int", "email": "str"},
                                auto_approve=True)
orders = await liquid.fetch(db, "/public/orders")
await liquid.write(db, "/public/orders", op="insert",
                   values={"email": "a@b.com", "total_cents": 9900},
                   allow_write=True)               # opt-in; mutates the store

Você não escreve nenhum conector nem schema: um LLM aprende a interface uma vez na configuração (bancos de dados se autoinspecionam e pulam até isso), e a integração se auto-repara quando a fonte muda. O runtime é transporte determinístico puro — custo previsível, comportamento reproduzível, nada para supervisionar.

Feito para as restrições que agentes reais enfrentam

Alcançar tudo é metade da história. A outra metade é que agentes pagam por cada token, se confundem com formatos inconsistentes e não conseguem interpretar textos de erro. O Liquid responde a cada um com um primitivo concreto — tudo incluído, tudo no PyPI.

Controle de orçamento de contexto

# Search / aggregate server-side instead of fetch-then-filter — 10-100x fewer tokens
orders = await liquid.search(adapter, "/orders",
    where={"total_cents": {"$gt": 10000}, "status": "paid"}, limit=20)

stats = await liquid.aggregate(adapter, "/orders",
    group_by="status", agg={"total_cents": "sum", "id": "count"})

hits = await liquid.text_search(adapter, "/tickets", "shipping delay")  # BM25-lite

data = await liquid.fetch(adapter, "/orders", max_tokens=2000)      # budget cap
data = await liquid.fetch(adapter, "/customers", verbosity="terse") # id + 1-2 fields

Normalização entre fontes

liquid = Liquid(..., normalize_output=True)
# Stripe {amount:1000,currency:"usd"} · PayPal {value:"10.00",currency_code:"USD"}
#   → Money(amount_cents=1000, currency="USD", amount_decimal=Decimal("10.00"))

Carimbos de data/hora (Unix / ISO 8601 / RFC 2822) são convertidos para UTC datetime; envelopes de paginação ({data:[…]} / {results:[…]} / cabeçalhos Link) são achatados; campos de ID são normalizados entre id / _id / uuid / *_id.

Intenções canônicas — um modelo mental único entre serviços

await liquid.execute_intent(adapter, "charge_customer",
    {"customer_id": "cus_xyz", "amount_cents": 9999, "currency": "USD"})
# Same intent on Stripe / Braintree / Square / Adyen — 71 canonical intents

Recuperação estruturada — agentes se auto-curam sem interpretar texto

try:
    await liquid.fetch(adapter, "/orders")
except LiquidError as e:
    if e.recovery and e.recovery.next_action:
        await agent.call_tool(e.recovery.next_action.tool, e.recovery.next_action.args)

Todo erro carrega um Recovery com next_action: ToolCall, retry_safe e retry_after_seconds. 401 → store_credentials. 404/410 → repair_adapter. 429 → tente novamente após o atraso informado. E quando o schema da fonte muda, os adaptadores se auto-curam (repair_adapter) — o agente continua funcionando.

Custo previsível — saiba antes de chamar

est = await liquid.estimate_fetch(adapter, "/orders")
# FetchEstimate(expected_items=250, expected_tokens=52_000, confidence="high", …)
if est.expected_tokens < my_budget:
    data = await liquid.fetch(adapter, "/orders")

Ferramentas emitidas por to_tools() carregam um bloco metadata (cost_credits, typical_latency_ms, cached, idempotent, side_effects, related_tools) para que o agente possa raciocinar sobre qual ferramenta escolher — e ferramentas ambientais (liquid_check_quota, liquid_list_adapters, …) permitem perguntar sobre o estado em vez de memorizá-lo.


Impacto medido

Benchmarks determinísticos em tarefas realistas de agentes (cenários com 500 pedidos, 200 chamados, HTTP simulado) — reproduzíveis via python -m benchmarks.run:

TarefaMétricaLinha de baseCom LiquidDelta
Encontrar 10 pedidos acima de $100tokens75.4821.519−98%
Receita por status (agregado)tokens75.482115−100%
Buscar cliente (apenas id+email)tokens42412−97%
Recuperar de erro 401próxima_ação estruturadanãosim—
Encontrar o chamado de enviotokens14.588154−99%
Consistência Stripe↔PayPalsobreposição de campos0,111,00+9×
Pular chamada desperdiçada via estimativatokens14.9430−100%
Limite de orçamento max_tokens=2000tokens14.9431.999−87%

Metodologia completa + detalhamento por tarefa: benchmarks/RESULTS.md.

Instalação

pip install liquid-api                 # core + bundled MCP server (the `liquid-mcp` command)
pip install 'liquid-api[discovery]'    # + an LLM for discovering spec-less REST APIs & field mapping

Você precisa de um LLM extra? Interfaces autodescritivas — OpenAPI, GraphQL, gRPC, MCP, A2A, WSDL — e todos os bancos de dados (introspecção) são descobertos sem LLM, e todo o runtime (fetch/query/write/sense) nunca chama um modelo. Você só precisa de um backend LLM para descobrir uma API REST que não tem spec legível por máquina (heurística + LLM) e para mapear seus campos. [discovery] puxa o LiteLLM, que alcança OpenAI / Gemini / Anthropic / local / 100+ provedores; ou escolha um diretamente:

pip install 'liquid-api[gemini]'     # Google Gemini   (or [anthropic]; OpenAI/local work with no extra via base_url)
pip install 'liquid-api[grpc]'       # gRPC transport (reflection)
pip install 'liquid-api[ws]'         # WebSocket transport
pip install 'liquid-api[pg]'         # Postgres / pgvector (asyncpg)
pip install 'liquid-api[mysql]'      # MySQL / MariaDB (aiomysql); SQLite needs no extra
pip install 'liquid-api[neo4j]'      # Neo4j graph (Bolt / Cypher)
pip install 'liquid-api[duckdb]'     # DuckDB (embedded analytics)
pip install 'liquid-api[mssql]'      # SQL Server (ODBC; needs a system ODBC driver)
pip install 'liquid-api[mongodb]'    # MongoDB (collections as endpoints)
pip install 'liquid-api[redis]'      # Redis (keyspace namespaces as endpoints)
pip install 'liquid-api[mqtt]'       # MQTT (IoT pub/sub)
pip install 'liquid-api[modbus]'     # Modbus (industrial registers)
pip install 'liquid-api[opcua]'      # OPC UA (Industry-4.0 nodes + subscriptions)
pip install 'liquid-api[bacnet]'     # BACnet (building automation; ADB needs the system `adb` binary)
# Framework integration (LangChain / OpenAI / Anthropic / MCP) is built in — no extra package.

O núcleo não tem dependências — a biblioteca de cada backend é um extra opcional, importada apenas quando usada.

Veja funcionando — ao vivo, sem pré-configuração

Aponte o Liquid para uma API que ele nunca viu (sem adaptador, sem spec OpenAPI, sem auth) e receba registros tipados — você não escreve nenhum conector; descoberta + mapeamento é o único lugar onde um modelo roda. Executável de ponta a ponta via examples/live_quickstart.py:

Connecting to an API Liquid has never seen:
  https://api.openbrewerydb.org/v1/breweries

  discovery method : rest_heuristic
  mapped fields    : ['name', 'city', 'state', 'country']
  LLM calls so far : 2  (discovery + mapping)

fetch() -> 50 typed records; first 3:
   {'name': '(405) Brewing Co', 'city': 'Norman', 'state': 'Oklahoma', 'country': 'United States'}
   {'name': '(512) Brewing Co', 'city': 'Austin', 'state': 'Texas', 'country': 'United States'}
   {'name': '1 of Us Brewing Company', 'city': 'Mount Pleasant', 'state': 'Wisconsin', 'country': 'United States'}

  LLM calls during fetch : 0
  LLM calls on 2nd fetch : 0

Você não escreveu conector, schema ou cola de autenticação — o Liquid aprendeu a interface por você e vai reaprendê-la se ela mudar. Esse é o ponto: integrações que você não constrói nem supervisiona.

Execute como servidor MCP (código aberto, auto-hospedado)

Exponha o motor para qualquer cliente MCP (Claude Desktop, Cursor, Claude Code) — ele roda no seu próprio processo, sem nuvem, sem conta, sem aprisionamento:

Add to Cursor

Um clique no Cursor (o botão escreve o servidor no seu mcp.json; adicione seu OPENAI_API_KEY nas configurações de MCP do Cursor depois). Ou configure manualmente:

pip install liquid-api
export OPENAI_API_KEY=sk-...        # or GEMINI_API_KEY / ANTHROPIC_API_KEY,
                                    # or OPENAI_BASE_URL=http://localhost:11434/v1 for local (Ollama/vLLM)
liquid-mcp                          # or: python -m liquid.mcp_server

Instalação zero com uvx (o pacote liquid-mcp faz o comando funcionar pelo nome) — Claude Code:

claude mcp add liquid --scope user -e OPENAI_API_KEY=sk-... -- uvx liquid-mcp

Claude Desktop / qualquer cliente MCP:

{ "mcpServers": { "liquid": {
  "command": "uvx",
  "args": ["liquid-mcp"],
  "env": { "OPENAI_API_KEY": "sk-..." }
} } }

(ou após pip install liquid-api, remova uvx e use "command": "liquid-mcp" diretamente.)

Um clique no Claude Desktop: instale o pacote .mcpb — ele pede sua chave de modelo na instalação (armazenada no chaveiro do SO), sem JSON para editar. Requer uv na máquina.

Ferramentas: liquid_connect (descubra + mapeie qualquer interface), liquid_fetch, liquid_query (busca/agregação no servidor), liquid_estimate (pré-verificação de custo/tamanho, sem chamada), liquid_list_adapters, liquid_discover. A superfície é somente leitura por padrão; inicie o servidor com LIQUID_ALLOW_WRITES=1 para também expor liquid_execute (insert/update/delete em banco). Adaptadores e credenciais persistem em ~/.liquid. Suportado por qualquer LLM — OpenAI, Gemini, Anthropic, qualquer endpoint compatível com OpenAI/local via base_url, 100+ provedores via LiteLLM, ou sua própria função através de CallableBackend.

Início rápido — agente LangGraph

from liquid import Liquid, InMemoryCache, RateLimiter
from liquid._defaults import InMemoryVault, InMemoryAdapterRegistry, CollectorSink
from liquid_langchain import LiquidToolkit
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

liquid = Liquid(
    llm=my_llm, vault=InMemoryVault(), sink=CollectorSink(),
    registry=InMemoryAdapterRegistry(), cache=InMemoryCache(), rate_limiter=RateLimiter(),
    normalize_output=True,    # cross-source canonical shapes
    include_meta=True,        # _meta block on every response
)

adapter = await liquid.get_or_create(
    "https://api.shopify.com",
    target_model={"id": "str", "total_cents": "int", "customer_email": "str"},
    credentials={"access_token": "shpat_..."},
    auto_approve=True,
)

tools = LiquidToolkit(adapter, liquid).get_tools()
agent = create_react_agent(ChatOpenAI(model="gpt-4o-mini"), tools)
result = await agent.ainvoke(
    {"messages": [("user", "Find 5 recent orders over $100 from VIP customers")]}
)

As ferramentas do agente vêm com descrições ricas (QUANDO usar, NÃO PARA o quê, formato de retorno, custo), recuperação estruturada em todo erro e busca no servidor para que ele nunca puxe 500 pedidos para encontrar 5.

Toda interface, uma API

A descoberta identifica o alvo e marca cada endpoint com um protocolo; um driver de transporte plugável o executa — mas a API voltada para agentes (fetch, query, write, mapeamento, recuperação, cache, limites de taxa) é idêntica em todos eles.

InterfaceRuntimeEscritaInstalação
REST / HTTP+JSON✅✅ ações (POST/PUT/PATCH/DELETE)—
GraphQL✅ query + paginação Relay✅ mutações—
SOAP / WSDL✅ XML da stdlib——
gRPC✅ unário + server-streaming (reflexão)—liquid-api[grpc]
WebSocket✅ leituras em lote limitadas + assinatura + sense ao vivo—liquid-api[ws]
SSE / NDJSON (server-push HTTP)✅ leituras em lote limitadas + sense ao vivo——
MCP (agente)✅ chamar ferramentas / ler recursos + notificação sense✅ chamadas de ferramenta—
A2A (agente)✅ JSON-RPC message/send para habilidades do AgentCard——
Postgres (+pgvector)✅ tabelas/views, filtros, paginação, busca vetorial✅liquid-api[pg]
MySQL / MariaDB✅ tabelas/views, filtros, paginação✅liquid-api[mysql]
SQLite✅ tabelas/views, filtros, paginação✅— (stdlib)
DuckDB✅ tabelas/views, filtros, paginação✅liquid-api[duckdb]
SQL Server✅ tabelas/views, paginação OFFSET/FETCH✅liquid-api[mssql]
Neo4j (grafo)✅ tipos de rótulos/relacionamentos, filtros de propriedade✅ CRUD de nósliquid-api[neo4j]
MongoDB (documento)✅ coleções, filtros de campo, paginação✅liquid-api[mongodb]
Redis (chave-valor)✅ namespaces de keyspace, valores tipados, paginação SCAN✅ SET/HSET/DELliquid-api[redis]
MQTT (IoT pub/sub)✅ assinar → lote + sense ao vivo✅ publicarliquid-api[mqtt]
Modbus (industrial)✅ leitura de registradores/coils + polling por delta sense✅ escrita de registradores/coilsliquid-api[modbus]
OPC UA (industrial)✅ leitura de nós + assinatura nativa sense✅ escrita de nósliquid-api[opcua]
BACnet (edifícios)✅ leitura de propriedades de objetos + polling por delta sense✅ escrita de propriedadesliquid-api[bacnet]
ADB (Android)✅ leitura de shell + logcat sense✅ ações de shell (input/am)— (adb do sistema)
E-mail — IMAP/SMTP✅ leitura de caixa por UID + sense de novos e-mails✅ envio (MIME)— (stdlib)
E-mail — API Gmail✅ listar/obter + history sense✅ messages.send— (OAuth2)

Leitura e escrita. liquid.write(adapter, endpoint, op="insert", values={...}, allow_write=True) mutates any database (SQL INSERT/UPDATE/DELETE, insert/update/delete no Mongo, Redis SET/HSET/DEL, CRUD de nós no Neo4j); escritas web/agente passam por ações verificadas. Identificadores vêm da introspecção e valores são parametrizados; update/delete exigem um where (sem mutações genéricas); escritas ficam desligadas até você optar com allow_write=True. Sense — o órgão aferente. liquid.sense(adapter, endpoint) percebe um fluxo de eventos ao vivo onde quer que exista: deltas de linhas SQL (e LISTEN/NOTIFY do Postgres), Redis pub/sub, frames WebSocket, server-push HTTP (SSE/NDJSON) e notificações MCP — cada um produzido como um evento agnóstico de modalidade. Apontado para dentro, liquid.sense_webhook(port=…, verifier=…) hospeda um endpoint de entrada para que um serviço (ou um humano, via webhook) que faça POST para o agente se torne também um sinal perceptível. Tudo limitado por max_events / max_seconds, para que um agente possa drenar por pull.

O loop sensorimotor. react(stream, handler) aciona um handler para cada evento percebido — com isolamento de erros e concorrência limitada — para que um host possa perceber → acordar o agente → agir. merge_senses(*streams) distribui vários sentidos em um único loop, para que um agente possa observar um banco de dados, uma fila e um webhook ao mesmo tempo:

events = merge_senses(
    await liquid.sense(orders, "/orders"),
    await liquid.sense_webhook(port=8088, verifier=stripe_verifier),
)
await react(events, on_event, max_concurrency=4)

A descoberta é automática — e identifica em tempo real. Antes do pipeline ser executado, uma etapa de fingerprint nomeia o alvo: um host:port simples é normalizado pela porta conhecida (db:5432 → postgresql://db:5432), e liquid.identify(url) responde "o que é isso, e o driver está instalado?" com uma dica de instalação quando um backend está ausente. (Identificar um protocolo é viável em tempo real; falar um novo protocolo binário autenticado não é — então desconhecidos são nomeados, não adivinhados.)

DescobertaOnde procuraCusto
Bancos de dadosintrospecção de catálogo (postgres://, mysql://, mongodb://, redis://, neo4j://, …)Baixo
gRPC / WebSocket / SSEreflexão de servidor / amostragem de frames / detecção de content-typeBaixo
MCP / A2A / Plugin/mcp, /.well-known/agent-card.json, /.well-known/ai-plugin.jsonBaixo
OpenAPI / GraphQL / SOAPspec, introspecção ou WSDLBaixo
Heurística RESTcaminhos comuns + interpretação por LLMMédio
NavegadorPlaywright capturando redeAlto

Adicione um backend sem escrever código. Para a família SQL, o contrato é declarativo o suficiente para ser dados: um manifesto de dialeto (aspas, estilo de placeholder, paginação, SQL de introspecção, mapa de erros, módulo DBAPI2) registrado via register_sql_manifest({...}) instala um driver funcional + descoberta — então um novo armazenamento SQL / compatível com wire (CockroachDB, ClickHouse, qualquer driver DBAPI2), até mesmo um obtido da rede como JSON, conecta-se sem um release. Novos protocolos, caso contrário, plugam via protocolo liquid.transport.ProtocolDriver; backends SQL compartilham um núcleo ciente de dialeto, então um novo é um adaptador de ~80 linhas.

Quer ensinar um novo protocolo ao Liquid? Um driver de transporte completo (fetch/write/sense) normalmente tem ~150 linhas — veja docs/ADDING_A_DRIVER.md para o passo a passo e uma lista de desejos (CAN bus, CoAP, KNX, AMQP, NATS, SNMP, …). Contribuições são bem-vindas.

Mais de 2.500 APIs são pré-descobertas e pré-mapeadas no catálogo global — a maioria dos serviços populares conecta-se com custo zero de descoberta.

Arquitetura

URL / DSN                       Agent
   ↓                              ↑
 FINGERPRINT → DISCOVERY        FETCH · QUERY · WRITE · SEARCH · AGGREGATE
   ↓                              ↑
 one ProtocolDriver per          Deterministic per-protocol transport
 interface:                        • Query DSL (server-side filter)
   REST GraphQL gRPC WS SSE MQTT   • Output normalization
   MCP A2A · SQL graph doc KV ·    • Verbosity / max_tokens / _meta
   Modbus OPC-UA BACnet ADB …      • (full protocol list in the table above)
   ↓                              • Structured recovery + self-heal
 APISchema                        • Rate-limit-aware token bucket
   ↓                              • Response cache (Cache-Control aware)
 AI MAPPING (setup only)          • Empirical probing data (Cloud)
   ↓
 AdapterConfig

A IA participa apenas na configuração. O runtime é puramente transporte com transformações — sem LLM por chamada, custo previsível, comportamento reproduzível (exceto search_nl, que armazena em cache suas compilações).

Componentes substituíveis

Cada preocupação transversal é um Protocol que você pode substituir:

from liquid.protocols import (
    Vault, LLMBackend, DataSink, KnowledgeStore, AdapterRegistry, CacheStore,
)

Implementações em memória acompanham todos eles; liquid-cloud fornece PostgresVault, RedisCache, etc. para implantações hospedadas.

Suporte a frameworks

adapter.to_tools(format="anthropic")   # Claude tool use
adapter.to_tools(format="openai")      # OpenAI function calling (LangChain/CrewAI consume these)
adapter.to_tools(format="mcp")         # MCP (Claude Desktop, Cursor)

Integração com frameworks

Nenhum pacote extra para instalar — está embutido no liquid-api. adapter.to_tools(format="anthropic" | "openai" | "mcp") emite definições de ferramentas prontas para uso para uso de ferramentas do Claude, chamada de funções da OpenAI (que LangChain / LangGraph e CrewAI consomem diretamente) e qualquer cliente MCP (Claude Desktop, Cursor, …). O servidor liquid-mcp incluído também expõe o Liquid como ferramentas MCP prontas para uso.

Comparação

RecursoLiquidZapierFerramenta LangChainDIY
Descobre automaticamente qualquer interface (sem conector curado)simnãonãonão
APIs + bancos de dados + agentes em uma única camadasimparcialnãonão
Leitura e escrita através de uma única APIsimsimparcialnão
Busca / agregação no servidorsimnãonãoparcial
Normalização de saída entre fontessimparcialnãonão
Recuperação estruturada com next_actionsimnãonãonão
Autocorreção em desvio de esquemasimnãonãonão
Estimativa de custo pré-execuçãosimnãonãonão
MCP + A2A + LangChain + CrewAI nativosimnãoparcialnão
Código abertosimnãosimn/a

Documentação