= []
133 |
134 | // Create the initial generation and add it to memory
135 | const generation = yield* claude.provide(generate(task))
136 | memory.push(generation.solution)
137 |
138 | // Loop until we find a solution (can setup a maximum number of generations, if desired)
139 | while (true) {
140 | // Evaluate the solution
141 | const evaluation = yield* gpt4o.provide(evaluate(task, generation.solution))
142 |
143 | if (evaluation.status === "PASS") {
144 | yield* Console.log("=== FINAL SOLUTION ===")
145 | yield* Console.log(`${generation.solution}`)
146 | yield* Console.log("=== FINAL SOLUTION ===\n")
147 | break
148 | }
149 |
150 | // Provide previous solutions and feedback to the next generation attempt
151 | const previousAttempts = memory.map((solution) => `- ${solution}\n`)
152 | const context = String.stripMargin(
153 | `|Previous Attempts:
154 | |${previousAttempts}
155 | |
156 | |Feedback:
157 | |${evaluation.feedback}`
158 | )
159 |
160 | // Perform the next generation with the added context
161 | const nextGeneration = yield* claude.provide(generate(task, context))
162 | memory.push(nextGeneration.solution)
163 | }
164 | })
165 |
166 | const Anthropic = AnthropicClient.layerConfig({
167 | apiKey: Config.redacted("ANTHROPIC_API_KEY")
168 | }).pipe(Layer.provide(NodeHttpClient.layerUndici))
169 |
170 | const OpenAi = OpenAiClient.layerConfig({
171 | apiKey: Config.redacted("OPENAI_API_KEY")
172 | }).pipe(Layer.provide(NodeHttpClient.layerUndici))
173 |
174 | program.pipe(
175 | Effect.provide([Anthropic, OpenAi]),
176 | Effect.runPromise
177 | )
178 |
--------------------------------------------------------------------------------
/slides.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Keynote | Effect Days 2025
3 | favicon: /favicon.png
4 | theme: default
5 | fonts:
6 | sans: Inter
7 | serif: Inter
8 | mono: JetBrains Mono
9 | colorSchema: dark
10 | class: cursor-default
11 | ---
12 |
13 | # Building Effect-ive Agents
14 |
15 | Exploring agentic systems with Effect
16 |
17 |
27 |
28 | ---
29 | layout: about-me
30 | ---
31 |
32 |
40 |
41 | ---
42 | layout: center
43 | ---
44 |
45 | # What We'll Cover Today
46 |
47 |
48 |
49 | - Why use Effect to build agentic systems?
50 | - What makes building an agentic system complex?
51 | - What even is an agentic system?
52 | - How do we use Effect to build these systems?
53 |
54 |
55 |
56 |
72 |
73 | ---
74 | layout: center
75 | ---
76 |
77 | # Why Use Effect?
78 |
79 |
80 |

85 |
86 |
87 |
97 |
98 | ---
99 | layout: statement
100 | ---
101 |
102 |
103 | Developing any production-grade system is hard!
104 |
105 |
106 |
130 |
131 | ---
132 | layout: fact
133 | ---
134 |
135 |
136 | Non-Determinism
137 |
138 |
139 |
151 |
152 | ---
153 | layout: center
154 | ---
155 |
156 |
157 |
158 | > In systems where non-determinism is the default, Effect is the antidote
159 |
160 | - Maxwell Brown - Effect Meetup 2024, San Francisco
161 |
162 |
163 |
164 |
179 |
180 | ---
181 | layout: center
182 | ---
183 |
184 |
185 |

190 |
191 |
192 |
213 |
214 | ---
215 | layout: center
216 | ---
217 |
218 |
219 |
220 |
221 |
222 | # What is an agentic system?
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 | ### Workflows
231 |
232 | - Execution is Pre-Determined
233 | - Minimal Dynamicism
234 |
235 |
236 |
237 |
238 |
239 | ### Agents
240 |
241 | - Execution is Self-Directed
242 | - Maximum Dynamicism
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
265 |
266 | ---
267 | layout: center
268 | ---
269 |
270 | # The Augmented LLM
271 |
272 |
273 |

