Skip to content

Agent

Agent is a class. It offers atomic-level natural language (NL) functions, allowing the straightforward integration of large language model (LLM) capabilities into code evaluations. In addition, it also provides tool functions, enabling you to generate JSON

AgentClient

Agent does not possess built-in large language model (LLM) functionality; instead, it requires you to supply an AgentClient to facilitate communication with the LLM.

The AgentClient is an async function that takes a prompt and a systemMessage as input. You'll need to call the LLM service and return a string in response.

It's important to note that both the prompt and systemMessage passed into the AgentClient should be of type string.

Here is an example:

ts
import type { AgentClient } from "@browser-ai/ai-expression"
// ./src/openaiClient.ts

// How this function like will depend on your LLM service endpoints

export const chatCompletion = async (payload: any) => {
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${import.meta.env.VITE_OPENAI_API_KEY}`,
    },
    mode: "cors",
    body: JSON.stringify(payload),
  }).then((res) => res.json())

  return response.choices[0].message.content
}

// This is the AgentClient you use to initialize Agent
export const openaiClient: AgentClient = async ({
  prompt,
  systemMessage,
}) => {
  const messages = []
  if (systemMessage) {
    messages.push({
      role: "system",
      content: systemMessage,
    })
  }
  messages.push({
    role: "user",
    content: prompt,
  })
  const message = await chatCompletion({
    model: "gpt-3.5-turbo",
    temperature: 0,
    messages,
  })

  return message as string
}

Init Agent

After you setup the AgentClient, you can use it to initialize Agent.

ts
import { Agent } from "@browser-ai/ai-expression"
import { openaiClient } from "./openaiClient"

const agent = new Agent(openaiClient)

The Agent will use the content you provide as judgment material. You need to set the content to be judged before making a judgment. Please use agent.withContext() for configuration.

ts
agent.withContext(`
User: Hello
Ai: Hello! How can I assist you today ?
User: I want to login
`, () => {
  // do something with current content
})

It is important to note that if the content is a conversation, you need to convert them into a format similar to the example above. Actually, there are no restrictions on the format of the content, as long as the input content is a string.

ts
// raw content OK
agent.withContext("I have 2 apples, 3 banana and 4 organges.", () => {
  // do something with current content
})

// any name is OK
agent.withContext(`
foo: Hello
bar: Hello! How can I assist you today ?
foo: I want to login
Agent: User logged in.
Agent: User picked a product.
`, () => {
  // do something with current content
})

withContext will reset the context after execution. Read more about withContext

Yes or No

You can then execute the NL function, and here we use .yesNo().

ts
await agent.withContext(`
User: Hello
Ai: Hello! How can I assist you today ?
User: I want to login
`, async () => {
  const userWantToLogin = await agent.yesNo("Does user want to login")
  
  console.log(userWantToLogin) // Typically, it would be `true`
})

In this example. If user want to login, .yesNo() will return true; if user don't want to login, it will return false. If agent don't know if user want to login, .yesNo() will return null

So you can easily perform actions based on the result:

ts
if(userWantToLogin) {
  // do something 
} else {
  // do something
}

This type of NL function prompts the language model to respond with either yes or no, and if unsure, it responds with none. The result from the language model's response is then converted into a boolean value or left as null.

A similar function with the same characteristics is .is(), .does().

Choice

The yesNo is actually kind of choice. It selects the most suitable option from the choices you provide. If unsure, it responds with none (null).

It's important to note that, apart from none, .choice() might also return a response that is not among the options you provided.

ts
await agent.withContext(`
User: Hello
Ai: Hello! How can I assist you today ?
User: I want to login
`, async () => {
  const purpose = await agent.choice("user's purpose", [ "login", "checkout" ] )
  
  switch(purpose){
    case "login":
      // do something
      break
    case "checkout":
      // do something
      break
    default:
      // handle "none" or unexpected result
  }
})

handler

You may notice that we always handle various result branches using switch, which may not be ideal for coding.

.choice can take a handler as an argument and directly process the specific branch:

ts
await agent.withContext(`
User: Hello
Ai: Hello! How can I assist you today ?
User: I want to login
`, async () => {
  const result = await agent.choice("user's purpose", 
    [ 
      ["login", () => {
        // do something
        return "some result 1"
      }]
      ["checkout" () => {
        // do something
        return "some result 2"
      }]
      [ () => { 
        // handle "none" or unexpected result
        return "some result 3"
      }]
    ] 
  )
  console.log(result)
})

Read more about choice.

Tools

ts
(method) Agent.pickTool(tools: Tool<any>[]): Promise<{
    func: string;
    args: ToolFunctionParams;
    useIt: () => any;
    error?: string | undefined;
}>

When you need the Agent to dynamically generate JSON content, you can use Tool.

The concept of Tool involves representing the name of a function, its description, and the structure of its parameters using JSON Schema. Subsequently, the language model will generate corresponding function parameters based on the schema, in other words, it generates JSON. You can pass this JSON into a function call or use the JSON data directly.

Typical scenarios for using Tool include:

  • Needing to generate different field data in one go based on the content.
  • Providing natural language parameters to a function, such as prompt messages, for a more lively interaction.
  • Dynamically choosing a function and executing it at runtime.
ts
import { Tool } from "@browser-ai/ai-expression"

const tool = new Tool(
  ({ message }) => {
    console.log(message)
    return message
  },
  {
    name: "welcome",
    description: "Welcome user to use this chatbot",
    parameters: {
      type: "object",
      properties: {
        message: {
          type: "string",
          description: "The message to welcome user",
        },
      },
    },
    required: ["message"],
  },
)

Once you've created a Tool, you can then pass it into agent.pickTool().

ts
agent.withContext(`
User: Hello
`, async () => {
  const { error, useIt, func, args } = await agent.pickTool([tool])

  // func => function name to invoke
  // args => function args

  if (!error) {
    useIt() // invoke tool function with parameter
    // typically will log "Hello! How can I assist you today"
  }
})

The args should be an object-literal with any key-value.

The error will be string if Model response with invalid JSON format, or not match to the schema, or tool validate function return false.

You can also pass in multiple tools, and the Agent will choose an appropriate one that fits the conversation context.

ts
// Agent will pick one of these tools
const result = await agent.pickTool([
  showMessageTool,
  guideUserTool,
])

WARNING

.pickTool() does not support auto correction currently.

validate

You can pass your validation function to validate Model response.

It accept a args object with any key with value.

Please return true when it is valid, or useTool() will failed with error message.

Here is its signature:

ts


const tool = new Tool(
  /** tool function */,
  /** tool schema */,

  // validate
  // type ToolValidateFunction = (args: Record<string, any>) => boolean
  (args) => {
    return typeof args.name === 'string' && args.name.length > 3
  }
)

type the tool function

It is possible to define args type and return type use generics

ts
const tool = new Tool<{ message: string }, string>(
  ({ message }) => {
    return "should be string"
  }
)

Correction

NL functions come with an automatic correction. If the model's initial response doesn't match the format, it will automatically prompt for a correction. If it still doesn't conform to the format, the original response from the model will be returned.

You can also directly utilize this function.

ts
const wrong = "Ye"
const correct = "Yes"
await agent.correction(wrong, correct) // typically "Yes"

const wrong: "lo"
const choices = ["login", "checkout"]
await agent.correctionByChoices(wrong, choices) // typically "login"

Event and Suggest Actions

Agent has a method .suggestActions(), which can suggest 1 or more actions based on Event records. This is useful when suggest next actions to user.

To record the event, use .recordEvent(). We can call it multiple times

ts
agent.recordEvent("User enter the page: Index")
agent.recordEvent("User enter the page: Product")
agent.recordEvent("User click the 'detail' button fo Macbook Pro 13")
agent.recordEvent("User enter the page: Product Detail")
agent.recordEvent("User is viewing Product info Macbook Pro 13")

// will make the record like this
/**
 * 
  User is viewing Product info Macbook Pro 13
  User enter the page: Product Detail
  User click the 'detail' button fo Macbook Pro 13
  User enter the page: Product
  User enter the page: Index
 */

After recording, we can ask Agent to .suggestActions().

ts
await agent.suggestActions([
  { id: "login", description: "Login" },
  { id: "register", description: "Register" },
  { id: "addToCart", description: "Add product to cart" },
  { id: "addProductToFavorite", description: "Add product to favorite list" },
])

/**
 * [
 *   { id: "addToCart", description: "Add product to cart" },
 *   { id: "addProductToFavorite", description: "Add product to favorite list" }
 * ]
 */

You can also add data to your action item for convenience. data is a object-literal can contain anything you need.

ts
await agent.suggestActions([
  // .....
  { id: "addToCart", 
    description: "Add product to cart", 
    data: { 
      action: () => {},
      product: { /** product data */}
      // .....
    } 
  },
  // ...
])

INFO

The max record now is 10. When the 11-th record comes-in, use First in, First out logic to remove record.