Any app, two lines
When you call a provider SDK directly — or you’re not on LangChain — put one observe() right where you already have the prompt and the reply. That’s the whole integration.
Python
from doairight_evals import create_client
client = create_client(api_key="dar_...") # key: /app/developer
# right where you already have the prompt and the model's reply:
resp = model.generate(prompt) # your existing call
client.observe(
prompt=prompt,
response=resp.text,
kind="generative",
model="gemini-2.5-flash", # optional, but useful
provider="vertex", # optional
latency_ms=1234, # optional
prompt_tokens=42, completion_tokens=17, # optional
metadata={"app": "my-app"}, # optional
)Only prompt and response are required — everything else is optional metadata that makes the run richer to score and filter. observe() enqueues and returns immediately: no network I/O on your thread, and it never raises into your caller.
TypeScript / JavaScript
import { createClient } from "@doairight/evals";
const client = createClient({ apiKey: "dar_..." }); // key: /app/developer
const reply = await model.generate(prompt); // your existing call
client.observe({
prompt,
response: reply.text,
kind: "generative",
model: "gpt-4o",
metadata: { app: "my-app" },
});Install with npm install @doairight/evals. Same shape as Python, camelCase fields.
Any language — raw HTTP
curl -X POST https://doairight.com/api/evals/observe \
-H "authorization: Bearer dar_..." \
-H "content-type: application/json" \
-d '{
"prompt": "...",
"response": "...",
"kind": "generative",
"model": "gpt-4o",
"metadata": { "app": "my-app" }
}'No SDK for your stack? POST the same JSON to /api/evals/observe with your dar_ key as a bearer token. Full request/response shape: /docs/api.
Streaming responses
Assemble the streamed chunks into the final string as you already do, then call observe() once with the complete response. There’s no need to send partial tokens — DoAIRight scores the finished reply.
Shutdown (optional)
# On process shutdown, drain anything still queued: client.close()
The client sends on a background worker. In short-lived scripts or workers, call close() (Python) / flush() (TS) before exit so queued runs aren’t lost. Long-running servers don’t need this.
On LangChain?
Skip the per-call-site work entirely — attach one callback handler instead: LangChain & LangGraph.