Skip to content

Commit df36774

Browse files
Add basic Effect example extracted from wrapper repo
1 parent 9795a98 commit df36774

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* Example: Using OpenRouter with @effect/ai and @effect/ai-openrouter
3+
*
4+
* This example demonstrates idiomatic Effect patterns for AI interactions:
5+
* - Effect.gen for generator-style effect composition
6+
* - Layer-based dependency injection
7+
* - Type-safe error handling with Effect
8+
* - Streaming responses with Effect streams
9+
*/
10+
11+
import * as Chat from "@effect/ai/Chat"
12+
import * as LanguageModel from "@effect/ai/LanguageModel"
13+
import * as OpenRouterClient from "@effect/ai-openrouter/OpenRouterClient"
14+
import * as OpenRouterLanguageModel from "@effect/ai-openrouter/OpenRouterLanguageModel"
15+
import { FetchHttpClient } from "@effect/platform"
16+
import * as BunContext from "@effect/platform-bun/BunContext"
17+
import { Console, Effect, Layer, Redacted, Stream } from "effect"
18+
19+
/**
20+
* Main program using Effect.gen for composable effects
21+
*
22+
* Effect.gen is the idiomatic way to write Effect code - it provides
23+
* a generator-based syntax similar to async/await but with full
24+
* error handling and dependency injection capabilities
25+
*/
26+
const program = Effect.gen(function* () {
27+
// Log separator for readability
28+
yield* Console.log("\n=== Example 1: Simple Chat Completion ===\n")
29+
30+
// Generate text using the language model
31+
// The LanguageModel service is injected via the Effect context
32+
const response = yield* LanguageModel.generateText({
33+
prompt: "Explain what Effect is in functional programming in 2 sentences.",
34+
})
35+
36+
// Access the generated text from the response
37+
yield* Console.log("Response:", response.text)
38+
yield* Console.log("Finish reason:", response.finishReason)
39+
yield* Console.log("Usage:", response.usage)
40+
41+
// Example 2: Stateful conversation with Chat
42+
yield* Console.log("\n=== Example 2: Stateful Chat Conversation ===\n")
43+
44+
// Chat.empty creates a new chat session with empty history
45+
// Chat maintains conversation context across multiple turns
46+
const chat = yield* Chat.empty
47+
48+
// First turn - the model responds to our greeting
49+
const greeting = yield* chat.generateText({
50+
prompt: "Hi! I'm learning about Effect.",
51+
})
52+
yield* Console.log("Assistant:", greeting.text)
53+
54+
// Second turn - the model has context from the previous message
55+
// This demonstrates how Chat maintains conversation state
56+
const followUp = yield* chat.generateText({
57+
prompt: "What are the main benefits?",
58+
})
59+
yield* Console.log("Assistant:", followUp.text)
60+
61+
// Example 3: Streaming responses
62+
yield* Console.log("\n=== Example 3: Streaming Text Generation ===\n")
63+
64+
yield* Console.log("Streaming response:")
65+
66+
// streamText returns a Stream of response parts
67+
// Streams in Effect are lazy and composable
68+
// Stream.runForEach processes each part as it arrives
69+
yield* LanguageModel.streamText({
70+
prompt: "Count from 1 to 5, explaining each number briefly.",
71+
}).pipe(
72+
Stream.runForEach((part) => {
73+
// Only print text deltas to show streaming effect
74+
if (part.type === "text-delta") {
75+
// TODO: print without newlines
76+
return Console.log(part.delta)
77+
}
78+
// Log other part types for demonstration
79+
return Console.log(`[${part.type}]`)
80+
})
81+
)
82+
83+
yield* Console.log("\n=== All examples completed ===")
84+
})
85+
86+
/**
87+
* Layer composition for dependency injection
88+
*
89+
* Effect uses Layers to construct the dependency graph.
90+
* Layers are composable and type-safe, ensuring all dependencies
91+
* are satisfied at compile time.
92+
*/
93+
94+
// Create the OpenRouter HTTP client layer with API key
95+
// Redacted.make ensures the API key is never accidentally logged
96+
const OpenRouterClientLayer = OpenRouterClient.layer({
97+
apiKey: Redacted.make(process.env.OPENROUTER_API_KEY!),
98+
}).pipe(
99+
// Provide the Fetch HTTP client implementation
100+
// Layer.provide composes layers, satisfying dependencies
101+
Layer.provide(FetchHttpClient.layer)
102+
)
103+
104+
// Create the language model layer using OpenRouter
105+
// This uses the "openai/gpt-4o-mini" model via OpenRouter
106+
const OpenRouterModelLayer = OpenRouterLanguageModel.layer({
107+
model: "openai/gpt-4o-mini",
108+
config: {
109+
// Optional: configure model parameters
110+
temperature: 0.7,
111+
max_tokens: 500,
112+
},
113+
}).pipe(
114+
// The model layer depends on the OpenRouter client
115+
Layer.provide(OpenRouterClientLayer)
116+
)
117+
118+
/**
119+
* Run the program with dependency injection
120+
*
121+
* Effect.provide supplies all required dependencies (layers) to the program.
122+
* The layers are constructed once and shared across the entire program.
123+
*
124+
* Effect.runPromise executes the Effect and returns a Promise.
125+
* In production, you'd typically use Effect.runFork or other runners
126+
* for better resource management.
127+
*/
128+
await program.pipe(
129+
// Provide the language model layer (includes all dependencies)
130+
Effect.provide(OpenRouterModelLayer),
131+
// Provide the Bun runtime context for platform services
132+
Effect.provide(BunContext.layer),
133+
// Run the effect - returns a Promise<void>
134+
Effect.runPromise
135+
)
136+
137+
console.log("\n✓ Program completed successfully")

0 commit comments

Comments
 (0)