# Calling AI models

> Call chat, embedding, image, transcription, and speech models with ctx.ai(alias): credentials injected by the platform and an estimated cost on every response.

`ctx.ai(alias)` gives you a real [AI SDK](https://ai-sdk.dev/) provider bound to an authentication alias in `helix.config.ts`. The platform injects the model provider's API key, so it never appears in your code. Call `generateText`, `streamText`, `generateObject`, `streamObject`, `embed`, `embedMany`, `generateImage`, `transcribe`, or `generateSpeech` on it, the same methods the AI SDK itself exposes.

## Declare a provider

Add an authentication alias and tell Helix which service backs it with `aiProviders`:

```typescript
// helix.config.ts
import { defineConfig } from '@trayai/helix-sdk';

export default defineConfig({
  authentications: {
    anthropicKey: '<your-anthropic-auth-uuid>',
  },
  aiProviders: {
    anthropicKey: 'anthropic', // 'openai' | 'anthropic' | 'google' | 'mistral' | 'cohere'
  },
});
```

`aiProviders` maps the same alias `authentications` uses to the AI SDK package that talks to it. An alias with no matching `aiProviders` entry throws `SdkError.aiProviderNotConfigured`, naming the exact config line to add.

## Generate text

```typescript
// functions/api/v1/summarize.post.ts
import { z } from 'zod';
import { defineFunction } from '@trayai/helix-sdk';

export const input = z.object({
  text: z.string().min(1),
});

export default defineFunction<typeof input>(async (ctx) => {
  const result = await ctx.ai('anthropicKey').generateText({
    model: 'claude-sonnet-4-5',
    prompt: `Summarize in two sentences: ${ctx.input.text}`,
  });

  ctx.log.info('Summarized', { cost: result.helix.cost.amount });

  return { summary: result.text };
});
```

`ctx.ai(alias)` takes the model as a string id (`'claude-sonnet-4-5'`, `'gpt-4o'`, and so on) resolved against the provider you declared for that alias.

## Stream a response

`streamText` returns immediately with a stream you can pipe to the browser:

```typescript
// functions/api/v1/chat.post.ts
import { z } from 'zod';
import { defineFunction } from '@trayai/helix-sdk';

export const input = z.object({
  prompt: z.string().min(1),
});

export default defineFunction<typeof input>(async (ctx) => {
  const result = ctx.ai('anthropicKey').streamText({
    model: 'claude-sonnet-4-5',
    prompt: ctx.input.prompt,
  });

  return result.toUIMessageStreamResponse();
});
```

See [streaming](/documentation/guides/streaming/) for handling client disconnects and other production streaming behavior.

## Structured output

`generateObject` and `streamObject` validate the response against a Zod schema instead of returning free text:

```typescript
const result = await ctx.ai('anthropicKey').generateObject({
  model: 'claude-sonnet-4-5',
  schema: z.object({
    sentiment: z.enum(['positive', 'neutral', 'negative']),
    summary: z.string(),
  }),
  prompt: ctx.input.text,
});

result.object.sentiment; // typed as the enum above
```

## Embeddings

```typescript
const one = await ctx.ai('openaiKey').embed({
  model: 'text-embedding-3-small',
  value: 'How do I reset my password?',
});

const many = await ctx.ai('openaiKey').embedMany({
  model: 'text-embedding-3-small',
  values: ['First document', 'Second document', 'Third document'],
});
```

## Images, transcription, and speech

```typescript
const image = await ctx.ai('openaiKey').generateImage({
  model: 'gpt-image-1',
  prompt: 'A serene mountain landscape at sunset',
});

const transcript = await ctx.ai('openaiKey').transcribe({
  model: 'whisper-1',
  audio: audioBuffer,
});

const speech = await ctx.ai('openaiKey').generateSpeech({
  model: 'tts-1',
  text: 'Your order has shipped.',
});
```

## Cost and request IDs

`generateText`, `generateObject`, and `generateImage` resolve with a `result.helix` field alongside the AI SDK's own result fields:

| Field | Type | Description |
|---|---|---|
| `result.helix.cost.amount` | `number` | Estimated cost of the request, in USD |
| `result.helix.cost.currency` | `'USD'` | Always `'USD'` today |
| `result.helix.requestId` | `string` | An ID for correlating this call in your own logs |

Cost is estimated from the provider's reported token usage against public list prices; an unrecognized model estimates to `0`. Most image models bill per image and report no token usage at all, so `generateImage`'s cost estimates to `0` for them regardless of whether the model is recognized. Token-billed image models get a real estimate.

## Using a model instance directly

`ctx.ai(alias)` also exposes `languageModel`, `embeddingModel`, `imageModel`, `transcriptionModel`, and `speechModel`, which return a model instance instead of calling it immediately. Reach for these when you're handing a model to a custom agent loop or a third-party helper that expects an AI SDK model instance:

```typescript
const model = ctx.ai('anthropicKey').languageModel('claude-sonnet-4-5');

const result = await ctx.ai.generateText({ model, prompt: ctx.input.prompt });
```

The top-level `ctx.ai.generateText`, `ctx.ai.streamText`, and the other `ctx.ai.*` functions only accept a model instance obtained from `ctx.ai(alias)`. Passing a string throws `SdkError.aiModelInstanceRequired`, and passing a model instance built directly against a raw AI SDK provider (bypassing `ctx.ai`) throws `SdkError.aiForeignModelRejected`, since that instance would carry its own credentials and skip cost tracking.

AI SDK helpers and types (`tool`, `dynamicTool`, `jsonSchema`, `zodSchema`, `stepCountIs`, `hasToolCall`, `convertToModelMessages`, and the message, stream, and result types) are re-exported from `@trayai/helix-sdk`, so your project needs no separate `ai` dependency:

```typescript
import { tool, stepCountIs } from '@trayai/helix-sdk';
```

The `ai` package's own call functions (`generateText`, `streamText`, and so on) are not re-exported. Always call through `ctx.ai` or `ctx.ai(alias)`.

## Supported providers

| Service | Notes |
|---|---|
| `openai` | |
| `anthropic` | |
| `google` | |
| `mistral` | |
| `cohere` | |
| `openai-compatible` | Self-hosted endpoints (vLLM, Ollama, and similar). Use the object form: `{ service: 'openai-compatible', baseUrl: 'https://...' }`. `baseUrl` must be `https:`, checked when your config loads. |

## Errors

| Error | When it happens |
|---|---|
| `SdkError.aiProviderNotConfigured` | The alias has no matching `aiProviders` entry |
| `SdkError.aiUnsupportedService` | The alias resolves to a service `ctx.ai` doesn't support |
| `SdkError.aiModelIdRequired` | `ctx.ai(alias).<method>` was called without a string `model` |
| `SdkError.aiModelInstanceRequired` | A top-level `ctx.ai.*` function was called with a string instead of a model instance |
| `SdkError.aiForeignModelRejected` | A top-level `ctx.ai.*` function was called with a model instance built outside `ctx.ai(alias)` |

## Authentication

Like `ctx.http.authed` and `ctx.kv()`, `ctx.ai` requires you to be logged in during local development. Run `helix login` once, then `helix dev`; on the deployed platform, no token is needed.

:::roadmap{title="No spend controls or usage reporting yet"}
There's no dashboard budget, model allow list, rate limit, or usage report for `ctx.ai` calls today. `result.helix.cost` is a client-side estimate for your own logging; it isn't a billing record.
:::

---

Canonical: https://helix.tray.ai/documentation/guides/ai/
Any link on this page is available as markdown by appending .md to its URL.
Full corpus: https://helix.tray.ai/documentation/llms-full.txt