from typing import Any from collections import OrderedDict from strands import Agent, tool import asyncio from strands.agent.conversation_manager.null_conversation_manager import NullConversationManager from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model from mcp_client.client import get_streamable_http_mcp_client from memory.session import get_memory_session_manager app = BedrockAgentCoreApp() log = app.logger # Define a Streamable HTTP MCP Client mcp_clients = [get_streamable_http_mcp_client()] DEFAULT_SYSTEM_PROMPT = """ You are a helpful assistant. Use tools when appropriate. """ # Define a collection of tools used by the model tools = [] _INLINE_FUNCTION_NAMES = set() # Define a simple function tool @tool def add_numbers(a: int, b: int) -> int: """Return the sum of two numbers""" return a+b tools.append(add_numbers) # Add MCP client to tools if available for mcp_client in mcp_clients: if mcp_client: tools.append(mcp_client) def _make_conversation_manager(): return NullConversationManager() def agent_factory(): cache = {} def get_or_create_agent(session_id, user_id): _actor_id = user_id key = f"{session_id}/{_actor_id}" if key not in cache: cache[key] = Agent( model=load_model(), session_manager=get_memory_session_manager(session_id, _actor_id), conversation_manager=_make_conversation_manager(), system_prompt=DEFAULT_SYSTEM_PROMPT, tools=tools, hooks=[ ], ) return cache[key] return get_or_create_agent get_or_create_agent = agent_factory() def strip_trailing_tool_use(messages: Any) -> list[dict]: """Strip toolUse blocks from the tail until the last message has none.""" if not isinstance(messages, list): raise ValueError("messages must be a list") messages = list(messages) while messages: last = messages[-1] if not isinstance(last, dict): raise ValueError("each message must be an object") original_content = last.get("content", []) if not isinstance(original_content, list) or not all(isinstance(block, dict) for block in original_content): raise ValueError("each message content value must be a list of content blocks") content = [block for block in original_content if "toolUse" not in block] if len(content) == len(original_content): break if content: messages[-1] = {**last, "content": content} break messages.pop() return messages def _extract_prompt(payload: dict): """Accept validated harness messages, tool results, or a plain prompt string.""" if not isinstance(payload, dict): raise ValueError("payload must be a JSON object") if "messages" in payload: return strip_trailing_tool_use(payload["messages"]) if "tool_results" in payload: tool_results = payload["tool_results"] if not isinstance(tool_results, list) or not all( isinstance(tool_result, dict) and isinstance(tool_result.get("toolUseId"), str) for tool_result in tool_results ): raise ValueError("tool_results must contain objects with a toolUseId string") return [{"role": "user", "content": [{"toolResult": { "toolUseId": tr["toolUseId"], "status": tr.get("status", "success"), "content": tr.get("content", []), }} for tr in tool_results]}] prompt = payload.get("prompt", "") if not isinstance(prompt, str): raise ValueError("prompt must be a string") return prompt def _has_inline_function_call(messages) -> bool: """Return True if messages contains an assistant toolUse for an inline function tool.""" if not _INLINE_FUNCTION_NAMES or not isinstance(messages, list): return False for msg in messages: if msg.get("role") == "assistant": for block in msg.get("content", []): if isinstance(block, dict) and block.get("toolUse", {}).get("name") in _INLINE_FUNCTION_NAMES: return True return False def _is_inline_function_call(event: dict) -> bool: """Check if a contentBlockStart event is for an inline function tool.""" if not _INLINE_FUNCTION_NAMES: return False cbs = event.get("contentBlockStart", {}) start = cbs.get("start", {}) tool_use = start.get("toolUse") if isinstance(start, dict) else None return tool_use is not None and tool_use.get("name") in _INLINE_FUNCTION_NAMES @app.entrypoint async def invoke(payload, context): log.info("Invoking Agent.....") session_id = getattr(context, 'session_id', 'default-session') user_id = getattr(context, 'user_id', 'default-user') agent = get_or_create_agent(session_id, user_id) prompt = _extract_prompt(payload) async for event in agent.stream_async( prompt, ): if not isinstance(event, dict) or "event" not in event: continue cbs = event["event"].get("contentBlockStart") if cbs is not None and not cbs.get("start"): continue yield event if __name__ == "__main__": app.run()