Skip to main content

Overview

This quickstart will guide you through building a simple AI-powered application using FF. You’ll create an Effect.ts program that:
  1. Uses the AI SDK wrapper to generate text with typed callbacks
  2. Implements a tool with Effect.ts patterns
  3. 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

1

Create a new project

mkdir ff-quickstart
cd ff-quickstart
npm init -y
2

Install FF and dependencies

npm install ff-effect effect ai @ai-sdk/openai
3

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:
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:
.env
OPENAI_API_KEY=your-api-key-here
Update index.ts to load environment variables:
import 'dotenv/config'; // Add at the top
Install dotenv:
npm install dotenv

Step 4: Run Your Application

npx tsx index.ts
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

Tool Definition

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?