DEV Community

ZNY
ZNY

Posted on

TypeScript AI SDK: Building Type-Safe AI-Powered Applications

TypeScript's type system transforms AI API integration from guesswork into reliable, maintainable code. When you're building production AI applications, TypeScript catches errors at compile time, not runtime. Here's how to build type-safe AI integrations.

Why TypeScript for AI APIs?

  1. Catch errors early — Invalid API responses caught at compile time
  2. IDE autocompletion — Know exactly what the API returns
  3. Refactoring confidence — Change types and the compiler finds all affected code
  4. Documentation — Types serve as living documentation

Type-Safe API Client

`typescript
import { z } from 'zod';

// Define response schemas
const MessageSchema = z.object({
role: z.enum(['system', 'user', 'assistant']),
content: z.string()
});

const ChoiceSchema = z.object({
message: MessageSchema,
finish_reason: z.string(),
index: z.number()
});

const UsageSchema = z.object({
prompt_tokens: z.number(),
completion_tokens: z.number(),
total_tokens: z.number()
});

const ChatCompletionResponseSchema = z.object({
id: z.string(),
object: z.string(),
created: z.number(),
model: z.string(),
choices: z.array(ChoiceSchema),
usage: UsageSchema
});

type ChatMessage = z.infer;
type ChatResponse = z.infer;

// Type-safe client
class ClaudeClient {
constructor(
private readonly apiKey: string,
private readonly baseURL: string = 'https://api.ofox.ai/v1'
) {}

async chat(messages: ChatMessage[]): Promise {
const response = await fetch(${this.baseURL}/chat/completions, {
method: 'POST',
headers: {
'Authorization': Bearer ${this.apiKey},
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'claude-3-5-sonnet-20241022',
messages
})
});

if (!response.ok) {
throw new Error(API error: ${response.status});
}

const data = await response.json();

// Zod validates at runtime AND gives you typed result
return ChatCompletionResponseSchema.parse(data);
}
}
`

Streaming with Types

`typescript
async function* streamChat(
client: ClaudeClient,
messages: ChatMessage[]
): AsyncGenerator {
const response = await fetch(${client.baseURL}/chat/completions, {
method: 'POST',
headers: {
'Authorization': Bearer ${client.apiKey},
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'claude-3-5-sonnet-20241022',
messages,
stream: true
})
});

if (!response.body) throw new Error('No response body');

const decoder = new TextDecoder();

for await (const chunk of response.body) {
const lines = decoder.decode(chunk).split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
if (data.choices[0].delta.content) {
yield data.choices[0].delta.content;
}
}
}
}
}

// Usage with full type safety
for await (const token of streamChat(client, messages)) {
process.stdout.write(token);
}
`

Building a Type-Safe Agent Framework

`typescript
// Tool definitions with full type safety
const ToolSchema = z.object({
name: z.string(),
description: z.string(),
parameters: z.record(z.string(), z.unknown())
});

type Tool = z.infer;

interface ToolResult {
success: boolean;
data?: T;
error?: string;
}

class TypedAgent {
constructor(
private client: ClaudeClient,
private tools: Tool[]
) {}

async execute(systemPrompt: string, task: string): Promise {
const messages: ChatMessage[] = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: task }
];

while (true) {
const response = await this.client.chat(messages);
const content = response.choices[0].message.content;

// Check if agent wants to use a tool
const toolCall = this.parseToolCall(content);
if (toolCall) {
const result = await this.executeTool(toolCall);
messages.push({ role: 'assistant', content });
messages.push({
role: 'user',
content: Tool result: ${JSON.stringify(result)}
});
} else {
return content;
}
}
}

private parseToolCall(content: string): { name: string; args: unknown } | null {
// Parse tool call from response
const match = content.match(/([\s\S]*?)<\/toolcall>/);
if (!match) return null;
return JSON.parse(match[1]);
}

private async executeTool(call: { name: string; args: unknown }): Promise> {
const tool = this.tools.find(t => t.name === call.name);
if (!tool) return { success: false, error: Unknown tool: ${call.name} };

try {
// Tool execution would be implemented here
return { success: true, data: null };
} catch (e) {
return { success: false, error: (e as Error).message };
}
}
}
`

Validation with Zod

Zod validates API responses at runtime:

`typescript
import { z } from 'zod';

// Define the exact shape you expect
const APIResponse = z.object({
model: z.string(),
id: z.string(),
choices: z.array(z.object({
message: z.object({
role: z.string(),
content: z.string()
}),
finish_reason: z.string()
}))
});

async function safeChat(client: ClaudeClient, messages: ChatMessage[]) {
try {
const raw = await client.chatRaw(messages); // Returns unknown
const validated = APIResponse.parse(raw); // Throws if shape doesn't match
return validated;
} catch (error) {
if (error instanceof z.ZodError) {
console.error('API response validation failed:', error.issues);
throw new Error('Unexpected API response format');
}
throw error;
}
}
`

Getting Started

Build type-safe AI applications with TypeScript and ofox.ai — their OpenAI-compatible API integrates seamlessly with TypeScript's type system. Sign up and start building.

👉 Get started with ofox.ai

This article contains affiliate links.

Tags: typescript,nodejs,ai,programming,developer
Canonical URL: https://dev.to/zny10289

Top comments (0)