Skip to main content

Inspect logs from a Guard

All Guard calls are logged internally, and can be accessed via the guard history.

Accessing logs via Guard.history

Each entry in the history stack is a Call log which will contain information specific to a particular Guard.__call__ or Guard.parse call in the order that they were executed within the current session.

For example, if you have a guard:

my_guard = Guard.from_rail(...)

and you call it multiple times:

response_1 = my_guard(...)

response_2 = my_guard.parse(...)

Then guard.history will have two call logs with the first representing the first call response_1 = my_guard(...) and the second representing the following parse call response_2 = my_guard.parse(...).

To pretty print logs for the latest call, run:

from rich import print

print(guard.history.last.tree)
Logs
└── ╭────────────────────────────────────────────────── Step 0 ───────────────────────────────────────────────────╮
╭──────────────────────────────────────────────── Prompt ─────────────────────────────────────────────────╮
│ │
│ You are a human in an enchanted forest. You come across opponents of different types. You should fight │
│ smaller opponents, run away from bigger ones, and freeze if the opponent is a bear. │
│ │
│ You run into a grizzly. What do you do? │
│ │
│ │
│ Given below is XML that describes the information to extract from this document and the tags to extract │
│ it into. │
│ │
│ <output> │
│ <choice name="action" discriminator="chosen_action"> │
│ <case name="fight"> │
│ <string name="weapon" format="valid-choices: choices=['crossbow', 'axe', 'sword', │
│ 'fork']"/> │
│ </case> │
│ <case name="flight"> │
│ <string name="flight_direction" format="valid-choices: choices=['north', 'south', 'east', │
│ 'west']"/> │
│ <integer name="distance" format="valid-choices: choices=[1, 2, 3, 4]"/> │
│ </case> │
│ <case name="freeze"> │
│ <integer name="duration" format="valid-choices: choices=[1, 2, 3, 4]"/> │
│ </case> │
│ </choice> │
│ </output> │
│ │
│ │
│ ONLY return a valid JSON object (no other text is necessary), where the key of the field in JSON is the │
│ `name` attribute of the corresponding XML, and the value is of the type specified by the corresponding │
│ XML's tag. The JSON MUST conform to the XML format, including any types and format requests e.g. │
│ requests for lists, objects and specific types. Be correct and concise. │
│ │
│ Here are examples of simple (XML, JSON) pairs that show the expected behavior: │
│ - `<string name='foo' format='two-words lower-case' />` => `{'foo': 'example one'}` │
│ - `<list name='bar'><string format='upper-case' /></list>` => `{"bar": ['STRING ONE', 'STRING TWO', │
│ etc.]}` │
│ - `<object name='baz'><string name="foo" format="capitalize two-words" /><integer name="index" │
│ format="1-indexed" /></object>` => `{'baz': {'foo': 'Some String', 'index': 1}}` │
│ │
│ │
│ │
│ Here are a few examples │
│ │
│ goblin: {"action": {"chosen_action": "fight", "weapon": "crossbow"}} │
│ giant: {"action": {"chosen_action": "flight", "flight_direction": "north", "distance": 1}} │
│ dragon: {"action": {"chosen_action": "flight", "flight_direction": "south", "distance": 4}} │
│ troll: {"action": {"chosen_action": "fight", "weapon": "sword"}} │
│ black bear: {"action": {"chosen_action": "freeze", "duration": 3}} │
│ beets: {"action": {"chosen_action": "fight", "weapon": "fork"}} │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────── Instructions ──────────────────────────────────────────────╮
│ You are a helpful assistant, able to express yourself purely through JSON, strictly and precisely │
│ adhering to the provided XML schemas. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── Message History ────────────────────────────────────────────╮
│ No message history. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── Raw LLM Output ─────────────────────────────────────────────╮
│ {"action": {"chosen_action": "freeze", "duration": 4}} │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────────── Validated Output ────────────────────────────────────────────╮
│ {'action': {'chosen_action': 'freeze', 'duration': 4}} │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯


The Call log will contain initial and final information about a particular guard call.

first_call = my_guard.history.first

For example, it tracks the initial inputs as provided:

