|
| 1 | +"""RecommendationClient for AgentCore system prompt and tool description recommendations.""" |
| 2 | + |
| 3 | +import logging |
| 4 | +from typing import Any, Dict, Optional |
| 5 | + |
| 6 | +import boto3 |
| 7 | +from botocore.config import Config |
| 8 | + |
| 9 | +from bedrock_agentcore._utils.config import WaitConfig |
| 10 | +from bedrock_agentcore._utils.endpoints import DP_ENDPOINT_OVERRIDE |
| 11 | +from bedrock_agentcore._utils.polling import wait_until |
| 12 | +from bedrock_agentcore._utils.snake_case import accept_snake_case_kwargs, convert_kwargs |
| 13 | +from bedrock_agentcore._utils.user_agent import build_user_agent_suffix |
| 14 | + |
| 15 | +logger = logging.getLogger(__name__) |
| 16 | + |
| 17 | + |
| 18 | +class RecommendationClient: |
| 19 | + """Client for AgentCore recommendations. |
| 20 | +
|
| 21 | + Provides pass-through access to the recommendation APIs on the |
| 22 | + bedrock-agentcore service, plus ``start_recommendation_and_wait`` for the |
| 23 | + asynchronous start-then-poll flow every recommendation needs. |
| 24 | +
|
| 25 | + .. warning:: |
| 26 | + This feature is in preview and may change in future releases. |
| 27 | +
|
| 28 | + Example:: |
| 29 | +
|
| 30 | + client = RecommendationClient(region_name="us-west-2") |
| 31 | +
|
| 32 | + recommendation = client.start_recommendation_and_wait( |
| 33 | + name="my-prompt-rec", |
| 34 | + type="SYSTEM_PROMPT_RECOMMENDATION", |
| 35 | + recommendationConfig={"systemPromptRecommendationConfig": { |
| 36 | + "systemPrompt": {"text": "You are a helpful assistant."}, |
| 37 | + "agentTraces": {"batchEvaluation": {"batchEvaluationArn": arn}}, |
| 38 | + "evaluationConfig": {"evaluators": [ |
| 39 | + {"evaluatorArn": "arn:aws:bedrock-agentcore:::evaluator/Builtin.GoalSuccessRate"} |
| 40 | + ]}, |
| 41 | + }}, |
| 42 | + ) |
| 43 | + print(recommendation["recommendationResult"]) |
| 44 | +
|
| 45 | + # Pass-through to any recommendation API |
| 46 | + client.list_recommendations(maxResults=10) |
| 47 | + """ |
| 48 | + |
| 49 | + _ALLOWED_DP_METHODS = { |
| 50 | + "start_recommendation", |
| 51 | + "get_recommendation", |
| 52 | + "list_recommendations", |
| 53 | + "delete_recommendation", |
| 54 | + } |
| 55 | + |
| 56 | + def __init__( |
| 57 | + self, |
| 58 | + region_name: Optional[str] = None, |
| 59 | + integration_source: Optional[str] = None, |
| 60 | + boto3_session: Optional[boto3.Session] = None, |
| 61 | + ): |
| 62 | + """Initialize the RecommendationClient. |
| 63 | +
|
| 64 | + Args: |
| 65 | + region_name: AWS region. Falls back to boto3 session region or us-west-2. |
| 66 | + integration_source: Optional integration framework identifier for telemetry. |
| 67 | + boto3_session: Optional boto3 Session. If not provided, a default is created. |
| 68 | + """ |
| 69 | + session = boto3_session if boto3_session else boto3.Session() |
| 70 | + self.region_name = region_name or session.region_name or "us-west-2" |
| 71 | + self.integration_source = integration_source |
| 72 | + |
| 73 | + client_kwargs: Dict[str, Any] = { |
| 74 | + "region_name": self.region_name, |
| 75 | + "config": Config(user_agent_extra=build_user_agent_suffix(integration_source)), |
| 76 | + } |
| 77 | + if DP_ENDPOINT_OVERRIDE: |
| 78 | + client_kwargs["endpoint_url"] = DP_ENDPOINT_OVERRIDE |
| 79 | + self._dp_client = session.client("bedrock-agentcore", **client_kwargs) |
| 80 | + |
| 81 | + logger.info("Initialized RecommendationClient in region %s", self.region_name) |
| 82 | + |
| 83 | + def __getattr__(self, name: str): |
| 84 | + """Dynamically forward allowlisted method calls to the boto3 client.""" |
| 85 | + if "_dp_client" not in self.__dict__: |
| 86 | + raise AttributeError(name) |
| 87 | + |
| 88 | + if name in self._ALLOWED_DP_METHODS and hasattr(self._dp_client, name): |
| 89 | + method = getattr(self._dp_client, name) |
| 90 | + logger.debug("Forwarding method '%s' to _dp_client", name) |
| 91 | + return accept_snake_case_kwargs(method) |
| 92 | + |
| 93 | + raise AttributeError( |
| 94 | + f"'{self.__class__.__name__}' object has no attribute '{name}'. " |
| 95 | + f"Method not found on data plane client. " |
| 96 | + f"Available methods can be found in the boto3 documentation for " |
| 97 | + f"'bedrock-agentcore' service." |
| 98 | + ) |
| 99 | + |
| 100 | + def start_recommendation_and_wait( |
| 101 | + self, |
| 102 | + wait_config: Optional[WaitConfig] = None, |
| 103 | + **kwargs, |
| 104 | + ) -> Dict[str, Any]: |
| 105 | + """Start a recommendation and wait for it to reach COMPLETED status. |
| 106 | +
|
| 107 | + Recommendations typically take several minutes, so the default |
| 108 | + ``WaitConfig`` (5 minutes) may be too short. Pass a longer ``max_wait`` |
| 109 | + for large trace sets. |
| 110 | +
|
| 111 | + Args: |
| 112 | + wait_config: Optional WaitConfig for polling behavior. |
| 113 | + **kwargs: Arguments forwarded to the start_recommendation API. |
| 114 | +
|
| 115 | + Returns: |
| 116 | + The GetRecommendation response when COMPLETED. The generated result is |
| 117 | + under ``recommendationResult``. Note that ``recommendationConfig`` |
| 118 | + echoes the input you submitted, so read the result from |
| 119 | + ``recommendationResult`` rather than searching the response. |
| 120 | +
|
| 121 | + Raises: |
| 122 | + RuntimeError: If the recommendation reaches FAILED status. |
| 123 | + TimeoutError: If the recommendation doesn't complete within max_wait. |
| 124 | + """ |
| 125 | + response = self._dp_client.start_recommendation(**convert_kwargs(kwargs)) |
| 126 | + recommendation_id = response["recommendationId"] |
| 127 | + logger.info("Started recommendation %s", recommendation_id) |
| 128 | + return wait_until( |
| 129 | + lambda: self._dp_client.get_recommendation(recommendationId=recommendation_id), |
| 130 | + "COMPLETED", |
| 131 | + {"FAILED"}, |
| 132 | + wait_config, |
| 133 | + error_field="recommendationResult", |
| 134 | + ) |
0 commit comments