Developing a Fixie Agent

The Fixie platform enables you to build agents in any language by writing a service that conforms to the protocol described in Agent Protocol. For Python developers, we provide a Python library for implementing agents using the API described below.

For a quick start on building your own Fixie agent, review the Quick Start guide.

See Fixie Agent Python API for the full API reference.

CodeShotAgent

The base class for agents in Fixie is CodeShotAgent. This class handles communication with the Fixie platform via the Agent Protocol and provides a simple API for registering functions that can be invoked by the few-shot examples used by the agent.

A typical CodeShotAgent structure looks like this:

import fixieai

BASE_PROMPT = "Base prompt for the agent."
FEW_SHOTS = """
Q: Example query for the agent
Ask Func[my_func]: query to the func
Func[my_func] says: response to the query
A: Response generated by the agent

Q: Second example query to the agent
A: Second response generated by the agent
"""

agent = fixieai.CodeShotAgent(BASE_PROMPT, FEW_SHOTS)

@agent.register_func()
def my_func(query: fixieai.Message) -> str:
    return "Response to the query"

The BASE_PROMPT and FEW_SHOTS strings provide examples for the underlying Large Language Model (LLM), such as GPT-4, as well as supply the Fixie Platform with information about the types of queries this agent can support.

FEW_SHOTS must be a string composed of one or more stanzas, where each stanza consists of a question, one or more rounds of internal actions taken by the agent, and a final answer. Stanzas must be separated by a blank line. The query line in the stanza must start with Q:, and the answer line must start with A:.

Internal actions taken by the agent can be one of two forms:

  1. Ask Func[<func_name>]: <query_text>: This indicates that the function <func_name> should be invoked when the output of the underlying LLM starts with this string. The string following Ask Func[<func_name>]: is passed to the function as the query.text parameter.
  2. Ask Agent[<agent_name>]: <query_text>: This indicates that the agent <agent_name> should be invoked when the output of the underlying LLM starts with this string. The string following Ask Agent[<agent_name>]: is passed to the agent as the query.text parameter.

Agent Funcs

A Func is a Python function that the agent can invoke. (To implement Funcs in languages other than Python, create a service that implements the Fixie Agent Protocol.) When the language model for an agent emits the token Ask Func[<func_name>], the function <func_name> will be invoked.

The register_func decorator is used to register a function for invocation by the agent.

A function registered with the register_func decorator has the signature:

@agent.register_func()
def my_func(query, user_storage=None, oauth_handler=None):
    ...

The query parameter is either a str or a Message object. If the query parameter is a string, this parameter contains the text of the agent query. If the query parameter is a Message object, this parameter contains the text of the agent along with zero or more Embed objects, as described in the Embeds section below.

The optional user_storage parameter provides the Func an interface to the Fixie User Storage service, as described below.

The optional oauth_handler parameter provides the Func an interface for performing OAuth authentication with external services, as described in the OAuth section below.

The function must return either a str or a Message object. Returning a string is equivalent to returning a Message object with the string as its text field and no embeds.

Embeds

Embeds enable the association of arbitrary binary data with a query or response Message in Fixie, similar to email attachments. Embeds can be used to store images, videos, text, or any other binary data.

Embeds are represented by the Embed class. Agents can access the Embeds associated with a Message as follows:

@agent.register_func()
def my_func(query: fixieai.Message) -> str:
    for key, embed in query.embeds.items():
        print(f"Embed key: {key}")
        print(f"Embed content-type: {embed.content_type}")
        embed_value_as_text = embed.text
        embed_value_as_bytes = embed.content

An agent function can also add an Embed to its response Message by adding it to the embeds dictionary of the Message object:

@agent.register_func()
def my_func(query: fixieai.Message) -> fixieai.Message:
    reply = fixieai.Message("Response to the query")
    reply.embeds["my_embed"] = fixieai.Embed(content_type="text/plain")
    reply.embeds["my_embed"].text = "Hello, world!"

Embeds can be queried by leveraging the built-in fixie_query_embed func. e.g., Ask Func[fixie_query_embed]. See example of how this works.

External Knowledge Base Support

Fixie agents come with built-in support for indexing and querying external knowledge in the form of URLs. See the support agent for an example of how to use this.

