···11-\{ "OpenAI API key" } ->
11+\{ "Letta API key" } ->
2233-let key = .'OpenAI API key'
33+let key = .'Letta API key'
4455let question = prompt
66 { key
+2-2
examples/code.ffg
···11-\{ "OpenAI API key" } ->
11+\{ "Letta API key" } ->
2233-let key = .'OpenAI API key'
33+let key = .'Letta API key'
4455in import prompt{ key, text: "Uppercase the input" } "abc" : Text
+2-2
examples/emotion-wheel.ffg
···11# Ask ChatGPT how it feels using this emotion wheel:
22#
33# https://www.betterup.com/hs-fs/hubfs/Emotion-Wheel-I.jpg?width=900&height=915&name=Emotion-Wheel-I.jpg
44-\{ "OpenAI API key" } ->
44+\{ "Letta API key" } ->
5566-let key = .'OpenAI API key'
66+let key = .'Letta API key'
7788in fold
99 { "Uncomfortable emotions" { }: fold
+1-1
examples/history.ffg
···11\arguments ->
2233-let key = arguments."OpenAI API key" in
33+let key = arguments."Letta API key" in
4455prompt
66 { key
+2-2
examples/poem.ffg
···11-\{ "OpenAI API key" } ->
11+\{ "Letta API key" } ->
2233-let key = .'OpenAI API key'
33+let key = .'Letta API key'
4455let concatSep = import github
66 { owner: "Gabriella439"
+2-2
examples/prompt.ffg
···11-\{ "OpenAI API key" } ->
11+\{ "Letta API key" } ->
2233-let key = .'OpenAI API key'
33+let key = .'Letta API key'
4455let { x, y } = prompt{ key, text: "Give me two numbers" }
66
+2-2
examples/tools.ffg
···11-\{ "OpenAI API key" } ->
11+\{ "Letta API key" } ->
2233-let key = .'OpenAI API key'
33+let key = .'Letta API key'
4455let concatSep = import github
66 { owner: "Gabriella439"
+2-2
examples/transform-text.ffg
···11-\{ "OpenAI API key", passage, code } ->
11+\{ "Letta API key", passage, code } ->
2233prompt
44- { key: .'OpenAI API key'
44+ { key: .'Letta API key'
55 , text: "
66 Take this passage:
77
+1-1
examples/tutorial/coding.ffg
···11\arguments ->
2233-let key = arguments."OpenAI API key" in
33+let key = arguments."Letta API key" in
4455# What do you think this code will do? Run it to test your guess:
66import prompt{ key }
+5-7
examples/tutorial/prompting.ffg
···11# Grace provides built-in language support for LLMs using the `prompt` function.
22-# To run these examples you will need to provide an OpenAI API key below and.
33-# and then click "Submit".
22+# To run these examples you will need to provide a Letta API key below, then click "Submit".
43\arguments ->
5466-let key = arguments."OpenAI API key" in
55+let key = arguments."Letta API key" in
7688-{ # You can prompt a model with `Text`, which will (by default) return `Text`:
77+{ # You can prompt a Letta agent with `Text`, which returns `Text`:
98 names: prompt{ key, text: "Give me a list of names" }
1091110, # You can request structured output with a type annotation, like this:
···2019, # In fact, that type is descriptive enough that we can just omit the prompt:
2120 tacitFullNames: prompt{ key } : List { firstName: Text, lastName: Text }
22212323-, # By default the `prompt` keyword selects the `gpt-5-mini` model, but you can
2424- # specify other models using the `model` argument:
2222+, # The `model` argument selects which Letta agent to call, so pass the agent id there:
2523 differentModel:
2626- prompt{ key, model: "gpt-5-nano" } : List { firstName: Text, lastName: Text }
2424+ prompt{ key, model: "agent-YOUR_AGENT_ID" } : List { firstName: Text, lastName: Text }
2725}
28262927# Try switching to the "Code" tab below to view the code for the result, then
+383-35
ghc/Grace/HTTP.hs
···1414 , Grace.HTTP.createChatCompletion
1515 ) where
16161717+import Control.Applicative ((<|>))
1818+import Control.Concurrent (threadDelay)
1719import Control.Concurrent.MVar (MVar)
1818-import Control.Exception.Safe (Exception(..), Handler(..))
2020+import Control.Exception.Safe (Exception(..))
2121+import Data.Map.Strict (Map)
2222+import Data.Maybe (fromMaybe)
1923import Data.Text (Text)
2424+import Numeric.Natural (Natural)
2025import Data.Text.Encoding.Error (UnicodeException)
2121-import OpenAI.V1 (Methods(..))
2222-import OpenAI.V1.Chat.Completions (ChatCompletionObject, CreateChatCompletion)
2323-import Servant.Client.Core.ClientError (ClientError(..))
2424-import Servant.Client.Core.Response (ResponseF(..))
2626+import Data.Time.Clock.POSIX (POSIXTime)
2727+import qualified Data.ByteString as ByteString
2828+import OpenAI.V1.Chat.Completions
2929+ ( ChatCompletionObject(..)
3030+ , Choice(..)
3131+ , Content(..)
3232+ , CreateChatCompletion(..)
3333+ , Message(..)
3434+ , LogProbs
3535+ , ServiceTier
3636+ , _CreateChatCompletion
3737+ )
3838+import OpenAI.V1.Models (Model)
3939+import OpenAI.V1.Usage
4040+ ( CompletionTokensDetails(..)
4141+ , PromptTokensDetails(..)
4242+ , Usage(..)
4343+ )
25442645import Grace.HTTP.Type
2746 ( Header(..)
···42614362import qualified Control.Concurrent.MVar as MVar
4463import qualified Control.Exception.Safe as Exception
4545-import qualified Control.Retry as Retry
4664import qualified Data.Aeson as Aeson
6565+import qualified Data.Map.Strict as Map
4766import qualified Data.Text as Text
4867import qualified Data.Text.Encoding as Encoding
4968import qualified Data.Text.Lazy as Text.Lazy
5069import qualified Data.Text.Lazy.Encoding as Lazy.Encoding
7070+import qualified Data.Vector as Vector
5171import qualified Network.HTTP.Types.Status as Status
5272import qualified Network.HTTP.Client as HTTP
5373import qualified Network.HTTP.Client.TLS as TLS
5474import qualified Network.HTTP.Types as HTTP.Types
5555-import qualified OpenAI.V1 as OpenAI
5656-import qualified Servant.Client as Client
7575+import qualified OpenAI.V1.Chat.Completions.Stream as Stream
5776import qualified System.IO.Unsafe as Unsafe
58775978-- | Exception type thrown by `fetch` in the event of any failure
···7089{-# NOINLINE managerMVar #-}
71907291retry :: IO a -> IO a
7373-retry io =
7474- Retry.recovering
7575- retryPolicy
7676- [ \_ -> Handler handler
7777- ]
7878- (\_ -> io)
9292+retry io = go 0 io
7993 where
8080- retryPolicy = Retry.fullJitterBackoff 1000000 <> Retry.limitRetries 3
9494+ go :: Int -> IO b -> IO b
9595+ go attempt action =
9696+ Exception.catch action (handler attempt action)
81978282- handler (FailureResponse _ Response{ responseStatusCode }) =
8383- return (Status.statusIsServerError responseStatusCode)
8484- handler (ConnectionError _) =
8585- return True
8686- handler _ =
8787- return False
9898+ handler :: Int -> IO b -> HTTP.HttpException -> IO b
9999+ handler attempt action exception =
100100+ if attempt < 3 then do
101101+ threadDelay 1000000
102102+ go (attempt + 1) action
103103+ else
104104+ Exception.throwIO exception
8810589106-- | Acquire a new `Manager`
90107--
···96113 Nothing -> do
97114 TLS.newTlsManagerWith TLS.tlsManagerSettings
98115 { managerResponseTimeout = HTTP.responseTimeoutNone
9999- , managerRetryableException = \exception ->
100100- case Exception.fromException exception of
101101- Just (FailureResponse _ Response{ responseStatusCode }) ->
102102- Status.statusIsServerError responseStatusCode
103103- Just (ConnectionError _) ->
104104- True
105105- _ ->
106106- False
116116+ , managerRetryableException = \_ -> True
107117 }
108118109119 Just manager -> do
···259269 \" <> Text.pack (displayException unicodeException)
260270261271-- | Initialize API for prompting
272272+type Methods = Text
273273+262274getMethods :: IO (Text -> Methods)
263263-getMethods = do
264264- baseUrl <- Client.parseBaseUrl "https://api.openai.com"
275275+getMethods =
276276+ return (\key -> key)
277277+278278+data StreamState = StreamState
279279+ { streamStateId :: Maybe Text
280280+ , streamStateCreated :: Maybe POSIXTime
281281+ , streamStateModel :: Maybe Model
282282+ , streamStateServiceTier :: Maybe ServiceTier
283283+ , streamStateSystemFingerprint :: Maybe Text
284284+ , streamStateUsage :: Maybe (Usage CompletionTokensDetails PromptTokensDetails)
285285+ , streamStateChoices :: Map Natural ChoiceState
286286+ }
287287+288288+data ChoiceState = ChoiceState
289289+ { choiceStateContent :: Text
290290+ , choiceStateRefusal :: Text
291291+ , choiceStateFinishReason :: Maybe Text
292292+ , choiceStateLogProbs :: Maybe LogProbs
293293+ }
294294+295295+emptyStreamState :: StreamState
296296+emptyStreamState = StreamState
297297+ { streamStateId = Nothing
298298+ , streamStateCreated = Nothing
299299+ , streamStateModel = Nothing
300300+ , streamStateServiceTier = Nothing
301301+ , streamStateSystemFingerprint = Nothing
302302+ , streamStateUsage = Nothing
303303+ , streamStateChoices = Map.empty
304304+ }
305305+306306+zeroUsage :: Usage CompletionTokensDetails PromptTokensDetails
307307+zeroUsage = Usage
308308+ { completion_tokens = 0
309309+ , prompt_tokens = 0
310310+ , total_tokens = 0
311311+ , completion_tokens_details = Nothing
312312+ , prompt_tokens_details = Nothing
313313+ }
265314266266- manager <- newManager
315315+stepStreamState :: StreamState -> Stream.ChatCompletionChunk -> StreamState
316316+stepStreamState state Stream.ChatCompletionChunk
317317+ { id
318318+ , choices
319319+ , created
320320+ , model
321321+ , service_tier
322322+ , system_fingerprint
323323+ , object = _
324324+ , usage
325325+ } =
326326+ state
327327+ { streamStateId = Just id
328328+ , streamStateCreated = Just created
329329+ , streamStateModel = Just model
330330+ , streamStateServiceTier = service_tier <|> streamStateServiceTier state
331331+ , streamStateSystemFingerprint = system_fingerprint <|> streamStateSystemFingerprint state
332332+ , streamStateUsage = usage <|> streamStateUsage state
333333+ , streamStateChoices = Vector.foldl' stepChoiceState (streamStateChoices state) choices
334334+ }
335335+ where
336336+ stepChoiceState :: Map Natural ChoiceState -> Stream.ChunkChoice -> Map Natural ChoiceState
337337+ stepChoiceState choiceStates Stream.ChunkChoice
338338+ { delta = Stream.Delta
339339+ { delta_content
340340+ , delta_refusal
341341+ , delta_role = _
342342+ , delta_tool_calls = _
343343+ }
344344+ , finish_reason
345345+ , index
346346+ , logprobs
347347+ } =
348348+ Map.insertWith combine index chunkState choiceStates
349349+ where
350350+ chunkState :: ChoiceState
351351+ chunkState = ChoiceState
352352+ { choiceStateContent = fromMaybe "" delta_content
353353+ , choiceStateRefusal = fromMaybe "" delta_refusal
354354+ , choiceStateFinishReason = finish_reason
355355+ , choiceStateLogProbs = logprobs
356356+ }
267357268268- let clientEnv = Client.mkClientEnv manager baseUrl
358358+ combine :: ChoiceState -> ChoiceState -> ChoiceState
359359+ combine newer older = ChoiceState
360360+ { choiceStateContent = choiceStateContent older <> choiceStateContent newer
361361+ , choiceStateRefusal = choiceStateRefusal older <> choiceStateRefusal newer
362362+ , choiceStateFinishReason = choiceStateFinishReason newer <|> choiceStateFinishReason older
363363+ , choiceStateLogProbs = choiceStateLogProbs newer <|> choiceStateLogProbs older
364364+ }
269365270270- return (\key -> OpenAI.makeMethods clientEnv key organization Nothing)
366366+buildChatCompletion
367367+ :: CreateChatCompletion
368368+ -> StreamState
369369+ -> IO ChatCompletionObject
370370+buildChatCompletion request@CreateChatCompletion
371371+ { reasoning_effort = requestReasoningEffort
372372+ } StreamState
373373+ { streamStateId
374374+ , streamStateCreated
375375+ , streamStateModel
376376+ , streamStateServiceTier
377377+ , streamStateSystemFingerprint
378378+ , streamStateUsage
379379+ , streamStateChoices
380380+ } = do
381381+ id <- require "The chat completion stream did not provide an id."
382382+ streamStateId
383383+384384+ created <- require "The chat completion stream did not provide a created timestamp."
385385+ streamStateCreated
386386+387387+ model <- require "The chat completion stream did not provide a model."
388388+ streamStateModel
389389+390390+ let buildChoice (index, ChoiceState
391391+ { choiceStateContent
392392+ , choiceStateRefusal
393393+ , choiceStateFinishReason
394394+ , choiceStateLogProbs
395395+ }) =
396396+ Choice
397397+ { finish_reason = fromMaybe "stop" choiceStateFinishReason
398398+ , index
399399+ , message = Assistant
400400+ { assistant_content =
401401+ if Text.null choiceStateContent
402402+ then Nothing
403403+ else Just choiceStateContent
404404+ , refusal =
405405+ if Text.null choiceStateRefusal
406406+ then Nothing
407407+ else Just choiceStateRefusal
408408+ , name = Nothing
409409+ , assistant_audio = Nothing
410410+ , tool_calls = Nothing
411411+ }
412412+ , logprobs = choiceStateLogProbs
413413+ }
414414+415415+ return ChatCompletionObject
416416+ { id
417417+ , choices = Vector.fromList (fmap buildChoice (Map.toAscList streamStateChoices))
418418+ , created
419419+ , model
420420+ , reasoning_effort = requestReasoningEffort
421421+ , service_tier = streamStateServiceTier
422422+ , system_fingerprint = streamStateSystemFingerprint
423423+ , object = "chat.completion"
424424+ , usage = fromMaybe zeroUsage streamStateUsage
425425+ }
426426+ where
427427+ require :: String -> Maybe a -> IO a
428428+ require message = maybe (Exception.throwString message) pure
271429272430-- | This powers the @prompt@ keyword
273431createChatCompletion
274432 :: Methods
275433 -> CreateChatCompletion
276434 -> IO ChatCompletionObject
277277-createChatCompletion Methods{ createChatCompletion = c } x = retry (c x)
435435+createChatCompletion key request =
436436+ retry do
437437+ manager <- newManager
438438+439439+ request₀ <- HTTP.parseUrlThrow "https://api.letta.com/v1/chat/completions"
440440+441441+ let body = RequestBodyLBS (Aeson.encode request{ stream = Just True })
442442+443443+ let organizationHeader = case organization of
444444+ Nothing -> [ ]
445445+ Just o ->
446446+ [ ( "OpenAI-Organization"
447447+ , Encoding.encodeUtf8 o
448448+ )
449449+ ]
450450+451451+ let request₁ = request₀
452452+ { method = HTTP.Types.methodPost
453453+ , requestHeaders =
454454+ [ ( "Content-Type"
455455+ , "application/json"
456456+ )
457457+ , ( "Authorization"
458458+ , "Bearer " <> Encoding.encodeUtf8 key
459459+ )
460460+ ] <> organizationHeader
461461+ , requestBody = body
462462+ }
463463+464464+ let handler :: HTTP.HttpException -> IO a
465465+ handler httpException = Exception.throwIO (HttpException httpException)
466466+467467+ Exception.handle handler do
468468+ HTTP.withResponse request₁ manager $ \response -> do
469469+ if not (Status.statusIsSuccessful (HTTP.responseStatus response)) then do
470470+ body' <- readBody (HTTP.responseBody response)
471471+472472+ Exception.throwIO
473473+ ( HttpException
474474+ ( HTTP.HttpExceptionRequest
475475+ request₁
476476+ (StatusCodeException (fmap (const ()) response) body')
477477+ )
478478+ )
479479+ else do
480480+ finalState <- readStream (HTTP.responseBody response) emptyStreamParseState
481481+ buildChatCompletion request (streamParseStreamState finalState)
482482+483483+readBody :: HTTP.BodyReader -> IO ByteString.ByteString
484484+readBody bodyReader = go [ ]
485485+ where
486486+ go chunks = do
487487+ chunk <- HTTP.brRead bodyReader
488488+489489+ if ByteString.null chunk then
490490+ return (ByteString.concat (reverse chunks))
491491+ else
492492+ go (chunk : chunks)
493493+494494+data StreamParseState = StreamParseState
495495+ { streamParseBuffer :: ByteString.ByteString
496496+ , streamParseDataLines :: [ByteString.ByteString]
497497+ , streamParseDone :: Bool
498498+ , streamParseStreamState :: StreamState
499499+ }
500500+501501+emptyStreamParseState :: StreamParseState
502502+emptyStreamParseState = StreamParseState
503503+ { streamParseBuffer = ByteString.empty
504504+ , streamParseDataLines = [ ]
505505+ , streamParseDone = False
506506+ , streamParseStreamState = emptyStreamState
507507+ }
508508+509509+readStream :: HTTP.BodyReader -> StreamParseState -> IO StreamParseState
510510+readStream bodyReader state =
511511+ if streamParseDone state then
512512+ return state
513513+ else do
514514+ chunk <- HTTP.brRead bodyReader
515515+516516+ if ByteString.null chunk then
517517+ flushStreamParseState state
518518+ else do
519519+ state' <- processStreamChunk state chunk
520520+521521+ if streamParseDone state' then
522522+ return state'
523523+ else
524524+ readStream bodyReader state'
525525+526526+processStreamChunk :: StreamParseState -> ByteString.ByteString -> IO StreamParseState
527527+processStreamChunk state chunk =
528528+ consumeBufferedLines state
529529+ { streamParseBuffer = streamParseBuffer state <> chunk
530530+ }
531531+ where
532532+ consumeBufferedLines currentState =
533533+ case ByteString.break (== 10) (streamParseBuffer currentState) of
534534+ (line, rest)
535535+ | ByteString.null rest ->
536536+ return currentState
537537+ { streamParseBuffer = line
538538+ }
539539+ | otherwise -> do
540540+ let nextState = currentState
541541+ { streamParseBuffer = ByteString.drop 1 rest
542542+ }
543543+544544+ nextState' <- processStreamLine nextState line
545545+546546+ if streamParseDone nextState' then
547547+ return nextState'
548548+ else
549549+ consumeBufferedLines nextState'
550550+551551+processStreamLine :: StreamParseState -> ByteString.ByteString -> IO StreamParseState
552552+processStreamLine state rawLine =
553553+ case stripCR rawLine of
554554+ line
555555+ | ByteString.null line ->
556556+ emitStreamEvent state
557557+ | ByteString.isPrefixOf dataPrefix line ->
558558+ let payload = ByteString.dropWhile (== 32) (ByteString.drop (ByteString.length dataPrefix) line)
559559+ in return state
560560+ { streamParseDataLines = payload : streamParseDataLines state
561561+ }
562562+ | otherwise ->
563563+ return state
564564+ where
565565+ dataPrefix = Encoding.encodeUtf8 "data:"
566566+567567+stripCR :: ByteString.ByteString -> ByteString.ByteString
568568+stripCR line
569569+ | ByteString.null line = line
570570+ | ByteString.last line == 13 = ByteString.init line
571571+ | otherwise = line
572572+573573+emitStreamEvent :: StreamParseState -> IO StreamParseState
574574+emitStreamEvent state =
575575+ case streamParseDataLines state of
576576+ [ ] ->
577577+ return state
578578+ { streamParseDataLines = [ ]
579579+ }
580580+ dataLines -> do
581581+ let payloadBytes = ByteString.intercalate (ByteString.singleton 10) (reverse dataLines)
582582+583583+ payloadText <- case Encoding.decodeUtf8' payloadBytes of
584584+ Left exception -> Exception.throwIO (NotUTF8 exception)
585585+ Right text -> return text
586586+587587+ if Text.strip payloadText == "[DONE]" then
588588+ return state
589589+ { streamParseDataLines = [ ]
590590+ , streamParseDone = True
591591+ }
592592+ else do
593593+ chunk <- case Aeson.eitherDecodeStrict' payloadBytes of
594594+ Left message ->
595595+ Exception.throwString
596596+ ( "Could not decode SSE chunk: "
597597+ <> message
598598+ <> "\n"
599599+ <> Text.unpack payloadText
600600+ )
601601+ Right chunk' ->
602602+ return chunk'
603603+604604+ return state
605605+ { streamParseDataLines = [ ]
606606+ , streamParseStreamState =
607607+ stepStreamState (streamParseStreamState state) chunk
608608+ }
609609+610610+flushStreamParseState :: StreamParseState -> IO StreamParseState
611611+flushStreamParseState state = do
612612+ state' <-
613613+ if ByteString.null (streamParseBuffer state) then
614614+ return state
615615+ else
616616+ processStreamLine
617617+ state
618618+ { streamParseBuffer = ByteString.empty
619619+ }
620620+ (streamParseBuffer state)
621621+622622+ if streamParseDone state' then
623623+ return state'
624624+ else
625625+ emitStreamEvent state'
+2-2
ghcjs/Grace/HTTP.hs
···130130renderError :: HttpException -> Text
131131renderError = Text.pack . displayException
132132133133--- | The GHCJS implementation of OpenAI bindings just stores the API key
133133+-- | The GHCJS implementation of the chat-completions bindings just stores the API key
134134type Methods = Text
135135136136-- | Initialize API for prompting
···154154 Just o -> [("OpenAI-Organization", Encoding.encodeUtf8 o)]
155155156156 let request = Request
157157- { reqUrl = "https://api.openai.com/v1/chat/completions"
157157+ { reqUrl = "https://api.letta.com/v1/chat/completions"
158158 , reqOptions = Fetch.defaultRequestOptions
159159 { reqOptMethod = "POST"
160160 , reqOptHeaders =
+3-3
prompts/inference.md
···265265266266```bash
267267$ grace repl
268268->>> prompt{ key: ./openai.key, text: "Give me a first and last name" } : { first: Text, last: Text }
268268+>>> prompt{ key: ./letta.key, text: "Give me a first and last name" } : { first: Text, last: Text }
269269{ "first": "Emily", "last": "Johnson" }
270270->>> prompt{ key: ./openai.key, text: "Give me a list of names" } : List Text
270270+>>> prompt{ key: ./letta.key, text: "Give me a list of names" } : List Text
271271[ "Alice"
272272, "Bob"
273273, "Charlie"
···384384We can explore this idea of using the schema to drive the prompt instead of prose using an example like this:
385385386386```haskell
387387-prompt{ key: ./openai.key, text: "Generate some characters for a story" }
387387+prompt{ key: ./letta.key, text: "Generate some characters for a story" }
388388 : List
389389 { "The character's name": Text
390390 , "The most memorable thing about the character": Text
+11-17
src/Grace/Prompt.hs
···3838 , CreateChatCompletion(..)
3939 , Message(..)
4040 , ReasoningEffort(..)
4141- , WebSearchOptions(..)
4241 , _CreateChatCompletion
4342 )
4443···215214 (Aeson.object
216215 [ ("type", "number")
217216 -- , ("minimum", Aeson.toJSON (0 :: Int))
218218- -- ^ Not supported by OpenAI
217217+ -- ^ Not supported by the current chat completions API
219218 ]
220219 )
221220 loop Type.Scalar{ scalar = Monotype.Text } =
···251250252251 let methods = keyToMethods (Text.strip key)
253252254254- let defaultedSearch = case search of
255255- Just s -> s
256256- Nothing -> False
257257-258258- let web_search_options
259259- | defaultedSearch = Just WebSearchOptions
260260- { search_context_size = Nothing
261261- , user_location = Nothing
262262- }
263263- | otherwise = Nothing
253253+ case search of
254254+ Just True ->
255255+ Exception.throwString "Grace's Letta backend does not support the `search` flag yet; use a Letta agent with search tools instead."
256256+ _ ->
257257+ return ()
264258265265- let defaultedModel = case model of
266266- Just m -> m
267267- _ | defaultedSearch -> "gpt-5-search-api"
268268- | otherwise -> "gpt-5-mini"
259259+ defaultedModel <- case model of
260260+ Just m -> return m
261261+ Nothing ->
262262+ Exception.throwString "Grace's Letta backend requires a `model` value with the Letta agent id, for example `model: \"agent-...\"`."
269263270264 let reasoning_effort = do
271265 e <- effort
···469463 HTTP.createChatCompletion methods _CreateChatCompletion
470464 { messages
471465 , model = Model defaultedModel
472472- , web_search_options
466466+ , web_search_options = Nothing
473467 , reasoning_effort
474468 }
475469
+3-3
tasty/data/complex/examples-output.ffg
···5050 }
5151, "prompting":
5252 \arguments ->
5353- let key = arguments."OpenAI API key"
5353+ let key = arguments."Letta API key"
54545555 in { "names":
5656 prompt
···121121 { "key":
122122 key
123123 , "model":
124124- some "gpt-5-nano"
124124+ some "agent-YOUR_AGENT_ID"
125125 , "text":
126126 null
127127 , "history":
···682682 }
683683, "coding":
684684 \arguments ->
685685- let key = arguments."OpenAI API key"
685685+ let key = arguments."Letta API key"
686686687687 in import prompt
688688 { "key":
+2-2
tasty/data/complex/examples-type.ffg
···4545 }
4646 }
4747 , prompting:
4848- { "OpenAI API key": Key, q } ->
4848+ { "Letta API key": Key, q } ->
4949 { names:
5050 p
5151 , structuredNames:
···110110 Bool -> Bool
111111 }
112112 , coding:
113113- { "OpenAI API key": Key, l } ->
113113+ { "Letta API key": Key, l } ->
114114 { "Job Description": Text } ->
115115 { "Is Finance?": Bool, "Rationale": Text }
116116 , conclusion: