Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add updateMessageList to Chat module #39

Merged
merged 4 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions examples/roc-agent.roc
Original file line number Diff line number Diff line change
Expand Up @@ -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 })

Expand Down Expand Up @@ -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.
"""
Expand All @@ -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 = [
Expand Down
48 changes: 32 additions & 16 deletions package/Chat.roc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module [
buildHttpRequest,
decodeErrorResponse,
decodeResponse,
updateMessageList,
decodeTopMessageChoice,
encodeRequestBody,
initClient,
Expand All @@ -23,6 +24,7 @@ import InternalTools exposing [ToolCall, ToolChoice]
import Shared exposing [
RequestObject,
ApiError,
HttpResponse,
dropLeadingGarbage,
optionToStr,
optionToList,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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: [],
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -217,15 +219,15 @@ 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",
text,
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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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 }
10 changes: 10 additions & 0 deletions package/Shared.roc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module [
ErrorResponse,
RequestObject,
ResponseFormat,
HttpResponse,
dropLeadingGarbage,
decodeErrorResponse,
optionToStr,
Expand Down Expand Up @@ -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 ->
Expand Down
2 changes: 1 addition & 1 deletion package/Toolkit/OpenWeatherMap.roc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
24 changes: 1 addition & 23 deletions package/Tools.roc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
## ```
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 -> ...
Expand Down