agentrun-sdk 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of agentrun-sdk might be problematic. Click here for more details.

Files changed (115) hide show
  1. agentrun_operation_sdk/cli/__init__.py +1 -0
  2. agentrun_operation_sdk/cli/cli.py +19 -0
  3. agentrun_operation_sdk/cli/common.py +21 -0
  4. agentrun_operation_sdk/cli/runtime/__init__.py +1 -0
  5. agentrun_operation_sdk/cli/runtime/commands.py +203 -0
  6. agentrun_operation_sdk/client/client.py +75 -0
  7. agentrun_operation_sdk/operations/runtime/__init__.py +8 -0
  8. agentrun_operation_sdk/operations/runtime/configure.py +101 -0
  9. agentrun_operation_sdk/operations/runtime/launch.py +82 -0
  10. agentrun_operation_sdk/operations/runtime/models.py +31 -0
  11. agentrun_operation_sdk/services/runtime.py +152 -0
  12. agentrun_operation_sdk/utils/logging_config.py +72 -0
  13. agentrun_operation_sdk/utils/runtime/config.py +94 -0
  14. agentrun_operation_sdk/utils/runtime/container.py +280 -0
  15. agentrun_operation_sdk/utils/runtime/entrypoint.py +203 -0
  16. agentrun_operation_sdk/utils/runtime/schema.py +56 -0
  17. agentrun_sdk/__init__.py +7 -0
  18. agentrun_sdk/agent/__init__.py +25 -0
  19. agentrun_sdk/agent/agent.py +696 -0
  20. agentrun_sdk/agent/agent_result.py +46 -0
  21. agentrun_sdk/agent/conversation_manager/__init__.py +26 -0
  22. agentrun_sdk/agent/conversation_manager/conversation_manager.py +88 -0
  23. agentrun_sdk/agent/conversation_manager/null_conversation_manager.py +46 -0
  24. agentrun_sdk/agent/conversation_manager/sliding_window_conversation_manager.py +179 -0
  25. agentrun_sdk/agent/conversation_manager/summarizing_conversation_manager.py +252 -0
  26. agentrun_sdk/agent/state.py +97 -0
  27. agentrun_sdk/event_loop/__init__.py +9 -0
  28. agentrun_sdk/event_loop/event_loop.py +499 -0
  29. agentrun_sdk/event_loop/streaming.py +319 -0
  30. agentrun_sdk/experimental/__init__.py +4 -0
  31. agentrun_sdk/experimental/hooks/__init__.py +15 -0
  32. agentrun_sdk/experimental/hooks/events.py +123 -0
  33. agentrun_sdk/handlers/__init__.py +10 -0
  34. agentrun_sdk/handlers/callback_handler.py +70 -0
  35. agentrun_sdk/hooks/__init__.py +49 -0
  36. agentrun_sdk/hooks/events.py +80 -0
  37. agentrun_sdk/hooks/registry.py +247 -0
  38. agentrun_sdk/models/__init__.py +10 -0
  39. agentrun_sdk/models/anthropic.py +432 -0
  40. agentrun_sdk/models/bedrock.py +649 -0
  41. agentrun_sdk/models/litellm.py +225 -0
  42. agentrun_sdk/models/llamaapi.py +438 -0
  43. agentrun_sdk/models/mistral.py +539 -0
  44. agentrun_sdk/models/model.py +95 -0
  45. agentrun_sdk/models/ollama.py +357 -0
  46. agentrun_sdk/models/openai.py +436 -0
  47. agentrun_sdk/models/sagemaker.py +598 -0
  48. agentrun_sdk/models/writer.py +449 -0
  49. agentrun_sdk/multiagent/__init__.py +22 -0
  50. agentrun_sdk/multiagent/a2a/__init__.py +15 -0
  51. agentrun_sdk/multiagent/a2a/executor.py +148 -0
  52. agentrun_sdk/multiagent/a2a/server.py +252 -0
  53. agentrun_sdk/multiagent/base.py +92 -0
  54. agentrun_sdk/multiagent/graph.py +555 -0
  55. agentrun_sdk/multiagent/swarm.py +656 -0
  56. agentrun_sdk/py.typed +1 -0
  57. agentrun_sdk/session/__init__.py +18 -0
  58. agentrun_sdk/session/file_session_manager.py +216 -0
  59. agentrun_sdk/session/repository_session_manager.py +152 -0
  60. agentrun_sdk/session/s3_session_manager.py +272 -0
  61. agentrun_sdk/session/session_manager.py +73 -0
  62. agentrun_sdk/session/session_repository.py +51 -0
  63. agentrun_sdk/telemetry/__init__.py +21 -0
  64. agentrun_sdk/telemetry/config.py +194 -0
  65. agentrun_sdk/telemetry/metrics.py +476 -0
  66. agentrun_sdk/telemetry/metrics_constants.py +15 -0
  67. agentrun_sdk/telemetry/tracer.py +563 -0
  68. agentrun_sdk/tools/__init__.py +17 -0
  69. agentrun_sdk/tools/decorator.py +569 -0
  70. agentrun_sdk/tools/executor.py +137 -0
  71. agentrun_sdk/tools/loader.py +152 -0
  72. agentrun_sdk/tools/mcp/__init__.py +13 -0
  73. agentrun_sdk/tools/mcp/mcp_agent_tool.py +99 -0
  74. agentrun_sdk/tools/mcp/mcp_client.py +423 -0
  75. agentrun_sdk/tools/mcp/mcp_instrumentation.py +322 -0
  76. agentrun_sdk/tools/mcp/mcp_types.py +63 -0
  77. agentrun_sdk/tools/registry.py +607 -0
  78. agentrun_sdk/tools/structured_output.py +421 -0
  79. agentrun_sdk/tools/tools.py +217 -0
  80. agentrun_sdk/tools/watcher.py +136 -0
  81. agentrun_sdk/types/__init__.py +5 -0
  82. agentrun_sdk/types/collections.py +23 -0
  83. agentrun_sdk/types/content.py +188 -0
  84. agentrun_sdk/types/event_loop.py +48 -0
  85. agentrun_sdk/types/exceptions.py +81 -0
  86. agentrun_sdk/types/guardrails.py +254 -0
  87. agentrun_sdk/types/media.py +89 -0
  88. agentrun_sdk/types/session.py +152 -0
  89. agentrun_sdk/types/streaming.py +201 -0
  90. agentrun_sdk/types/tools.py +258 -0
  91. agentrun_sdk/types/traces.py +5 -0
  92. agentrun_sdk-0.1.2.dist-info/METADATA +51 -0
  93. agentrun_sdk-0.1.2.dist-info/RECORD +115 -0
  94. agentrun_sdk-0.1.2.dist-info/WHEEL +5 -0
  95. agentrun_sdk-0.1.2.dist-info/entry_points.txt +2 -0
  96. agentrun_sdk-0.1.2.dist-info/top_level.txt +3 -0
  97. agentrun_wrapper/__init__.py +11 -0
  98. agentrun_wrapper/_utils/__init__.py +6 -0
  99. agentrun_wrapper/_utils/endpoints.py +16 -0
  100. agentrun_wrapper/identity/__init__.py +5 -0
  101. agentrun_wrapper/identity/auth.py +211 -0
  102. agentrun_wrapper/memory/__init__.py +6 -0
  103. agentrun_wrapper/memory/client.py +1697 -0
  104. agentrun_wrapper/memory/constants.py +103 -0
  105. agentrun_wrapper/memory/controlplane.py +626 -0
  106. agentrun_wrapper/py.typed +1 -0
  107. agentrun_wrapper/runtime/__init__.py +13 -0
  108. agentrun_wrapper/runtime/app.py +473 -0
  109. agentrun_wrapper/runtime/context.py +34 -0
  110. agentrun_wrapper/runtime/models.py +25 -0
  111. agentrun_wrapper/services/__init__.py +1 -0
  112. agentrun_wrapper/services/identity.py +192 -0
  113. agentrun_wrapper/tools/__init__.py +6 -0
  114. agentrun_wrapper/tools/browser_client.py +325 -0
  115. agentrun_wrapper/tools/code_interpreter_client.py +186 -0
