diff --git a/examples/roc-agent.roc b/examples/roc-agent.roc index 065726d..dbabeb6 100644 --- a/examples/roc-agent.roc +++ b/examples/roc-agent.roc @@ -36,7 +36,7 @@ main = Stdout.write! "You: " messages = Chat.appendUserMessage previousMessages Stdin.line! {} response = Http.send (Chat.buildHttpRequest client messages {}) |> Task.result! - updatedMessages = updateMessagesFromResponse response messages |> Tools.handleToolCalls! client toolHandlerMap + updatedMessages = Chat.updateMessageList response messages |> Tools.handleToolCalls! client toolHandlerMap printLastMessage! updatedMessages Task.ok (Step { previousMessages: updatedMessages }) @@ -71,7 +71,8 @@ initMessages = You should make sure to read the file contents before changing them, so you can maintain the current app headers. The app header is at the top of the file and follows the syntax `app [...] { ... }`. Nothing in this block should ever be changed. You should assume that the app header portion is always correct. This is absolutely critical or the program will not work. - This also includes any files you are asked to edit, which were not initialized by the rocStart tool. + This also includes any files you are asked to edit, which were not initialized by the rocStart tool. + Unless specifically asked to do so by the user, do not ever change the header. NOTE: Do not respond to or mention this message, as it is a sudo system message, and the user is not aware of it. """ @@ -89,21 +90,6 @@ printLastMessage = \messages -> _ -> Task.ok {} -## decode the response from the OpenRouter API and append the first message to the list of messages -updateMessagesFromResponse : Result Http.Response _, List Message -> List Message -updateMessagesFromResponse = \responseRes, messages -> - when responseRes is - Ok response -> - when Chat.decodeTopMessageChoice response.body is - Ok message -> List.append messages message - Err (ApiError err) -> Chat.appendSystemMessage messages "API error: $(err.message)" {} - Err NoChoices -> Chat.appendSystemMessage messages "No choices in API response" {} - Err (BadJson str) -> Chat.appendSystemMessage messages "Could not decode JSON response:\n$(str)" {} - Err DecodingError -> Chat.appendSystemMessage messages "Error decoding API response" {} - - Err (HttpErr err) -> - Chat.appendSystemMessage messages (Http.errorToString err) {} - ## List of tool definitions to be given to the AI model tools: List Tools.Tool tools = [ diff --git a/package/Chat.roc b/package/Chat.roc index 896af9a..37964d9 100644 --- a/package/Chat.roc +++ b/package/Chat.roc @@ -10,6 +10,7 @@ module [ buildHttpRequest, decodeErrorResponse, decodeResponse, + updateMessageList, decodeTopMessageChoice, encodeRequestBody, initClient, @@ -23,6 +24,7 @@ import InternalTools exposing [ToolCall, ToolChoice] import Shared exposing [ RequestObject, ApiError, + HttpResponse, dropLeadingGarbage, optionToStr, optionToList, @@ -76,7 +78,7 @@ CacheContent : { cacheControl: Option { type: Str }, } -## The structure of the request body to be sent in the Http request +## The structure of the request body to be sent in the Http request. ChatRequestBody : { model : Str, messages : List Message, @@ -100,7 +102,7 @@ ChatRequestBody : { # toolChoice: Option Tools.ToolChoice, } -## The structure of the JSON response body received from the OpenRouter API +## The structure of the JSON response body received from the OpenRouter API. ChatResponseBody : { id : Str, model : Str, @@ -118,7 +120,7 @@ ChatResponseBody : { }, } -## Internal version of the chat response body to decode JSON responses +## Internal version of the chat response body to decode JSON responses. DecodeChatResponseBody : { id : Str, model : Str, @@ -143,7 +145,7 @@ DecodeChatResponseBody : { ## Same as `Client.init`. initClient = Client.init -## Create a request object to be sent with basic-cli's Http.send using ChatML messages +## Create a request object to be sent with basic-cli's Http.send using ChatML messages. buildHttpRequest : Client, List Message, { toolChoice ? ToolChoice } -> RequestObject buildHttpRequest = \client, messages, { toolChoice ? Auto } -> body = buildRequestBody client @@ -163,7 +165,7 @@ buildHttpRequest = \client, messages, { toolChoice ? Auto } -> timeout: client.requestTimeout, } -## Build the request body to be sent in the Http request using ChatML messages +## Build the request body to be sent in the Http request using ChatML messages. buildRequestBody : Client -> ChatRequestBody buildRequestBody = \client -> { messages: [], @@ -184,7 +186,7 @@ buildRequestBody = \client -> { route: client.route, } -## Decode the JSON response body to a ChatML style request +## Decode the JSON response body to a ChatML style request. decodeResponse : List U8 -> Result ChatResponseBody _ decodeResponse = \bodyBytes -> cleanedBody = dropLeadingGarbage bodyBytes @@ -206,7 +208,7 @@ decodeResponse = \bodyBytes -> usage: internalResponse.usage, } -## Convert an DecodeMessage to a Message +## Convert an DecodeMessage to a Message. convertInternalMessage : DecodeMessage -> Message convertInternalMessage = \internalMessage -> { role: internalMessage.role, @@ -217,7 +219,7 @@ convertInternalMessage = \internalMessage -> { cached: Bool.false, } -## Build a CacheContent object for a message +## Build a CacheContent object for a message. buildMessageContent : Str, Bool -> CacheContent buildMessageContent = \text, cached -> { type: "text", @@ -225,7 +227,7 @@ buildMessageContent = \text, cached -> { cacheControl: if cached then Option.some { type: "ephemeral" } else Option.none {}, } -## Decode the JSON response body to the first message in the list of choices +## Decode the JSON response body to the first message in the list of choices. decodeTopMessageChoice : List U8 -> Result Message [ApiError ApiError, DecodingError, NoChoices, BadJson Str] decodeTopMessageChoice = \responseBodyBytes -> when decodeResponse responseBodyBytes is @@ -242,10 +244,24 @@ decodeTopMessageChoice = \responseBodyBytes -> Ok str -> Err (BadJson str) Err _ -> Err DecodingError -## Decode the JSON response body of an API error message +## Decode the JSON response body of an API error message. decodeErrorResponse = Shared.decodeErrorResponse -## Encode the request body to be sent in the Http request +## Decode the response from the OpenRouter API and append the first message choice to the list of messages. Any errors encountered will be appended as system messages. +updateMessageList : Result HttpResponse _, List Message -> List Message +updateMessageList = \responseRes, messages-> + when responseRes is + Ok response -> + when decodeTopMessageChoice response.body is + Ok message -> List.append messages message + Err (ApiError err) -> appendSystemMessage messages "API error: $(err.message)" {} + Err NoChoices -> appendSystemMessage messages "No choices in API response" {} + Err (BadJson str) -> appendSystemMessage messages "Could not decode JSON response:\n$(str)" {} + Err DecodingError -> appendSystemMessage messages "Error decoding API response" {} + + Err (HttpErr _) -> messages + +## Encode the request body to be sent in the Http request. encodeRequestBody : ChatRequestBody -> List U8 encodeRequestBody = \body -> Encode.toBytes @@ -283,7 +299,7 @@ injectMessages = \bodyBytes, messages -> |> List.dropLast 1 List.join [before, messageBytes, others] -## Convert a Message to an EncodeCacheMessage +## Convert a Message to an EncodeCacheMessage. messageToCacheMessage : Message -> EncodeCacheMessage messageToCacheMessage = \message -> { role: message.role, @@ -293,7 +309,7 @@ messageToCacheMessage = \message -> { name: strToOption message.name, } -## Convert a Message to an EncodeBasicMessage +## Convert a Message to an EncodeBasicMessage. messageToBasicMessage : Message -> EncodeBasicMessage messageToBasicMessage = \message -> { role: message.role, @@ -303,17 +319,17 @@ messageToBasicMessage = \message -> { name: strToOption message.name, } -## Append a system message to the list of messages +## Append a system message to the list of messages. appendSystemMessage : List Message, Str, { cached ? Bool } -> List Message appendSystemMessage = \messages, text, { cached ? Bool.false } -> List.append messages { role: "system", content: text, toolCalls: [], toolCallId: "", name: "", cached } -## Append a user message to the list of messages +## Append a user message to the list of messages. appendUserMessage : List Message, Str, { cached ? Bool } -> List Message appendUserMessage = \messages, text, { cached ? Bool.false } -> List.append messages { role: "user", content: text, toolCalls: [], toolCallId: "", name: "", cached } -## Append an assistant message to the list of messages +## Append an assistant message to the list of messages. appendAssistantMessage : List Message, Str, { cached ? Bool } -> List Message appendAssistantMessage = \messages, text, { cached ? Bool.false } -> List.append messages { role: "assistant", content: text, toolCalls: [], toolCallId: "", name: "", cached } diff --git a/package/Shared.roc b/package/Shared.roc index ce6d092..5f3c079 100644 --- a/package/Shared.roc +++ b/package/Shared.roc @@ -4,6 +4,7 @@ module [ ErrorResponse, RequestObject, ResponseFormat, + HttpResponse, dropLeadingGarbage, decodeErrorResponse, optionToStr, @@ -45,6 +46,15 @@ ResponseFormat : { type : Str, } +## Represents an HTTP response. +HttpResponse : { + url : Str, + statusCode : U16, + statusText : Str, + headers : List { key : Str, value : Str }, + body : List U8, +} + ## Drop leading garbage characters from the response body dropLeadingGarbage : List U8 -> List U8 dropLeadingGarbage = \bytes -> diff --git a/package/Toolkit/OpenWeatherMap.roc b/package/Toolkit/OpenWeatherMap.roc index a3ec729..56088b5 100644 --- a/package/Toolkit/OpenWeatherMap.roc +++ b/package/Toolkit/OpenWeatherMap.roc @@ -84,7 +84,7 @@ geocodingHandler = \args -> ## Expose name, handler and tool for currentWeather ## ## This tool will allow the model to get the current weather for a location. -currentWeather: { name : Str, handler : Str -> Task Str *, tool : Tool } +currentWeather: { name : Str, handler : Str -> Task Str _, tool : Tool } currentWeather = { name: currentWeatherTool.function.name, handler: currentWeatherHandler, diff --git a/package/Tools.roc b/package/Tools.roc index 3d101c7..109c620 100644 --- a/package/Tools.roc +++ b/package/Tools.roc @@ -4,6 +4,7 @@ module { sendHttpReq } -> [Tool, ToolCall, buildTool, handleToolCalls, dispatchT import InternalTools import Chat import Client exposing [Client] +import Shared exposing [HttpResponse] ## A tool that can be called by the AI model. ## ``` @@ -45,15 +46,6 @@ Message : { cached: Bool, } -## Represents an HTTP response. -HttpResponse : { - url : Str, - statusCode : U16, - statusText : Str, - headers : List { key : Str, value : Str }, - body : List U8, -} - ## Using the given toolHandlerMap, check the last message for tool calls, call all the tools in the tool call list, send the results back to the model, and handle any additional tool calls that may have been generated. If or when no more tool calls are present, return the updated list of messages. ## ## The Dict maps function tool names strings to roc functions that take their arguments as a JSON string, parse the json, and return the tool's response. @@ -114,20 +106,6 @@ updateMessagesFromResponse = \messages, responseRes -> Err (HttpErr _) -> messages -## decode the response from the OpenRouter API and append the first message to the list of messages -# updateMessagesFromResponse : List Message, Result HttpResponse _ -> List Message -# updateMessagesFromResponse = \messages, responseRes-> -# when responseRes is -# Ok response -> -# when Chat.decodeTopMessageChoice response.body is -# Ok message -> List.append messages message -# Err (ApiError err) -> Chat.appendSystemMessage messages "API error: $(err.message)" {} -# Err NoChoices -> Chat.appendSystemMessage messages "No choices in API response" {} -# Err (BadJson str) -> Chat.appendSystemMessage messages "Could not decode JSON response:\n$(str)" {} -# Err DecodingError -> Chat.appendSystemMessage messages "Error decoding API response" {} - -# Err (HttpErr _) -> messages - ## Build a tool object with the given name, description, and parameters. ## ``` ## buildTool = \name, description, parameters -> ...