Skip to content

Instantly share code, notes, and snippets.

@isaacabraham
Created November 1, 2024 13:35
Show Gist options
  • Save isaacabraham/2f60d5eb51b17f73e671822a94e6b9b4 to your computer and use it in GitHub Desktop.
Save isaacabraham/2f60d5eb51b17f73e671822a94e6b9b4 to your computer and use it in GitHub Desktop.
F# OpenAI FunctionTools
#r "nuget: OpenAI, 2.0.0"
open OpenAI.Chat
open System.Text.Json
open System.Collections.Generic
open System.IO
let client = ChatClient("gpt-4o", "KEY GOES HERE")
module FsOpenAi =
type FunctionTool = Map<string, obj> -> string
open System.Text.Json.Serialization
open System
let jsonOptions =
JsonSerializerOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault)
type PropertyType =
| Enum of string list
| String
| Number
type Property = {
Name: string
Description: string
Type: PropertyType
} with
static member Create name description type_ = {
Name = name
Description = description
Type = type_
}
type FunctionToolConfig = {
Name: string
Description: string
Parameters: Property list
Func: FunctionTool
} with
static member Create name description parameters func = {
Name = name
Description = description
Parameters = parameters
Func = func
}
let makeTool config =
let asKeyValuePair (property: Property) =
property.Name,
{|
``type`` =
match property.Type with
| Enum _
| String -> "string"
| Number -> "number"
description = property.Description
``enum`` =
match property.Type with
| Enum values -> box values
| _ -> null
|}
let parameters =
match config.Parameters with
| [] -> null
| _ ->
box {|
``type`` = "object"
required = config.Parameters |> List.map _.Name
properties = config.Parameters |> List.map asKeyValuePair |> Map
|}
ChatTool.CreateFunctionTool(
config.Name,
config.Description,
JsonSerializer.Serialize(parameters, jsonOptions) |> BinaryData.FromString
)
type Conversation = {
Messages: ResizeArray<ChatMessage>
Tools: Map<string, FunctionTool>
Options: ChatCompletionOptions
} with
/// Starts a brand new conversation
static member StartNew(?tools) =
let tools = defaultArg tools []
{
Messages = ResizeArray()
Tools =
tools
|> List.map (fun (config: FunctionToolConfig) -> config.Name, config.Func)
|> Map
Options =
let options = ChatCompletionOptions()
for tool in tools do
options.Tools.Add(makeTool tool)
options
}
/// Sends a message to the bot and returns the response, storing the response in the conversation value.
member this.SendMessage(message: string) =
this.Messages.Add(UserChatMessage message)
let response = client.CompleteChat(this.Messages, this.Options)
this.Messages.Add(AssistantChatMessage response.Value)
let rec handleToolCalls (toolCalls: ChatToolCall IReadOnlyList) =
match toolCalls.Count with
| 0 -> ()
| _ ->
for toolCall in toolCalls do
let tool = this.Tools.[toolCall.FunctionName]
let args = JsonSerializer.Deserialize<Map<string, obj>> toolCall.FunctionArguments
let toolResponse = tool args
this.Messages.Add(ToolChatMessage(toolCall.Id, toolResponse))
let response = client.CompleteChat(this.Messages, this.Options)
this.Messages.Add(AssistantChatMessage response.Value)
handleToolCalls response.Value.ToolCalls
handleToolCalls response.Value.ToolCalls
this.History() |> Seq.last |> snd
/// Returns the history of the conversation, as a tuple array of message type and content.
member this.History() =
this.Messages
|> Seq.map (fun message ->
let description =
match message with
| :? UserChatMessage as m -> m.Content[0].Text
| :? AssistantChatMessage as m ->
match m.ToolCalls.Count with
| 0 -> m.Content[0].Text
| _ ->
m.ToolCalls
|> Seq.map (fun call ->
$"Calling {call.FunctionName} with arguments %A{call.FunctionArguments
|> JsonSerializer.Deserialize<Map<string, obj>>
|> Seq.toArray}")
|> String.concat ", "
| :? ToolChatMessage as m -> $"{m.Content[0].Text}"
| _ -> failwith "Unknown message type"
message.GetType().Name, description)
|> Seq.toArray
open FsOpenAi
//1. Basic call
let response =
client.CompleteChat([ UserChatMessage "Please send a funny greeting back to me!" :> ChatMessage ])
//2. Chat history
let responseOfConversation =
client.CompleteChat [
UserChatMessage "Please send a funny greeting back to me!" :> ChatMessage
AssistantChatMessage
"Sure thing! Here you go: Why did the computer go to therapy? Because it had too many bytes of emotional baggage! Hope your day is full of laughter and zero glitches!"
UserChatMessage "Now send me a short one."
]
responseOfConversation.Value.Content[0].Text
//3. Stateful conversation
let conversation = Conversation.StartNew()
conversation.SendMessage "Please send a funny greeting back to me!"
conversation.SendMessage "Now send a short, unfunny one."
conversation.SendMessage "Yes, I'm having a great day thanks! How about you?"
conversation.History()
let fsConversation = Conversation.StartNew()
fsConversation.SendMessage "What is the F# programming language?"
fsConversation.SendMessage
"Give me those points as some JSON that I can parse, with properties Name and Description. Don't include any other information or headings - just plain JSON please that can be directly parsed by a machine."
fsConversation.SendMessage "No markdown formatting. PLAIN JSON ONLY."
let data =
JsonSerializer.Deserialize<{| Name: string; Description: string |} array>(
fsConversation.History() |> Seq.item 5 |> snd
)
fsConversation.SendMessage
"Now return that as a fully-featured HTML page with styles built on top of the Bulma CSS framework."
// Working with Function Tools
let tools = [
FunctionToolConfig.Create
"ReadFile"
"Loads the contents of a file"
[
Property.Create "Path" "The relative path (from the current directory) to the file" String
]
(fun args -> File.ReadAllText(System.IO.Path.Combine(__SOURCE_DIRECTORY__, args.["Path"] |> string)))
{
Name = "Greet"
Description = "Greets the user"
Parameters = [
Property.Create "Name" "The name of the person to greet" String
Property.Create "Friendliness" "How friendly the greeting should be, on the scale of 1-5" Number
]
Func =
fun args ->
let name = string args.["Name"]
let friendliness = int (string args.["Friendliness"])
if friendliness < 3 then
$"Hi, {name}."
else
$"Hello, {name}!"
}
]
let toolConversation = Conversation.StartNew(tools)
toolConversation.SendMessage "Send greetings to all the people listed in the GuestList.txt file."
toolConversation.History()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment