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.