Skip to main content

AI

LLM access for Telo — defines the Ai.Model abstract every provider implements and ships ready-to-use buffered and streaming consumers.

Why use this

  • Provider-agnostic — swap models by changing one resource reference; no controller code touches LLM SDKs directly.
  • Buffered and streamingAi.Text returns a complete response; Ai.TextStream exposes an async iterable of StreamPart records.
  • Composable encoding — pipe a stream through any Codec.Encoder (NDJSON, SSE, plain text, raw bytes) without bespoke serialization.
  • Open for extensionAi.Model is a Telo.Abstract; any module declaring extends: Ai.Model is a drop-in provider.
  • Typed contract — provider input (messages, options) and output (text, usage, finishReason) are validated by JSON Schema.

Kinds

KindPurpose
Ai.ModelAbstract contract every LLM provider implements (invoke + stream).
Ai.TextBuffered single-turn LLM call delegating to any Ai.Model implementation.
Ai.TextStreamStreaming counterpart that returns { output: Stream<StreamPart> }.
Ai.AgentTool-use loop over any Ai.Model — calls tools, replays results, loops to a final answer.
Ai.ToolProviderAbstract contract every agent tool source implements (listTools + callTool).
Ai.ToolsBuilt-in Ai.ToolProvider: a static list of tools, each wrapping any Telo.Invocable.

Example

kind: Telo.Application
metadata: { name: my-app, version: 1.0.0 }
imports:
Ai: pkg:npm/@telorun/ai@^1.0.0
AiOpenai: pkg:npm/@telorun/ai-openai@^1.0.0
secrets:
openaiApiKey:
env: OPENAI_API_KEY
type: string
---
kind: AiOpenai.OpenaiModel
metadata: { name: Gpt4o }
model: gpt-4o-mini
apiKey: "${{ secrets.openaiApiKey }}"
---
kind: Ai.Text
metadata: { name: Summarizer }
model: !ref Gpt4o
system: "Summarize concisely."

Reference

Provider Contract

Any module declaring kind: Telo.Definition with capability: Telo.Invocable and extends: Ai.Model is a drop-in provider. The runtime contract every provider honours:

interface AiModelInstance {
invoke(input: { messages: Message[]; options?: Record<string, unknown> }):
Promise<{ text: string; usage: Usage; finishReason: FinishReason }>;

stream(input: { messages: Message[]; options?: Record<string, unknown> }):
AsyncIterable<StreamPart>;

snapshot?(): Record<string, unknown>;
}

type StreamPart =
| { type: "text-delta"; delta: string }
| { type: "finish"; usage: Usage; finishReason: FinishReason }
| { type: "error"; error: { message: string; code?: string; data?: unknown } };

Ai.Text calls invoke(); Ai.TextStream wraps stream() and exposes the iterable as { output: Stream<StreamPart> }. StreamPart.error is a plain JSON-serializable object — providers translate native Error instances at yield time so generic encoders can frame error parts without bespoke serialization.

Tool use

Tool use / function calling is provided by Ai.Agent: it advertises tools to the model, executes the ones the model requests, and loops. Tools come from any Ai.ToolProvider — a static Ai.Tools list, or runtime discovery from an MCP server via AiMcp.ToolProvider. The Ai.Model contract carries tools additively (tools in, toolCalls out, the tool message role); Ai.Text/Ai.TextStream never pass tools and are unaffected.

Out of Scope

  • Multimodal inputcontent is string today; widening to string | ContentPart[] is additive when needed.
  • Structured outputs / JSON mode — not in the core contract; providers may expose via options.
  • Streaming agentAi.Agent is buffered; a streaming agent is a clean additive kind once the buffered loop is in use.