Code blocks

When defining a playbook, you can optionally provide code blocks, which are inline Python code that lets you better control agent behavior. This code consists of functions with special decorators and any utility functions that you require.

When writing your code, you can use the code block system library to control agent behavior.

Limitations

The following limitations apply:

  • Code blocks can't contain any objects that persist data. However, you can use tools to persist data and maintain state.
  • Code blocks can't make remote calls directly. For example, you can't use the Python requests library. However, you can use tools to make remote calls indirectly.
  • Resource names that are converted to Python names must be legal Python names.
  • Code blocks can't write session parameters, unless a flow transition takes place.

Inline actions

Inline actions behave similarly to tool actions. They have a defined input and output schema, which is determined by the Python function signature, including type annotations and docstring. Like tool calls, the LLM is unaware of the code that implements the action.

For example:

@Action
def is_prime(n: int) -> bool:
  """Returns true if n is prime."""
  if not isinstance(n, int) or n < 2:
    return False

  for i in range(2, int(n ** 0.5) + 1):
    if n % i == 0:
      return False

  return True

For this function, the LLM receives information about the action, its input, and its output.

To reference an inline action in your playbook instructions, reference the action name in backticks and describe how to use it.

Here's an example for a place_order inline action:

Take the customer's order, and then call the `place_order` action when the
order is ready.

To create examples that use inline actions, use the inline-action tool type in the Input & Output section.

For more information, see the @Action reference documentation.

Trigger functions

Trigger functions are used to define conditional actions in code.

You declare trigger functions by using decorators.

You can use the following trigger function decorators:

Decorator Decorator Parameters Description
@EventTrigger event: str, condition: str, where condition is optional Triggered by an event
@BeforeModelTrigger condition: str, where condition is optional Triggered every time before the LLM predicts the next action.
@BeforeActionTrigger condition: str, where condition is optional Triggered every time before the LLM executes an action.
@BeforePlaybookTrigger condition: str, where condition is optional Triggered the first time a playbook is started.

For example, these functions show how to use these decorators and decorator parameters, and also how to use the respond code block system library function.

# No decorator parameter
@PlaybookStartTrigger
def my_playbook_conditional_action():
  respond("How can I help?")

# With a condition decorator parameter
@BeforeActionTrigger('$next-action.name = "search"')
def my_before_action_conditional_action():
  respond("One moment please")

# Event
@EventTrigger(event="welcome")
def my_welcome_event():
  respond("hi")

# Event with a condition:
@EventTrigger(event="welcome",
              condition="$sys.func.NOW().hours < 10")
def my_good_morning_event():
  respond("Good morning ☕")

Referencing flows, playbooks, and tools

In your code block, you can reference specific flows, playbooks, and tools using the flows, playbooks, and tools global variables.

Each of these objects has members that match the names of the corresponding resources. These names need to be legal Python names.

For example:

  add_override(playbooks.troubleshooting, {})
  add_override(flows.billing)
  add_override(tools.weather_api.get_weather, {"location": "San Francisco"})

When you reference flows and playbooks in a code block, you must also reference them in the playbook instructions using the following syntax:

${RESOURCE_TYPE: my_resource_name}

For example, if your code block contains flows.myflow and playbooks.myplaybook, your playbook instructions should include:

${FLOW: myflow}
${PLAYBOOK: myplaybook}

Action overrides

Use code blocks to create a queue of actions that will take place before any further actions determined by the LLM, and potentially override them. Create action overrides by using the add_override global function.

All queued override actions execute sequentially, and the action output is available to the LLM. Once the queue is empty, operation returns to the LLM for action and input selection, unless an override finishes the turn with respond or another function that completes the turn.

Function arguments:

  • Action: Specify the action to take place. For an inline action, use my_function_name. For a tool action, use tools.my_tool_name.my_tool_action. For a flow action, use flows.my_flow_name.
  • Inputs: Specify optional inputs for the action, such as {"location": "Omaha"}.

For example:

# Assuming remote tool named "dmv" with operationId "search_offices"
# remote tool with only requestBody
add_override(tools.dmv.search_offices,{"address": "95030"})

# remote tool with only parameters
add_override(tools.pets.get_pet, {"id": "123"})

# remote tool with requestBody + parameters:
add_override(tools.pets.create_pet, {"requestBody": {"arg1":"foo"}, "param1": "bar"})

# datastore. Assumes existing datastore tool named "Menu".
add_override(tools.menu.Menu, {"query": "what is the menu"})

# code block action. Assumes another code block @Action my_action.
add_override(my_action)

Response overrides

You can use the respond global function to force the agent to respond to the user with specific content, similar to action overrides, but specifically for agent responses.

For example:

respond("Hello")

Calling tools

You can call the tools that are defined for your agent in your code block functions. Unlike when you override a tool action, when you call a tool directly, the LLM cannot access the results of the tool execution.

For example:

# Assumes existing tool named "DMV" with operationId "search_offices"
# remote tool with only request body.
offices = tools.dmv.search_offices({"address": "95030"})

# remote tool with parameters and request body
offices = tools.dmv.search_offices({"requestBody": {"address":"95030"}, "param1":"foo"})

# datastore actions. Assumes existing datastore tool named "Menu".
data = tools.menu.Menu({"query": "get the menu"})["snippets"]

Match intent

Code blocks can programmatically match an intent for a given flow using the Flow.match_intent function.

For example:

matches = flows.flow1.match_intent(history.last_user_utterance).matches
if matches and matches[0].intent == "some_intent":
  to_country = matches[0].parameters.get("to_country")
  if to_country:
    respond(f"To confirm, you're going to {to_country}, right?")

Read session parameters

Code blocks can read parameters that are available at the time of execution.

For example:

state["$session.params.my-param"]
state["$request.session-id"]

Debugging

Use the simulator to debug your code block functions. These functions appear as actions in the simulator and provide debugging details as needed.

Additional control

This document covers some common uses of the code block system library. For additional types of control, see the library documentation.