Skip to main content

Filtering Input/Output

The input and output of Agents can be filtered/transformed using the filterInput and filterOutput blocks respectively.

FilterInput The filterInput blocks have access to the following fields:

Name
inputConversationThe entire conversation.writable
inputMessageConversationMessageThe latest message of the conversation.writable
messageStringThe content of the latest message of the conversation.writable

FilterOutput The filterInput blocks have access to the following fields:

Name
inputConversationThe conversation that was provided as input.readonly
outputConversationThe conversation plus the output message.writable
outputMessageConversationMessageThe message generated by the Agent.writable
messageStringThe content of message generated by the Agent.writable

Overriding In/Output

Filters can be used change the input and output of the conversation. Simply assign a new value to one of the message fields, i.e. message, inputMessage, outputMessage, input, or output.

agent {
name = "weather"
description = "Agent that provides weather data."
prompt { """ Some system prompt """ }
filterInput {
message = "This is the input message $message"
}
}

The breakWith function

The breakWith function can be used to stop the agent from further processing the message and a return a dedicated response. This is similar to the throwing an exception with the difference that the Agent does not fail, but instead returns the provided message.

agent {
name = "weather"
description = "Agent that provides weather data."
prompt { """ Some system prompt """ }
filterInput {
val eval = llm("""
Evaluate if the following input is referring to bananas.
If so return BANANAS otherwise return CONTINUE.
Input: $message""").getOrNull()

if ("BANANAS" == eval?.content) {
breakWith("I cannot answer questions about bananas...")
}
}
}

Running Filters in Parallel

The runAsync function can be used to run one or more filters in parallel.

agent {
name = "weather"
description = "Agent that provides weather data."
prompt { """ Some system prompt """ }
filterInput {
runAsync { +MyFilter() }
runAsync { +MySecondFilter() }
}
}

Caution: The runAsync function should only be used with filters that do not try to modify the Conversation as this will have unpredictable results.

Replacing values

Within the filter blocks the keyword replaces can be used to replace occurrences of values with new values.

Example

agent {
name = "weather"
description = "Agent that provides weather data."
prompt { """ Some system prompt """ }
filterInput {
"A transformed question" replaces "A question"
"A new message replacing the old" replaces ".*".toRegex()
}
}

Removing values

Within the filter blocks the unaryMinus on String and Regex - can be used to remove values.

Example

agent {
name = "weather"
description = "Agent that provides weather data."
prompt { """ Some system prompt """ }
filterInput {
-"Bad Word"
-"Malware.*".toRegex()
}
}

Custom Filter classes

Sometimes filter logic can be complex, or you may wish to reuse filters. In these cases, the AgentFilter interface can be used.

fun interface AgentFilter {
/**
* Filters or transform Conversation Messages.
* If the fun returns null, the message will be removed from the conversation transcript.
*/
suspend fun filter(message: ConversationMessage): ConversationMessage?
}

Any AgentFilter implementation on the classpath, can be added to the filterInput and filterOutput blocks using the UnaryPlus + operator.

For example, adding a custom filter class MyFilter

agent {
name = "weather"
description = "Agent that provides weather data."
prompt { """ Some system prompt """ }
filterInput {
+MyFilter::class // context is used to lookup instance of MyFilter
+MyFilter() // MyFilter is directly instantiated
}
}

Filters can be directly instantiated in the filter blocks, or if the filter class is provided, then a matching bean will be retrieved from the context, for example, the Spring Boot container.