From fda6971a4636fce2c1175b36a72b36cef1404977 Mon Sep 17 00:00:00 2001 From: extio-de Date: Sat, 28 Jun 2025 17:26:38 +0200 Subject: [PATCH] Updated README --- README.md | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index daa0367..7690a42 100644 --- a/README.md +++ b/README.md @@ -171,9 +171,186 @@ Streaming is also supported. See class AgentTest for examples. -# Agent implementations +## Documentation for `BaseAgent` Interface Methods +This document details the public methods of the `BaseAgent` interface, providing developers with information on their purpose and usage within agentic LLM flows. These methods allow customization of agent behavior, pre/post processing of contexts, merging of results, and choosing the next agent in a flow. +**1. `String name()`** + +* **Purpose:** Returns a unique string identifier for this agent. This name is used to reference the agent within an agent flow and for logging/debugging purposes. +* **Usage:** Implementations *must* override this method to return a meaningful name. This name should be consistent across all instances of the same agent type. +* **Returns:** A `String` representing the agent's name. +* **Throws:** `UnsupportedOperationException` if not implemented. + +**2. `AgentType agentType()`** + +* **Purpose:** Defines the core functionality or role of this agent within the overall flow. The `AgentType` enum (not shown in provided code but implied) likely defines categories like "completion", "processing", "start conversation" etc., influencing how the agent interacts with the LLM client and other agents. +* **Usage:** Implementations *must* override this method to return the appropriate `AgentType`. This is crucial for controlling the execution logic within the `execute()` method. +* **Returns:** An `AgentType` enum value. +* **Throws:** `UnsupportedOperationException` if not implemented. + +**3. `ModelCategory modelCategory()`** + +* **Purpose:** Specifies the desired size/capability category of the language model to use when interacting with the LLM client. This allows for cost optimization or performance tuning by selecting models appropriate for the task. +* **Usage:** Implementations can optionally override this method to customize the model category. The default implementation returns `ModelCategory.MEDIUM`. +* **Returns:** A `ModelCategory` enum value. +* **Default:** `ModelCategory.MEDIUM` + +**4. `String systemPrompt()`** + +* **Purpose:** Provides a system-level instruction that sets the context and behavior for the LLM. This prompt is prepended to user input when creating a conversation. +* **Usage:** Implementations can optionally override this method to provide a custom system prompt tailored to their specific task. If no prompt is needed, it should return `null`. +* **Returns:** A `String` containing the system prompt. +* **Default:** `null` + +**5. `String textTemplate()`** + +* **Purpose:** Defines a template string for generating input text for the LLM. The template can include placeholders (e.g., `{{key}}`) which are dynamically replaced with values from the `AgentContext`. `[[key]]` style placeholders are a convenient way to branch out / parallelize execution flows. See also the section about text template placeholders below. +* **Usage:** Implementations can optionally override this method to define a template. This is useful for constructing prompts based on data in the agent context. +* **Returns:** A `String` containing the text template. +* **Default:** `null` + +**6. `AgentResponseHandler responseHandler()`** + +* **Purpose:** Provides a mechanism for processing the raw LLM response. Different handlers can parse responses into structured data, extract specific information, or perform validation. +* **Usage:** Implementations can optionally override this method to return a custom `AgentResponseHandler`. The default implementation returns a `TextAgentResponseHandler` configured to handle responses under the "response" key. +* **Returns:** An `AgentResponseHandler` instance. +* **Default:** `new TextAgentResponseHandler("response")` + +**7. `void preProcess(final AgentContext context)`** + +* **Purpose:** Allows performing actions *before* the main logic of the agent is executed. This could involve modifying the `AgentContext`, preparing data, or setting up necessary resources. +* **Usage:** Implementations can optionally override this method to add pre-processing steps. +* **Parameters:** `context`: The `AgentContext` object representing the current state of the agent flow. + +**8. `void postProcess(final AgentContext context)`** + +* **Purpose:** Allows performing actions *after* the main logic of the agent is executed. This could involve cleaning up resources, updating the `AgentContext`, or logging results. +* **Usage:** Implementations can optionally override this method to add post-processing steps. +* **Parameters:** `context`: The `AgentContext` object representing the current state of the agent flow. + +**9. `List merge(final List contexts)`** + +* **Purpose:** Combines multiple `AgentContext` objects into a collection of potentially new `AgentContext` objects. This method provides flexible context aggregation, supporting scenarios beyond simply reducing many contexts to one. It allows for one-to-one, one-to-many, many-to-one, and many-to-many merging strategies. The merge() step is executed after all branch executions (depth 1) and their postProcess() steps have been completed. +* **Usage:** Implementations can optionally override this method to define a custom merging strategy tailored to their specific needs. The default implementation returns `null`, indicating no merging is performed. A non-null return value signifies the merged contexts. +* **Parameters:** `contexts`: A list of `AgentContext` objects to merge. +* **Returns:** A `List` containing the resulting merged contexts. The size of the returned list can vary depending on the implemented merging logic: + * **One-to-One:** If each input context results in exactly one output context, the returned list will have the same size as the input list. + * **Many-to-One:** If multiple input contexts are combined into a single output context, the returned list will typically contain only one element. + * **One-to-Many:** If a single input context generates multiple output contexts, the returned list will be larger than the input list. + * **Many-to-Many:** The returned list’s size may not directly correlate with the input list’s size, reflecting a more complex merging relationship. +* **Default:** `null` (no merging occurs). + +**Important Considerations:** + +The flexibility of the `merge()` method allows developers to implement sophisticated context aggregation strategies based on the requirements of their agentic flow. For example, you could use it to: + +* Combine data from parallel executions (depth 1). +* Split a single context into multiple contexts based on certain criteria. +* Filter or transform contexts during the merging process. +* Create new contexts based on relationships between existing ones. + +**10. `AgentNext chooseNextAgent(final AgentContext context)`** + +* **Purpose:** Determines which agent should be executed next in the flow based on the current `AgentContext`. +* **Usage:** Implementations *must* override this method to implement the logic for selecting the next agent. It should return an `AgentNext` enum value indicating either the name of the next agent or `AgentNext.END` to signal the end of the flow. +* **Parameters:** `context`: The `AgentContext` object representing the current state of the agent flow. +* **Returns:** An `AgentNext` enum value. +* **Throws:** `UnsupportedOperationException` if not implemented. + +## Important Considerations for Overall Agent Flow & AgentContext Flags + +Beyond the individual methods, several parameters and flags within the `AgentContext` significantly influence the behavior of the overall agent flow. Developers should understand these to fine-tune their agents effectively. Here's a breakdown: + +**1. `skipNextCompletion` Flag (Boolean):** + +* **Purpose:** Controls whether the LLM client (`client.conversation()` or `client.streamConversation()`) is called for the next turn in the conversation. This is useful for agents that perform processing *without* requiring an LLM completion. +* **Usage:** Set this flag to `true` within an agent's `preProcess()` or `postProcess()` method if the agent only needs to manipulate the context and doesn’t require generating text from the LLM. It's automatically reset after the completion attempt (or skip) within the `execute()` method. +* **Impact:** Reduces API calls and processing time when an LLM interaction isn't necessary. + +**2. `isStreaming` Flag (Boolean):** + +* **Purpose:** Enables or disables streaming responses from the LLM. Streaming provides incremental results as they become available, improving perceived responsiveness. +* **Usage:** Set this flag in the `AgentContext` before calling `execute()`. Agents can also dynamically adjust this flag based on conditions during execution. +* **Impact:** Affects how the response is handled by the `responseHandler()`. If `true`, a `StreamedAgentResponseHandler` is expected to handle chunks of data instead of a complete response. You can register a callback handler (Consumer) when calling AgentExecutorService.execute() to receive streamed responses. + +**3. `conversation` (Conversation Object):** + +* **Purpose:** Stores the ongoing conversation history with the LLM. This is crucial for maintaining context across multiple turns. +* **Usage:** Agents should update the `conversation` object with each turn. The `setupConversation()` method handles initial creation and potential merging of system prompts. +* **Impact:** Directly influences the LLM's behavior by providing conversational context. + +**4. `requestStatistic` (Object tracking request details - implementation not shown):** + +* **Purpose:** Collects statistics about API requests made by the agent. +* **Usage:** The `execute()` method adds each `Completion` to this statistic tracker. +* **Impact:** Helps monitor cost, performance, and identify potential issues with API calls. + +**5. `error` Flag (Boolean):** + +* **Purpose:** Indicates whether an error occurred during the agent’s execution. +* **Usage:** Set to `true` within `postProcess()` or during exception handling in `execute()`. +* **Impact:** Used by the `execute()` method to filter out erroneous results and prevent further processing of failed agents. + +**6. `graph` (Object representing the execution graph - implementation not shown):** + +* **Purpose:** A visual representation of the agent flow, showing the path taken through different agents and any branching that occurred. +* **Usage:** Agents add nodes to the graph using methods like `add()`, indicating their execution and status. +* **Impact:** Valuable for debugging, understanding complex flows, and visualizing the decision-making process. + +**7. Custom Context Keys:** + +* **Purpose:** The `AgentContext` allows storing arbitrary data in its `context` map (a `HashMap`). +* **Usage:** Developers can add custom keys and values to the context to share information between agents or track application-specific state. This is heavily leveraged by the template system. +* **Impact:** Enables highly flexible communication and data sharing within the agentic flow. + +## Text Template Placeholders + +**1. `{{{key}}}` - JSON String Replacement** + +* **Purpose:** This placeholder type expects the value associated with the given `key` in the `AgentContext` to be a valid JSON-serializable object (e.g., a string, number, boolean, list, or map). The entire object will be converted into a JSON string using an `ObjectMapper` and inserted into the template. +* **How it works:** The code iterates through the entries in the `AgentContext`'s context map (`context.getContext()`). If it finds a key matching the placeholder (e.g., `{{{myObject}}}`), it attempts to serialize the corresponding value into a JSON string using `objectMapper.writeValueAsString(entry.getValue())`. +* **Example:** + * If `context.getContext().get("myObject")` returns `[1, 2, 3]`, then `{{{myObject}}}` would be replaced with `"[1, 2, 3]"`. + * If `context.getContext().get("userName")` returns `"Alice"`, then `{{{userName}}}` would be replaced with `"Alice"`. + +**2. `{{key}}` - Text / List-to-String Conversion** + +* **Purpose:** This placeholder type expects the value associated with the given `key` in the `AgentContext` to be a *List* of objects. It joins the elements of this list into a single string, separated by newline characters (`\n\n`). +* **How it works:** The code checks if `context.getContext().get(key)` returns a List. If so, it uses `entry.getValue().stream().map(Object::toString).collect(Collectors.joining("\n\n"))` to convert each element of the list to its string representation and concatenate them with double newlines. +* **Example:** + * If `context.getContext().get("item")` returns `["apple"]`, then `{{items}}` would be replaced with: + ``` + apple + ``` + * If `context.getContext().get("names")` returns `[1, 2, 3]`, then `{{names}}` would be replaced with: + ``` + 1 + 2 + 3 + ``` + +**3. `[[key]]` - Branched Template Expansion (Advanced)** + +* **Purpose:** This placeholder enables branching or looping within the template based on the size of a list stored in the `AgentContext`. It's used to create multiple variations of the template, one for each element in the list. +* **How it works:** + * The code identifies all keys using the `[[key]]` pattern. + * It determines the minimum size of all lists associated with these branched keys. This minimum size dictates how many "splits" are created. + * For each index from 0 up to the minimum size, it creates a new `Split` object. Each split has: + * A modified version of the template where `[[key]]` is replaced with the element at that index from each corresponding list. + * A new `AgentContext` containing only the selected elements from the branched lists. +* **Example:** + * If `context.getContext().get("[[cities]]")` returns `["London", "Paris", "Tokyo"]` and `context.getContext().get("[[countries]]")` returns `["UK", "France", "Japan"]`, then two splits will be created (because the minimum size is 2). + * **Split 1:** `text` will have `[[cities]]` replaced with `"London"` and `[[countries]]` replaced with `"UK"`. The context will contain only `{"cities": ["London"], "countries": ["UK"]}`. + * **Split 2:** `text` will have `[[cities]]` replaced with `"Paris"` and `[[countries]]` replaced with `"France"`. The context will contain only `{"cities": ["Paris"], "countries": ["France"]}`. + +**In Summary:** + +| Placeholder | Value Type Expected | Replacement Behavior | +|-------------|---------------------|-----------------------------------------------------------| +| `{{{key}}}` | Any JSON-serializable | Serializes the entire value to a JSON string. | +| `{{key}}}` | List | Joins list elements into a string with newlines. | +| `[[key]]` | List | Creates multiple template variations based on list size. | # Announcement generated by AgentTest#agenticFlow