278 |
279 |
280 |
281 | Image source: Anthropic. (2024). Building Effective Agents. https://www.anthropic.com/engineering/building-effective-agents
282 |
283 |
284 |
310 |
311 | ---
312 | layout: center
313 | ---
314 |
315 |
316 | LLM Interactions with Effect
317 |
318 |
319 |
320 |
321 | ````md magic-move {lines:false}
322 |
323 | ```sh
324 | pnpm add @effect/ai
325 | ```
326 |
327 | ```sh
328 | pnpm add @effect/ai
329 | pnpm add @effect/ai-anthropic
330 | pnpm add @effect/ai-openai
331 | ```
332 |
333 | ````
334 |
335 |
336 |
337 |
353 |
354 | ---
355 | layout: center
356 | ---
357 |
358 | # Describing LLM Interactions
359 |
360 | <<< @/src/examples/001_using-effect-ai-01.ts ts {|1,9|10-12|4-7|}
361 |
362 |
386 |
387 | ---
388 | layout: center
389 | ---
390 |
391 | # Using Provider Integrations
392 |
393 | ````md magic-move
394 |
395 | <<< @/src/examples/002_using-provider-integrations-01.ts ts {|1|4-9|17|18|11-15}
396 |
397 | <<< @/src/examples/002_using-provider-integrations-02.ts ts {17-20}
398 |
399 | <<< @/src/examples/002_using-provider-integrations-03.ts ts {|1,5,14,18|8-12}
400 |
401 | <<< @/src/examples/002_using-provider-integrations-04.ts ts {|7-8|9-12|16-21|23-}
402 |
403 | ````
404 |
405 |
420 |
421 |
459 |
460 | ---
461 | layout: center
462 | ---
463 |
464 | # Creating Clients for Providers
465 |
466 | ````md magic-move
467 |
468 | <<< @/src/examples/003_creating-provider-clients-01.ts ts {|10-12}
469 |
470 | <<< @/src/examples/003_creating-provider-clients-02.ts ts {10-12|5-9|2,14-18|20-}
471 |
472 | ````
473 |
474 |
494 |
495 | ---
496 | layout: center
497 | ---
498 |
499 | # Handling Failure Scenarios
500 |
501 |
502 |
503 |
504 |
505 |
510 |
511 |
512 |
513 |
514 |
515 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
536 |
537 | ---
538 | layout: center
539 | ---
540 |
541 | # Planning Provider Interactions
542 |
543 | ````md magic-move
544 |
545 | <<< @/src/examples/004_planning-interactions-01.ts ts {|5,7|9-}
546 |
547 | <<< @/src/examples/004_planning-interactions-02.ts ts {1|9|10-12|7-13}
548 |
549 | <<< @/src/examples/004_planning-interactions-03.ts ts {14-17|15|16|8-10,15|19-}
550 |
551 | ````
552 |
553 |
599 |
600 | ---
601 | layout: center
602 | ---
603 |
604 | # Provider-Specific Configuration
605 |
606 | ````md magic-move
607 |
608 | <<< @/src/examples/005_provider-specific-configuration-01.ts ts
609 |
610 | <<< @/src/examples/005_provider-specific-configuration-02.ts ts {2,11-14}
611 |
612 | <<< @/src/examples/005_provider-specific-configuration-03.ts ts {2,3,12-18}
613 |
614 | <<< @/src/examples/005_provider-specific-configuration-04.ts ts {6-8,19-22}
615 |
616 | ````
617 |
618 |
636 |
637 | ---
638 | layout: center
639 | ---
640 |
641 | # The Augmented LLM
642 |
643 |
644 |