@@ -0,0 +1,46 @@
1
+ """Agent result handling for SDK.
2
+
3
+ This module defines the AgentResult class which encapsulates the complete response from an agent's processing cycle.
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+ from ..telemetry.metrics import EventLoopMetrics
10
+ from ..types.content import Message
11
+ from ..types.streaming import StopReason
12
+
13
+
14
+ @dataclass
15
+ class AgentResult:
16
+ """Represents the last result of invoking an agent with a prompt.
17
+
18
+ Attributes:
19
+ stop_reason: The reason why the agent's processing stopped.
20
+ message: The last message generated by the agent.
21
+ metrics: Performance metrics collected during processing.
22
+ state: Additional state information from the event loop.
23
+ """
24
+
25
+ stop_reason: StopReason
26
+ message: Message
27
+ metrics: EventLoopMetrics
28
+ state: Any
29
+
30
+ def __str__(self) -> str:
31
+ """Get the agent's last message as a string.
32
+
33
+ This method extracts and concatenates all text content from the final message, ignoring any non-text content
34
+ like images or structured data.
35
+
36
+ Returns:
37
+ The agent's last message as a string.
38
+ """
39
+ content_array = self.message.get("content", [])
40
+
41
+ result = ""
42
+ for item in content_array:
43
+ if isinstance(item, dict) and "text" in item:
44
+ result += item.get("text", "") + "\n"
45
+
46
+ return result
@@ -0,0 +1,26 @@
1
+ """This package provides classes for managing conversation history during agent execution.
2
+
3
+ It includes:
4
+
5
+ - ConversationManager: Abstract base class defining the conversation management interface
6
+ - NullConversationManager: A no-op implementation that does not modify conversation history
7
+ - SlidingWindowConversationManager: An implementation that maintains a sliding window of messages to control context
8
+ size while preserving conversation coherence
9
+ - SummarizingConversationManager: An implementation that summarizes older context instead
10
+ of simply trimming it
11
+
12
+ Conversation managers help control memory usage and context length while maintaining relevant conversation state, which
13
+ is critical for effective agent interactions.
14
+ """
15
+
16
+ from .conversation_manager import ConversationManager
17
+ from .null_conversation_manager import NullConversationManager
18
+ from .sliding_window_conversation_manager import SlidingWindowConversationManager
19
+ from .summarizing_conversation_manager import SummarizingConversationManager
20
+
21
+ __all__ = [
22
+ "ConversationManager",
23
+ "NullConversationManager",
24
+ "SlidingWindowConversationManager",
25
+ "SummarizingConversationManager",
26
+ ]
@@ -0,0 +1,88 @@
1
+ """Abstract interface for conversation history management."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING, Any, Optional
5
+
6
+ from ...types.content import Message
7
+
8
+ if TYPE_CHECKING:
9
+ from ...agent.agent import Agent
10
+
11
+
12
+ class ConversationManager(ABC):
13
+ """Abstract base class for managing conversation history.
14
+
15
+ This class provides an interface for implementing conversation management strategies to control the size of message
16
+ arrays/conversation histories, helping to:
17
+
18
+ - Manage memory usage
19
+ - Control context length
20
+ - Maintain relevant conversation state
21
+ """
22
+
23
+ def __init__(self) -> None:
24
+ """Initialize the ConversationManager.
25
+
26
+ Attributes:
27
+ removed_message_count: The messages that have been removed from the agents messages array.
28
+ These represent messages provided by the user or LLM that have been removed, not messages
29
+ included by the conversation manager through something like summarization.
30
+ """
31
+ self.removed_message_count = 0
32
+
33
+ def restore_from_session(self, state: dict[str, Any]) -> Optional[list[Message]]:
34
+ """Restore the Conversation Manager's state from a session.
35
+
36
+ Args:
37
+ state: Previous state of the conversation manager
38
+ Returns:
39
+ Optional list of messages to prepend to the agents messages. By default returns None.
40
+ """
41
+ if state.get("__name__") != self.__class__.__name__:
42
+ raise ValueError("Invalid conversation manager state.")
43
+ self.removed_message_count = state["removed_message_count"]
44
+ return None
45
+
46
+ def get_state(self) -> dict[str, Any]:
47
+ """Get the current state of a Conversation Manager as a Json serializable dictionary."""
48
+ return {
49
+ "__name__": self.__class__.__name__,
50
+ "removed_message_count": self.removed_message_count,
51
+ }
52
+
53
+ @abstractmethod
54
+ def apply_management(self, agent: "Agent", **kwargs: Any) -> None:
55
+ """Applies management strategy to the provided agent.
56
+
57
+ Processes the conversation history to maintain appropriate size by modifying the messages list in-place.
58
+ Implementations should handle message pruning, summarization, or other size management techniques to keep the
59
+ conversation context within desired bounds.
60
+
61
+ Args:
62
+ agent: The agent whose conversation history will be manage.
63
+ This list is modified in-place.
64
+ **kwargs: Additional keyword arguments for future extensibility.
65
+ """
66
+ pass
67
+
68
+ @abstractmethod
69
+ def reduce_context(self, agent: "Agent", e: Optional[Exception] = None, **kwargs: Any) -> None:
70
+ """Called when the model's context window is exceeded.
71
+
72
+ This method should implement the specific strategy for reducing the window size when a context overflow occurs.
73
+ It is typically called after a ContextWindowOverflowException is caught.
74
+
75
+ Implementations might use strategies such as:
76
+
77
+ - Removing the N oldest messages
78
+ - Summarizing older context
79
+ - Applying importance-based filtering
80
+ - Maintaining critical conversation markers
81
+
82
+ Args:
83
+ agent: The agent whose conversation history will be reduced.
84
+ This list is modified in-place.
85
+ e: The exception that triggered the context reduction, if any.
86
+ **kwargs: Additional keyword arguments for future extensibility.
87
+ """
88
+ pass
@@ -0,0 +1,46 @@
1
+ """Null implementation of conversation management."""
2
+
3
+ from typing import TYPE_CHECKING, Any, Optional
4
+
5
+ if TYPE_CHECKING:
6
+ from ...agent.agent import Agent
7
+
8
+ from ...types.exceptions import ContextWindowOverflowException
9
+ from .conversation_manager import ConversationManager
10
+
11
+
12
+ class NullConversationManager(ConversationManager):
13
+ """A no-op conversation manager that does not modify the conversation history.
14
+
15
+ Useful for:
16
+
17
+ - Testing scenarios where conversation management should be disabled
18
+ - Cases where conversation history is managed externally
19
+ - Situations where the full conversation history should be preserved
20
+ """
21
+
22
+ def apply_management(self, agent: "Agent", **kwargs: Any) -> None:
23
+ """Does nothing to the conversation history.
24
+
25
+ Args:
26
+ agent: The agent whose conversation history will remain unmodified.
27
+ **kwargs: Additional keyword arguments for future extensibility.
28
+ """
29
+ pass
30
+
31
+ def reduce_context(self, agent: "Agent", e: Optional[Exception] = None, **kwargs: Any) -> None:
32
+ """Does not reduce context and raises an exception.
33
+
34
+ Args:
35
+ agent: The agent whose conversation history will remain unmodified.
36
+ e: The exception that triggered the context reduction, if any.
37
+ **kwargs: Additional keyword arguments for future extensibility.
38
+
39
+ Raises:
40
+ e: If provided.
41
+ ContextWindowOverflowException: If e is None.
42
+ """
43
+ if e:
44
+ raise e
45
+ else:
46
+ raise ContextWindowOverflowException("Context window overflowed!")
@@ -0,0 +1,179 @@
1
+ """Sliding window conversation history management."""
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING, Any, Optional
5
+
6
+ if TYPE_CHECKING:
7
+ from ...agent.agent import Agent
8
+
9
+ from ...types.content import Messages
10
+ from ...types.exceptions import ContextWindowOverflowException
11
+ from .conversation_manager import ConversationManager
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class SlidingWindowConversationManager(ConversationManager):
17
+ """Implements a sliding window strategy for managing conversation history.
18
+
19
+ This class handles the logic of maintaining a conversation window that preserves tool usage pairs and avoids
20
+ invalid window states.
21
+ """
22
+
23
+ def __init__(self, window_size: int = 40, should_truncate_results: bool = True):
24
+ """Initialize the sliding window conversation manager.
25
+
26
+ Args:
27
+ window_size: Maximum number of messages to keep in the agent's history.
28
+ Defaults to 40 messages.
29
+ should_truncate_results: Truncate tool results when a message is too large for the model's context window
30
+ """
31
+ super().__init__()
32
+ self.window_size = window_size
33
+ self.should_truncate_results = should_truncate_results
34
+
35
+ def apply_management(self, agent: "Agent", **kwargs: Any) -> None:
36
+ """Apply the sliding window to the agent's messages array to maintain a manageable history size.
37
+
38
+ This method is called after every event loop cycle to apply a sliding window if the message count
39
+ exceeds the window size.
40
+
41
+ Args:
42
+ agent: The agent whose messages will be managed.
43
+ This list is modified in-place.
44
+ **kwargs: Additional keyword arguments for future extensibility.
45
+ """
46
+ messages = agent.messages
47
+
48
+ if len(messages) <= self.window_size:
49
+ logger.debug(
50
+ "message_count=<%s>, window_size=<%s> | skipping context reduction", len(messages), self.window_size
51
+ )
52
+ return
53
+ self.reduce_context(agent)
54
+
55
+ def reduce_context(self, agent: "Agent", e: Optional[Exception] = None, **kwargs: Any) -> None:
56
+ """Trim the oldest messages to reduce the conversation context size.
57
+
58
+ The method handles special cases where trimming the messages leads to:
59
+ - toolResult with no corresponding toolUse
60
+ - toolUse with no corresponding toolResult
61
+
62
+ Args:
63
+ agent: The agent whose messages will be reduce.
64
+ This list is modified in-place.
65
+ e: The exception that triggered the context reduction, if any.
66
+ **kwargs: Additional keyword arguments for future extensibility.
67
+
68
+ Raises:
69
+ ContextWindowOverflowException: If the context cannot be reduced further.
70
+ Such as when the conversation is already minimal or when tool result messages cannot be properly
71
+ converted.
72
+ """
73
+ messages = agent.messages
74
+
75
+ # Try to truncate the tool result first
76
+ last_message_idx_with_tool_results = self._find_last_message_with_tool_results(messages)
77
+ if last_message_idx_with_tool_results is not None and self.should_truncate_results:
78
+ logger.debug(
79
+ "message_index=<%s> | found message with tool results at index", last_message_idx_with_tool_results
80
+ )
81
+ results_truncated = self._truncate_tool_results(messages, last_message_idx_with_tool_results)
82
+ if results_truncated:
83
+ logger.debug("message_index=<%s> | tool results truncated", last_message_idx_with_tool_results)
84
+ return
85
+
86
+ # Try to trim index id when tool result cannot be truncated anymore
87
+ # If the number of messages is less than the window_size, then we default to 2, otherwise, trim to window size
88
+ trim_index = 2 if len(messages) <= self.window_size else len(messages) - self.window_size
89
+
90
+ # Find the next valid trim_index
91
+ while trim_index < len(messages):
92
+ if (
93
+ # Oldest message cannot be a toolResult because it needs a toolUse preceding it
94
+ any("toolResult" in content for content in messages[trim_index]["content"])
95
+ or (
96
+ # Oldest message can be a toolUse only if a toolResult immediately follows it.
97
+ any("toolUse" in content for content in messages[trim_index]["content"])
98
+ and trim_index + 1 < len(messages)
99
+ and not any("toolResult" in content for content in messages[trim_index + 1]["content"])
100
+ )
101
+ ):
102
+ trim_index += 1
103
+ else:
104
+ break
105
+ else:
106
+ # If we didn't find a valid trim_index, then we throw
107
+ raise ContextWindowOverflowException("Unable to trim conversation context!") from e
108
+
109
+ # trim_index represents the number of messages being removed from the agents messages array
110
+ self.removed_message_count += trim_index
111
+
112
+ # Overwrite message history
113
+ messages[:] = messages[trim_index:]
114
+
115
+ def _truncate_tool_results(self, messages: Messages, msg_idx: int) -> bool:
116
+ """Truncate tool results in a message to reduce context size.
117
+
118
+ When a message contains tool results that are too large for the model's context window, this function
119
+ replaces the content of those tool results with a simple error message.
120
+
121
+ Args:
122
+ messages: The conversation message history.
123
+ msg_idx: Index of the message containing tool results to truncate.
124
+
125
+ Returns:
126
+ True if any changes were made to the message, False otherwise.
127
+ """
128
+ if msg_idx >= len(messages) or msg_idx < 0:
129
+ return False
130
+
131
+ message = messages[msg_idx]
132
+ changes_made = False
133
+ tool_result_too_large_message = "The tool result was too large!"
134
+ for i, content in enumerate(message.get("content", [])):
135
+ if isinstance(content, dict) and "toolResult" in content:
136
+ tool_result_content_text = next(
137
+ (item["text"] for item in content["toolResult"]["content"] if "text" in item),
138
+ "",
139
+ )
140
+ # make the overwriting logic togglable
141
+ if (
142
+ message["content"][i]["toolResult"]["status"] == "error"
143
+ and tool_result_content_text == tool_result_too_large_message
144
+ ):
145
+ logger.info("ToolResult has already been updated, skipping overwrite")
146
+ return False
147
+ # Update status to error with informative message
148
+ message["content"][i]["toolResult"]["status"] = "error"
149
+ message["content"][i]["toolResult"]["content"] = [{"text": tool_result_too_large_message}]
150
+ changes_made = True
151
+
152
+ return changes_made
153
+
154
+ def _find_last_message_with_tool_results(self, messages: Messages) -> Optional[int]:
155
+ """Find the index of the last message containing tool results.
156
+
157
+ This is useful for identifying messages that might need to be truncated to reduce context size.
158
+
159
+ Args:
160
+ messages: The conversation message history.
161
+
162
+ Returns:
163
+ Index of the last message with tool results, or None if no such message exists.
164
+ """
165
+ # Iterate backwards through all messages (from newest to oldest)
166
+ for idx in range(len(messages) - 1, -1, -1):
167
+ # Check if this message has any content with toolResult
168
+ current_message = messages[idx]
169
+ has_tool_result = False
170
+
171
+ for content in current_message.get("content", []):
172
+ if isinstance(content, dict) and "toolResult" in content:
173
+ has_tool_result = True
174
+ break
175
+
176
+ if has_tool_result:
177
+ return idx
178
+
179
+ return None
@@ -0,0 +1,252 @@
1
+ """Summarizing conversation history management with configurable options."""
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING, Any, List, Optional
5
+
6
+ from typing_extensions import override
7
+
8
+ from ...types.content import Message
9
+ from ...types.exceptions import ContextWindowOverflowException
10
+ from .conversation_manager import ConversationManager
11
+
12
+ if TYPE_CHECKING:
13
+ from ..agent import Agent
14
+
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ DEFAULT_SUMMARIZATION_PROMPT = """You are a conversation summarizer. Provide a concise summary of the conversation \
20
+ history.
21
+
22
+ Format Requirements:
23
+ - You MUST create a structured and concise summary in bullet-point format.
24
+ - You MUST NOT respond conversationally.
25
+ - You MUST NOT address the user directly.
26
+
27
+ Task:
28
+ Your task is to create a structured summary document:
29
+ - It MUST contain bullet points with key topics and questions covered
30
+ - It MUST contain bullet points for all significant tools executed and their results
31
+ - It MUST contain bullet points for any code or technical information shared
32
+ - It MUST contain a section of key insights gained
33
+ - It MUST format the summary in the third person
34
+
35
+ Example format:
36
+
37
+ ## Conversation Summary
38
+ * Topic 1: Key information
39
+ * Topic 2: Key information
40
+ *
41
+ ## Tools Executed
42
+ * Tool X: Result Y"""
43
+
44
+
45
+ class SummarizingConversationManager(ConversationManager):
46
+ """Implements a summarizing window manager.
47
+
48
+ This manager provides a configurable option to summarize older context instead of
49
+ simply trimming it, helping preserve important information while staying within
50
+ context limits.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ summary_ratio: float = 0.3,
56
+ preserve_recent_messages: int = 10,
57
+ summarization_agent: Optional["Agent"] = None,
58
+ summarization_system_prompt: Optional[str] = None,
59
+ ):
60
+ """Initialize the summarizing conversation manager.
61
+
62
+ Args:
63
+ summary_ratio: Ratio of messages to summarize vs keep when context overflow occurs.
64
+ Value between 0.1 and 0.8. Defaults to 0.3 (summarize 30% of oldest messages).
65
+ preserve_recent_messages: Minimum number of recent messages to always keep.
66
+ Defaults to 10 messages.
67
+ summarization_agent: Optional agent to use for summarization instead of the parent agent.
68
+ If provided, this agent can use tools as part of the summarization process.
69
+ summarization_system_prompt: Optional system prompt override for summarization.
70
+ If None, uses the default summarization prompt.
71
+ """
72
+ super().__init__()
73
+ if summarization_agent is not None and summarization_system_prompt is not None:
74
+ raise ValueError(
75
+ "Cannot provide both summarization_agent and summarization_system_prompt. "
76
+ "Agents come with their own system prompt."
77
+ )
78
+
79
+ self.summary_ratio = max(0.1, min(0.8, summary_ratio))
80
+ self.preserve_recent_messages = preserve_recent_messages
81
+ self.summarization_agent = summarization_agent
82
+ self.summarization_system_prompt = summarization_system_prompt
83
+ self._summary_message: Optional[Message] = None
84
+
85
+ @override
86
+ def restore_from_session(self, state: dict[str, Any]) -> Optional[list[Message]]:
87
+ """Restores the Summarizing Conversation manager from its previous state in a session.
88
+
89
+ Args:
90
+ state: The previous state of the Summarizing Conversation Manager.
91
+
92
+ Returns:
93
+ Optionally returns the previous conversation summary if it exists.
94
+ """
95
+ super().restore_from_session(state)
96
+ self._summary_message = state.get("summary_message")
97
+ return [self._summary_message] if self._summary_message else None
98
+
99
+ def get_state(self) -> dict[str, Any]:
100
+ """Returns a dictionary representation of the state for the Summarizing Conversation Manager."""
101
+ return {"summary_message": self._summary_message, **super().get_state()}
102
+
103
+ def apply_management(self, agent: "Agent", **kwargs: Any) -> None:
104
+ """Apply management strategy to conversation history.
105
+
106
+ For the summarizing conversation manager, no proactive management is performed.
107
+ Summarization only occurs when there's a context overflow that triggers reduce_context.
108
+
109
+ Args:
110
+ agent: The agent whose conversation history will be managed.
111
+ The agent's messages list is modified in-place.
112
+ **kwargs: Additional keyword arguments for future extensibility.
113
+ """
114
+ # No proactive management - summarization only happens on context overflow
115
+ pass
116
+
117
+ def reduce_context(self, agent: "Agent", e: Optional[Exception] = None, **kwargs: Any) -> None:
118
+ """Reduce context using summarization.
119
+
120
+ Args:
121
+ agent: The agent whose conversation history will be reduced.
122
+ The agent's messages list is modified in-place.
123
+ e: The exception that triggered the context reduction, if any.
124
+ **kwargs: Additional keyword arguments for future extensibility.
125
+
126
+ Raises:
127
+ ContextWindowOverflowException: If the context cannot be summarized.
128
+ """
129
+ try:
130
+ # Calculate how many messages to summarize
131
+ messages_to_summarize_count = max(1, int(len(agent.messages) * self.summary_ratio))
132
+
133
+ # Ensure we don't summarize recent messages
134
+ messages_to_summarize_count = min(
135
+ messages_to_summarize_count, len(agent.messages) - self.preserve_recent_messages
136
+ )
137
+
138
+ if messages_to_summarize_count <= 0:
139
+ raise ContextWindowOverflowException("Cannot summarize: insufficient messages for summarization")
140
+
141
+ # Adjust split point to avoid breaking ToolUse/ToolResult pairs
142
+ messages_to_summarize_count = self._adjust_split_point_for_tool_pairs(
143
+ agent.messages, messages_to_summarize_count
144
+ )
145
+
146
+ if messages_to_summarize_count <= 0:
147
+ raise ContextWindowOverflowException("Cannot summarize: insufficient messages for summarization")
148
+
149
+ # Extract messages to summarize
150
+ messages_to_summarize = agent.messages[:messages_to_summarize_count]
151
+ remaining_messages = agent.messages[messages_to_summarize_count:]
152
+
153
+ # Keep track of the number of messages that have been summarized thus far.
154
+ self.removed_message_count += len(messages_to_summarize)
155
+ # If there is a summary message, don't count it in the removed_message_count.
156
+ if self._summary_message:
157
+ self.removed_message_count -= 1
158
+
159
+ # Generate summary
160
+ self._summary_message = self._generate_summary(messages_to_summarize, agent)
161
+
162
+ # Replace the summarized messages with the summary
163
+ agent.messages[:] = [self._summary_message] + remaining_messages
164
+
165
+ except Exception as summarization_error:
166
+ logger.error("Summarization failed: %s", summarization_error)
167
+ raise summarization_error from e
168
+
169
+ def _generate_summary(self, messages: List[Message], agent: "Agent") -> Message:
170
+ """Generate a summary of the provided messages.
171
+
172
+ Args:
173
+ messages: The messages to summarize.
174
+ agent: The agent instance to use for summarization.
175
+
176
+ Returns:
177
+ A message containing the conversation summary.
178
+
179
+ Raises:
180
+ Exception: If summary generation fails.
181
+ """
182
+ # Choose which agent to use for summarization
183
+ summarization_agent = self.summarization_agent if self.summarization_agent is not None else agent
184
+
185
+ # Save original system prompt and messages to restore later
186
+ original_system_prompt = summarization_agent.system_prompt
187
+ original_messages = summarization_agent.messages.copy()
188
+
189
+ try:
190
+ # Only override system prompt if no agent was provided during initialization
191
+ if self.summarization_agent is None:
192
+ # Use custom system prompt if provided, otherwise use default
193
+ system_prompt = (
194
+ self.summarization_system_prompt
195
+ if self.summarization_system_prompt is not None
196
+ else DEFAULT_SUMMARIZATION_PROMPT
197
+ )
198
+ # Temporarily set the system prompt for summarization
199
+ summarization_agent.system_prompt = system_prompt
200
+ summarization_agent.messages = messages
201
+
202
+ # Use the agent to generate summary with rich content (can use tools if needed)
203
+ result = summarization_agent("Please summarize this conversation.")
204
+
205
+ return result.message
206
+
207
+ finally:
208
+ # Restore original agent state
209
+ summarization_agent.system_prompt = original_system_prompt
210
+ summarization_agent.messages = original_messages
211
+
212
+ def _adjust_split_point_for_tool_pairs(self, messages: List[Message], split_point: int) -> int:
213
+ """Adjust the split point to avoid breaking ToolUse/ToolResult pairs.
214
+
215
+ Uses the same logic as SlidingWindowConversationManager for consistency.
216
+
217
+ Args:
218
+ messages: The full list of messages.
219
+ split_point: The initially calculated split point.
220
+
221
+ Returns:
222
+ The adjusted split point that doesn't break ToolUse/ToolResult pairs.
223
+
224
+ Raises:
225
+ ContextWindowOverflowException: If no valid split point can be found.
226
+ """
227
+ if split_point > len(messages):
228
+ raise ContextWindowOverflowException("Split point exceeds message array length")
229
+
230
+ if split_point == len(messages):
231
+ return split_point
232
+
233
+ # Find the next valid split_point
234
+ while split_point < len(messages):
235
+ if (
236
+ # Oldest message cannot be a toolResult because it needs a toolUse preceding it
237
+ any("toolResult" in content for content in messages[split_point]["content"])
238
+ or (
239
+ # Oldest message can be a toolUse only if a toolResult immediately follows it.
240
+ any("toolUse" in content for content in messages[split_point]["content"])
241
+ and split_point + 1 < len(messages)
242
+ and not any("toolResult" in content for content in messages[split_point + 1]["content"])
243
+ )
244
+ ):
245
+ split_point += 1
246
+ else:
247
+ break
248
+ else:
249
+ # If we didn't find a valid split_point, then we throw
250
+ raise ContextWindowOverflowException("Unable to trim conversation context!")
251
+
252
+ return split_point