hashicorp / terraform-provider-aws

The AWS Provider enables Terraform to manage AWS resources.
https://registry.terraform.io/providers/hashicorp/aws
Mozilla Public License 2.0
9.76k stars 9.12k forks source link

[Bug]: Bedrock agent association has no knowledgebase attached #38660

Closed tleibovich closed 1 month ago

tleibovich commented 2 months ago

Terraform Core Version

1.8.5

AWS Provider Version

5.61.0

Affected Resource(s)

aws_bedrockagent_agent_knowledge_base_association

Expected Behavior

There should be a knowledgebase associated as in this screenshot of an agent created manually.

Screenshot 2024-08-02 095834

Actual Behavior

There is no knowledgebase associated when using the attached code.

Screenshot 2024-08-02 100418

Relevant Error/Panic Output Snippet

No response

Terraform Configuration Files

data "aws_bedrock_foundation_model" "kb" {
  model_id = var.kb_foundation_model_arn
}

resource "aws_bedrockagent_knowledge_base" "bedrock_kb" {
  name     = "${var.cluster}-${var.application}-kb"
  role_arn = aws_iam_role.bedrock_kb_role.arn
  knowledge_base_configuration {
    vector_knowledge_base_configuration {
      embedding_model_arn = var.kb_foundation_model_arn
    }
    type = "VECTOR"
  }
  storage_configuration {
    type = "OPENSEARCH_SERVERLESS"
    opensearch_serverless_configuration {
      collection_arn    = aws_opensearchserverless_collection.oss_collection.arn
      vector_index_name = "bedrock-knowledge-base-default-index"
      field_mapping {
        vector_field   = "bedrock-knowledge-base-default-vector"
        text_field     = "AMAZON_BEDROCK_TEXT_CHUNK"
        metadata_field = "AMAZON_BEDROCK_METADATA"
      }
    }
  }
  depends_on = [
    aws_iam_role_policy.bedrock_kb_invoke,
    aws_iam_role_policy.bedrock_kb_s3,
    opensearch_index.oss_index,
    time_sleep.aws_iam_role_policy_bedrock_kb_oss
  ]
}

resource "aws_bedrockagent_data_source" "kb_datasource" {
  knowledge_base_id = aws_bedrockagent_knowledge_base.bedrock_kb.id
  name              = "${aws_bedrockagent_knowledge_base.bedrock_kb.name}-datasource"
  data_source_configuration {
    type = "S3"
    s3_configuration {
        bucket_arn = aws_s3_bucket.kb_source_bucket.arn
        inclusion_prefixes = ["live/"]
    }
  }
}

resource "aws_bedrockagent_agent" "kb_agent" {
  agent_name              = "${var.cluster}-${var.application}-kb-agent"
  agent_resource_role_arn = aws_iam_role.bedrock_kb_role.arn
  description             = "${var.cluster}-${var.application}-kb-agent"
  foundation_model        = var.agent_foundation_model
  instruction             = "The user is a customer of Experity software products. Please provide full, detailed, accurate answers to their questions based on the search results."
}

resource "aws_bedrockagent_agent_knowledge_base_association" "kb_agent_association" {
  agent_id             = aws_bedrockagent_agent.kb_agent.id
  description          = "${var.cluster}-${var.application}-kb-agent"
  knowledge_base_id    = aws_bedrockagent_knowledge_base.bedrock_kb.id
  knowledge_base_state = "ENABLED"
  agent_version = "DRAFT"
}

resource "aws_bedrockagent_agent_alias" "live" {
  agent_id         = aws_bedrockagent_agent.kb_agent.id
  agent_alias_name = "live"
  description      = "Live alias for the ${var.cluster}-${var.application} KB agent"
}

Steps to Reproduce

Run the code in the above section. There are several resources with dependencies defined in other files, but I assume it would be simpler for Hashicorp to create those resources in their own way and update those references. If the rest of the code I am using is needed, I can provide it on request.

Debug Output

No response

Panic Output

No response

Important Factoids

I opened a support ticket with AWS Enterprise Support. They were able to replicate the issue using terraform, but when they used their API's directly (python using boto3), they were able to create the agent and associate it successfully. Below is the code they used:

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

"""
Purpose

Shows how to use the AWS SDK for Python (Boto3) with Amazon Bedrock to manage
Bedrock Agents.
"""

import logging
import boto3
from botocore.exceptions import ClientError

logger = logging.getLogger(__name__)