649 |
650 |
651 |
652 | Image source: Anthropic. (2024). Building Effective Agents. https://www.anthropic.com/engineering/building-effective-agents
653 |
654 |
655 |
674 |
675 | ---
676 | layout: center
677 | ---
678 |
679 | # Defining a Tool Call
680 |
681 | ```ts twoslash
682 | import { AiToolkit, Completions } from "@effect/ai"
683 | import { OpenAiClient, OpenAiCompletions } from "@effect/ai-openai"
684 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "@effect/platform"
685 | import { NodeHttpClient } from "@effect/platform-node"
686 | import { Array, Config, Console, Effect, Layer, Schema } from "effect"
687 |
688 | class DadJoke extends Schema.Class("DadJoke")({
689 | id: Schema.String,
690 | joke: Schema.String
691 | }) {}
692 |
693 | class SearchResponse extends Schema.Class("SearchResponse")({
694 | results: Schema.Array(DadJoke)
695 | }) {}
696 |
697 | class ICanHazDadJoke extends Effect.Service()("ICanHazDadJoke", {
698 | dependencies: [NodeHttpClient.layerUndici],
699 | effect: Effect.gen(function*() {
700 | const httpClient = yield* HttpClient.HttpClient
701 | const httpClientOk = httpClient.pipe(
702 | HttpClient.filterStatusOk,
703 | HttpClient.mapRequest(HttpClientRequest.prependUrl("https://icanhazdadjoke.com"))
704 | )
705 |
706 | const search = Effect.fn("ICanHazDadJoke.search")(
707 | function*(params: typeof GetDadJoke.Type) {
708 | return yield* httpClientOk.get("/search", {
709 | acceptJson: true,
710 | urlParams: { ...params }
711 | }).pipe(
712 | Effect.flatMap(HttpClientResponse.schemaBodyJson(SearchResponse)),
713 | Effect.flatMap(({ results }) => Array.head(results)),
714 | Effect.map((joke) => joke.joke),
715 | Effect.scoped,
716 | Effect.orDie
717 | )
718 | }
719 | )
720 |
721 | return {
722 | search
723 | } as const
724 | })
725 | }) {}
726 | //---cut---
727 | // Create the tool request
728 | class GetDadJoke extends Schema.TaggedRequest()("GetDadJoke", {
729 | payload: {
730 | searchTerm: Schema.String.annotations({
731 | description: "The search term to use to find dad jokes"
732 | })
733 | },
734 | success: Schema.String,
735 | failure: Schema.Never
736 | }, {
737 | description: "Get a hilarious dad joke from the icanhazdadjoke API"
738 | }) {}
739 |
740 | // Add tool requests to an AiToolkit
741 | const DadJokeTools = AiToolkit.empty.add(GetDadJoke)
742 |
743 | // Implement the handlers for each tool
744 | const ToolsLayer = DadJokeTools.implement((handlers) =>
745 | Effect.gen(function*() {
746 | const icanhazdadjoke = yield* ICanHazDadJoke
747 | return handlers.handle("GetDadJoke", (params) => icanhazdadjoke.search(params))
748 | })
749 | )
750 | //---cut-after---
751 | .pipe(Layer.provide(ICanHazDadJoke.Default))
752 |
753 | // Use the toolkit
754 | const getDadJoke = Effect.gen(function*() {
755 | const completions = yield* Completions.Completions
756 | const tools = yield* DadJokeTools
757 | return yield* completions.toolkit({
758 | input: "Generate a hilarious dad joke",
759 | tools
760 | }).pipe(
761 | Effect.andThen((response) => response.value),
762 | Effect.andThen((value) => Console.log(value))
763 | )
764 | })
765 |
766 | const program = Effect.gen(function*() {
767 | const model = yield* OpenAiCompletions.model("gpt-4o")
768 | yield* model.provide(getDadJoke)
769 | })
770 |
771 | const OpenAiLayer = OpenAiClient.layerConfig({
772 | apiKey: Config.redacted("OPENAI_API_KEY")
773 | }).pipe(Layer.provide(NodeHttpClient.layerUndici))
774 |
775 | program.pipe(
776 | Effect.provide([OpenAiLayer, ToolsLayer]),
777 | Effect.runPromise
778 | )
779 | ```
780 |
813 |
814 | ---
815 | layout: center
816 | ---
817 |
818 | # Using Tools
819 |
820 | ```ts twoslash
821 | import { AiToolkit, Completions } from "@effect/ai"
822 | import { OpenAiClient, OpenAiCompletions } from "@effect/ai-openai"
823 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "@effect/platform"
824 | import { NodeHttpClient } from "@effect/platform-node"
825 | import { Array, Config, Console, Effect, Layer, Schema } from "effect"
826 |
827 | class DadJoke extends Schema.Class("DadJoke")({
828 | id: Schema.String,
829 | joke: Schema.String
830 | }) {}
831 |
832 | class SearchResponse extends Schema.Class("SearchResponse")({
833 | results: Schema.Array(DadJoke)
834 | }) {}
835 |
836 | class ICanHazDadJoke extends Effect.Service()("ICanHazDadJoke", {
837 | dependencies: [NodeHttpClient.layerUndici],
838 | effect: Effect.gen(function*() {
839 | const httpClient = yield* HttpClient.HttpClient
840 | const httpClientOk = httpClient.pipe(
841 | HttpClient.filterStatusOk,
842 | HttpClient.mapRequest(HttpClientRequest.prependUrl("https://icanhazdadjoke.com"))
843 | )
844 |
845 | const search = Effect.fn("ICanHazDadJoke.search")(
846 | function*(params: typeof GetDadJoke.Type) {
847 | return yield* httpClientOk.get("/search", {
848 | acceptJson: true,
849 | urlParams: { ...params }
850 | }).pipe(
851 | Effect.flatMap(HttpClientResponse.schemaBodyJson(SearchResponse)),
852 | Effect.flatMap(({ results }) => Array.head(results)),
853 | Effect.map((joke) => joke.joke),
854 | Effect.scoped,
855 | Effect.orDie
856 | )
857 | }
858 | )
859 |
860 | return {
861 | search
862 | } as const
863 | })
864 | }) {}
865 |
866 | // Create the tool request
867 | class GetDadJoke extends Schema.TaggedRequest()("GetDadJoke", {
868 | payload: {
869 | searchTerm: Schema.String.annotations({
870 | description: "The search term to use to find dad jokes"
871 | })
872 | },
873 | success: Schema.String,
874 | failure: Schema.Never
875 | }, {
876 | description: "Get a hilarious dad joke from the icanhazdadjoke API"
877 | }) {}
878 |
879 | // Add tool requests to an AiToolkit
880 | const DadJokeTools = AiToolkit.empty.add(GetDadJoke)
881 |
882 | // Implement the handlers for each tool request
883 | const ToolsLayer = DadJokeTools.implement((handlers) =>
884 | Effect.gen(function*() {
885 | const icanhazdadjoke = yield* ICanHazDadJoke
886 | return handlers.handle("GetDadJoke", (params) => icanhazdadjoke.search(params))
887 | })
888 | ).pipe(Layer.provide(ICanHazDadJoke.Default))
889 |
890 | // Use the toolkit
891 | //---cut---
892 | const getDadJoke = Effect.gen(function*() {
893 | const completions = yield* Completions.Completions
894 | const tools = yield* DadJokeTools
895 | return yield* completions.toolkit({
896 | input: "Generate a hilarious dad joke",
897 | tools
898 | }).pipe(
899 | Effect.andThen((response) => response.value),
900 | Effect.andThen((value) => Console.log(value))
901 | )
902 | })
903 | //---cut-after---
904 | const program = Effect.gen(function*() {
905 | const model = yield* OpenAiCompletions.model("gpt-4o")
906 | yield* model.provide(getDadJoke)
907 | })
908 |
909 | const OpenAiLayer = OpenAiClient.layerConfig({
910 | apiKey: Config.redacted("OPENAI_API_KEY")
911 | }).pipe(Layer.provide(NodeHttpClient.layerUndici))
912 |
913 | program.pipe(
914 | Effect.provide([OpenAiLayer, ToolsLayer]),
915 | Effect.runPromise
916 | )
917 | ```
918 |
919 |
937 |
938 | ---
939 | layout: center
940 | ---
941 |
942 | # Future Directions
943 |
944 |
945 |
946 | - Implementing Provider Services
947 | - Model Context Protocol
948 | - Un-Implemented Features
949 |
950 |
951 |
952 |
966 |
967 | ---
968 | layout: two-cols
969 | ---
970 |
971 |
972 |
Thank You!
973 |
974 | -
975 |
976 | @imax153
977 |
978 | -
979 |
980 | github.com/imax153
981 |
982 |
983 |
984 |
985 | ::right::
986 |
987 |
988 |
989 |

994 |
995 |
Scan to get the code for this presentation
996 |
997 |
998 |
--------------------------------------------------------------------------------