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:
ff-effect AI SDK, Drizzle, Inngest, and oRPC wrappers
ff-ai Conversation management and persistence
ff-serv HTTP services, logging, and caching
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?