自定义特性
特性提供了一种在运行时扩展和增强 AI 代理功能的方法。它们被设计为模块化且可组合,允许您根据需要进行混合和匹配。
除了 Koog 开箱即用的特性外,您还可以通过扩展适当的特性接口来实现自己的特性。本页面介绍了使用当前 Koog API 构建自定义特性的基本组件。
特性接口
Koog 提供了以下接口,您可以扩展这些接口来实现自定义特性:
- AIAgentGraphFeature:表示特定于已定义工作流的代理(基于图的代理)的特性。
- AIAgentFunctionalFeature:表示可用于函数式代理的特性。
- AIAgentPlannerFeature:表示特定于规划器代理的特性类型。
NOTE
要创建一个可以安装在基于图的代理、函数式代理和规划器代理中的自定义特性,您需要实现所有接口。
实现自定义特性
要实现自定义特性,您需要按照以下步骤创建特性结构:
- 创建特性类。
- 定义配置类。配置类是 FeatureConfig 类的扩展。
- 创建一个实现以下部分或全部接口的伴生对象:AIAgentGraphFeature、AIAgentFunctionalFeature、AIAgentPlannerFeature。
- 为您的特性提供一个唯一的存储键 (storage key),用于在代理流水线中进行特性识别和检索。该键用于代理流水线内部的 map 中,该 map 包含代理的所有已注册特性。运行代理时,它需要处理所有已注册特性,而该键则用于从该 map 中检索特性。
- 实现所需的方法。
下面的代码示例展示了实现自定义特性的通用模式,该特性可以安装在基于图的代理、函数式代理和规划器代理中:
class MyFeature(val someProperty: String) {
class Config : FeatureConfig() {
var configProperty: String = "default"
}
companion object Feature : AIAgentGraphFeature<Config, MyFeature>, AIAgentFunctionalFeature<Config, MyFeature>, AIAgentPlannerFeature<Config, MyFeature> {
// 用于在上下文中检索的唯一存储键
override val key = createStorageKey<MyFeature>("my-feature")
override fun createInitialConfig(agentConfig: AIAgentConfig): Config = Config()
// 针对基于图的代理的特性安装
override fun install(config: Config, pipeline: AIAgentGraphPipeline) : MyFeature {
val feature = MyFeature(config.configProperty)
pipeline.interceptAgentStarting(this) { context ->
// 事件处理程序实现
}
return feature
}
// 针对函数式代理的特性安装
override fun install(config: Config, pipeline: AIAgentFunctionalPipeline) : MyFeature {
val feature = MyFeature(config.configProperty)
pipeline.interceptAgentStarting(this) { context ->
// 事件处理程序实现
}
return feature
}
// 针对规划器代理的特性安装
override fun install(config: Config, pipeline: AIAgentPlannerPipeline) : MyFeature {
val feature = MyFeature(config.configProperty)
pipeline.interceptAgentStarting(this) { context ->
// 事件处理程序实现
}
return feature
}
}
}创建代理时,使用 install 方法安装您的特性:
val agent = AIAgent(
promptExecutor = simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")),
systemPrompt = "You are a helpful assistant. Answer user questions concisely.",
llmModel = OpenAIModels.Chat.GPT4o
) {
install(MyFeature) {
configProperty = "value"
}
}流水线拦截器
拦截器代表了代理生命周期中的各个点,您可以挂载到代理执行流水线中以实现自定义逻辑。Koog 包含一系列预定义的拦截器,您可以使用它们来观察各种事件。
以下是您可以从特性的 install 方法中注册的拦截器。列出的拦截器按类型分组,适用于基于图的代理、函数式代理和规划器代理流水线。为了在开发实际特性时减少噪音并优化成本,请仅注册特性所需的拦截器。
代理和环境生命周期:
interceptEnvironmentCreated:在代理环境创建时对其进行转换。interceptAgentStarting:在代理运行开始前调用。interceptAgentCompleted:在代理运行成功完成时调用。interceptAgentExecutionFailed:在代理运行失败时调用。interceptAgentClosing:在代理运行关闭前调用(清理点)。
策略生命周期:
interceptStrategyStarting:在策略执行开始前调用。interceptStrategyCompleted:在策略执行成功完成时调用。
LLM 调用生命周期:
interceptLLMCallStarting:在 LLM 调用前调用。interceptLLMCallFailed:在 LLM 调用失败时调用(底层 prompt 执行器或审查调用抛出异常)。interceptLLMCallCompleted:在 LLM 调用后调用。
LLM 流式传输生命周期:
interceptLLMStreamingStarting:在流式传输开始前调用。interceptLLMStreamingFrameReceived:为每个接收到的流帧调用。interceptLLMStreamingFailed:在流式传输失败时调用。interceptLLMStreamingCompleted:在流式传输完成后调用。
工具调用生命周期:
interceptToolCallStarting:在工具调用前调用。interceptToolValidationFailed:在工具输入验证失败时调用。interceptToolCallFailed:在工具执行失败时调用。interceptToolCallCompleted:在工具完成(带结果)后调用。
特定于基于图的代理的拦截器
以下拦截器仅在 AIAgentGraphPipeline 上可用,允许您观察节点和子图的生命周期事件。
节点执行生命周期:
interceptNodeExecutionStarting:在节点开始执行前调用。interceptNodeExecutionCompleted:在节点执行完成后调用。interceptNodeExecutionFailed:在节点执行失败并报错时调用。
子图执行生命周期:
interceptSubgraphExecutionStarting:在子图开始执行前调用。interceptSubgraphExecutionCompleted:在子图执行完成后调用。interceptSubgraphExecutionFailed:在子图执行失败时调用。
为了让特性处理特定类型的事件,它需要注册相应的流水线拦截器。
过滤代理事件
在代理中安装特性时,您可能不想处理该特性中注册的所有事件。要过滤掉某些事件,您可以使用 FeatureConfig.setEventFilter 函数应用过滤器。
以下示例展示了如何仅允许特性的 LLM 调用开始和结束事件:
install(MyFeature) {
setEventFilter { context ->
context.eventType is AgentLifecycleEventType.LLMCallStarting ||
context.eventType is AgentLifecycleEventType.LLMCallCompleted
}
}禁用特性的事件过滤
如果您的特性逻辑依赖于完整的代理事件结构,事件过滤可能会导致非预期的行为。为了防止这种情况,您需要在实现特性时通过在特性配置中重写 setEventFilter 来禁用事件过滤,从而忽略安装特性时设置的任何自定义过滤器。
依赖于处理整个代理事件流的一个特性示例是 OpenTelemetry,因为它使用完整的代理事件结构来组合 span 的继承结构。
以下是如何禁用特性的事件过滤的示例:
class MyFeatureConfig : FeatureConfig() {
override fun setEventFilter(filter: (AgentLifecycleEventContext) -> Boolean) {
// 停用该特性的事件过滤
throw UnsupportedOperationException("Event filtering is not allowed.")
}
}示例:基础日志特性
以下示例展示了如何实现一个基础日志特性,该特性用于记录代理生命周期事件。由于该特性应可用于基于图的代理、函数式代理和规划器代理,因此所有代理类型通用的拦截器都在 installCommon 方法中实现,以避免代码重复。特定于各代理类型的拦截器则在 installGraphPipeline、installFunctionalPipeline 和 installPlannerPipeline 方法中实现。
class LoggingFeature(val loggerName: String) {
class Config : FeatureConfig() {
var loggerName: String = "agent-logs"
}
companion object Feature :
AIAgentGraphFeature<Config, LoggingFeature>,
AIAgentFunctionalFeature<Config, LoggingFeature>,
AIAgentPlannerFeature<Config, LoggingFeature> {
override val key = createStorageKey<LoggingFeature>("logging-feature")
override fun createInitialConfig(agentConfig: AIAgentConfig): Config = Config()
override fun install(config: Config, pipeline: AIAgentGraphPipeline) : LoggingFeature {
val logging = LoggingFeature(config.loggerName)
val logger = KotlinLogging.logger(config.loggerName)
installGraphPipeline(pipeline, logger)
return logging
}
override fun install(config: Config, pipeline: AIAgentFunctionalPipeline) : LoggingFeature {
val logging = LoggingFeature(config.loggerName)
val logger = KotlinLogging.logger(config.loggerName)
installFunctionalPipeline(pipeline, logger)
return logging
}
override fun install(config: Config, pipeline: AIAgentPlannerPipeline) : LoggingFeature {
val logging = LoggingFeature(config.loggerName)
val logger = KotlinLogging.logger(config.loggerName)
installPlannerPipeline(pipeline, logger)
return logging
}
private fun installCommon(
pipeline: AIAgentPipeline,
logger: KLogger,
) {
pipeline.interceptAgentStarting(this) { e ->
logger.info { "Agent starting: runId=${e.runId}" }
}
pipeline.interceptStrategyStarting(this) { e ->
logger.info { "Strategy ${e.strategy.name} starting" }
}
pipeline.interceptLLMCallStarting(this) { e ->
logger.info { "Making LLM call with ${e.tools.size} tools" }
}
pipeline.interceptLLMCallCompleted(this) { e ->
logger.info { "Received ${e.responses.size} response(s)" }
}
}
private fun installGraphPipeline(
pipeline: AIAgentGraphPipeline,
logger: KLogger,
) {
installCommon(pipeline, logger)
pipeline.interceptNodeExecutionStarting(this) { e ->
logger.info { "Node ${e.node.name} input: ${e.input}" }
}
pipeline.interceptNodeExecutionCompleted(this) { e ->
logger.info { "Node ${e.node.name} output: ${e.output}" }
}
}
private fun installFunctionalPipeline(
pipeline: AIAgentFunctionalPipeline,
logger: KLogger
) {
installCommon(pipeline, logger)
}
private fun installPlannerPipeline(
pipeline: AIAgentPlannerPipeline,
logger: KLogger
) {
installCommon(pipeline, logger)
}
}
}以下是在代理中安装自定义日志特性的示例。该示例展示了基本的特性安装,以及自定义配置属性 loggerName,它允许您指定记录器的名称:
val agent = AIAgent(
promptExecutor = simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")),
systemPrompt = "You are a helpful assistant. Answer user questions concisely.",
llmModel = OpenAIModels.Chat.GPT4o
) {
install(LoggingFeature) {
loggerName = "my-custom-logger"
}
}
agent.run("What is Kotlin?")