|
33 | 33 | from .. import _identifier |
34 | 34 | from .._async import run_async |
35 | 35 | from ..event_loop.event_loop import event_loop_cycle |
| 36 | +from ..tools._tool_helpers import generate_missing_tool_result_content |
36 | 37 |
|
37 | 38 | if TYPE_CHECKING: |
38 | 39 | from ..experimental.tools import ToolProvider |
|
57 | 58 | from ..tools.watcher import ToolWatcher |
58 | 59 | from ..types._events import AgentResultEvent, InitEventLoopEvent, ModelStreamChunkEvent, ToolInterruptEvent, TypedEvent |
59 | 60 | from ..types.agent import AgentInput |
60 | | -from ..types.content import ContentBlock, Message, Messages |
| 61 | +from ..types.content import ContentBlock, Message, Messages, SystemContentBlock |
61 | 62 | from ..types.exceptions import ContextWindowOverflowException |
62 | 63 | from ..types.interrupt import InterruptResponseContent |
63 | 64 | from ..types.tools import ToolResult, ToolUse |
@@ -216,7 +217,7 @@ def __init__( |
216 | 217 | model: Union[Model, str, None] = None, |
217 | 218 | messages: Optional[Messages] = None, |
218 | 219 | tools: Optional[list[Union[str, dict[str, str], "ToolProvider", Any]]] = None, |
219 | | - system_prompt: Optional[str] = None, |
| 220 | + system_prompt: Optional[str | list[SystemContentBlock]] = None, |
220 | 221 | structured_output_model: Optional[Type[BaseModel]] = None, |
221 | 222 | callback_handler: Optional[ |
222 | 223 | Union[Callable[..., Any], _DefaultCallbackHandlerSentinel] |
@@ -253,6 +254,7 @@ def __init__( |
253 | 254 |
|
254 | 255 | If provided, only these tools will be available. If None, all tools will be available. |
255 | 256 | system_prompt: System prompt to guide model behavior. |
| 257 | + Can be a string or a list of SystemContentBlock objects for advanced features like caching. |
256 | 258 | If None, the model will behave according to its default settings. |
257 | 259 | structured_output_model: Pydantic model type(s) for structured output. |
258 | 260 | When specified, all agent calls will attempt to return structured output of this type. |
@@ -280,14 +282,15 @@ def __init__( |
280 | 282 | Defaults to None. |
281 | 283 | session_manager: Manager for handling agent sessions including conversation history and state. |
282 | 284 | If provided, enables session-based persistence and state management. |
283 | | - tool_executor: Definition of tool execution stragety (e.g., sequential, concurrent, etc.). |
| 285 | + tool_executor: Definition of tool execution strategy (e.g., sequential, concurrent, etc.). |
284 | 286 |
|
285 | 287 | Raises: |
286 | 288 | ValueError: If agent id contains path separators. |
287 | 289 | """ |
288 | 290 | self.model = BedrockModel() if not model else BedrockModel(model_id=model) if isinstance(model, str) else model |
289 | 291 | self.messages = messages if messages is not None else [] |
290 | | - self.system_prompt = system_prompt |
| 292 | + # initializing self.system_prompt for backwards compatibility |
| 293 | + self.system_prompt, self._system_prompt_content = self._initialize_system_prompt(system_prompt) |
291 | 294 | self._default_structured_output_model = structured_output_model |
292 | 295 | self.agent_id = _identifier.validate(agent_id or _DEFAULT_AGENT_ID, _identifier.Identifier.AGENT) |
293 | 296 | self.name = name or _DEFAULT_AGENT_NAME |
@@ -816,6 +819,21 @@ def _convert_prompt_to_messages(self, prompt: AgentInput) -> Messages: |
816 | 819 |
|
817 | 820 | messages: Messages | None = None |
818 | 821 | if prompt is not None: |
| 822 | + # Check if the latest message is toolUse |
| 823 | + if len(self.messages) > 0 and any("toolUse" in content for content in self.messages[-1]["content"]): |
| 824 | + # Add toolResult message after to have a valid conversation |
| 825 | + logger.info( |
| 826 | + "Agents latest message is toolUse, appending a toolResult message to have valid conversation." |
| 827 | + ) |
| 828 | + tool_use_ids = [ |
| 829 | + content["toolUse"]["toolUseId"] for content in self.messages[-1]["content"] if "toolUse" in content |
| 830 | + ] |
| 831 | + self._append_message( |
| 832 | + { |
| 833 | + "role": "user", |
| 834 | + "content": generate_missing_tool_result_content(tool_use_ids), |
| 835 | + } |
| 836 | + ) |
819 | 837 | if isinstance(prompt, str): |
820 | 838 | # String input - convert to user message |
821 | 839 | messages = [{"role": "user", "content": [{"text": prompt}]}] |
@@ -965,6 +983,30 @@ def _filter_tool_parameters_for_recording(self, tool_name: str, input_params: di |
965 | 983 | properties = tool_spec["inputSchema"]["json"]["properties"] |
966 | 984 | return {k: v for k, v in input_params.items() if k in properties} |
967 | 985 |
|
| 986 | + def _initialize_system_prompt( |
| 987 | + self, system_prompt: str | list[SystemContentBlock] | None |
| 988 | + ) -> tuple[str | None, list[SystemContentBlock] | None]: |
| 989 | + """Initialize system prompt fields from constructor input. |
| 990 | +
|
| 991 | + Maintains backwards compatibility by keeping system_prompt as str when string input |
| 992 | + provided, avoiding breaking existing consumers. |
| 993 | +
|
| 994 | + Maps system_prompt input to both string and content block representations: |
| 995 | + - If string: system_prompt=string, _system_prompt_content=[{text: string}] |
| 996 | + - If list with text elements: system_prompt=concatenated_text, _system_prompt_content=list |
| 997 | + - If list without text elements: system_prompt=None, _system_prompt_content=list |
| 998 | + - If None: system_prompt=None, _system_prompt_content=None |
| 999 | + """ |
| 1000 | + if isinstance(system_prompt, str): |
| 1001 | + return system_prompt, [{"text": system_prompt}] |
| 1002 | + elif isinstance(system_prompt, list): |
| 1003 | + # Concatenate all text elements for backwards compatibility, None if no text found |
| 1004 | + text_parts = [block["text"] for block in system_prompt if "text" in block] |
| 1005 | + system_prompt_str = "\n".join(text_parts) if text_parts else None |
| 1006 | + return system_prompt_str, system_prompt |
| 1007 | + else: |
| 1008 | + return None, None |
| 1009 | + |
968 | 1010 | def _append_message(self, message: Message) -> None: |
969 | 1011 | """Appends a message to the agent's list of messages and invokes the callbacks for the MessageCreatedEvent.""" |
970 | 1012 | self.messages.append(message) |
|
0 commit comments