-
Notifications
You must be signed in to change notification settings - Fork 295
Add custom feature creation documentation #1295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,254 @@ | ||
| # Custom features | ||
|
|
||
| Features provide a way to extend and enhance the functionality of AI agents at runtime. They are designed to be modular | ||
| and composable, allowing you to mix and match them according to your needs. | ||
|
|
||
| In addition to features that are available in Koog out of the box, you can also implement your own features by | ||
| extending a proper feature interface. This page presents the basic building blocks for your own feature using the | ||
| current Koog APIs. | ||
|
|
||
| ## Feature interfaces | ||
|
|
||
| Koog provides two interfaces that you can extend to implement custom features: | ||
|
|
||
| - `AIAgentGraphFeature`: Represents a feature specific to [agents that have defined workflows](complex-workflow-agents.md) (graph-based agents). | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| - `AIAgentFunctionalFeature`: Represents a feature that can be used with [functional agents](functional-agents.md). | ||
|
|
||
| !!! note | ||
| To create a custom feature that can be installed in both graph-based and functional agents, you need to extend both | ||
| interfaces. | ||
|
|
||
| ## Implementing custom features | ||
|
|
||
| To implement a custom feature, you need to create a feature structure according to the following steps: | ||
|
|
||
| 1. Create a feature class. | ||
| 2. Define a configuration class. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention that it's an extension of the |
||
| 3. Create a companion object that implements `AIAgentGraphFeature`, `AIAgentFunctionalFeature`, or both. | ||
| 4. Give your feature a stable storage key so it can be retrieved in contexts. | ||
| 5. Implement the required methods. | ||
|
|
||
| The code sample below shows the general pattern for implementing a custom feature that can be installed in both graph-based and functional agents: | ||
|
|
||
| <!--- INCLUDE | ||
| import ai.koog.agents.core.agent.entity.createStorageKey | ||
| import ai.koog.agents.core.feature.AIAgentFunctionalFeature | ||
| import ai.koog.agents.core.feature.AIAgentGraphFeature | ||
| import ai.koog.agents.core.feature.config.FeatureConfig | ||
| import ai.koog.agents.core.feature.pipeline.AIAgentFunctionalPipeline | ||
| import ai.koog.agents.core.feature.pipeline.AIAgentGraphPipeline | ||
| --> | ||
| ```kotlin | ||
| class MyFeature(val someProperty: String) { | ||
| class Config : FeatureConfig() { | ||
| var configProperty: String = "default" | ||
| } | ||
|
|
||
| companion object Feature : AIAgentGraphFeature<Config, MyFeature>, AIAgentFunctionalFeature<Config, MyFeature> { | ||
| // Stable storage key for retrieval in contexts | ||
| override val key = createStorageKey<MyFeature>("my-feature") | ||
| override fun createInitialConfig(): Config = Config() | ||
|
|
||
| // Feature installation for graph-based agents | ||
| override fun install(config: Config, pipeline: AIAgentGraphPipeline) : MyFeature { | ||
| val feature = MyFeature(config.configProperty) | ||
|
|
||
| pipeline.interceptAgentStarting(this) { context -> | ||
| // Method implementation | ||
| } | ||
| return feature | ||
| } | ||
|
|
||
| // Feature installation for functional agents | ||
| override fun install(config: Config, pipeline: AIAgentFunctionalPipeline) : MyFeature { | ||
| val feature = MyFeature(config.configProperty) | ||
|
|
||
| pipeline.interceptAgentStarting(this) { context -> | ||
| // Method implementation | ||
| } | ||
| return feature | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| <!--- KNIT example-custom-features-01.kt --> | ||
|
|
||
| When creating an agent, install your feature using the `install` method: | ||
|
|
||
| <!--- INCLUDE | ||
| import ai.koog.agents.core.agent.AIAgent | ||
| import ai.koog.prompt.executor.clients.openai.OpenAIModels | ||
| import ai.koog.prompt.executor.llms.all.simpleOpenAIExecutor | ||
| import ai.koog.agents.features.tracing.feature.Tracing | ||
|
|
||
| val MyFeature = Tracing | ||
| var configProperty = "" | ||
| --> | ||
| ```kotlin | ||
| val agent = AIAgent( | ||
| promptExecutor = simpleOpenAIExecutor(System.getenv("YOUR_API_KEY")), | ||
| systemPrompt = "You are a helpful assistant. Answer user questions concisely.", | ||
| llmModel = OpenAIModels.Chat.GPT4o | ||
| ) { | ||
| install(MyFeature) { | ||
| configProperty = "value" | ||
| } | ||
| } | ||
| ``` | ||
| <!--- KNIT example-custom-features-02.kt --> | ||
|
|
||
| ### Pipeline interceptors | ||
|
|
||
| Interceptors represent various points in the agent lifecycle where you can hook into the agent execution pipeline to | ||
| implement your custom logic. Koog includes a range of predefined interceptors that you can use to observe various | ||
| events. | ||
|
|
||
| Below are the interceptors that you can register from your feature’s `install` method. The listed interceptors are | ||
| grouped by type and apply both the graph-based and functional agent pipelines. To reduce noise and optimize cost when | ||
| developing actual features, register only the interceptors you need for the feature. | ||
|
|
||
| Agent and environment lifecycle: | ||
|
|
||
| - `interceptEnvironmentCreated`: Transform the agent environment when it’s created. | ||
| - `interceptAgentStarting`: Invoked when an agent run starts. | ||
| - `interceptAgentCompleted`: Invoked when an agent run completes successfully. | ||
| - `interceptAgentExecutionFailed`: Invoked when an agent run fails. | ||
| - `interceptAgentClosing`: Invoked just before the agent run closes (cleanup point). | ||
|
|
||
| Strategy lifecycle: | ||
|
|
||
| - `interceptStrategyStarting`: Strategy begins execution. | ||
| - `interceptStrategyCompleted`: Strategy finishes execution. | ||
|
|
||
| LLM call lifecycle: | ||
|
|
||
| - `interceptLLMCallStarting`: Before an LLM call. | ||
| - `interceptLLMCallCompleted`: After an LLM call. | ||
|
|
||
| LLM streaming lifecycle: | ||
|
|
||
| - `interceptLLMStreamingStarting`: Before streaming starts. | ||
| - `interceptLLMStreamingFrameReceived`: For each received stream frame. | ||
| - `interceptLLMStreamingFailed`: When streaming fails. | ||
| - `interceptLLMStreamingCompleted`: After streaming completes. | ||
|
|
||
| Tool call lifecycle: | ||
|
|
||
| - `interceptToolCallStarting`: Before a tool is invoked. | ||
| - `interceptToolValidationFailed`: When tool input validation fails. | ||
| - `interceptToolCallFailed`: When the tool execution fails. | ||
| - `interceptToolCallCompleted`: After the tool completes (with a result). | ||
|
|
||
| #### Interceptors specific to graph-based agents | ||
|
|
||
| The following interceptors are available only on `AIAgentGraphPipeline` and let you observe node and subgraph lifecycle events. | ||
|
|
||
| Node execution lifecycle: | ||
|
|
||
| - `interceptNodeExecutionStarting`: Before a node starts executing. | ||
| - `interceptNodeExecutionCompleted`: After a node finishes executing. | ||
| - `interceptNodeExecutionFailed`: When a node execution fails with an error. | ||
|
|
||
| Subgraph execution lifecycle: | ||
|
|
||
| - `interceptSubgraphExecutionStarting`: Before a subgraph starts. | ||
| - `interceptSubgraphExecutionCompleted`: After a subgraph completes. | ||
| - `interceptSubgraphExecutionFailed`: When a subgraph execution fails. | ||
|
|
||
| Note that interceptors are feature-scoped: only the feature that registers a handler receives those events (subject to | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a note?) Also, I am lost at this point. You never mentioned handlers before this point. Event handlers are a predefined feature, so I am not sure if that's what you mean. The FeatureConfig subclass in your example doesn't have |
||
| any `setEventFilter` you configure in your `FeatureConfig`). | ||
|
|
||
| ### Disabling event filtering for a feature | ||
|
|
||
| Some features, such as debugger and OpenTelemetry, must observe the entire event stream. If your feature depends on the | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Link? |
||
| full event stream, disable event filtering by overriding `setEventFilter` in your feature configuration to ignore custom | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here you mention |
||
| filters and allow all events: | ||
|
|
||
| <!--- INCLUDE | ||
| import ai.koog.agents.core.feature.config.FeatureConfig | ||
| import ai.koog.agents.core.feature.handler.AgentLifecycleEventContext | ||
| import io.github.oshai.kotlinlogging.KotlinLogging | ||
|
|
||
| private val logger = KotlinLogging.logger {} | ||
| --> | ||
| ```kotlin | ||
| class MyFeatureConfig : FeatureConfig() { | ||
| override fun setEventFilter(filter: (AgentLifecycleEventContext) -> Boolean) { | ||
| logger.warn { "Events filtering is not allowed for MyFeature." } | ||
| super.setEventFilter { true } | ||
| } | ||
| } | ||
| ``` | ||
| <!--- KNIT example-custom-features-03.kt --> | ||
|
|
||
| ## Example: A basic logging feature | ||
|
|
||
| The following example shows how to implement a basic logging feature that logs agent lifecycle events. The example | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why "The example" at the end here?) |
||
|
|
||
| <!--- INCLUDE | ||
| import ai.koog.agents.core.agent.entity.createStorageKey | ||
| import ai.koog.agents.core.feature.AIAgentFunctionalFeature | ||
| import ai.koog.agents.core.feature.AIAgentGraphFeature | ||
| import ai.koog.agents.core.feature.config.FeatureConfig | ||
| import ai.koog.agents.core.feature.pipeline.AIAgentFunctionalPipeline | ||
| import ai.koog.agents.core.feature.pipeline.AIAgentGraphPipeline | ||
| import io.github.oshai.kotlinlogging.KLogger | ||
| import io.github.oshai.kotlinlogging.KotlinLogging | ||
|
|
||
| val logger = KotlinLogging.logger {} | ||
| --> | ||
| ```kotlin | ||
| class LoggingFeature(private val logger: KLogger) { | ||
| class Config : FeatureConfig() { | ||
| var loggerName: String = "agent-logs" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need this loggerName if it's not used anywhere? |
||
| } | ||
|
|
||
| companion object Feature : AIAgentGraphFeature<Config, LoggingFeature>, AIAgentFunctionalFeature<Config, LoggingFeature> { | ||
| override val key = createStorageKey<LoggingFeature>("logging-feature") | ||
| override fun createInitialConfig(): Config = Config() | ||
|
|
||
| override fun install(config: Config, pipeline: AIAgentGraphPipeline) : LoggingFeature { | ||
| val logging = LoggingFeature(logger = logger) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where does this |
||
|
|
||
| pipeline.interceptAgentStarting(this) { e -> | ||
| logger.info { "Agent starting: runId=${e.runId}" } | ||
| } | ||
| pipeline.interceptStrategyStarting(this) { _ -> | ||
| logger.info { "Strategy starting" } | ||
| } | ||
| 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}" } | ||
| } | ||
| 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)" } | ||
| } | ||
| return logging | ||
| } | ||
|
|
||
| override fun install(config: Config, pipeline: AIAgentFunctionalPipeline) : LoggingFeature { | ||
| val logging = LoggingFeature(logger = logger) | ||
|
|
||
| pipeline.interceptAgentStarting(this) { e -> | ||
| logger.info { "Agent starting: runId=${e.runId}" } | ||
| } | ||
| pipeline.interceptStrategyStarting(this) { _ -> | ||
| logger.info { "Strategy 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)" } | ||
| } | ||
| return logging | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| <!--- KNIT example-custom-features-04.kt --> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to show how to install the feature, or is it straightforward? In the first example, you pass a string to the property when installing the feature. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe link to
features-overviewand update that page to clearly list the predefined features and mention and link to this new page about custom features from there as well