URLS = [
    "https://docs.fixie.ai/*",
]
DOCS = [fixieai.DocumentCorpus(urls=URLS)]
agent = fixieai.CodeShotAgent(BASE_PROMPT, FEW_SHOTS, DOCS)

Appending * to the end of the URL will tell Fixie to automatically index all children of the root URL.

After indexing, you can query the corpus using the built-in Ask Func[fixie_query_corpus] function.

Note: It can take up to 10 minutes to index content, depending on the number of URLs and content size. We're working on a mechanism to alert you to indexing progress. Stay tuned!

User Storage

Fixie agents can store and retrieve arbitrary data associated with a user using the UserStorage class. This class provides a simple interface to a persistent key/value storage service, with a separate key/value store for each Fixie user. This can be used to maintain state about a particular user that persists across agent invocations.

The UserStorage instance for a given query can be obtained by providing a user_storage parameter to an agent function. The UserStorage object acts as a Python dict that stores state associated with an arbitrary string key. UserStorage values may consist of Python primitive types, such as str, int, float, bool, None, or bytes, as well as lists of these types, or a dict mapping a str to one of these types.

User Storage Example

@agent.register_func()
def my_func(query: fixieai.Message, user_storage: fixieai.UserStorage) -> str:
    user_storage["my_key"] = "my_value"
    return user_storage["my_key"]

Agent OAuth Support

Fixie Agents can authenticate to third-party services to perform actions on behalf of the user. This is done using OAuth 2.0, a standard protocol for authorization. OAuth 2.0 allows users to grant limited access to their accounts on one service, to another service, without having to share their password.

Fixie provides a simple interface for agents to perform OAuth authentication, using the OAuthParams class. Using this class, an agent function can use the OAuthHandler class, passed to the function as the oauth_handler parameter, to obtain an access token for the user.

OAuth Example

import fixieai

oauth_params = fixieai.OAuthParams(
    client_id="XXXXX.apps.googleusercontent.com",
    auth_uri="https://accounts.google.com/o/oauth2/auth",
    token_uri="https://oauth2.googleapis.com/token",
    client_secret="XXXXXXXXX",
    scopes=["https://www.googleapis.com/auth/calendar.events"]
)

agent = fixieai.CodeShotAgent(BASE_PROMPT, FEW_SHOTS, oauth_params=oauth_params)

@agent.register_func
def my_func(query, oauth_handler: fixieai.OAuthHandler):
    user_token = oauth_handler.user_token()
    if user_token is None:
    # Return the URL that the user should click on to authorize the agent.
    return oauth_handler.get_authorization_url()

  # Do something with the user_token returned by the OAuth handler.
  client = gcalendar_client.GcalendarClient(user_token)
  # ...

Default Agent Model and Model Parameters

By default, agents use the text-davinci-003 model from OpenAI with a default temperature of 0 and a maximum_tokens size of 1,000. We've found this to be the right default for many use cases, but it's easy to change by passing llm_settings when initializing your CodeShotAgent.

Example:

agent = fixieai.CodeShotAgent(
    BASE_PROMPT,
    [],
    conversational=True,
    llm_settings=fixieai.LlmSettings(temperature=1.0, model="openai/gpt-4", maximum_tokens=500),
)

Supported Models:

Model Name Identifier String
GPT-3 openai/text-davinci-003 (default)
GPT-3.5 (ChatGPT) openai/gpt-3.5-turbo
GPT-4 openai/gpt-4
AI21 J2 Grande ai21/j2-grande
AI21 J2 Jumbo ai21/j2-jumbo
GooseAI GPTJ 6B gooseai/gpt-j-6b
GooseAI GPT NEO 20B gooseai/gpt-neo-20b

Built-In Functions

All Fixie agents have access to the following built-in functions that they can invoke:

  • Ask Func[fixie_base_prompt]: Returns the base prompt for the agent.
  • Ask Func[fixie_local_datetime]: Returns the current date and time in the user's local timezone.
  • Ask Func[fixie_utc_datetime]: Returns the current date and time in the UTC timezone.
  • Ask Func[fixie_query_embed]: Executes the prompt in the query against the contents of the embed.
  • Ask Func[fixie_query_corpus]: Executes the prompt in the query against the contents of the agent-defined corpus.