O blog da AWS

Crie um chatbot multi-tenant com o RAG usando o Amazon Bedrock e o Amazon EKS

Por Farooq Ashraf, Jared Dean e Ravi Yadav

Introdução

Com toda a disponibilidade de modelos de Inteligência Artificial Generativa, muitos clientes estão explorando maneiras de criar aplicativos de chatbot que possam atender a uma ampla variedade de clientes finais, com cada instância de chatbot se especializando nas informações contextuais de um tenant específico, e executar esses aplicativos multi-tenant em grande escala com uma infraestrutura economicamente rentável e familiar para suas equipes de desenvolvimento. O Amazon Bedrock, um serviço na nuvem totalmente gerenciado, enfrenta esse desafio de frente, oferecendo uma variedade de modelos de base (Foundation Models– FMs) poderosos por meio de uma interface de programação de aplicação (API), sem gerenciar infraestrutura, simplificando o desenvolvimento de chatbots de IA generativa altamente adaptáveis e economicamente rentáveis.

O Amazon Elastic Kubernetes Service (Amazon EKS) endereça vários desafios relacionados à criação de aplicativos de chatbot, como multi-tenancy, desafios de escalabilidade, resiliência e custo. O Amazon EKS é comumente adotado pelos clientes da AWS para implantação em grande escala de aplicativos multi-tenant baseados em contêineres que atendem ao público interno e externo. Várias pesquisas e relatórios, inclusive da Cloud Native Computing Foundation (CNCF), Dynatrace, Splunke Datadog, também indicam o crescimento do Kubernetes como a solução preferida de orquestração de contêineres em ambientes corporativos.

Este post investiga uma solução para criar um chatbot multi-tenant com Retrieval Augmented Generation (RAG). O RAG é um padrão comum em que um modelo de linguagem de uso geral é consultado com uma pergunta do usuário junto com informações contextuais adicionais extraídas de documentos privados. Fornecemos instruções de implantação passo a passo e código de referência para a solução em um repositório do GitHub.

Visão geral da solução

Conforme ilustrado na Figura 1, a solução usa o Amazon EKS como base para executar o aplicativo chatbot em contêineres, com namespaces Kubernetes servindo como uma unidade lógica de workload (carga de trabalho) computacional por tenant. O Istio é um service mesh (malha de serviços) de código aberto comumente usada com o Kubernetes para implementar aplicações multi-tenant e fornece recursos como gerenciamento de tráfego, segurança e observabilidade no nível do pod. Usamos o service mesh Istio para funções de controle de ingress e funções de roteamento, pois ele suporta autorização externa, validação de JWT na borda e roteamento de requisição com base em vários parâmetros HTTP. Usamos um proxy de Open ID Connect (OIDC) para retransmitir requisições de autorização para um provedor de identidade que, no nosso caso, é o User Pools do Amazon Cognito. As requisições dos clientes são recebidas por meio de um Network Load Balancer (NLB). Para cada tenant, usamos o padrão de subdomínio DNS para direcionar o tráfego do usuário final, onde o subdomínio DNS é mapeado para os endereços IP do NLB. O NLB encaminha todo o tráfego para um Istio Ingress Gateway. Uma discussão detalhada sobre como o Istio oferece suporte à multi-tenancy no Amazon EKS pode ser encontrada no post SaaS Identity and Routing with Istio Service Mesh e Amazon EKS, junto com um código de referência.

Figura 1 — Arquitetura de alto nível