# snippet-start:[python.example_code.bedrock-agent.BedrockAgentWrapper.class]
#r snippet-start:[python.example_code.bedrock-agent.BedrockAgentWrapper.decl]
class BedrockAgentWrapper:
    """Encapsulates Amazon Bedrock Agent actions."""

    def __init__(self, client):
        """
        :param client: A Boto3 Amazon Bedrock Agents client, which is a low-level client that
                       represents Agents for Amazon Bedrock and describes the API operations
                       for creating and managing Bedrock Agent resources.
        """
        self.client = client

    # snippet-end:[python.example_code.bedrock-agent.BedrockAgentWrapper.decl]

    # snippet-start:[python.example_code.bedrock-agent.CreateAgent]
    def create_agent(self, agent_name, foundation_model, role_arn, instruction):
        """
        Creates an agent that orchestrates interactions between foundation models,
        data sources, software applications, user conversations, and APIs to carry
        out tasks to help customers.

        :param agent_name: A name for the agent.
        :param foundation_model: The foundation model to be used for orchestration by the agent.
        :param role_arn: The ARN of the IAM role with permissions needed by the agent.
        :param instruction: Instructions that tell the agent what it should do and how it should
                            interact with users.
        :return: The response from Agents for Bedrock if successful, otherwise raises an exception.
        """
        try:
            response = self.client.create_agent(
                agentName=agent_name,
                foundationModel=foundation_model,
                agentResourceRoleArn=role_arn,
                instruction=instruction,
            )
        except ClientError as e:
            logger.error(f"Error: Couldn't create agent. Here's why: {e}")
            raise
        else:
            return response["agent"]

    # snippet-end:[python.example_code.bedrock-agent.CreateAgent]

    # snippet-start:[python.example_code.bedrock-agent.CreateAgentActionGroup]
    def create_agent_action_group(
        self, name, description, agent_id, agent_version, function_arn, api_schema
    ):
        """
        Creates an action group for an agent. An action group defines a set of actions that an
        agent should carry out for the customer.

        :param name: The name to give the action group.
        :param description: The description of the action group.
        :param agent_id: The unique identifier of the agent for which to create the action group.
        :param agent_version: The version of the agent for which to create the action group.
        :param function_arn: The ARN of the Lambda function containing the business logic that is
                             carried out upon invoking the action.
        :param api_schema: Contains the OpenAPI schema for the action group.
        :return: Details about the action group that was created.
        """
        try:
            response = self.client.create_agent_action_group(
                actionGroupName=name,
                description=description,
                agentId=agent_id,
                agentVersion=agent_version,
                actionGroupExecutor={"lambda": function_arn},
                apiSchema={"payload": api_schema},
            )
            agent_action_group = response["agentActionGroup"]
        except ClientError as e:
            logger.error(f"Error: Couldn't create agent action group. Here's why: {e}")
            raise
        else:
            return agent_action_group

    # snippet-end:[python.example_code.bedrock-agent.CreateAgentActionGroup]

    # snippet-start:[python.example_code.bedrock-agent.CreateAgentAlias]
    def create_agent_alias(self, name, agent_id):
        """
        Creates an alias of an agent that can be used to deploy the agent.

        :param name: The name of the alias.
        :param agent_id: The unique identifier of the agent.
        :return: Details about the alias that was created.
        """
        try:
            response = self.client.create_agent_alias(
                agentAliasName=name, agentId=agent_id
            )
            agent_alias = response["agentAlias"]
        except ClientError as e:
            logger.error(f"Couldn't create agent alias. {e}")
            raise
        else:
            return agent_alias

    # snippet-end:[python.example_code.bedrock-agent.CreateAgentAlias]

    # snippet-start:[python.example_code.bedrock-agent.DeleteAgent]
    def delete_agent(self, agent_id):
        """
        Deletes an Amazon Bedrock agent.

        :param agent_id: The unique identifier of the agent to delete.
        :return: The response from Agents for Bedrock if successful, otherwise raises an exception.
        """

        try:
            response = self.client.delete_agent(
                agentId=agent_id, skipResourceInUseCheck=False
            )
        except ClientError as e:
            logger.error(f"Couldn't delete agent. {e}")
            raise
        else:
            return response

    # snippet-end:[python.example_code.bedrock-agent.DeleteAgent]

    # snippet-start:[python.example_code.bedrock-agent.DeleteAgentAlias]
    def delete_agent_alias(self, agent_id, agent_alias_id):
        """
        Deletes an alias of an Amazon Bedrock agent.

        :param agent_id: The unique identifier of the agent that the alias belongs to.
        :param agent_alias_id: The unique identifier of the alias to delete.
        :return: The response from Agents for Bedrock if successful, otherwise raises an exception.
        """

        try:
            response = self.client.delete_agent_alias(
                agentId=agent_id, agentAliasId=agent_alias_id
            )
        except ClientError as e:
            logger.error(f"Couldn't delete agent alias. {e}")
            raise
        else:
            return response

    # snippet-end:[python.example_code.bedrock-agent.DeleteAgentAlias]

    # snippet-start:[python.example_code.bedrock-agent.GetAgent]
    def get_agent(self, agent_id, log_error=True):
        """
        Gets information about an agent.

        :param agent_id: The unique identifier of the agent.
        :param log_error: Whether to log any errors that occur when getting the agent.
                          If True, errors will be logged to the logger. If False, errors
                          will still be raised, but not logged.
        :return: The information about the requested agent.
        """

        try:
            response = self.client.get_agent(agentId=agent_id)
            agent = response["agent"]
        except ClientError as e:
            if log_error:
                logger.error(f"Couldn't get agent {agent_id}. {e}")
            raise
        else:
            return agent

    # snippet-end:[python.example_code.bedrock-agent.GetAgent]

    # snippet-start:[python.example_code.bedrock-agent.ListAgents]
    def list_agents(self):
        """
        List the available Amazon Bedrock Agents.

        :return: The list of available bedrock agents.
        """

        try:
            all_agents = []

            paginator = self.client.get_paginator("list_agents")
            for page in paginator.paginate(PaginationConfig={"PageSize": 10}):
                all_agents.extend(page["agentSummaries"])

        except ClientError as e:
            logger.error(f"Couldn't list agents. {e}")
            raise
        else:
            return all_agents

    # snippet-end:[python.example_code.bedrock-agent.ListAgents]

    # snippet-start:[python.example_code.bedrock-agent.ListAgentActionGroups]
    def list_agent_action_groups(self, agent_id, agent_version):
        """
        List the action groups for a version of an Amazon Bedrock Agent.

        :param agent_id: The unique identifier of the agent.
        :param agent_version: The version of the agent.
        :return: The list of action group summaries for the version of the agent.
        """

        try:
            action_groups = []

            paginator = self.client.get_paginator("list_agent_action_groups")
            for page in paginator.paginate(
                agentId=agent_id,
                agentVersion=agent_version,
                PaginationConfig={"PageSize": 10},
            ):
                action_groups.extend(page["actionGroupSummaries"])

        except ClientError as e:
            logger.error(f"Couldn't list action groups. {e}")
            raise
        else:
            return action_groups

    # snippet-end:[python.example_code.bedrock-agent.ListAgentActionGroups]

    # snippet-start:[python.example_code.bedrock-agent.ListAgentKnowledgeBases]
    def list_agent_knowledge_bases(self, agent_id, agent_version):
        """
        List the knowledge bases associated with a version of an Amazon Bedrock Agent.

        :param agent_id: The unique identifier of the agent.
        :param agent_version: The version of the agent.
        :return: The list of knowledge base summaries for the version of the agent.
        """

        try:
            knowledge_bases = []

            paginator = self.client.get_paginator("list_agent_knowledge_bases")
            for page in paginator.paginate(
                agentId=agent_id,
                agentVersion=agent_version,
                PaginationConfig={"PageSize": 10},
            ):
                knowledge_bases.extend(page["agentKnowledgeBaseSummaries"])

        except ClientError as e:
            logger.error(f"Couldn't list knowledge bases. {e}")
            raise
        else:
            return knowledge_bases

    # snippet-end:[python.example_code.bedrock-agent.ListAgentKnowledgeBases]
    def associate_agent_knowledge_base(self, agent_id, agent_version, description, knowledge_base_id, knowledge_base_state):
        """
        Associate a knowledge base with a version of an Amazon Bedrock Agent.

        :param agent_id: The unique identifier of the agent.
        :param agent_version: The version of the agent.
        :param knowledge_base_id: The unique identifier of the knowledge base.
        :return: The response from Agents for Bedrock if successful, otherwise raises an exception.
        """

        try:
            response = self.client.associate_agent_knowledge_base(
                agentId=agent_id,
                agentVersion=agent_version,
                description=description,
                knowledgeBaseId=knowledge_base_id,
                knowledgeBaseState=knowledge_base_state,
            )
        except ClientError as e:
            logger.error(f"Couldn't associate knowledge base. {e}")
            raise
        else:
            return response

    # snippet-start:[python.example_code.bedrock-agent.PrepareAgent]
    def prepare_agent(self, agent_id):
        """
        Creates a DRAFT version of the agent that can be used for internal testing.

        :param agent_id: The unique identifier of the agent to prepare.
        :return: The response from Agents for Bedrock if successful, otherwise raises an exception.
        """
        try:
            prepared_agent_details = self.client.prepare_agent(agentId=agent_id)
        except ClientError as e:
            logger.error(f"Couldn't prepare agent. {e}")
            raise
        else:
            return prepared_agent_details

    # snippet-end:[python.example_code.bedrock-agent.PrepareAgent]

