Overview
This quickstart will guide you through building a simple AI-powered application using FF. You’ll create an Effect.ts program that:
- Uses the AI SDK wrapper to generate text with typed callbacks
- Implements a tool with Effect.ts patterns
- Logs structured output with Effect’s logging
By the end, you’ll understand how FF brings type safety and composability to AI development.
Prerequisites
Before starting, ensure you have:
- Node.js 18+ or Bun 1.0+ installed
- An OpenAI API key (or another AI SDK-compatible provider)
This guide uses OpenAI, but FF works with any AI SDK provider (Anthropic, Google, etc.)
Step 1: Install Dependencies
Create a new project
mkdir ff-quickstart
cd ff-quickstart
npm init -y
Install FF and dependencies
npm install ff-effect effect ai @ai-sdk/openai
Set up TypeScript
npm install -D typescript tsx
npx tsc --init
Update your tsconfig.json:{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
Step 2: Create Your First Effect Program
Create a file called index.ts:
import { Effect } from 'effect';
import { generateText, tool } from 'ff-effect/for/ai';
import { runPromiseUnwrapped } from 'ff-effect';
import { openai } from '@ai-sdk/openai';
// Define a weather tool with Effect.ts
const getWeather = yield* tool({
description: 'Get the current weather in a given city',
inputSchema: {
type: 'object',
properties: {
city: { type: 'string', description: 'The city name' },
},
required: ['city'],
},
execute: (input) =>
Effect.gen(function* () {
// In a real app, this would call a weather API
yield* Effect.log(`Fetching weather for ${input.city}`);
return `The weather in ${input.city} is sunny and 72°F`;
}),
});
// Main program
const program = Effect.gen(function* () {
yield* Effect.log('Starting AI conversation...');
const result = yield* generateText({
model: openai('gpt-4-turbo'),
prompt: 'What is the weather like in San Francisco?',
tools: { getWeather },
maxSteps: 5,
// Typed callback that returns an Effect
onStepFinish: (step) =>
Effect.gen(function* () {
yield* Effect.log(
`Step ${step.stepType} completed. ` +
`Tool calls: ${step.toolCalls?.length ?? 0}`
);
}),
});
yield* Effect.log(`Final response: ${result.text}`);
return result;
});
// Run the program
await runPromiseUnwrapped(program);
Step 3: Add Environment Variables
Create a .env file:
OPENAI_API_KEY=your-api-key-here
Update index.ts to load environment variables:
import 'dotenv/config'; // Add at the top
Install dotenv:
Step 4: Run Your Application
You should see output like:
timestamp=... level=INFO message="Starting AI conversation..."
timestamp=... level=INFO message="Fetching weather for San Francisco"
timestamp=... level=INFO message="Step tool-result completed. Tool calls: 1"
timestamp=... level=INFO message="Final response: The weather in San Francisco is sunny and 72°F"
Understanding the Code
Let’s break down what makes this FF code special:
Effect.ts Integration
const program = Effect.gen(function* () {
yield* Effect.log('Starting...');
const result = yield* generateText({ ... });
return result;
});
FF wraps AI SDK functions as Effects, allowing you to compose them with other Effect operations like logging, error handling, and service access.
Typed Callbacks
onStepFinish: (step) =>
Effect.gen(function* () {
yield* Effect.log(`Step completed`);
}),
Unlike AI SDK’s Promise-based callbacks, FF callbacks return Effects. This gives you:
- Access to Effect services and context
- Structured error handling
- Type-safe composition with other Effects
- Automatic fiber management
const getWeather = yield* tool({
description: 'Get weather',
inputSchema: { ... },
execute: (input) =>
Effect.gen(function* () {
yield* Effect.log(`Fetching weather for ${input.city}`);
return `Weather data...`;
}),
});
Tools execute as Effects, enabling:
- Database queries with Effect services
- Error handling with typed errors
- Logging and telemetry
- Access to your application context
Next Steps
Add Database Operations
Combine FF’s AI wrappers with Drizzle ORM:
import { createDrizzle } from 'ff-effect/for/drizzle';
const { db, withTransaction } = createDrizzle(createClient);
const saveTool = yield* tool({
description: 'Save data to database',
inputSchema: { ... },
execute: (input) =>
Effect.gen(function* () {
// Access database within tool execution
yield* db((client) => client.insert(table).values(input));
return 'Saved successfully';
}),
});
See the ff-effect Drizzle guide for details.
Manage Conversations
Add persistent conversation history with ff-ai:
import { createTurnHandler } from 'ff-ai';
import { DrizzleConversationStore } from 'ff-ai/providers/drizzle';
const handler = yield* createTurnHandler({
identifier: {
resourceId: 'user-123',
threadId: 'conversation-456',
},
});
// Get conversation history
const history = yield* handler.getHistory({ windowSize: 10 });
// Save messages automatically
yield* handler.onStep(stepResult);
See the ff-ai guide for conversation management.
Build HTTP Services
Create HTTP endpoints that serve AI responses:
import { basicHandler, createFetchHandler } from 'ff-serv';
const aiHandler = basicHandler('/api/chat', (request) =>
Effect.gen(function* () {
const body = yield* Effect.promise(() => request.json());
const result = yield* generateText({
model: openai('gpt-4-turbo'),
prompt: body.message,
tools: { getWeather },
});
return Response.json({ text: result.text });
})
);
const server = yield* createFetchHandler([aiHandler]);
See the ff-serv guide for HTTP utilities.
Learn More
Explore the full capabilities of each package:
Common Patterns
Error Handling
import { AiError } from 'ff-effect/for/ai';
const program = Effect.gen(function* () {
const result = yield* generateText({ ... }).pipe(
Effect.catchTag('AiError', (error) =>
Effect.gen(function* () {
yield* Effect.logError(`AI request failed: ${error.message}`);
return { text: 'Failed to generate response' };
})
)
);
return result;
});
Service Integration
class WeatherService extends Effect.Service<WeatherService>()(
'WeatherService',
{
succeed: {
getWeather: (city: string) => `Weather in ${city}: sunny`,
},
}
) {}
const getWeather = yield* tool({
description: 'Get weather',
inputSchema: { ... },
execute: (input) =>
Effect.gen(function* () {
const weather = yield* WeatherService;
return weather.getWeather(input.city);
}),
});
// Provide service when running
Effect.provide(program, WeatherService.Default);
Concurrent Operations
const [weather, news] = yield* Effect.all([
generateText({ prompt: 'Weather forecast' }),
generateText({ prompt: 'Latest news' }),
], { concurrency: 2 });
Get Help
Need assistance?