Os documentos privados que servem como base de conhecimento para a implementação do RAG são convertidos em embeddings vetoriais e são indexados e armazenados no índice de banco de dados vetorial em memória do Facebook AI Similarity Search (FAISS), para uma busca de similaridade eficiente. Usamos o modelo Titan Embeddings para converter dados textuais em embeddings vetoriais e o Claude Instant para gerar a resposta às consultas do usuário. Ambos os modelos estão disponíveis no Amazon Bedrock. O RAG é implementado com o FAISS, uma biblioteca de código aberto para implementar o armazenamento em memória de embeddings vetoriais e busca por similaridade. Usamos o FAISS por sua simplicidade e menor espaço, que se adequam aos casos de uso de demonstração. Para implementações corporativas, bancos de dados vetoriais, como pgvector, Amazon OpenSearch e outros produtos de parceiros da AWS, seriam as opções preferidas, oferecendo suporte à escalabilidade, persistência e acesso onipresente. Os embeddings vetoriais são gerados usando o modelo Titan Embeddings. A biblioteca LangChain fornece a funcionalidade para executar consultas no modelo de texto e gerenciar o histórico de chat salvando-o no Amazon DynamoDB. Essa biblioteca ajuda a orquestrar várias etapas durante o processamento da entrada do usuário, incluindo a recuperação do histórico do chat, a requisição do embedding vetorial do histórico do chat combinada com a entrada do usuário através do modelo de embedding e, finalmente, o uso dos embeddings recebidos para pesquisar o banco de dados vetorial e o envio dos documentos recuperados junto com o histórico do chat e a entrada do usuário ao modelo Claude Instant para receber a saída.

Passo a passo

Containerização de componentes de chatbot e RAG

O aplicativo chatbot consiste em dois componentes de microsserviços: uma interface de front-end do chat que interage com o cliente e uma API RAG. Ambos os microsserviços são criados como imagens de contêiner do Docker para agendamento como pods do Kubernetes no Amazon EKS.

O Dockerfile para a interface de chat é mostrado no trecho a seguir.