# snippet-end:[python.example_code.bedrock-agent.BedrockAgentWrapper.class]

References

No response

Would you like to implement a fix?

None

github-actions[bot] commented 2 months ago

Community Note

Voting for Prioritization

Volunteering to Work on This Issue

drewtul commented 1 month ago

I've not had a chance to run your sample code, but I notice in our acceptance tests we're not creating an alias and I'm wondering if there's an issue with timing there or just ordering.

Do you see AssociateAgentKnowledgeBase events in CloudTrail and if so do they have an error?

Could you try the alias resource with a depends_on the knowledge base association:

resource "aws_bedrockagent_agent_alias" "live" {
  agent_id         = aws_bedrockagent_agent.kb_agent.id
  agent_alias_name = "live"
  description      = "Live alias for the ${var.cluster}-${var.application} KB agent"
  depends_on = [aws_bedrockagent_agent_knowledge_base_association.kb_agent_association] 
}
tleibovich commented 1 month ago

Thanks for the suggestion, Andrew. Unfortunately, that did not work, and I also tried adding a sleep, and that was unsuccessful as well.

resource "time_sleep" "before_alias" { create_duration = "20s" depends_on = [aws_bedrockagent_agent_knowledge_base_association.kb_agent_association] }

resource "aws_bedrockagent_agent_alias" "live" { agent_id = aws_bedrockagent_agent.kb_agent.id agent_alias_name = "live" description = "Live alias for the ${var.cluster}-${var.application} KB agent" depends_on = [ time_sleep.before_alias ] }

