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 | |||
---|---|---|---|
input | Conversation | The entire conversation. | writable |
inputMessage | ConversationMessage | The latest message of the conversation. | writable |
message | String | The content of the latest message of the conversation. | writable |
FilterOutput The filterInput blocks have access to the following fields:
Name | |||
---|---|---|---|
input | Conversation | The conversation that was provided as input. | readonly |
output | Conversation | The conversation plus the output message. | writable |
outputMessage | ConversationMessage | The message generated by the Agent. | writable |
message | String | The 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.