FROM public.ecr.aws/docker/library/python:3.11.4-slim AS installer-image
WORKDIR /app
RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update -y 2>/dev/null >/dev/null && \
    DEBIAN_FRONTEND=noninteractive apt-get -qq install -y \
    build-essential \
    curl \
    software-properties-common 2>/dev/null >/dev/null \
    && rm -rf /var/lib/apt/lists/*
ADD app/* ./
RUN pip install --user --upgrade -q -q pip && pip install --user -q -q -r requirements.txt


FROM public.ecr.aws/docker/library/python:3.11.4-slim
RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update -y 2>/dev/null >/dev/null && \
    DEBIAN_FRONTEND=noninteractive apt-get -qq upgrade -y 2>/dev/null >/dev/null && \
    DEBIAN_FRONTEND=noninteractive apt install -qq -y curl 2>/dev/null >/dev/null && \
    addgroup --gid 8000 streamlit && \
    adduser --uid 8000 --gid 8000 --disabled-password --gecos "" streamlit
USER streamlit
WORKDIR /home/streamlit/app
COPY --chown=streamlit:streamlit --from=installer-image /root/.local /home/streamlit/.local/
COPY --chown=streamlit:streamlit app/*.py /home/streamlit/app/
ENV PATH=/home/streamlit/.local/bin:$PATH
EXPOSE 8501
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
ENTRYPOINT ["streamlit", "run", "webapp.py", "--server.port=8501", "--server.address=0.0.0.0"]

Usamos uma imagem base bem leve de Python da Amazon ECR Public Gallery e usamos a abordagem de criação em vários estágios. Na primeira etapa, construímos e instalamos os pacotes Python necessários no diretório home do usuário. No segundo estágio, criamos um usuário e um grupo Linux não- root para execução da interface de chat baseada em Streamlit, copiamos os pacotes Python instalados da imagem do primeiro estágio e também copiamos o código do aplicativo junto com todos os módulos associados no diretório de trabalho do aplicativo na imagem do contêiner.

O Dockerfile para a imagem Docker da API RAG é mostrado no seguinte trecho de código.

FROM public.ecr.aws/docker/library/python:3.11.4-slim AS installer-image
WORKDIR /app
RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update -y 2>/dev/null >/dev/null && \
    DEBIAN_FRONTEND=noninteractive apt-get -qq install -y \
    build-essential \
    curl 2>/dev/null >/dev/null \
    && rm -rf /var/lib/apt/lists/*
ADD api/requirements.txt ./
RUN pip install --upgrade -q -q pip && \
    pip install --user --upgrade -q -q pip && pip install --user -q -q -r requirements.txt && \
    python -m pip install --user -q -q botocore && \
    python -m pip install --user -q -q boto3

FROM public.ecr.aws/docker/library/python:3.11.4-slim
RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update -y 2>/dev/null >/dev/null && \
    DEBIAN_FRONTEND=noninteractive apt-get -qq upgrade -y 2>/dev/null >/dev/null && \
    DEBIAN_FRONTEND=noninteractive apt install -qq -y curl 2>/dev/null >/dev/null && \
    addgroup --gid 8000 ragapi && \
    adduser --uid 8000 --gid 8000 --disabled-password --gecos "" ragapi
USER ragapi
WORKDIR /home/ragapi/app
COPY --chown=ragapi:ragapi --from=installer-image /root/.local /home/ragapi/.local/
COPY --chown=ragapi:ragapi api/app /home/ragapi/app/
ENV PATH=/home/ragapi/.local/bin:$PATH
EXPOSE 8000
ENTRYPOINT ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "main:app"]

Para a API RAG, usamos a mesma imagem base do Python do front-end e seguimos etapas semelhantes às descritas para o front-end. Para servir a API, usamos o framework FastAPI e uma combinação de Gunicorn/Uvicorn para executar um servidor web ASGI eficiente.

Depois que as imagens do contêiner são criadas, elas são enviadas para o Amazon Elastic Container Registry (Amazon ECR) e estão prontas para implantação. As imagens do contêiner do ECR são extraídas durante a implantação do pod de chatbot no Amazon EKS.

Conforme mostrado na Figura 2, um namespace Kubernetes é criado para cada implantação do tenant, no qual o pod do chatbot é implantado com variáveis de ambiente configuradas de acordo com as configurações específicas do tenant. O pod do chatbot consiste em dois contêineres, com o contêiner principal sendo o front-end, enquanto a API RAG atua como um contêiner sidecar que serve a API necessária para a tarefa de perguntas e respostas. Também criamos uma role do AWS Identity and Access Management (AWS IAM) por tenant que tem a permissão necessária para o bucket específico do Amazon Simple Storage Service (Amazon S3) e para a tabela do Amazon DynamoDB. Para simplificar o caso de uso, criamos recursos dedicados do Amazon S3 e do Amazon DynamoDB por tenant. A role do AWS IAM está associada a uma conta de serviço do Kubernetes que também é provisionada junto com o pod e permite que os contêineres no pod acessem os recursos de dados necessários.

Figura 2 — Arquitetura de pod do Amazon EKS

Também é um padrão comum combinar os dados do tenant em menos buckets e tabelas em relação ao número de tenants, com condições do IAM para gerar credenciais dinâmicas usando o AWS Security Token Service (STS) a fim de restringir o acesso aos dados específicos do tenant e do usuário por requisição recebida. Detalhes e exemplos de código para implementar o padrão de credenciais dinâmicas podem ser encontrados no Workshop de microsserviços de SaaS da AWS e na publicação sobre isolamento de dados de SaaS com credenciais dinâmicas usando o HashiCorp Vault no Amazon EKS.

O manifesto do Kubernetes usado para implantar a conta de serviço, a própria implantação e o serviço está listado a seguir.

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::${ACCOUNT_ID}:role/${EKS_CLUSTER_NAME}-${TENANT}-chatbot-access-role-${RANDOM_STRING}
  name: ${SA_NAME}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: chatbot
  labels:
    app: chatbot
spec:
  replicas: 1
  selector:
    matchLabels:
      app: chatbot
  template:
    metadata:
      labels:
        workload-tier: frontend
        app: chatbot
    spec:
      serviceAccountName: ${SA_NAME}
      containers:
        - image: ${REPO_URI_CHATBOT}:latest
          imagePullPolicy: Always
          name: chatbot
          ports:
            - containerPort: 8501
          env:
          - name: ISSUER_URI
            value: ${ISSUER_URI}
          - name: SESSIONS_TABLE
            value: ${SESSIONS_TABLE}
        - image: ${REPO_URI_RAGAPI}:latest
          imagePullPolicy: Always
          name: ragapi
          ports:
            - containerPort: 8000
          env:
          - name: CONTEXTUAL_DATA_BUCKET
            value: contextual-data-${TENANT}-${RANDOM_STRING}
          - name: CHATHISTORY_TABLE
            value: ${CHATHISTORY_TABLE}
          - name: TEXT2TEXT_MODEL_ID
            value: ${TEXT2TEXT_MODEL_ID}
          - name: EMBEDDING_MODEL_ID
            value: ${EMBEDDING_MODEL_ID}
          - name: BEDROCK_SERVICE
            value: ${BEDROCK_SERVICE}
          - name: AWS_DEFAULT_REGION
            value: ${AWS_DEFAULT_REGION}
---
kind: Service
apiVersion: v1
metadata:
  name: chatbot
  labels:
    app: chatbot
spec:
  selector:
    app: chatbot
  ports:
    - port: 80
      name: http
      targetPort: 8501

Como pode ser visto nas definições de contêiner, várias variáveis de ambiente são passadas para os contêineres que são usados pelo aplicativo para localizar recursos de dados e endpoints de API específicos por tenant.

Também é importante observar que os locais das imagens de contêineres também são parametrizados e podem ser transmitidos durante a implantação do manifesto, que, em nosso caso, são definidos com os endpoints do Amazon ECR para cada imagem.

Ingestão de dados com o FAISS e Modelo de Embeddings Titan

Os dados contextuais que o chatbot usa para gerar respostas às consultas dos usuários são inseridos em um índice FAISS como parte da implantação. Conforme mostrado na Figura 3, os dados contextuais são lidos primeiro no bucket Amazon S3 do tenant.

Figura 3 — Ingestão de dados contextuais

O mecanismo de ingestão usa o CharacterTextSplitter do LangChain para fragmentar os dados ingeridos em tamanhos razoáveis, e a biblioteca FAISS converte cada um dos fragmentos em um embedding vetorial chamando o modelo de embedding Titan, criando um índice dos embeddings. Essa indexação melhora o tempo de resposta da pesquisa por similaridade. O conjunto de índices, assim criado, tem sua saída como um arquivo formatado binário e carregado de volta no bucket do Amazon S3, que é acessado pelo microsserviço RAG-API durante a inicialização e carregado em sua memória. Com bancos de dados vetoriais maiores, o índice é criado e gerenciado pela instância do banco de dados e pode ser consultado pelo aplicativo usando APIs específicas do banco de dados. Ter um banco de dados vetorial dedicado dissocia o carregamento, a atualização e a indexação dos dados de contexto do aplicativo, bem como o escalonamento da instância do banco de dados.

O trecho de código a seguir mostra os detalhes de implementação da sequência acima.

boto3_bedrock = boto3.client('bedrock', BEDROCK_REGION, endpoint_url=BEDROCK_ENDPOINT)
br_embeddings = BedrockEmbeddings(client=boto3_bedrock)

...

loader = CSVLoader(f"./{LOCAL_RAG_DIR}/{DATAFILE}")
documents_aws = loader.load() #
print(f"documents:loaded:size={len(documents_aws)}")

docs = CharacterTextSplitter(chunk_size=2000, chunk_overlap=400, separator=",").split_documents(documents_aws)

...

vector_db = FAISS.from_documents(
    documents=docs,
    embedding=br_embeddings, 
)

...

vector_db.save_local(f"{FAISS_INDEX_DIR}-{t}")

try:
    to_upload = os.listdir(f"./{FAISS_INDEX_DIR}-{t}")
    for file in to_upload:
        s3.Bucket(S3_BUCKET).upload_file(f"./{FAISS_INDEX_DIR}-{t}/{file}", f"{FAISS_INDEX_DIR}/{file}", )
except botocore.exceptions.ClientError as e:
    if e.response['Error']['Code'] == "404":
        print("The object does not exist.")
    else:
        raise

Integração com Claude Instant

O microsserviço da API RAG recebe as consultas do usuário do front-end, as converte em um prompt formatado para o FM apropriado e interage com o modelo usando a biblioteca LangChain. A biblioteca LangChain é inicializada para manter o histórico de chat em uma tabela do Amazon DynamoDB específica por tenants. Quando uma consulta é recebida, o LangChain é chamado para recuperar os resultados da pesquisa por similaridade do banco de dados vetoriais e enviar o prompt junto com os resultados correspondentes da pesquisa e o histórico de chat anterior como contexto para o modelo Claude Instant. A resposta recebida do FM é retornada pela API RAG ao cliente por meio da interface de chat.

O fluxo de uma conversa de chat é mostrado na Figura 4. Quando o usuário inicia uma conexão pela primeira vez com a interface do chat apontando um navegador para seu Fully Qualified Domain Name (FQDN), como tenant.example.com, a requisição será interceptada pelo Istio Ingress Gateway e encaminhada ao proxy OIDC para autorização. Como a requisição não contém o cookie associado ao token de autorização, isso redireciona o navegador do usuário para a interface de usuário hospedada (Hosted UI) do User Pool do tenant. Depois que o usuário faz login, a requisição recebe um código do User Pool que é passado para o proxy do OIDC, que o troca com o User Pool pelo token de identidade do usuário (formatado como JSON Web Token [JWT]). O proxy OIDC armazena em cache o JWT e retorna um cookie ao navegador do usuário para ser usado em requisições futuras. Quando a requisição chega ao pod do tenant, o JWT é verificado em relação ao URL do emissor para garantir que o JWT seja genuíno. Também é verificado que a requisição vem do Istio Ingress Gateway. Depois de passar por essas verificações, a requisição chegou à interface do chat implantada no namespace do tenant.

Figura 4 — Fluxo da conversa no chat

O trecho de código a seguir mostra que há duas tabelas do Amazon DynamoDB criadas para cada tenant durante a implantação. A tabela Sessions acompanha as interações do usuário, e a tabela ChatHistory é usada pelo LangChain para registrar o histórico da conversa de chat.

TENANTS="tenanta tenantb"

for t in $TENANTS
do

    export TABLE_NAME="Sessions_${t}_${RANDOM_STRING}"
    
    echo "Creating DynamoDB table ${TABLE_NAME}"
    export DDB_TABLE=$(aws dynamodb create-table \
                        --table-name ${TABLE_NAME} \
                        --attribute-definitions \
                            AttributeName=TenantId,AttributeType=S \
                        --provisioned-throughput \
                            ReadCapacityUnits=5,WriteCapacityUnits=5 \
                        --key-schema \
                            AttributeName=TenantId,KeyType=HASH \
                        --table-class STANDARD
                        )
        
    export TABLE_NAME="ChatHistory_${t}_${RANDOM_STRING}"

    echo "Creating DynamoDB table ${TABLE_NAME}"
    export DDB_TABLE=$(aws dynamodb create-table \
                        --table-name ${TABLE_NAME} \
                        --attribute-definitions \
                            AttributeName=SessionId,AttributeType=S \
                        --provisioned-throughput \
                            ReadCapacityUnits=5,WriteCapacityUnits=5 \
                        --key-schema \
                            AttributeName=SessionId,KeyType=HASH \
                        --table-class STANDARD
                        )
done

O TenantId e o UserEmail são extraídos do JWT de autenticação pelo sidecar proxy do Istio e injetados como cabeçalhos X-Auth-Request-Tenantid e X-Auth-Request-Email, respectivamente. O aplicativo de chat, ao receber a entrada do usuário, cria um SessionID associado ao TenantId e ao UserEmail. Ele também registra a hora UNIX da última interação do usuário. O SessionID é uma string UUID da versão 4. Uma string concatenada de tenantID:userEmail:sessionID é enviada à API RAG junto com a consulta de chat, para ser usada como identificador de sessão pelo back-end, para que os históricos de chat possam ser vinculados a usuários específicos que podem fornecer informações contextuais ricas para uso posterior, como recomendações de produtos, relações com clientes, suporte técnico etc. O trecho de código a seguir mostra esse processamento no aplicativo.

...

headers = _get_websocket_headers()
tenantid = headers.get("X-Auth-Request-Tenantid")
user_email = headers.get("X-Auth-Request-Email")

tenant_id = tenantid + ":" + user_email
dyn_resource = boto3.resource('dynamodb')
IDLE_TIME = 600                                     # seconds
current_time = int(datetime.now().timestamp())

...
    
if user_input:
    try:
        sessions = Sessions(dyn_resource)
        sessions_exists = sessions.exists(table_name)
        if sessions_exists:
            session = sessions.get_session(tenant_id)
            if session:
                if ((current_time - session['last_interaction']) < IDLE_TIME):
                    sessions.update_session_last_interaction(tenant_id, current_time)
                    updated_session = sessions.get_session(tenant_id)
                    print(updated_session['session_id'])
                else:
                    sessions.update_session(tenant_id, current_time)
                    updated_session = sessions.get_session(tenant_id)
            else:
                sessions.add_session(tenant_id)
                session = sessions.get_session(tenant_id)
    except Exception as e:
        print(f"Something went wrong: {e}")

    headers: Dict = {"accept": "application/json",
                     "Content-Type": "application/json"
                    }
    if mode == MODE_RAG:
        user_session_id = tenant_id + ":" + session["session_id"]
        data = {"q": user_input, "user_session_id": user_session_id, "verbose": True}
        resp = req.post(api_rag_ep, headers=headers, json=data)
        if resp.status_code != HTTP_OK:
            output = resp.text
        else:
            resp = resp.json()
            sources = [d['metadata']['source'] for d in resp['docs']]
            output = f"{resp['answer']} \n \n Sources: {sources}"
    else:
        print("error")
        output = f"unhandled mode value={mode}"
    st.session_state.past.append(user_input)  
    st.session_state.generated.append(output)

Como a interface de chat e os microsserviços da API RAG estão localizados no mesmo pod do Kubernetes, a interface de chat se comunica com a API RAG pela interface de rede localhost (127.0.0.1).

Quando o microsserviço da API RAG é iniciado, ele carrega o índice FAISS em sua memória e começa a escutar as conexões. Quando uma requisição é recebida, ela inicializa um cliente boto3 da AWS para o Bedrock contendo credenciais e passa os dados da requisição junto com os parâmetros de FM e a estrutura de dados do cliente Bedrock para o LangChain. A biblioteca LangChain está configurada para salvar conversas de chat em uma tabela do Amazon DynamoDB e inicia uma ConversationalRetrievalChain, que pesquisa automaticamente o contexto no índice FAISS, recupera o histórico salvo, formata a consulta com base em uma definição de template, agrupa o contexto com a consulta formatada, a envia para o modelo Claude Instant e salva a resposta do modelo. A API RAG retorna a resposta recebida do LangChain para o aplicativo de front-end para apresentação ao usuário. A sequência de eventos gerenciados pela API RAG está detalhada no trecho de código a seguir.

...

VECTOR_DB_DIR = os.path.join("/tmp", "_vectordb")
_vector_db = None
vectordb_s3_path: str = f"s3://{os.environ.get('CONTEXTUAL_DATA_BUCKET')}/faiss_index/"

if _vector_db is None:
    _vector_db = load_vector_db_faiss(vectordb_s3_path,
                                      VECTOR_DB_DIR,
                                      BEDROCK_ENDPOINT,
                                      BEDROCK_REGION)
...

@router.post("/rag")
def rag_handler(req: Request) -> Dict[str, Any]:
    docs = _vector_db.similarity_search(req.q, k=req.max_matching_docs)

...

    parameters = {
        "max_tokens_to_sample": req.maxTokenCount,
        "stop_sequences": req.stopSequences,
        "temperature": req.temperature,
        "top_k": req.topK,
        "top_p": req.topP
        }

    endpoint_name = req.text_generation_model

    session_id = req.user_session_id
    boto3_bedrock = boto3.client(service_name=BEDROCK_SERVICE)
    bedrock_llm = Bedrock(model_id=TEXT2TEXT_MODEL_ID, client=boto3_bedrock)
    bedrock_llm.model_kwargs = parameters
    
    message_history = DynamoDBChatMessageHistory(table_name=CHATHISTORY_TABLE, session_id=session_id)

    memory_chain = ConversationBufferMemory(
        memory_key="chat_history",
        chat_memory=message_history,
        input_key="question",
        ai_prefix="Assistant",
        return_messages=True
    )
    
    condense_prompt_claude = PromptTemplate.from_template("""
    Answer only with the new question.
    
    Human: How would you ask the question considering the previous conversation: {question}
    
    Assistant: Question:""")

    qa = ConversationalRetrievalChain.from_llm(
        llm=bedrock_llm, 
        retriever=_vector_db.as_retriever(search_type='similarity', search_kwargs={"k": req.max_matching_docs}), 
        memory=memory_chain,
        condense_question_prompt=condense_prompt_claude,
        chain_type='stuff', # 'refine',
    )

    qa.combine_docs_chain.llm_chain.prompt = PromptTemplate.from_template("""
    {context}

    Human: Answer the question inside the  XML tags.
    
    {question}
    
    Do not use any XML tags in the answer. If you don't know the answer or if the answer is not in the context say "Sorry, I don't know."

    Assistant:""")

    answer = ""
    answer = qa.run({'question': req.q })

    logger.info(f"answer received from llm,\nquestion: \"{req.q}\"\nanswer: \"{answer}\"")
    resp = {'question': req.q, 'answer': answer, 'session_id': req.user_session_id}
    if req.verbose is True:
        resp['docs'] = docs

    return resp

Conclusão

Neste post, mostramos como criar um chatbot multi-tenant com o RAG usando o Amazon Bedrock e o Amazon EKS. A implementação do RAG usa dados privados como contexto para grandes modelos de linguagem (Large Language Models – LLMs) para produzir respostas previsíveis e relevantes às consultas de chat. Embora este post demonstre o caso de uso do chatbot, essa abordagem e componentes também podem ser aplicados a casos de uso e tarefas, como resposta a perguntas, resumo de texto e criação de conteúdo.

O Amazon EKS fornece multi-tenant nativo, permitindo o isolamento da carga de trabalho entre tenants ou usuários, permitindo o compartilhamento eficiente de recursos. Recomendamos que você explore os modelos Amazon Titan e o Amazon Bedrock para seus casos de uso de IA generativa e crie uma solução resiliente e econômica com o Amazon EKS como plataforma de orquestração subjacente.

Confira as instruções de implantação passo a passo e o código de amostra da solução discutida no post, no repositório do GitHub aqui.

Se você estiver interessado nos conceitos apresentados neste post, sinta-se à vontade para entrar em contato usando as mídias sociais (Farooq Ashraf, Ravi Yadav e Jared Dean)

Este blog é uma tradução do conteúdo original em inglês (link aqui ).

TAGS: Amazon EKS, Aprendizado de máquina

Autores

Farooq Ashraf

Farooq Ashraf é arquiteto sênior de soluções na AWS, com sede na área da Baía de São Francisco, oferecendo suporte a clientes na área de ISVs. Ele tem ampla experiência em arquiteturas SaaS, IA generativa e microsserviços baseados em contêineres.

Jared Dean

Jared Dean é arquiteto principal de soluções de IA/ML na AWS. Jared trabalha com clientes de vários setores para desenvolver aplicativos de aprendizado de máquina que melhoram a eficiência. Ele está interessado em todas as coisas relacionadas à IA, tecnologia e churrasco.

Ravi Yadav

Ravi lideraa estratégia de entrada no mercado do AWS Container Services, oferecendo suporte a ISVs e clientes nativos digitais. Antes da AWS, Ravi teve experiência em gerenciamento de produtos, estratégia de produtos/corporativos e engenharia em várias empresas, incluindo Moody’s, Mesosphere, IBM e Cerner.

Tradutor

Rafael Weffort

Rafael Weffort é engenheiro da computação pós-graduado, quase-biólogo, pai do Martin e montanhista. É arquiteto de soluções da AWS para o segmento de Fornecedores de Software Independente (ISV – Independent Software Vendor). Começou a carreira nos primórdios da internet brasileira, desenvolvendo e vendendo websites aos 12 anos. Por uma década foi desenvolvedor full-stack no mercado financeiro e desde 2019 vem apoiando clientes do mundo de SaaS e e-commerce em modernização e otimização na nuvem, se especializando em arquitetura, design de APIs, integrações de sistemas, micros-serviços e contêineres.
https://www.linkedin.com/in/rafaelweffort/

Revisor

Daniel Abib

Daniel Abib é arquiteto de soluções sênior na AWS, com mais de 25 anos trabalhando com gerenciamento de projetos, arquiteturas de soluções escaláveis, desenvolvimento de sistemas e CI/CD, microsserviços, arquitetura Serverless & Containers e segurança. Ele trabalha apoiando clientes corporativos, ajudando-os em sua jornada para a nuvem.
https://www.linkedin.com/in/danielabib/