Thanks, Tim [cid:email-signature-300x120-white_3096bf70-a89f-4161-a197-7947edd94c6a.jpg] Tim Leibovich DevOps Engineer II

This email may contain privileged information. If received in error, please immediately delete, then notify the sender.


From: Andrew Tulloch @.> Sent: Thursday, August 8, 2024 9:56 AM To: hashicorp/terraform-provider-aws @.> Cc: Tim Leibovich @.>; Author @.> Subject: Re: [hashicorp/terraform-provider-aws] [Bug]: Bedrock agent association has no knowledgebase attached (Issue #38660)

I've not had a chance to run your sample code, but I notice in our acceptance tests we're not creating an alias and I'm wondering if there's an issue with timing there or just ordering.

Could you try the alias resource with a depends_on the knowledge base association:

resource "aws_bedrockagent_agent_alias" "live" { agent_id = aws_bedrockagent_agent.kb_agent.id agent_alias_name = "live" description = "Live alias for the ${var.cluster}-${var.application} KB agent" depends_on = [aws_bedrockagent_agent_knowledge_base_association.kb_agent_association] }

— Reply to this email directly, view it on GitHubhttps://github.com/hashicorp/terraform-provider-aws/issues/38660#issuecomment-2276036244, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BATEPY72I6YFTDFCIMT255LZQOBJZAVCNFSM6AAAAABL4X6TFWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENZWGAZTMMRUGQ. You are receiving this because you authored the thread.Message ID: @.***>

drewtul commented 1 month ago

Thanks, did you manage to see if CloudTrail had the event I listed above and if there was an error from it?

tleibovich commented 1 month ago

Just spot checked several of the AssociateAgentKnowledgeBase events performed by the terraform user, and none of them had error codes or any indication of an issue. Below is the response text from one of them.

"responseElements": {
    "agentKnowledgeBase": {
        "createdAt": "2024-08-08T15:22:50.947769599Z",
        "description": "***",
        "knowledgeBaseId": "<redacted but correct>",
        "knowledgeBaseState": "ENABLED",
        "updatedAt": "2024-08-08T15:22:50.947769599Z"
    }
},
github-actions[bot] commented 1 month ago

[!WARNING] This issue has been closed, meaning that any additional comments are hard for our team to see. Please assume that the maintainers will not see them.

Ongoing conversations amongst community members are welcome, however, the issue will be locked after 30 days. Moving conversations to another venue, such as the AWS Provider forum, is recommended. If you have additional concerns, please open a new issue, referencing this one where needed.

drewtul commented 1 month ago

@tleibovich see the referenced PR for the fix, the agent needed preparing again after the knowledge base was associated. You still need the depends_on so the association is created before the alias.

tleibovich commented 1 month ago

Awesome! Thanks!

github-actions[bot] commented 1 month ago

This functionality has been released in v5.63.0 of the Terraform AWS Provider. Please see the Terraform documentation on provider versioning or reach out if you need any assistance upgrading.

For further feature requests or bug reports with this functionality, please create a new GitHub issue following the template. Thank you!

github-actions[bot] commented 2 weeks ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.