Agents
Knowledge-grounded assistants with deterministic rules and HTTP tools.
An agent grounds answers on attached Knowledge bases, applies deterministic rules, and can call HTTP tools via a function-calling loop.
Create and attach knowledge
import { createAgentClient } from "@unispec-ai/sdk";
const agents = createAgentClient({ apiKey: process.env.UNISPEC_API_KEY });
await agents.createAgent({
name: "support",
instructions: "Answer from knowledge; use tools for orders.",
knowledge: ["support-docs"],
});
const agent = agents.agent("support");Rules
Rules are evaluated deterministically around the LLM:
escalation— a keyword match short-circuits the LLM and returns the fallback message.forbidden/required— injected as prompt constraints.
await agent.addRule({ type: "escalation", condition: "chargeback,dispute" });Tools
Register an HTTP endpoint the agent may call. The model fills the parameters; the result is fed back into the answer.
await agent.addTool({
name: "lookup_order",
description: "Look up an order by id.",
http: { url: "https://api.example.com/orders", method: "POST" },
parameters: { type: "object", properties: { order_id: { type: "string" } }, required: ["order_id"] },
});Chat
const res = await agent.chat([{ role: "user", content: "Status of order A123?" }]);
console.log(res.message.content);
console.log(res.escalated, res.trace.tool_calls); // rule + tool traceOpenAI-compatible endpoint
Unispec exposes an OpenAI-compatible POST /v1/chat/completions, so any OpenAI
client works out of the box — point the base URL at Unispec and use your agent
name as the model. The full server-side pipeline (guardrails, flow, tools,
knowledge, tool policy) runs on every request; you don't manage any of it
client-side.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://unispec.ai/v1",
apiKey: process.env.UNISPEC_API_KEY, // sent as `Authorization: Bearer …`
});
// Non-streaming
const res = await client.chat.completions.create({
model: "support-bot", // your agent name
messages: [{ role: "user", content: "Status of order A123?" }],
});
console.log(res.choices[0].message.content);
// Streaming (Server-Sent Events)
const stream = await client.chat.completions.create({
model: "support-bot",
messages: [{ role: "user", content: "Status of order A123?" }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}Notes:
model= agent name (alibra:prefix is tolerated).GET /v1/modelslists your agents as models.- Streaming uses buffer-then-stream: the full pipeline runs, then the
approved answer is replayed as
chat.completion.chunkevents — so guardrails, the output-guard, and the enterprise risk-hold still apply (a naive token stream couldn't retract a blocked answer). - Client
systemmessages are ignored — the agent owns its instructions. - The typed extras (
ui_actions,citations,escalated) that don't fit the OpenAI schema ride in a non-standardx_librafield (OpenAI clients ignore it). For full fidelity, use the nativePOST /v1/agents/{name}/chat(SDKagent.chat(...)).
From the Unispec SDK
The Unispec SDK also offers streaming without the openai package, plus helpers
to configure an OpenAI client:
import { createAgentClient, openai, openaiConfig } from "@unispec-ai/sdk";
// Stream text deltas over the OpenAI-compatible endpoint (no `openai` dep):
const agent = createAgentClient().agent("support-bot");
for await (const delta of agent.chatStream([{ role: "user", content: "Hi" }], {
onFinal: (x) => console.log(x.ui_actions, x.escalated), // typed extras on the final frame
})) {
process.stdout.write(delta);
}
// Or hand back a configured OpenAI client (optional `openai` peer dependency):
const client = await openai(); // new OpenAI({ baseURL, apiKey })
// …or just the config for your own client:
// const client = new OpenAI(openaiConfig());Live token streaming (opt-in)
By default, streaming is buffer-then-stream (the pipeline completes, then the
approved answer replays as chunks). Set the agent's stream_mode to "live" to
stream tokens as the model generates them — first token in well under a second:
await client.configureAgent("support-bot", { streamMode: "live" });Live mode is guard-gated: it is honored only when the output guard is off and the agent has no tools and no flow (the output guard can't retract tokens that already streamed). Escalation rules and the input guard still run before the first token. When the gate doesn't allow it, streaming silently falls back to buffer-then-stream — same wire format either way.
Visitor memory (memory-native agents)
Pass a memory user id and the turn becomes memory-native: relevant long-term memories for that user are recalled before answering (grounding the reply), and the exchange is persisted after — scoped to this agent + project, powered by the Memory product's LLM fact extraction.
// Native chat
const res = await agent.chat(messages, { memory: { userId: "visitor-123" } });
// res.trace.memory_recalled → how many memories grounded this turn
// OpenAI-compatible endpoint: the standard `user` field activates it
await client.chat.completions.create({
model: "support-bot",
messages,
user: "visitor-123",
});recall/persist can be toggled individually (memory: { userId, persist: false }).
Works on buffered and live streaming (recall runs before the first token);
persistence is asynchronous — replies never wait on it.