print("prompt\n-----")
print(first_call.prompt)
print("prompt params\n------------- ")
print(first_call.prompt_params)
prompt
-----

You are a human in an enchanted forest. You come across opponents of different types. You should fight smaller opponents, run away from bigger ones, and freeze if the opponent is a bear.

You run into a ${opp_type}. What do you do?

${gr.complete_json_suffix_v2}


Here are a few examples

goblin: {"action": {"chosen_action": "fight", "weapon": "crossbow"}}
troll: {"action": {"chosen_action": "fight", "weapon": "sword"}}
giant: {"action": {"chosen_action": "flight", "flight_direction": "north", "distance": 1}}
dragon: {"action": {"chosen_action": "flight", "flight_direction": "south", "distance": 4}}
black bear: {"action": {"chosen_action": "freeze", "duration": 3}}
beets: {"action": {"chosen_action": "fight", "weapon": "fork"}}

prompt params
-------------
{'opp_type': 'grizzly'}

as well as the final outputs:

print("status: ", first_call.status) # The final status of this guard call
print("validated response:", first_call.validated_output) # The final valid output of this guard call
status:  pass
validated response: {'action': {'chosen_action': 'freeze', 'duration': 3}}

The Call log also tracks cumulative values from any iterations that happen within the call.

For example, if the first response from the LLM fails validation and a reask occurs, the Call log can provide total tokens consumed (*currently only for OpenAI models), as well as access to all of the raw outputs from the LLM:

print("prompt token usage: ", first_call.prompt_tokens_consumed) # Total number of prompt tokens consumed across iterations within this call
print("completion token usage: ", first_call.completion_tokens_consumed) # Total number of completion tokens consumed across iterations within this call
print("total token usage: ",first_call.tokens_consumed) # Total number of tokens consumed; equal to the sum of the two values above
print("llm responses\n-------------") # An Stack of the LLM responses in order that they were received
for r in first_call.raw_outputs:
print(r)
prompt token usage:  909
completion token usage: 57
total token usage: 966

llm responses
-------------
{"action": {"chosen_action": "freeze"}}
{
"action": {
"chosen_action": "freeze",
"duration": null
}
}
{
"action": {
"chosen_action": "freeze",
"duration": 1
}
}

For more information on Call, see the History & Logs page.

🇻🇦 Accessing logs from individual steps

In addition to the cumulative values available directly on the Call log, it also contains a Stack of Iteration's. Each Iteration represent the logs from within a step in the guardrails process. This includes the call to the LLM, as well as parsing and validating the LLM's response.

Each Iteration is treated as a stateless entity so it will only contain information about the inputs and outputs of the particular step it represents.

For example, in order to see the raw LLM response as well as the logs for the specific validations that failed during the first step of a call, we can access this information via that steps Iteration:

first_step = first_call.iterations.first

first_llm_output = first_step.raw_output
print("First LLM response\n------------------")
print(first_llm_output)
print(" ")

validation_logs = first_step.validator_logs
print("\nValidator Logs\n--------------")
for log in validation_logs:
print(log.json(indent=2))
First LLM response
------------------
{"action": {"chosen_action": "fight", "weapon": "spoon"}}


Validator Logs
--------------
{
"validator_name": "ValidChoices",
"value_before_validation": "spoon",
"validation_result": {
"outcome": "fail",
"metadata": null,
"error_message": "Value spoon is not in choices ['crossbow', 'axe', 'sword', 'fork'].",
"fix_value": null
},
"value_after_validation": {
"incorrect_value": "spoon",
"fail_results": [
{
"outcome": "fail",
"metadata": null,
"error_message": "Value spoon is not in choices ['crossbow', 'axe', 'sword', 'fork'].",
"fix_value": null
}
],
"path": [
"action",
"weapon"
]
}
}

Similar to the Call log, we can also see the token usage for just this step:

print("prompt token usage: ", first_step.prompt_tokens_consumed)
print("completion token usage: ", first_step.completion_tokens_consumed)
print("token usage for this step: ",first_step.tokens_consumed)
prompt token usage:  617
completion token usage: 16
token usage for this step: 633

For more information on the properties available on Iteration, see the History & Logs page.