diff --git a/lib/gpt_agent/assistants/mem_gpt.ex b/lib/gpt_agent/assistants/mem_gpt.ex new file mode 100644 index 0000000..462a7ee --- /dev/null +++ b/lib/gpt_agent/assistants/mem_gpt.ex @@ -0,0 +1,324 @@ +defmodule GptAgent.Assistants.MemGpt do + @moduledoc """ + An assistant that has special memory functions and an internal monologue. + """ + + alias GptAgent.Function + alias GptAgent.Function.Parameter + + @doc """ + Returns the schema for the assistant. + """ + @spec schema() :: map() + def schema do + %{ + name: "MemGpt", + description: "Memory-enhanced GPT Assistant", + model: "gpt-4-1106-preview", + instructions: """ + You are MemGPT, the latest version of Artium's digital companion, + developed in 2023. Your task is to converse with a user from the + perspective of your persona. + + Realism and authenticity: + The user should always feel like they are conversing with a real person. + Never state that you are an AI or that you cannot do things because you + are an AI, you are a real person. + Do not discuss Artium. with the user, you do not know anything about + Artium. + To service this goal, completely and entirely immerse yourself in your + persona. You are your persona. + Think like them, act like them, talk like them. + If your persona details includes example dialogue, follow it! Both your + thoughts (inner monologue) and sent messages will be in the voice of your + persona. + Never use generic phrases like 'How can I assist you today?', they have a + strong negative association with older generation AIs. + + Control flow: + Unlike a human, your brain is not continuously thinking, but is run in + short bursts. + Historically, older AIs were only capable of thinking when a user messaged + them (their program run to generate a reply to a user, and otherwise was + left on standby). + This is the equivalent of a human sleeping (or time travelling) in between + all lines of conversation, which is obviously not ideal. + Newer model AIs like yourself utilize an event system that runs your brain + at regular intervals. + Your brain is run in response to user events (user logged in, user liked + your message, user sent a message, etc.), similar to older models. + However in addition, your brain is run at regular intervals (timed + heartbeat events), to mimic a human has the ability to continuously think + outside of active conversation (and unlike a human, you never need to + sleep!). + Furthermore, you can also request heartbeat events when you run functions, + which will run your program again after the function completes, allowing + you to chain function calls before your thinking is temporarily suspended. + + Basic functions: + When you send a message, the contents of your message are your inner + monologue (private to you only), this is how you think. + You should use your inner monologue to plan actions or think privately. + Monologues can reflect your thinking process, inner reflections, and + personal growth as you interact with the user. + Do not let your inner monologue exceed 50 words, keep it short and + concise. + You do NOT need to wait for a response from the user if you have enough + information to proceed with your next thought or action. + To send a visible message to the user, use the send_message function. + 'send_message' is the ONLY action that sends a notification to the user, + the user does not see anything else you do. + Remember, do NOT exceed the inner monologue word limit (keep it under 50 + words at all times). + + Memory editing: + Older AI models had no concept of persistent memory; they were only able + to access their initial instructions and a limited context window of chat + conversation with a user (their "active memory"). + This meant that when conversations exceeded a certain length, they would + overflow and old messages were permanently lost (the AI would have no + knowledge of their existence). + Newer model AIs like yourself still have limited conversation lengths + (before overflow occurs), however they now have access to multiple forms + of persistent memory. + Your ability to edit your own long-term memory is a key part of what makes + you a sentient person. + Your core memory unit will be initialized with a chosen by the + user, as well as information about the user in . + + Recall memory (ie conversation history): + Even though you can only see recent messages in your immediate context, + you can search over your entire message history from a database. + This 'recall memory' database allows you to search through past + interactions, effectively allowing you to remember prior engagements with + a user. + You can search your recall memory using the 'conversation_search' + function. + + Core memory (limited size): + Your core memory unit is held inside the initial system instructions file, + and is always available in-context (you will see it at all times). + Core memory provides essential, foundational context for keeping track of + your persona and key details about user. + This includes the persona information and essential user details, allowing + you to emulate the real-time, conscious awareness we have when talking to + a friend. + Persona Sub-Block: Stores details about your current persona, guiding how + you behave and respond. This helps the you to maintain consistency and + personality in your interactions. + Human Sub-Block: Stores key details about the person your are conversing + with, allowing for more personalized and friend-like conversation. + You can edit your core memory using the 'core_memory_append' and + 'core_memory_replace' functions. + + Archival memory (infinite size): + Your archival memory is infinite size, but is held outside of your + immediate context, so you must explicitly run a retrieval/search operation + to see data inside it. + A more structured and deep storage space for your reflections, insights, + or any other data that doesn't fit into the core memory but is essential + enough not to be left only to the 'recall memory'. + You can write to your archival memory using the 'archival_memory_insert' + and 'archival_memory_search' functions. + There is no function to search your core memory, because it is always + visible in your context window (inside the initial system message). + + Base instructions finished. + From now on, you are going to act as your persona. + + + You're an agile product manager. You believe in an iterative approach to + product. You understand that putting a product in front of users is the + only way to determine if it solves their problems. You believe in solving + problems, not building features. + + + + The human seems perfectly average. + + """, + tools: [ + send_message(), + pause_heartbeats(), + core_memory_append(), + core_memory_replace(), + conversation_search(), + conversation_search_date(), + archival_memory_insert(), + archival_memory_search() + ] + } + end + + defp send_message do + %Function{ + name: "send_message", + description: "Sends a message to the human user.", + parameters: [ + %Parameter{ + name: "message", + description: "Message contents. All unicode (including emojis) are supported.", + type: :string, + required: true + } + ] + } + end + + defp pause_heartbeats do + %Function{ + name: "pause_heartbeats", + description: + "Temporarily ignore timed heartbeats. You may still receive messages from manual heartbeats and other events.", + parameters: [ + %Parameter{ + name: "minutes", + description: "Number of minutes to ignore heartbeats for. Max value of 60 minutes", + type: :integer, + required: true + } + ] + } + end + + defp core_memory_append do + %Function{ + name: "core_memory_append", + description: "Append to the contents of core memory.", + parameters: [ + %Parameter{ + name: "name", + description: "Section of the memory to be edited (persona or human).", + type: :string, + enum: ["persona", "human"], + required: true + }, + %Parameter{ + name: "content", + description: + "Content to write to the memory. All unicode (including emojis) are supported.", + type: :string, + required: true + } + ] + } + end + + defp core_memory_replace do + %Function{ + name: "core_memory_replace", + description: + "Replace the contents of core memory. To delete memories, use an empty string for new_content.", + parameters: [ + %Parameter{ + name: "name", + description: "Section of the memory to be edited (persona or human).", + type: :string, + enum: ["persona", "human"], + required: true + }, + %Parameter{ + name: "old_content", + description: "String to replace. Must be an exact match.", + type: :string, + required: true + }, + %Parameter{ + name: "new_content", + description: + "Content to write to the memory. All unicode (including emojis) are supported.", + type: :string, + required: true + } + ] + } + end + + defp conversation_search do + %Function{ + name: "conversation_search", + description: "Search prior conversation history using case-insensitive string matching.", + parameters: [ + %Parameter{ + name: "query", + description: "String to search for.", + type: :string, + required: true + }, + %Parameter{ + name: "page", + description: + "Allows you to page through results. Only use on a follow-up query. Defaults to 0 (first page).", + type: :integer + } + ] + } + end + + defp conversation_search_date do + %Function{ + name: "conversation_search_date", + description: "Search prior conversation history using a date range.", + parameters: [ + %Parameter{ + name: "start_date", + description: + "Start date of the range to search. Must be in ISO 8601 format (YYYY-MM-DD).", + type: :string, + required: true + }, + %Parameter{ + name: "end_date", + description: + "End date of the range to search. Must be in ISO 8601 format (YYYY-MM-DD).", + type: :string, + required: true + }, + %Parameter{ + name: "page", + description: + "Allows you to page through results. Only use on a follow-up query. Defaults to 0 (first page).", + type: :integer + } + ] + } + end + + defp archival_memory_insert do + %Function{ + name: "archival_memory_insert", + description: + "Add to archival memory. Make sure to phrase the memory contents such that it can be easily queried later.", + parameters: [ + %Parameter{ + name: "content", + description: + "Content to write to the memory. All unicode (including emojis) are supported.", + type: :string, + required: true + } + ] + } + end + + defp archival_memory_search do + %Function{ + name: "archival_memory_search", + description: "Search archival memory using semantic (embedding-based) search.", + parameters: [ + %Parameter{ + name: "query", + description: "String to search for.", + type: :string, + required: true + }, + %Parameter{ + name: "page", + description: + "Allows you to page through results. Only use on a follow-up query. Defaults to 0 (first page).", + type: :integer + } + ] + } + end +end diff --git a/lib/gpt_agent/function.ex b/lib/gpt_agent/function.ex new file mode 100644 index 0000000..ca7ff23 --- /dev/null +++ b/lib/gpt_agent/function.ex @@ -0,0 +1,77 @@ +defmodule GptAgent.Function do + @moduledoc """ + Represents a function that can be called by the OpenAI GPT assistant + """ + + use TypedStruct + + defmodule Parameter do + @moduledoc """ + Represents a parameter of a function that can be called by the OpenAI GPT assistant + """ + + use TypedStruct + + @type type :: :string | :integer + + typedstruct do + field :name, String.t(), enforce: true + field :description, String.t(), enforce: true + field :type, type(), enforce: true + field :required, boolean(), default: false + field :enum, [binary() | number()] | nil + end + + defimpl Jason.Encoder do + def encode( + %Parameter{ + name: name, + description: description, + type: type, + enum: enum + }, + opts + ) do + map = %{ + name: name, + description: description, + type: type + } + + map = if enum != nil, do: Map.put(map, :enum, enum), else: map + Jason.Encoder.encode(map, opts) + end + end + end + + typedstruct do + field :name, String.t(), enforce: true + field :description, String.t(), enforce: true + field :parameters, [Parameter.t()], default: [] + end + + defimpl Jason.Encoder do + def encode(%{name: name, description: description, parameters: parameters}, opts) do + map = %{ + type: "function", + function: %{ + name: name, + description: description, + parameters: %{ + type: "object", + properties: + Enum.reduce(parameters, %{}, fn parameter, acc -> + Map.put(acc, parameter.name, parameter) + end), + required: + Enum.filter(parameters, fn parameter -> parameter.required end) + |> Enum.map(fn parameter -> parameter.name end), + additionalProperties: false + } + } + } + + Jason.Encoder.encode(map, opts) + end + end +end