1. Introdução
Você já se sentiu frustrado e com preguiça de gerenciar todas as suas despesas pessoais? Eu também! Por isso, neste codelab, vamos criar um assistente de gerenciamento de despesas pessoais, com o Gemini 2.5, para fazer todas as tarefas por nós. Desde o gerenciamento dos comprovantes enviados até a análise de quanto você já gastou para comprar um café.
Esse assistente será acessível pelo navegador da Web na forma de uma interface de chat, em que você pode se comunicar com ele, fazer upload de imagens de recibos e pedir que ele os armazene. Talvez você queira pesquisar alguns recibos para acessar o arquivo e fazer uma análise de despesas. E tudo isso foi criado com base no framework do Kit de desenvolvimento de agentes do Google
O aplicativo é dividido em dois serviços: front-end e back-end. Isso permite que você crie um protótipo rápido e teste como ele funciona, além de entender como o contrato da API parece integrar os dois.
No codelab, você vai usar uma abordagem detalhada da seguinte forma:
- Prepare seu projeto do Google Cloud e ative todas as APIs necessárias nele
- Configurar o bucket no Google Cloud Storage e o banco de dados no Firestore
- Criar indexação do Firestore
- Configurar o espaço de trabalho para seu ambiente de programação
- Como estruturar o código-fonte do agente do ADK, ferramentas, avisos etc.
- Como testar o agente usando a interface de desenvolvimento da Web local do ADK
- Crie a interface de chat do serviço de front-end usando a biblioteca Gradio para enviar algumas consultas e fazer o upload de imagens de recibos
- Crie o serviço de back-end: servidor HTTP usando o FastAPI, que é onde o código do agente do ADK, o SessionService e o Artifact Service residem.
- Gerenciar variáveis de ambiente e configurar os arquivos necessários para implantar o aplicativo no Cloud Run
- Implantar o aplicativo no Cloud Run
Visão geral da arquitetura
Pré-requisitos
- Ter experiência com Python
- Conhecimento básico de arquitetura full-stack usando o serviço HTTP
O que você vai aprender
- Prototipagem de front-end da Web com o Gradio
- Desenvolvimento de serviços de back-end com a FastAPI e a Pydantic
- Como projetar o agente do ADK usando vários recursos
- Uso da ferramenta
- Gerenciamento de sessões e artefatos
- Uso de callback para modificação de entrada antes do envio ao Gemini
- Como usar o BuiltInPlanner para melhorar a execução de tarefas com planejamento
- Depuração rápida pela interface da Web local do ADK
- Estratégia para otimizar a interação multimodal com a análise e recuperação de informações usando a engenharia de comandos e a modificação de solicitações do Gemini com o callback do ADK
- Geração aumentada de recuperação de agentes usando o Firestore como banco de dados vetorial
- Gerenciar variáveis de ambiente no arquivo YAML com o Pydantic-settings
- Implantar o aplicativo no Cloud Run usando o Dockerfile e fornecer variáveis de ambiente com o arquivo YAML
O que é necessário
- Navegador da Web Google Chrome
- Uma conta do Gmail
- Um projeto do Cloud com faturamento ativado
Este codelab, desenvolvido para desenvolvedores de todos os níveis (inclusive iniciantes), usa o Python no aplicativo de exemplo. No entanto, não é necessário ter conhecimento de Python para entender os conceitos apresentados.
2. Antes de começar
Selecionar "Projeto ativo" no console do Cloud
Este codelab pressupõe que você já tenha um projeto do Google Cloud com o faturamento ativado. Se você ainda não tem uma, siga as instruções abaixo para começar.
- No console do Google Cloud, na página de seletor de projetos, selecione ou crie um projeto do Google Cloud.
- Confira se o faturamento está ativado para seu projeto do Cloud. Saiba como verificar se o faturamento está ativado em um projeto.
Preparar o banco de dados do Firestore
Em seguida, também vamos precisar criar um banco de dados do Firestore. O Firestore no modo nativo é um banco de dados de documentos NoSQL criado para oferecer escalonamento automático, alto desempenho e facilidade no desenvolvimento de aplicativos. Ele também pode atuar como um banco de dados vetorial que pode oferecer suporte à técnica de geração aprimorada de recuperação para nosso laboratório.
- Pesquise firestore na barra de pesquisa e clique no produto do Firestore.
- Em seguida, clique no botão Criar um banco de dados do Firestore.
- Use (padrão) como o nome do ID do banco de dados e mantenha a Edição padrão selecionada. Para esta demonstração, use o Firestore Native com regras de segurança abertas.
- Você também vai notar que esse banco de dados tem o Uso do nível sem custo financeiro YEAY! Depois, clique no botão Criar banco de dados.
Depois dessas etapas, você será redirecionado para o banco de dados do Firestore que acabou de criar.
Configurar o projeto do Cloud no terminal do Cloud Shell
- Você vai usar o Cloud Shell, um ambiente de linha de comando executado no Google Cloud que vem pré-carregado com bq. Clique em "Ativar o Cloud Shell" na parte de cima do console do Google Cloud.
- Depois de se conectar ao Cloud Shell, verifique se você já está autenticado e se o projeto está definido como seu ID usando o seguinte comando:
gcloud auth list
- Execute o comando a seguir no Cloud Shell para confirmar se o comando gcloud sabe sobre seu projeto.
gcloud config list project
- Se o projeto não estiver definido, use este comando:
gcloud config set project <YOUR_PROJECT_ID>
Também é possível conferir o ID do PROJECT_ID
no console.
Clique nele e você vai ver todo o projeto e o ID do projeto à direita.
- Ative as APIs necessárias usando o comando mostrado abaixo. Isso pode levar alguns minutos. Tenha paciência.
gcloud services enable aiplatform.googleapis.com \
firestore.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
cloudresourcemanager.googleapis.com
Se o comando for executado com sucesso, uma mensagem semelhante à mostrada abaixo vai aparecer:
Operation "operations/..." finished successfully.
A alternativa ao comando gcloud é pelo console, pesquisando cada produto ou usando este link.
Se alguma API for perdida, você poderá ativá-la durante a implementação.
Consulte a documentação para ver o uso e os comandos gcloud.
Preparar o bucket do Google Cloud Storage
Em seguida, no mesmo terminal, precisamos preparar o bucket do GCS para armazenar o arquivo enviado. Execute o comando a seguir para criar o bucket
gsutil mb -l us-central1 gs://personal-expense-assistant-receipts
Ele vai mostrar esta saída:
Creating gs://personal-expense-assistant-receipts/...
Para verificar isso, acesse o menu de navegação no canto superior esquerdo do navegador e selecione Cloud Storage -> Bucket.
Como criar um índice do Firestore para pesquisa
O Firestore é um banco de dados NoSQL nativo, que oferece desempenho superior e flexibilidade no modelo de dados, mas tem limitações em relação a consultas complexas. Como planejamos usar algumas consultas compostas de vários campos e a pesquisa vetorial, primeiro precisamos criar alguns índices. Leia mais sobre os detalhes nesta documentação.
- Execute o comando a seguir para criar um índice que ofereça suporte a consultas compostas
gcloud firestore indexes composite create \
--collection-group=personal-expense-assistant-receipts \
--field-config field-path=total_amount,order=ASCENDING \
--field-config field-path=transaction_time,order=ASCENDING \
--field-config field-path=__name__,order=ASCENDING \
--database="(default)"
- E execute este para oferecer suporte à pesquisa vetorial
gcloud firestore indexes composite create \
--collection-group="personal-expense-assistant-receipts" \
--query-scope=COLLECTION \
--field-config field-path="embedding",vector-config='{"dimension":"768", "flat": "{}"}' \
--database="(default)"
Para verificar o índice criado, acesse o Firestore no console do Google Cloud, clique na instância do banco de dados (padrão) e selecione Índices na barra de navegação.
Acessar o editor do Cloud Shell e configurar o diretório de trabalho do aplicativo
Agora, podemos configurar o editor de código para fazer algumas coisas de programação. Vamos usar o editor do Cloud Shell para isso.
- Clique no botão "Abrir editor" para abrir o editor do Cloud Shell. Você pode escrever seu código aqui
- Verifique se o projeto do Cloud Code está definido no canto inferior esquerdo (barra de status) do editor do Cloud Shell, conforme destacado na imagem abaixo, e se está definido como o projeto ativo do Google Cloud em que você ativou o faturamento. Autorizar se solicitado. Se você já seguiu o comando anterior, o botão também pode apontar diretamente para o projeto ativado, em vez do botão de login.
- Em seguida, vamos clonar o diretório de trabalho do modelo para este codelab do GitHub. Para isso, execute o comando a seguir. Ele vai criar o diretório de trabalho no diretório personal-expense-assistant.
git clone https://github.com/alphinside/personal-expense-assistant-adk-codelab-starter.git personal-expense-assistant
- Depois disso, acesse a seção de cima do editor do Cloud Shell e clique em File->Open Folder,encontre seu diretório username e o diretório personal-expense-assistant e clique no botão OK. Isso vai definir o diretório escolhido como o principal. Neste exemplo, o nome de usuário é alvinprayuda, portanto, o caminho do diretório é mostrado abaixo.
Agora, seu editor do Cloud Shell vai ficar assim:
Configuração do ambiente
Preparar o ambiente virtual do Python
A próxima etapa é preparar o ambiente de desenvolvimento. O terminal ativo atual precisa estar dentro do diretório de trabalho personal-expense-assistant. Usaremos o Python 3.12 neste codelab e o uv python project manager para simplificar a necessidade de criar e gerenciar a versão do Python e o ambiente virtual.
- Se você ainda não abriu o terminal, clique em Terminal -> Novo terminal ou use Ctrl + Shift + C para abrir uma janela de terminal na parte de baixo do navegador.
- Faça o download de
uv
e instale o Python 3.12 com o seguinte comando
curl -LsSf https://astral.sh/uv/0.6.16/install.sh | sh && \
source $HOME/.local/bin/env && \
uv python install 3.12
- Agora vamos inicializar o ambiente virtual usando
uv
. Execute este comando
uv sync --frozen
Isso vai criar o diretório .venv e instalar as dependências. Uma prévia rápida do pyproject.toml vai mostrar informações sobre as dependências mostradas assim:
dependencies = [ "datasets>=3.5.0", "google-adk>=0.2.0", "google-cloud-firestore>=2.20.1", "gradio>=5.23.1", "pydantic>=2.10.6", "pydantic-settings[yaml]>=2.8.1", ]
- Para testar o ambiente virtual, crie um novo arquivo main.py e copie o código a seguir.
def main():
print("Hello from personal-expense-assistant-adk!")
if __name__ == "__main__":
main()
- Em seguida, execute o comando a seguir:
uv run main.py
O resultado será exibido como mostrado abaixo:
Using CPython 3.12 Creating virtual environment at: .venv Hello from personal-expense-assistant-adk!
Isso mostra que o projeto Python está sendo configurado corretamente.
Arquivos de configuração do Setup
Agora vamos configurar os arquivos de configuração para este projeto. Usamos pydantic-settings para ler a configuração do arquivo YAML.
Crie um arquivo chamado settings.yaml com a seguinte configuração. Clique em File->New Text File e preencha com o código abaixo. Em seguida, salve como settings.yaml.
GCLOUD_LOCATION: "us-central1"
GCLOUD_PROJECT_ID: "your_gcloud_project_id"
BACKEND_URL: "http://localhost:8081/chat"
STORAGE_BUCKET_NAME: "personal-expense-assistant-receipts"
DB_COLLECTION_NAME: "personal-expense-assistant-receipts"
Para este codelab, vamos usar os valores pré-configurados para GCLOUD_LOCATION
,
BACKEND_URL
,
STORAGE_BUCKET_NAME
,
DB_COLLECTION_NAME
e BACKEND_URL
.
Agora podemos passar para a próxima etapa, criando o agente e os serviços.
3. Criar o agente usando o Google ADK e o Gemini 2.5
Introdução à estrutura de diretórios do ADK
Vamos começar analisando o que o ADK tem a oferecer e como criar o agente. Acesse a documentação completa do ADK neste URL . O ADK oferece muitos utilitários na execução de comandos da CLI. Alguns deles são os seguintes :
- Configurar a estrutura de diretórios do agente
- Testar rapidamente a interação pela saída de entrada da CLI
- Configurar rapidamente a interface da Web da IU de desenvolvimento local
Agora, vamos criar a estrutura de diretório do agente usando o comando da CLI. Execute o comando a seguir
uv run adk create expense_manager_agent \
--model gemini-2.5-flash-preview-04-17 \
--project {your-project-id} \
--region us-central1
Isso vai criar a seguinte estrutura de diretório do agente:
expense_manager_agent/ ├── __init__.py ├── .env ├── agent.py
E se você inspecionar o init.py e o agent.py, vai encontrar este código:
# __init__.py
from . import agent
# agent.py
from google.adk.agents import Agent
root_agent = Agent(
model='gemini-2.5-flash-preview-04-17',
name='root_agent',
description='A helpful assistant for user questions.',
instruction='Answer user questions to the best of your knowledge',
)
Como criar o agente do Gerenciador de despesas
Vamos criar nosso agente de gerenciamento de despesas. Abra o arquivo expense_manager_agent/agent.py e copie o código abaixo, que vai conter o root_agent.
# expense_manager_agent/agent.py
from google.adk.agents import Agent
from expense_manager_agent.tools import (
store_receipt_data,
search_receipts_by_metadata_filter,
search_relevant_receipts_by_natural_language_query,
get_receipt_data_by_image_id,
)
from expense_manager_agent.callbacks import modify_image_data_in_history
import os
from settings import get_settings
from google.adk.planners import BuiltInPlanner
from google.genai import types
SETTINGS = get_settings()
os.environ["GOOGLE_CLOUD_PROJECT"] = SETTINGS.GCLOUD_PROJECT_ID
os.environ["GOOGLE_CLOUD_LOCATION"] = SETTINGS.GCLOUD_LOCATION
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "TRUE"
# Get the code file directory path and read the task prompt file
current_dir = os.path.dirname(os.path.abspath(__file__))
prompt_path = os.path.join(current_dir, "task_prompt.md")
with open(prompt_path, "r") as file:
task_prompt = file.read()
root_agent = Agent(
name="expense_manager_agent",
model="gemini-2.5-flash-preview-04-17",
description=(
"Personal expense agent to help user track expenses, analyze receipts, and manage their financial records"
),
instruction=task_prompt,
tools=[
store_receipt_data,
get_receipt_data_by_image_id,
search_receipts_by_metadata_filter,
search_relevant_receipts_by_natural_language_query,
],
planner=BuiltInPlanner(
thinking_config=types.ThinkingConfig(
thinking_budget=2048,
)
),
before_model_callback=modify_image_data_in_history,
)
Explicação do código
Esse script contém a inicialização do agente, em que inicializamos o seguinte:
- Defina o modelo a ser usado como
gemini-2.5-flash-preview-04-17
- Configurar a descrição e a instrução do agente como o comando do sistema que está sendo lido em
task_prompt.md
- Fornecer as ferramentas necessárias para oferecer suporte à funcionalidade do agente
- Ativar o planejamento antes de gerar a resposta ou execução final usando os recursos de pensamento rápido do Gemini 2.5
- Configuração de interceptação de callback antes de enviar a solicitação para o Gemini para limitar o número de dados de imagem enviados antes de fazer a previsão
4. Como configurar as ferramentas do agente
Nosso agente de gerenciamento de despesas terá os seguintes recursos:
- Extrair dados da imagem do recibo e armazenar os dados e o arquivo
- Pesquisa exata nos dados de despesas
- Pesquisa contextual nos dados de despesas
Portanto, precisamos das ferramentas adequadas para oferecer suporte a essa funcionalidade. Crie um novo arquivo no diretório expense_manager_agent, nomeie-o como tools.py e copie o código abaixo.
# expense_manager_agent/tools.py
import datetime
from typing import Dict, List, Any
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector
from google.cloud.firestore_v1 import FieldFilter
from google.cloud.firestore_v1.base_query import And
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from settings import get_settings
from google import genai
SETTINGS = get_settings()
DB_CLIENT = firestore.Client(
project=SETTINGS.GCLOUD_PROJECT_ID
) # Will use "(default)" database
COLLECTION = DB_CLIENT.collection(SETTINGS.DB_COLLECTION_NAME)
GENAI_CLIENT = genai.Client(
vertexai=True, location=SETTINGS.GCLOUD_LOCATION, project=SETTINGS.GCLOUD_PROJECT_ID
)
EMBEDDING_DIMENSION = 768
EMBEDDING_FIELD_NAME = "embedding"
INVALID_ITEMS_FORMAT_ERR = """
Invalid items format. Must be a list of dictionaries with 'name', 'price', and 'quantity' keys."""
RECEIPT_DESC_FORMAT = """
Store Name: {store_name}
Transaction Time: {transaction_time}
Total Amount: {total_amount}
Currency: {currency}
Purchased Items:
{purchased_items}
Receipt Image ID: {receipt_id}
"""
def sanitize_image_id(image_id: str) -> str:
"""Sanitize image ID by removing any leading/trailing whitespace."""
if image_id.startswith("[IMAGE-"):
image_id = image_id.split("ID ")[1].split("]")[0]
return image_id.strip()
def store_receipt_data(
image_id: str,
store_name: str,
transaction_time: str,
total_amount: float,
purchased_items: List[Dict[str, Any]],
currency: str = "IDR",
) -> str:
"""
Store receipt data in the database.
Args:
image_id (str): The unique identifier of the image. For example IMAGE-POSITION 0-ID 12345,
the ID of the image is 12345.
store_name (str): The name of the store.
transaction_time (str): The time of purchase, in ISO format ("YYYY-MM-DDTHH:MM:SS.ssssssZ").
total_amount (float): The total amount spent.
purchased_items (List[Dict[str, Any]]): A list of items purchased with their prices. Each item must have:
- name (str): The name of the item.
- price (float): The price of the item.
- quantity (int, optional): The quantity of the item. Defaults to 1 if not provided.
currency (str, optional): The currency of the transaction, can be derived from the store location.
If unsure, default is "IDR".
Returns:
str: A success message with the receipt ID.
Raises:
Exception: If the operation failed or input is invalid.
"""
try:
# In case of it provide full image placeholder, extract the id string
image_id = sanitize_image_id(image_id)
# Check if the receipt already exists
doc = get_receipt_data_by_image_id(image_id)
if doc:
return f"Receipt with ID {image_id} already exists"
# Validate transaction time
if not isinstance(transaction_time, str):
raise ValueError(
"Invalid transaction time: must be a string in ISO format 'YYYY-MM-DDTHH:MM:SS.ssssssZ'"
)
try:
datetime.datetime.fromisoformat(transaction_time.replace("Z", "+00:00"))
except ValueError:
raise ValueError(
"Invalid transaction time format. Must be in ISO format 'YYYY-MM-DDTHH:MM:SS.ssssssZ'"
)
# Validate items format
if not isinstance(purchased_items, list):
raise ValueError(INVALID_ITEMS_FORMAT_ERR)
for _item in purchased_items:
if (
not isinstance(_item, dict)
or "name" not in _item
or "price" not in _item
):
raise ValueError(INVALID_ITEMS_FORMAT_ERR)
if "quantity" not in _item:
_item["quantity"] = 1
# Create a combined text from all receipt information for better embedding
result = GENAI_CLIENT.models.embed_content(
model="text-embedding-004",
contents=RECEIPT_DESC_FORMAT.format(
store_name=store_name,
transaction_time=transaction_time,
total_amount=total_amount,
currency=currency,
purchased_items=purchased_items,
receipt_id=image_id,
),
)
embedding = result.embeddings[0].values
doc = {
"receipt_id": image_id,
"store_name": store_name,
"transaction_time": transaction_time,
"total_amount": total_amount,
"currency": currency,
"purchased_items": purchased_items,
EMBEDDING_FIELD_NAME: Vector(embedding),
}
COLLECTION.add(doc)
return f"Receipt stored successfully with ID: {image_id}"
except Exception as e:
raise Exception(f"Failed to store receipt: {str(e)}")
def search_receipts_by_metadata_filter(
start_time: str,
end_time: str,
min_total_amount: float = -1.0,
max_total_amount: float = -1.0,
) -> str:
"""
Filter receipts by metadata within a specific time range and optionally by amount.
Args:
start_time (str): The start datetime for the filter (in ISO format, e.g. 'YYYY-MM-DDTHH:MM:SS.ssssssZ').
end_time (str): The end datetime for the filter (in ISO format, e.g. 'YYYY-MM-DDTHH:MM:SS.ssssssZ').
min_total_amount (float): The minimum total amount for the filter (inclusive). Defaults to -1.
max_total_amount (float): The maximum total amount for the filter (inclusive). Defaults to -1.
Returns:
str: A string containing the list of receipt data matching all applied filters.
Raises:
Exception: If the search failed or input is invalid.
"""
try:
# Validate start and end times
if not isinstance(start_time, str) or not isinstance(end_time, str):
raise ValueError("start_time and end_time must be strings in ISO format")
try:
datetime.datetime.fromisoformat(start_time.replace("Z", "+00:00"))
datetime.datetime.fromisoformat(end_time.replace("Z", "+00:00"))
except ValueError:
raise ValueError("start_time and end_time must be strings in ISO format")
# Start with the base collection reference
query = COLLECTION
# Build the composite query by properly chaining conditions
# Notes that this demo assume 1 user only,
# need to refactor the query for multiple user
filters = [
FieldFilter("transaction_time", ">=", start_time),
FieldFilter("transaction_time", "<=", end_time),
]
# Add optional filters
if min_total_amount != -1:
filters.append(FieldFilter("total_amount", ">=", min_total_amount))
if max_total_amount != -1:
filters.append(FieldFilter("total_amount", "<=", max_total_amount))
# Apply the filters
composite_filter = And(filters=filters)
query = query.where(filter=composite_filter)
# Execute the query and collect results
search_result_description = "Search by Metadata Results:\n"
for doc in query.stream():
data = doc.to_dict()
data.pop(
EMBEDDING_FIELD_NAME, None
) # Remove embedding as it's not needed for display
search_result_description += f"\n{RECEIPT_DESC_FORMAT.format(**data)}"
return search_result_description
except Exception as e:
raise Exception(f"Error filtering receipts: {str(e)}")
def search_relevant_receipts_by_natural_language_query(
query_text: str, limit: int = 5
) -> str:
"""
Search for receipts with content most similar to the query using vector search.
This tool can be use for user query that is difficult to translate into metadata filters.
Such as store name or item name which sensitive to string matching.
Use this tool if you cannot utilize the search by metadata filter tool.
Args:
query_text (str): The search text (e.g., "coffee", "dinner", "groceries").
limit (int, optional): Maximum number of results to return (default: 5).
Returns:
str: A string containing the list of contextually relevant receipt data.
Raises:
Exception: If the search failed or input is invalid.
"""
try:
# Generate embedding for the query text
result = GENAI_CLIENT.models.embed_content(
model="text-embedding-004", contents=query_text
)
query_embedding = result.embeddings[0].values
# Notes that this demo assume 1 user only,
# need to refactor the query for multiple user
vector_query = COLLECTION.find_nearest(
vector_field=EMBEDDING_FIELD_NAME,
query_vector=Vector(query_embedding),
distance_measure=DistanceMeasure.EUCLIDEAN,
limit=limit,
)
# Execute the query and collect results
search_result_description = "Search by Contextual Relevance Results:\n"
for doc in vector_query.stream():
data = doc.to_dict()
data.pop(
EMBEDDING_FIELD_NAME, None
) # Remove embedding as it's not needed for display
search_result_description += f"\n{RECEIPT_DESC_FORMAT.format(**data)}"
return search_result_description
except Exception as e:
raise Exception(f"Error searching receipts: {str(e)}")
def get_receipt_data_by_image_id(image_id: str) -> Dict[str, Any]:
"""
Retrieve receipt data from the database using the image_id.
Args:
image_id (str): The unique identifier of the receipt image. For example, if the placeholder is
[IMAGE-ID 12345], the ID to use is 12345.
Returns:
Dict[str, Any]: A dictionary containing the receipt data with the following keys:
- receipt_id (str): The unique identifier of the receipt image.
- store_name (str): The name of the store.
- transaction_time (str): The time of purchase in UTC.
- total_amount (float): The total amount spent.
- currency (str): The currency of the transaction.
- purchased_items (List[Dict[str, Any]]): List of items purchased with their details.
Returns an empty dictionary if no receipt is found.
"""
# In case of it provide full image placeholder, extract the id string
image_id = sanitize_image_id(image_id)
# Query the receipts collection for documents with matching receipt_id (image_id)
# Notes that this demo assume 1 user only,
# need to refactor the query for multiple user
query = COLLECTION.where(filter=FieldFilter("receipt_id", "==", image_id)).limit(1)
docs = list(query.stream())
if not docs:
return {}
# Get the first matching document
doc_data = docs[0].to_dict()
doc_data.pop(EMBEDDING_FIELD_NAME, None)
return doc_data
Explicação do código
Nesta implementação de função de ferramentas, projetamos as ferramentas com base nessas duas ideias principais:
- Analisar os dados do recibo e o mapeamento para o arquivo original usando o marcador de posição de string de ID de imagem
[IMAGE-ID <hash-of-image-1>]
- Como armazenar e recuperar dados usando o banco de dados do Firestore
Ferramenta "store_receipt_data"
Essa ferramenta é a de reconhecimento óptico de caracteres. Ela analisa as informações necessárias dos dados da imagem, reconhece a string do ID da imagem e as mapeia para serem armazenadas no banco de dados do Firestore.
Além disso, essa ferramenta também converte o conteúdo do recibo em incorporação usando text-embedding-004
para que todos os metadados e a incorporação sejam armazenados e indexados juntos. Permitir que a flexibilidade seja recuperada por consulta ou pesquisa contextual.
Depois de executar essa ferramenta, você vai notar que os dados do recibo já estão indexados no banco de dados do Firestore, conforme mostrado abaixo.
Ferramenta "search_receipts_by_metadata_filter"
Essa ferramenta converte a consulta do usuário em um filtro de consulta de metadados que oferece suporte à pesquisa por período e/ou transação total. Ele vai retornar todos os dados de recibos correspondentes, e vamos remover o campo de incorporação, porque ele não é necessário para que o agente entenda o contexto.
Ferramenta "search_relevant_receipts_by_natural_language_query"
Essa é a nossa ferramenta de geração aumentada de recuperação (RAG). Nosso agente tem a capacidade de criar a própria consulta para recuperar recibos relevantes do banco de dados vetorial e também pode escolher quando usar essa ferramenta. A ideia de permitir que o agente tome uma decisão independente sobre o uso da ferramenta RAG ou não e projete a própria consulta é uma das definições da abordagem Agentic RAG.
Além de permitir que ele crie a própria consulta, também permitimos que ele selecione quantos documentos relevantes quer recuperar. Combinado com uma engenharia de comando adequada, por exemplo,
# Example prompt Always filter the result from tool search_relevant_receipts_by_natural_language_query as the returned result may contain irrelevant information
Isso vai tornar essa ferramenta poderosa, capaz de pesquisar quase tudo, embora ela possa não retornar todos os resultados esperados devido à natureza não exata da pesquisa de vizinho mais próximo.
5. Modificação do contexto da conversa por meio de callbacks
O Google ADK permite "interceptar" o tempo de execução do agente em vários níveis. Leia mais sobre esse recurso detalhado nesta documentação . Neste laboratório, usamos o before_model_callback
para modificar a solicitação antes de enviar para o LLM e remover os dados de imagem no contexto do histórico de conversas antigo ( inclua apenas dados de imagem nas três últimas interações do usuário) para aumentar a eficiência.
No entanto, ainda queremos que o agente tenha o contexto dos dados da imagem quando necessário. Portanto, adicionamos um mecanismo para adicionar um marcador de posição de ID de imagem de string após cada dado de byte de imagem na conversa. Isso vai ajudar o agente a vincular o ID da imagem aos dados reais do arquivo, que podem ser usados no momento do armazenamento ou da recuperação da imagem. A estrutura vai ficar assim:
<image-byte-data-1> [IMAGE-ID <hash-of-image-1>] <image-byte-data-2> [IMAGE-ID <hash-of-image-2>] And so on..
E quando os dados de bytes se tornam obsoletos no histórico de conversas, o identificador de string ainda está lá para permitir o acesso aos dados com a ajuda da ferramenta. Exemplo de estrutura de histórico após a remoção dos dados de imagem
[IMAGE-ID <hash-of-image-1>] [IMAGE-ID <hash-of-image-2>] And so on..
Vamos começar! Crie um novo arquivo no diretório expense_manager_agent, nomeie-o como callbacks.py e copie o código abaixo.
# expense_manager_agent/callbacks.py
import hashlib
from google.genai import types
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.llm_request import LlmRequest
def modify_image_data_in_history(
callback_context: CallbackContext, llm_request: LlmRequest
) -> None:
# The following code will modify the request sent to LLM
# We will only keep image data in the last 3 user messages using a reverse and counter approach
# Count how many user messages we've processed
user_message_count = 0
# Process the reversed list
for content in reversed(llm_request.contents):
# Only count for user manual query, not function call
if (content.role == "user") and (content.parts[0].function_response is None):
user_message_count += 1
modified_content_parts = []
# Check any missing image ID placeholder for any image data
# Then remove image data from conversation history if more than 3 user messages
for idx, part in enumerate(content.parts):
if part.inline_data is None:
modified_content_parts.append(part)
continue
if (
(idx + 1 >= len(content.parts))
or (content.parts[idx + 1].text is None)
or (not content.parts[idx + 1].text.startswith("[IMAGE-ID "))
):
# Generate hash ID for the image and add a placeholder
image_data = part.inline_data.data
hasher = hashlib.sha256(image_data)
image_hash_id = hasher.hexdigest()[:12]
placeholder = f"[IMAGE-ID {image_hash_id}]"
# Only keep image data in the last 3 user messages
if user_message_count <= 3:
modified_content_parts.append(part)
modified_content_parts.append(types.Part(text=placeholder))
else:
# Only keep image data in the last 3 user messages
if user_message_count <= 3:
modified_content_parts.append(part)
# This will modify the contents inside the llm_request
content.parts = modified_content_parts
6. O comando
Para projetar um agente com interações e recursos complexos, precisamos encontrar um comando bom o suficiente para guiar o agente e fazer com que ele se comporte da maneira que queremos.
Antes, tínhamos um mecanismo para processar dados de imagens no histórico de conversas e ferramentas que podem não ser simples de usar, como search_relevant_receipts_by_natural_language_query.
. Também queremos que o agente possa pesquisar e recuperar a imagem do recibo correta. Isso significa que precisamos transmitir todas essas informações de maneira adequada em uma estrutura adequada.
Pediremos ao agente para estruturar a saída no formato Markdown a seguir para analisar o processo de pensamento, a resposta final e o anexo ( se houver).
# THINKING PROCESS Thinking process here # FINAL RESPONSE Response to the user here Attachments put inside json block { "attachments": [ "[IMAGE-ID <hash-id-1>]", "[IMAGE-ID <hash-id-2>]", ... ] }
Vamos começar com a seguinte solicitação para alcançar nossa expectativa inicial do comportamento do agente do gerenciador de despesas. O arquivo task_prompt.md já deve existir no nosso diretório de trabalho, mas precisamos movê-lo para o diretório expense_manager_agent. Execute o comando a seguir para movê-lo
mv task_prompt.md expense_manager_agent/task_prompt.md
7. Como testar o agente
Agora vamos tentar nos comunicar com o agente pela CLI. Execute o seguinte comando:
uv run adk run expense_manager_agent
Ele vai mostrar uma saída como esta, em que você pode conversar com o agente, mas só pode enviar texto por essa interface
Log setup complete: /tmp/agents_log/agent.xxxx_xxx.log To access latest log: tail -F /tmp/agents_log/agent.latest.log Running agent root_agent, type exit to exit. user: hello [root_agent]: Hello there! How can I help you today? user:
Além da interação com a CLI, o ADK também permite que tenhamos uma interface de desenvolvimento para interagir e inspecionar o que está acontecendo durante a interação. Execute o comando abaixo para iniciar o servidor da interface de desenvolvimento local.
uv run adk web --port 8080
Ele vai gerar uma saída como o exemplo a seguir, o que significa que já podemos acessar a interface da Web.
INFO: Started server process [xxxx] INFO: Waiting for application startup. +-----------------------------------------------------------------------------+ | ADK Web Server started | | | | For local testing, access at http://localhost:8080. | +-----------------------------------------------------------------------------+ INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
Para verificar, clique no botão Visualização da Web na parte de cima do editor do Cloud Shell e selecione Visualizar na porta 8080.
Você vai encontrar a página da Web a seguir, onde pode selecionar os agentes disponíveis no botão suspenso no canto superior esquerdo ( no nosso caso, expense_manager_agent) e interagir com o bot. Você vai encontrar muitas informações sobre os detalhes do registro durante o tempo de execução do agente na janela à esquerda.
Vamos testar algumas ações. Faça upload destes dois exemplos de recibos ( fonte : conjuntos de dados do Hugging Face mousserlane/id_receipt_dataset
) . Clique com o botão direito do mouse em cada imagem e escolha Salvar imagem como. Isso vai fazer o download da imagem do recibo. Em seguida, faça o upload do arquivo para o bot clicando no ícone "clip" e informando que você quer armazenar esses recibos.
Depois disso, tente as consultas a seguir para fazer uma pesquisa ou recuperar arquivos.
- "Fornecer o detalhamento das despesas e o total delas em 2023"
- "Give me receipt file from Indomaret" (Dê o arquivo de recibo da Indomaret).
Ao usar algumas ferramentas, é possível inspecionar o que está acontecendo na interface de desenvolvimento.
Confira como o agente responde a você e verifique se ele obedece a todas as regras fornecidas no comando em task_prompt.py. Parabéns! Agora você tem um agente de desenvolvimento completo.
Agora é hora de concluir o app com uma interface e recursos adequados para fazer upload e download do arquivo de imagem.
8. Criar um serviço de front-end usando o Gradio
Vamos criar uma interface da Web de chat com esta aparência
Ela contém uma interface de chat com um campo de entrada para os usuários enviarem texto e fazerem o upload dos arquivos de imagem do recibo.
Vamos criar o serviço de front-end usando o Gradio.
Crie um novo arquivo, clique em File->New Text File e nomeie-o como frontend.py. Em seguida,copie o código abaixo e salve-o.
import mimetypes
import gradio as gr
import requests
import base64
from typing import List, Dict, Any
from settings import get_settings
from PIL import Image
import io
from schema import ImageData, ChatRequest, ChatResponse
SETTINGS = get_settings()
def encode_image_to_base64_and_get_mime_type(image_path: str) -> ImageData:
"""Encode a file to base64 string and get MIME type.
Reads an image file and returns the base64-encoded image data and its MIME type.
Args:
image_path: Path to the image file to encode.
Returns:
ImageData object containing the base64 encoded image data and its MIME type.
"""
# Read the image file
with open(image_path, "rb") as file:
image_content = file.read()
# Get the mime type
mime_type = mimetypes.guess_type(image_path)[0]
# Base64 encode the image
base64_data = base64.b64encode(image_content).decode("utf-8")
# Return as ImageData object
return ImageData(serialized_image=base64_data, mime_type=mime_type)
def decode_base64_to_image(base64_data: str) -> Image.Image:
"""Decode a base64 string to PIL Image.
Converts a base64-encoded image string back to a PIL Image object
that can be displayed or processed further.
Args:
base64_data: Base64 encoded string of the image.
Returns:
PIL Image object of the decoded image.
"""
# Decode the base64 string and convert to PIL Image
image_data = base64.b64decode(base64_data)
image_buffer = io.BytesIO(image_data)
image = Image.open(image_buffer)
return image
def get_response_from_llm_backend(
message: Dict[str, Any],
history: List[Dict[str, Any]],
) -> List[str | gr.Image]:
"""Send the message and history to the backend and get a response.
Args:
message: Dictionary containing the current message with 'text' and optional 'files' keys.
history: List of previous message dictionaries in the conversation.
Returns:
List containing text response and any image attachments from the backend service.
"""
# Extract files and convert to base64
image_data = []
if uploaded_files := message.get("files", []):
for file_path in uploaded_files:
image_data.append(encode_image_to_base64_and_get_mime_type(file_path))
# Prepare the request payload
payload = ChatRequest(
text=message["text"],
files=image_data,
session_id="default_session",
user_id="default_user",
)
# Send request to backend
try:
response = requests.post(SETTINGS.BACKEND_URL, json=payload.model_dump())
response.raise_for_status() # Raise exception for HTTP errors
result = ChatResponse(**response.json())
if result.error:
return [f"Error: {result.error}"]
chat_responses = []
if result.thinking_process:
chat_responses.append(
gr.ChatMessage(
role="assistant",
content=result.thinking_process,
metadata={"title": "🧠 Thinking Process"},
)
)
chat_responses.append(gr.ChatMessage(role="assistant", content=result.response))
if result.attachments:
for attachment in result.attachments:
image_data = attachment.serialized_image
chat_responses.append(gr.Image(decode_base64_to_image(image_data)))
return chat_responses
except requests.exceptions.RequestException as e:
return [f"Error connecting to backend service: {str(e)}"]
if __name__ == "__main__":
demo = gr.ChatInterface(
get_response_from_llm_backend,
title="Personal Expense Assistant",
description="This assistant can help you to store receipts data, find receipts, and track your expenses during certain period.",
type="messages",
multimodal=True,
textbox=gr.MultimodalTextbox(file_count="multiple", file_types=["image"]),
)
demo.launch(
server_name="0.0.0.0",
server_port=8080,
)
Depois disso, podemos tentar executar o serviço de front-end com o comando abaixo. Não se esqueça de renomear o arquivo main.py para frontend.py.
uv run frontend.py
Você vai encontrar um resultado semelhante a este no console do Cloud
* Running on local URL: http://0.0.0.0:8080 To create a public link, set `share=True` in `launch()`.
Depois disso, você pode verificar a interface da Web ao pressionar Ctrl + clicar no link do URL local. Como alternativa, você também pode acessar o aplicativo de front-end clicando no botão Visualização da Web no canto superior direito do Cloud Editor e selecionando Visualizar na porta 8080.
A interface da Web aparece, mas você recebe um erro esperado ao tentar enviar o chat porque o serviço de back-end ainda não está configurado.
Agora, deixe o serviço em execução e não o encerre ainda. Vamos executar o serviço de back-end em outra guia de terminal.
Explicação do código
Neste código de front-end, primeiro permitimos que o usuário envie texto e faça upload de vários arquivos. O Gradio permite criar esse tipo de funcionalidade com o método gr.ChatInterface combinado com gr.MultimodalTextbox.
Antes de enviar o arquivo e o texto para o back-end, precisamos descobrir o tipo MIME do arquivo, já que ele é necessário para o back-end. Também precisamos codificar o byte do arquivo de imagem em base64 e enviar junto com o tipo MIME.
class ImageData(BaseModel): """Model for image data with hash identifier. Attributes: serialized_image: Optional Base64 encoded string of the image content. mime_type: MIME type of the image. """ serialized_image: str mime_type: str
O esquema usado para a interação entre front-end e back-end é definido em schema.py. Usamos o BaseModel do Pydantic para aplicar a validação de dados no esquema.
Ao receber a resposta, já separamos qual parte é o processo de pensamento, a resposta final e o anexo. Assim, podemos usar o componente Gradio para mostrar cada componente com o componente de interface.
class ChatResponse(BaseModel): """Model for a chat response. Attributes: response: The text response from the model. thinking_process: Optional thinking process of the model. attachments: List of image data to be displayed to the user. error: Optional error message if something went wrong. """ response: str thinking_process: str = "" attachments: List[ImageData] = [] error: Optional[str] = None
9. Criar um serviço de back-end usando a FastAPI
Em seguida, vamos precisar criar o back-end que pode inicializar nosso agente com os outros componentes para executar o ambiente de execução do agente.
Crie um novo arquivo, clique em File->New Text File e copie e cole o código a seguir. Em seguida,salve como backend.py.
from expense_manager_agent.agent import root_agent as expense_manager_agent
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google.adk.events import Event
from fastapi import FastAPI, Body, Depends
from typing import AsyncIterator
from types import SimpleNamespace
import uvicorn
from contextlib import asynccontextmanager
import asyncio
from utils import (
extract_attachment_ids_and_sanitize_response,
download_image_from_gcs,
extract_thinking_process,
format_user_request_to_adk_content_and_store_artifacts,
)
from schema import ImageData, ChatRequest, ChatResponse
import logger
from google.adk.artifacts import GcsArtifactService
from settings import get_settings
SETTINGS = get_settings()
APP_NAME = "expense_manager_app"
# Application state to hold service contexts
class AppContexts(SimpleNamespace):
"""A class to hold application contexts with attribute access"""
session_service: InMemorySessionService = None
artifact_service: GcsArtifactService = None
expense_manager_agent_runner: Runner = None
# Initialize application state
app_contexts = AppContexts()
@asynccontextmanager
async def lifespan(app: FastAPI):
# Initialize service contexts during application startup
app_contexts.session_service = InMemorySessionService()
app_contexts.artifact_service = GcsArtifactService(
bucket_name=SETTINGS.STORAGE_BUCKET_NAME
)
app_contexts.expense_manager_agent_runner = Runner(
agent=expense_manager_agent, # The agent we want to run
app_name=APP_NAME, # Associates runs with our app
session_service=app_contexts.session_service, # Uses our session manager
artifact_service=app_contexts.artifact_service, # Uses our artifact manager
)
logger.info("Application started successfully")
yield
logger.info("Application shutting down")
# Perform cleanup during application shutdown if necessary
# Helper function to get application state as a dependency
async def get_app_contexts() -> AppContexts:
return app_contexts
# Create FastAPI app
app = FastAPI(title="Personal Expense Assistant API", lifespan=lifespan)
@app.post("/chat", response_model=ChatResponse)
async def chat(
request: ChatRequest = Body(...),
app_context: AppContexts = Depends(get_app_contexts),
) -> ChatResponse:
"""Process chat request and get response from the agent"""
# Prepare the user's message in ADK format and store image artifacts
content = await asyncio.to_thread(
format_user_request_to_adk_content_and_store_artifacts,
request=request,
app_name=APP_NAME,
artifact_service=app_context.artifact_service,
)
final_response_text = "Agent did not produce a final response." # Default
# Use the session ID from the request or default if not provided
session_id = request.session_id
user_id = request.user_id
# Create session if it doesn't exist
if not app_context.session_service.get_session(
app_name=APP_NAME, user_id=user_id, session_id=session_id
):
app_context.session_service.create_session(
app_name=APP_NAME, user_id=user_id, session_id=session_id
)
try:
# Process the message with the agent
# Type annotation: runner.run_async returns an AsyncIterator[Event]
events_iterator: AsyncIterator[Event] = (
app_context.expense_manager_agent_runner.run_async(
user_id=user_id, session_id=session_id, new_message=content
)
)
async for event in events_iterator: # event has type Event
# Key Concept: is_final_response() marks the concluding message for the turn
if event.is_final_response():
if event.content and event.content.parts:
# Extract text from the first part
final_response_text = event.content.parts[0].text
elif event.actions and event.actions.escalate:
# Handle potential errors/escalations
final_response_text = f"Agent escalated: {event.error_message or 'No specific message.'}"
break # Stop processing events once the final response is found
logger.info(
"Received final response from agent", raw_final_response=final_response_text
)
# Extract and process any attachments and thinking process in the response
base64_attachments = []
sanitized_text, attachment_ids = extract_attachment_ids_and_sanitize_response(
final_response_text
)
sanitized_text, thinking_process = extract_thinking_process(sanitized_text)
# Download images from GCS and replace hash IDs with base64 data
for image_hash_id in attachment_ids:
# Download image data and get MIME type
result = await asyncio.to_thread(
download_image_from_gcs,
artifact_service=app_context.artifact_service,
image_hash=image_hash_id,
app_name=APP_NAME,
user_id=user_id,
session_id=session_id,
)
if result:
base64_data, mime_type = result
base64_attachments.append(
ImageData(serialized_image=base64_data, mime_type=mime_type)
)
logger.info(
"Processed response with attachments",
sanitized_response=sanitized_text,
thinking_process=thinking_process,
attachment_ids=attachment_ids,
)
return ChatResponse(
response=sanitized_text,
thinking_process=thinking_process,
attachments=base64_attachments,
)
except Exception as e:
logger.error("Error processing chat request", error_message=str(e))
return ChatResponse(
response="", error=f"Error in generating response: {str(e)}"
)
# Only run the server if this file is executed directly
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8081)
Depois disso, podemos tentar executar o serviço de back-end. Lembre-se de que, na etapa anterior, executamos o serviço de front-end. Agora, vamos precisar abrir um novo terminal e tentar executar esse serviço de back-end.
- Crie um terminal. Navegue até o terminal na área de baixo e encontre o botão "+" para criar um novo terminal. Você também pode usar Ctrl + Shift + C para abrir um novo terminal.
- Depois disso, verifique se você está no diretório de trabalho personal-expense-assistant e execute o seguinte comando:
uv run backend.py
- Se for bem-sucedido, a saída será exibida assim:
INFO: Started server process [xxxxx] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)
Explicação do código
Como inicializar o agente do ADK, o SessionService e o ArtifactService
Para executar o agente no serviço de back-end, precisamos criar um Runner que use o SessionService e nosso agente. O SessionService vai gerenciar o histórico e o estado da conversa. Portanto, quando integrado ao Runner, ele vai permitir que nosso agente receba o contexto das conversas em andamento.
Também usamos o ArtifactService para processar o arquivo enviado. Leia mais detalhes sobre a sessão e os artefatos do ADK.
... @asynccontextmanager async def lifespan(app: FastAPI): # Initialize service contexts during application startup app_contexts.session_service = InMemorySessionService() app_contexts.artifact_service = GcsArtifactService( bucket_name=SETTINGS.STORAGE_BUCKET_NAME ) app_contexts.expense_manager_agent_runner = Runner( agent=expense_manager_agent, # The agent we want to run app_name=APP_NAME, # Associates runs with our app session_service=app_contexts.session_service, # Uses our session manager artifact_service=app_contexts.artifact_service, # Uses our artifact manager ) logger.info("Application started successfully") yield logger.info("Application shutting down") # Perform cleanup during application shutdown if necessary ...
Nesta demonstração, usamos o InMemorySessionService e o GcsArtifactService para integrar o Runner do agente. Como o histórico de conversas é armazenado na memória, ele é perdido quando o serviço de back-end é encerrado ou reiniciado. Iniciamos esses métodos no ciclo de vida do aplicativo FastAPI para serem injetados como dependência na rota /chat
.
Como fazer upload e download de imagens com o GcsArtifactService
Toda a imagem enviada será armazenada como artefato pelo GcsArtifactService. Você pode verificar isso na função format_user_request_to_adk_content_and_store_artifacts
no utils.py.
... # Prepare the user's message in ADK format and store image artifacts content = await asyncio.to_thread( format_user_request_to_adk_content_and_store_artifacts, request=request, app_name=APP_NAME, artifact_service=app_context.artifact_service, ) ...
Todas as solicitações que serão processadas pelo agente precisam ser formatadas no tipo types.Content. Dentro da função, também processamos cada dado de imagem e extraímos o ID para ser substituído por um marcador de posição do ID da imagem.
Um mecanismo semelhante é usado para fazer o download dos anexos depois de extrair os IDs de imagem usando regex:
... sanitized_text, attachment_ids = extract_attachment_ids_and_sanitize_response( final_response_text ) sanitized_text, thinking_process = extract_thinking_process(sanitized_text) # Download images from GCS and replace hash IDs with base64 data for image_hash_id in attachment_ids: # Download image data and get MIME type result = await asyncio.to_thread( download_image_from_gcs, artifact_service=app_context.artifact_service, image_hash=image_hash_id, app_name=APP_NAME, user_id=user_id, session_id=session_id, ) ...
10. Teste de integração
Agora, vários serviços serão executados em diferentes guias do console do Cloud:
- Serviço de front-end executado na porta 8080
* Running on local URL: http://0.0.0.0:8080 To create a public link, set `share=True` in `launch()`.
- Serviço de back-end executado na porta 8081
INFO: Started server process [xxxxx] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)
No estado atual, é possível fazer upload das imagens de recibo e conversar com o assistente pelo aplicativo da Web na porta 8080.
Clique no botão Visualização da Web na parte de cima do editor do Cloud Shell e selecione Visualizar na porta 8080.
Agora vamos interagir com o assistente.
Faça o download dos comprovantes a seguir. O período de dados do recibo é entre 2023 e 2024, e peça para o assistente armazená-lo/fazer o upload.
- Receipt Drive ( conjuntos de dados de origem do Hugging Face
mousserlane/id_receipt_dataset
)
Fazer várias perguntas
- "Mostre o detalhamento das despesas mensais de 2023 a 2024"
- "Mostre o recibo da transação do café"
- "Give me receipt file from Yakiniku Like" (Mostre o arquivo de recibo da Yakiniku Like)
- Etc
Confira um trecho da interação bem-sucedida
11. Como implantar no Cloud Run
É claro que queremos acessar esse app incrível de qualquer lugar. Para isso, podemos empacotar esse aplicativo e implantá-lo no Cloud Run. Para esta demonstração, o serviço será exposto como um serviço público que pode ser acessado por outras pessoas. No entanto, lembre-se de que essa não é a prática recomendada para esse tipo de aplicativo, já que é mais adequado para aplicativos pessoais.
Neste codelab, vamos colocar o serviço de front-end e de back-end em um contêiner. Vamos precisar da ajuda do supervisord para gerenciar os dois serviços. É possível inspecionar o arquivo supervisord.conf e verificar no Dockerfile que definimos o supervisord como ponto de entrada.
Neste ponto, já temos todos os arquivos necessários para implantar nossos aplicativos no Cloud Run. Vamos implantar. Acesse o terminal do Cloud Shell e verifique se o projeto atual está configurado como seu projeto ativo. Caso contrário, use o comando gcloud configure para definir o ID do projeto:
gcloud config set project [PROJECT_ID]
Em seguida, execute o comando abaixo para implantar no Cloud Run.
gcloud run deploy personal-expense-assistant \
--source . \
--port=8080 \
--allow-unauthenticated \
--env-vars-file=settings.yaml \
--memory 1024Mi \
--region us-central1
Se você receber uma solicitação para confirmar a criação de um registro de artefatos para o repositório do Docker, basta responder Y. Estamos permitindo o acesso não autenticado porque este é um aplicativo de demonstração. Recomendamos usar a autenticação adequada para seus aplicativos corporativos e de produção.
Quando a implantação for concluída, você vai receber um link semelhante ao abaixo:
https://personal-expense-assistant-*******.us-central1.run.app
Use o aplicativo na janela anônima ou no dispositivo móvel. Ele já deve estar ativo.
12. Desafio
Agora é sua vez de brilhar e aprimorar suas habilidades de exploração. Você tem o que é necessário para mudar o código para que o back-end possa acomodar vários usuários? Quais componentes precisam ser atualizados?
13. Limpar
Para evitar cobranças na sua conta do Google Cloud pelos recursos usados neste codelab, siga estas etapas:
- No console do Google Cloud, acesse a página Gerenciar recursos.
- Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir.
- Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.
- Também é possível acessar o Cloud Run no console, selecionar o serviço que você acabou de implantar e excluir.