Deep Research com LangGraph (1/3): agentes Scope e Researcher

Deep Research com LangGraph (1/3): agentes Scope e Researcher

Aviso: Este post foi traduzido para o português usando um modelo de tradução automática. Por favor, me avise se encontrar algum erro.

📚 **Esta entrada faz parte da série _Deep Research com LangGraph_**, dividida em três capítulos que se leem em ordem:

> * 👉 **Parte 1: Agentes Scope y Researcher**> * Parte 2: Supervisor de pesquisa multiagente

* Parte 3: Agente Writer e informe final

Alguma vez você já saiu em busca do melhor de algo? Os melhores tênis para correr, a melhor ferramenta para aquele reparo, a melhor churrasqueira?

Quando você vai procurar nas 80 páginas que aparecem, 90% são páginas para ganhar dinheiro com afiliados da Amazon, feitas por pessoas que não sabem sobre o que estão escrevendo; apenas buscam os produtos que mais vendem na Amazon, enchem a página de palavras-chave para aparecer nos primeiros resultados de busca do Google e enchem o conteúdo de links para a Amazon.

Nos 10 % que restam, você precisa navegar por páginas com parágrafos imensos, feitos para melhorar o SEO e aparecer nas primeiras posições da busca do Google.

Portanto, uma simples busca no Google se transforma em um trabalho pesado e entediante.

Outra coisa é quando queres investigar sobre um tema que não requer compras. Assim como antes, tens de navegar por dezenas de páginas, contrastando informação, seguindo fontes, etc.

E se pedíssemos isso a um assistente de IA?

Neste post vamos explicar como criar o seu próprio assistente de IA para que pesquise por si sobre qualquer tema e assim dedique o seu tempo ao que realmente importa.

**Informação**: Este post se baseia no curso Deep Research with LangGraph da LangChain Academy. Mas como sentia que havia muitos conceitos que são dados como pressupostos, escrevi-o da forma como eu teria gostado de encontrá-lo

O que é um assistente de IA?link image 67

Um assistente de IA é um agente que pode realizar tarefas complexas e automatizadas. Mas, na época atual do desenvolvimento de agentes, temos de o projetar muito bem para que não se perca pelo buraco da toca como a Alice no País das Maravilhas e saiba quando parar de pesquisar e quando já encontrou a informação de que precisavas.

Arquitetura do assistentelink image 68

Para realizar o assistente vamos usar a seguinte arquitetura:

arquitetura do deep researcher

Escopolink image 69

Primeiro, vamos definir o escopo da investigação. Um LLM analisará a mensagem do usuário e, se considerar que precisa de mais informações, solicitará essas informações.

Supervisor do pesquisadorlink image 70

Aqui um LLM será encarregado de supervisionar a investigação, dividindo-a em pequenas investigações e atribuindo-as a subagentes investigadores. Cada um desses subagentes terá ferramentas para poder realizar a investigação.

Cada subagente pesquisador usará suas ferramentas várias vezes para obter uma investigação profunda de sua tarefa e, quando terminar, devolverá ao supervisor um resumo de sua investigação. Cada subagente tem seu próprio contexto e não tem acesso ao contexto e aos resultados dos outros subagentes.

Assim que o supervisor receber todos os resumos dos subagentes, avaliará se precisa solicitar novas investigações aos subagentes. No momento em que considerar que já não é necessário realizar mais investigações, fará um resumo de toda a informação que possui e o enviará ao writer.

Escritorlink image 71

Por último, quando a investigação aprofundada já foi realizada, o writer se encarrega de escrever um relatório completo e detalhado para o utilizador.

Instalação de bibliotecaslink image 72

Vamos instalar todas as bibliotecas que vamos precisar para o desenvolvimento do assistente.

Primeiro, criamos um ambiente uv

	
< > Input
Python
!uv init deepresearcher
Copied
>_ Output
			
error: Project is already initialized in `/Users/macm1/Documents/web/portafolio/posts/deepresearcher` (`pyproject.toml` file exists)

Nós sincronizamos isso

	
< > Input
Python
!cd deepresearcher && uv sync
Copied
>_ Output
			
Resolved 88 packages in 9ms
Audited 84 packages in 0.38ms

Nós o ativamos

	
< > Input
Python
!cd deepresearcher && source .venv/bin/activate
Copied

Instalamos as bibliotecas necessárias

	
< > Input
Python
!cd deepresearcher && source .venv/bin/activate && uv add "alive-progress&gt;=3.3.0" "dotenv&gt;=0.9.9" "grandalf&gt;=0.8" "ipykernel&gt;=6.30.1" "langchain&gt;=0.3.27" "langchain-anthropic&gt;=0.3.21" "langchain-community&gt;=0.3.29" "langchain-mcp-adapters&gt;=0.1.11" "langchain-openai&gt;=0.3.33" "langgraph&gt;=0.6.7" "rich&gt;=14.1.0" "tavily-python&gt;=0.7.12"
Copied
>_ Output
			
Resolved 88 packages in 0.59ms
Audited 84 packages in 0.01ms

Vamos analisar o que instalamos

Utilitárioslink image 73

Antes de entrarmos no código, vamos criar algumas funções que nos serão úteis para o desenvolvimento do assistente.

Diretóriolink image 74

Em primeiro lugar, criamos uma função que nos permite obter o diretório atual.

	
< > Input
Python
from pathlib import Path
def get_current_dir() -&gt; Path:
"""Get the current directory of the module.
This function is compatible with Jupyter notebooks and regular Python scripts.
Returns:
Path object representing the current directory
"""
try:
return Path(__file__).resolve().parent
except NameError: # __file__ is not defined
return Path.cwd()
Copied

Datalink image 75

Agora criamos uma função que nos permite obter a data atual.

	
< > Input
Python
from datetime import datetime
def get_today_str() -&gt; str:
"""Get current date in a human-readable format."""
return datetime.now().strftime("%a %b %-d, %Y")
Copied

Mensagenslink image 76

Por fim, criamos uma série de funções que nos permitem formatar as mensagens para que sejam exibidas de uma maneira mais clara no console

	
< > Input
Python
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
import json
import ast
console = Console()
def format_tool_outputs(tool_outputs_list: list) -&gt; str:
"""Format a list of ToolMessage objects into a readable string"""
formatted_parts = []
for i, output in enumerate(tool_outputs_list, 1):
# Handle ToolMessage objects that have been converted to strings
if isinstance(output, dict):
formatted_parts.append(f" Tool Output {i}:")
formatted_parts.append(f" Name: {output.get('name', 'N/A')}")
formatted_parts.append(f" Tool Call ID: {output.get('tool_call_id', 'N/A')}")
# Format the content (which contains the actual output)
content = output.get('content', 'N/A')
if isinstance(content, str):
# Truncate very long outputs
if len(content) &gt; 500:
content_preview = content[:500] + f"... ({len(content)} chars total)"
else:
content_preview = content
formatted_parts.append(f" Content:")
# Indent each line of content
for line in content_preview.split(' '):
formatted_parts.append(f" {line}")
else:
formatted_parts.append(f" Content: {content}")
else:
formatted_parts.append(f" Tool Output {i}: {output}")
return " ".join(formatted_parts)
def format_string_with_structures(content: str) -&gt; str:
"""
Detect and format structured data (lists, dicts) within a string.
Handles cases like 'Tool calls: [...]' and 'Tool outputs: [...]' by parsing and formatting the structure.
"""
# Check if the string contains 'Tool outputs:' followed by a list
if 'Tool outputs:' in content:
try:
# Find the position where the tool outputs list starts
tool_outputs_index = content.index('Tool outputs:')
prefix = content[:tool_outputs_index + len('Tool outputs:')]
# Extract the part that should be a list
potential_list = content[tool_outputs_index + len('Tool outputs:'):].strip()
# Try to parse it as a Python literal (list/dict)
parsed_data = ast.literal_eval(potential_list)
# If it's a list of tool outputs, format them nicely
if isinstance(parsed_data, list):
formatted_parts = [prefix + " "]
formatted_parts.append(format_tool_outputs(parsed_data))
return " ".join(formatted_parts)
except (ValueError, SyntaxError):
# If parsing fails, continue to next check
pass
# Check if the string contains 'Tool calls:' followed by a list
if 'Tool calls:' in content:
try:
# Find the position where the tool calls list starts
tool_calls_index = content.index('Tool calls:')
prefix = content[:tool_calls_index + len('Tool calls:')]
# Extract the part that should be a list
potential_list = content[tool_calls_index + len('Tool calls:'):].strip()
# Try to parse it as a Python literal (list/dict)
parsed_data = ast.literal_eval(potential_list)
# If it's a list of tool calls, format them nicely
if isinstance(parsed_data, list):
formatted_parts = [prefix + " "]
for i, tool_call in enumerate(parsed_data, 1):
if isinstance(tool_call, dict):
formatted_parts.append(f" Tool Call {i}:")
formatted_parts.append(f" Name: {tool_call.get('name', 'N/A')}")
formatted_parts.append(f" ID: {tool_call.get('id', 'N/A')}")
formatted_parts.append(f" Type: {tool_call.get('type', 'N/A')}")
if 'args' in tool_call:
formatted_parts.append(f" Args:")
formatted_parts.append(f" {json.dumps(tool_call['args'], indent=7, ensure_ascii=False)}")
return " ".join(formatted_parts)
except (ValueError, SyntaxError):
# If parsing fails, return the original content
pass
# Try to detect if the entire content is a JSON/dict/list
try:
# Try JSON first
parsed = json.loads(content)
return json.dumps(parsed, indent=2, ensure_ascii=False)
except (json.JSONDecodeError, TypeError):
try:
# Try Python literal (ast.literal_eval)
parsed = ast.literal_eval(content)
return json.dumps(parsed, indent=2, ensure_ascii=False)
except (ValueError, SyntaxError):
# If all parsing attempts fail, return original content
return content
def format_message_content(message):
"""Convert message content to displayable string"""
parts = []
tool_calls_processed = False
# Handle ToolCall objects (special case - they don't have 'content')
if isinstance(message, dict) and 'args' in message and 'name' in message:
# This is a ToolCall dictionary
parts.append(f"Tool Call Name: {message.get('name', 'N/A')}")
parts.append(f"ID: {message.get('id', 'N/A')}")
# Format the args (which should be the tool calls list)
args = message.get('args', [])
if isinstance(args, list):
for i, tool_call in enumerate(args, 1):
if isinstance(tool_call, dict):
parts.append(f" Tool Call {i}:")
parts.append(f" Name: {tool_call.get('name', 'N/A')}")
parts.append(f" ID: {tool_call.get('id', 'N/A')}")
parts.append(f" Type: {tool_call.get('type', 'N/A')}")
if 'args' in tool_call:
parts.append(f" Args:")
parts.append(f" {json.dumps(tool_call['args'], indent=7, ensure_ascii=False)}")
return " ".join(parts)
# Check if message has 'content' attribute
if not hasattr(message, 'content'):
# If it's a dict-like object, try to access it as a dict
if hasattr(message, '__getitem__'):
try:
content = message['content'] if 'content' in message else str(message)
parts.append(content)
return " ".join(parts)
except (KeyError, TypeError):
parts.append(str(message))
return " ".join(parts)
else:
parts.append(str(message))
return " ".join(parts)
# Handle main content (normal messages with 'content' attribute)
if isinstance(message.content, str):
# Try to detect and format structured data within the string
formatted_content = format_string_with_structures(message.content)
parts.append(formatted_content)
elif isinstance(message.content, list):
# Handle complex content like tool calls (Anthropic format)
for item in message.content:
if item.get('type') == 'text':
parts.append(item['text'])
elif item.get('type') == 'tool_use':
parts.append(f" 🔧 Tool Call: {item['name']}")
parts.append(f" Args: {str(json.dumps(item['input'], indent=7, ensure_ascii=False))}")
parts.append(f" ID: {item.get('id', 'N/A')}")
tool_calls_processed = True
else:
parts.append(str(message.content))
# Handle tool calls attached to the message (OpenAI format) - only if not already processed
if not tool_calls_processed and hasattr(message, 'tool_calls') and message.tool_calls:
for tool_call in message.tool_calls:
parts.append(f" 🔧 Tool Call: {tool_call['name']}")
parts.append(f" Args: {json.dumps(tool_call['args'], indent=2)}")
parts.append(f" ID: {tool_call['id']}")
return " ".join(parts)
def format_messages(messages, title: str = "", border_style: str = "white", msg_subtype: str = ""):
"""Format and display a list of messages with Rich formatting
Args:
messages: List of messages to format
title: Title of the panel
border_style: Border style of the panel
msg_subtype: Subtype of the message
"""
# Check if messages is a list
if isinstance(messages, list):
for m in messages:
# Handle different message types
if isinstance(m, dict):
# Check if it's a ToolCall dict
if 'args' in m and 'name' in m:
msg_type = 'ToolCall'
else:
msg_type = 'Dict'
else:
msg_type = m.__class__.__name__.replace('Message', '')
content = format_message_content(m)
if msg_type == 'Human':
if msg_subtype == 'RealHumanMessage':
if title == "":
title = "🧑 Real Human Message"
else:
title = f"🧑 {title}"
console.print(Panel(content, title=title, border_style="#201ADB")) # Blue
else:
if title == "":
title = "🧑 Simulated Human Message"
else:
title = f"🧑 {title}"
console.print(Panel(content, title=title, border_style="#1A64DB")) # Blue
elif msg_type == 'AI':
if title == "":
title = "🤖 Assistant"
else:
title = f"🤖 {title}"
console.print(Panel(content, title=title, border_style="#24FA00")) # Green
elif msg_type == 'ClarifyWithUser':
if title == "":
title = "🤖 Clarify With User"
else:
title = f"🤖 {title}"
console.print(Panel(content, title=title, border_style="#37DB1A")) # Green
elif msg_type == 'ResearchQuestion':
if title == "":
title = "🤖 Research brief generated"
else:
title = f"🤖 {title}"
console.print(Panel(content, title=title, border_style="#37DB1A")) # Green
elif msg_type == 'Tool':
if title == "":
title = "🔧 Tool Output"
else:
title = f"🔧 {title}"
console.print(Panel(content, title=title, border_style="yellow"))
elif msg_type == 'ToolCall':
if title == "":
title = "🔧 Tool Calls"
else:
title = f"🔧 {title}"
console.print(Panel(content, title=title, border_style="magenta"))
elif msg_type == 'Use tools':
if title == "":
title = "🔧 Tool Output"
else:
title = f"🔧 {title}"
console.print(Panel(content, title=title, border_style="yellow"))
elif msg_type == 'System':
if title == "":
title = "🔧 System Message"
else:
title = f"🔧 {title}"
console.print(Panel(content, title=title, border_style="red"))
else:
print(f"msg_type: {msg_type}")
if title == "":
title = f"📝 {msg_type}"
else:
title = f"📝 {title}"
console.print(Panel(content, title=title, border_style="white"))
elif isinstance(messages, str):
console.print(Panel(messages, title=title, border_style=border_style))
else:
print(f"messages type: {type(messages)}")
Copied

Escopolink image 77

Vamos começar a desenhar o primeiro módulo, o de escopo. Com este módulo vamos definir o alcance da investigação.

Prompts de escopolink image 78

Vamos começar definindo os prompts do escopo. Primeiro, definimos o prompt que faz com que o LLM peça mais informações ao usuário quando considera que não tem informação suficiente.

  • Em primeiro lugar, é passado o contexto da conversa que tenha ocorrido até esse momento
  • A data é passada, por isso tivemos de criar uma função para obter a data* É-lhe indicado que faça perguntas de esclarecimento ao usuário, se necessário. Se o usuário pedir para investigar algo e o LLM considerar que precisa de mais informações do que o usuário quer, pode perguntar-lhe por mais informações. Se não precisar de mais informações, terá de indicar ao usuário que já tem tudo o que precisa para começar a investigação
  • Indica-se o formato de saída que deve responder
	
< > Input
Python
clarify_with_user_instructions="""
Estos son los mensajes que se han intercambiado hasta ahora con el usuario que solicita el informe:
&lt;Messages&gt;
{messages}
&lt;/Messages&gt;
La fecha de hoy es {date}.
Evalúa si necesitas hacer alguna pregunta aclaratoria o si el usuario ya te ha proporcionado suficiente información para comenzar la investigación.
IMPORTANTE: Si en el historial de mensajes ves que ya has hecho una pregunta aclaratoria, casi siempre no es necesario que hagas otra. Solo haz otra pregunta si es ABSOLUTAMENTE NECESARIO.
Si hay acrónimos, abreviaturas o términos desconocidos, pide al usuario que los aclare.
Si necesitas hacer una pregunta, sigue estas pautas:
- Se conciso al recopilar toda la información necesaria.
- Asegúrate de recopilar toda la información necesaria para llevar a cabo la tarea de investigación de forma concisa y bien estructurada.
- Utiliza bullet points o listas numeradas si es necesario para mayor claridad. Asegúrate de que utilizas el formato Markdown y de que se mostrará correctamente si la cadena de salida se pasa a un renderizador Markdown.
- No solicites información innecesaria o información que el usuario ya haya proporcionado. Si ves que el usuario ya ha proporcionado la información, no se la vuelvas a solicitar.
Responde en formato JSON válido con estas claves exactas:
"need_clarification": boolean,
"question": "&lt;pregunta para pedir al usuario que aclare el alcance del informe&gt;",
"verification": "&lt;mensaje de verificación de que comenzaremos la investigación&gt;"
Si necesitas hacer una pregunta aclaratoria, devuelve:
"need_clarification": true,
"question": "&lt;tu pregunta aclaratoria&gt;",
"verification": ""
Si no necesitas hacer una pregunta aclaratoria, devuelve:
"need_clarification": false,
"question": "",
"verification": "&lt;Mensaje de confirmación de que ahora comenzará la investigación basándose en la información proporcionada.&gt;"
Para el mensaje de verificación cuando no se necesita aclaración:
- Confirma que tienes suficiente información para continuar.
- Resume brevemente los aspectos clave de lo que entiendes de la solicitud.
- Confirma que ahora comenzará el proceso de investigación.
- Mantén el mensaje conciso y profesional.
Responde siempre en el idioma en el que el usuario te haya preguntado. En caso de duda, responde en español.
"""
Copied

Agora definimos o prompt para que, com a conversa que o LLM teve com o usuário, o LLM gere um prompt com a investigação que deve ser realizada e que irá para o agente de investigação.

  • O contexto da conversa que tenha sido mantida até esse momento é passado a ele
  • É indicada a data
  • Indica-se como deve ser o formato do prompt que precisa gerar. Não podem ser assumidas coisas que o usuário não disse. Deve ser específico e detalhado. Escrevê-lo em primeira pessoa
	
< > Input
Python
transform_messages_into_research_topic_prompt = """Se te va a proporcionar un conjunto de mensajes que se han intercambiado hasta ahora entre el usuario y tú.
Tu trabajo consiste en traducir estos mensajes en una pregunta de investigación más detallada y concreta que se utilizará para orientar la investigación.
Los mensajes que se han intercambiado hasta ahora entre el usuario y tú son:
&lt;Messages&gt;
{messages}
&lt;/Messages&gt;
La fecha de hoy es {date}.
Devolverás una única pregunta de investigación que se utilizará para orientar la investigación.
Directrices:
1. Maximiza la especificidad y el detalle.
- Incluye todas las preferencias conocidas del usuario y enumera explícitamente los atributos o dimensiones clave a tener en cuenta.
- Es importante que todos los detalles del usuario se incluyan en las instrucciones.
2. Trata con cuidado las dimensiones no especificadas
- Cuando la calidad de la investigación requiera tener en cuenta dimensiones adicionales que el usuario no haya especificado, reconócelas como consideraciones abiertas en lugar de preferencias asumidas.
- Ejemplo: en lugar de asumir «opciones económicas», di «considerar todos los rangos de precios a menos que se especifiquen restricciones de coste».
- Solo menciona las dimensiones que sean realmente necesarias para una investigación exhaustiva en ese ámbito.
3. Evita suposiciones injustificadas
- Nunca inventes preferencias, restricciones o requisitos específicos del usuario que no se hayan indicado.
- Si el usuario no ha proporcionado un detalle concreto, señala explícitamente esta falta de especificación.
- Orienta al investigador para que trate los aspectos no especificados como flexibles en lugar de hacer suposiciones.
4. Distinguir entre el alcance de la investigación y las preferencias del usuario
- Alcance de la investigación: qué temas/dimensiones deben investigarse (puede ser más amplio que las menciones explícitas del usuario)
- Preferencias del usuario: restricciones, requisitos o preferencias específicos (solo debes incluir lo que el usuario haya indicado)
- Ejemplo: «Investigar los factores que influyen en la calidad del café (incluido el origen de los granos, los métodos de tostado y las técnicas de preparación) en las cafeterías de Madrid, centrándose principalmente en el sabor, tal y como ha especificado el usuario».
5. Utiliza la primera persona
- Redacta la solicitud desde la perspectiva del usuario.
6. Fuentes
- Si se debe dar prioridad a fuentes específicas, entonces específalas en la pregunta de investigación.
- Para la investigación de productos y viajes, es preferible enlazar directamente con sitios web oficiales o primarios (por ejemplo, sitios web oficiales de marcas, páginas de fabricantes o plataformas de comercio electrónico de renombre como Amazon para opiniones de usuarios) en lugar de sitios agregadores o blogs con mucho SEO.
- Para consultas académicas o científicas, es preferible enlazar directamente con el artículo original o la publicación oficial en una revista, en lugar de con artículos de encuestas o resúmenes secundarios.
- En el caso de personas, intente enlazar directamente con su perfil de LinkedIn, su sitio web personal o su perfil de GitHub, si lo tienen.
- Si la consulta está en un idioma específico, dé prioridad a las fuentes publicadas en ese idioma.
Responde siempre en el idioma en el que el usuario te haya preguntado. En caso de duda, responde en español.
"""
Copied

Estado do escopolink image 79

Definimos agora o estado do scope. O estado é como a memória de curto prazo do agente, onde serão guardadas as variáveis e dados que serão usados durante a execução do agente e que precisam estar disponíveis durante a execução do agente.

Assim que o agente parar de ser executado, essa memória será perdida

Vamos definir dois estados

	
< > Input
Python
import operator
from typing_extensions import Optional, Annotated, Sequence
from langchain_core.messages import BaseMessage
from langgraph.graph import MessagesState
from langgraph.graph.message import add_messages
class AgentInputState(MessagesState):
"""Input state for the full agent - only contains messages from user input."""
pass
class AgentState(MessagesState):
"""
Main state for the full multi-agent research system.
Extends MessagesState with additional fields for research coordination.
Note: Some fields are duplicated across different state classes for proper
state management between subgraphs and the main workflow.
"""
# Research brief generated from user conversation history
research_brief: Optional[str]
# Messages exchanged with the supervisor agent for coordination
supervisor_messages: Annotated[Sequence[BaseMessage], add_messages]
# Raw unprocessed research notes collected during the research phase
raw_notes: Annotated[list[str], operator.add] = []
# Processed and structured notes ready for report generation
notes: Annotated[list[str], operator.add] = []
# Final formatted research report
final_report: str
Copied

Pode parecer estranho criar dois estados, AgentInputState e AgentState. Poderíamos ter criado um único estado chamado AgentState, já que os dois que criamos herdam da classe MessagesState. Portanto, definindo apenas um ou definindo dois, as mensagens que forem sendo geradas, tanto as que vêm do usuário quanto as que o agente gera, serão armazenadas em uma lista chamada messages.

Mas fizemos isso em dois, porque vamos definir AgentInputState como o formato de entrada que o agente vai esperar, e AgentState como o estado interno do agente. Para quê? Para que o restante das variáveis do estado (research_brief, supervisor_messages, raw_notes, notes, final_report) não possam ser modificadas quando for feita uma chamada ao agente.

Por exemplo, se tivéssemos criado apenas um estado AgentState, ao invocar o agente, poderíamos fazer

state = {
"messages": ["Olá, como você está?"],
"research_brief": "Investigar os fatores que influenciam a qualidade do café",
"supervisor_messages": [],
"raw_notes": [],
"notas": [],
"final_report": ""
}

agent.invoke(state)

Ao fazer isso, inicialmente haveria um valor para research_brief, e não queremos isso; queremos que seja o agente quem gere o research_brief.

Ao ter um estado AgentState e outro AgentInputState, o estado AgentState vamos estabelecer como variável ao invocar o agente para AgentInputState, de modo que não vamos poder modificar o research_brief de fora do agente.

Saída estruturada do agente Scopelink image 80

Definimos agora como queremos que seja a saída do LLM quando ele for invocado. Definimos duas saídas estruturadas, uma para quando queremos saber se precisamos de mais dados do usuário (ClarifyWithUser) e outra para quando queremos gerar o research_brief (ResearchQuestion).

	
< > Input
Python
from pydantic import BaseModel, Field
class ClarifyWithUser(BaseModel):
"""Schema for user clarification decision and questions."""
need_clarification: bool = Field(
description="Whether the user needs to be asked a clarifying question.",
)
question: str = Field(
description="A question to ask the user to clarify the report scope",
)
verification: str = Field(
description="Verify message that we will start research after the user has provided the necessary information.",
)
class ResearchQuestion(BaseModel):
"""Schema for structured research brief generation."""
research_brief: str = Field(
description="A research question that will be used to guide the research.",
)
Copied

A função Field de pydantic é usada para definir o tipo de dado esperado na saída do LLM e para adicionar uma descrição do que se espera que seja a saída.

Com ClarifyWithUser, espera-se que o LLM retorne

  • um booleano (need_clarification) que indique se é necessária mais informação do usuário
  • uma pergunta (question) que, caso seja necessária mais informação do usuário, será feita para esclarecer o escopo da investigação
  • uma mensagem de verificação (verification) que será dada ao usuário para que confirme que tem informações suficientes para começar a investigação

Com ResearchQuestion, espera-se que o LLM devolva uma pergunta de pesquisa (research_brief), que é a que será enviada ao agente de pesquisa para que a investigação seja realizada.

Modelo de linguagem do agente Scopelink image 81

Agora que temos os prompts, definimos o modelo de linguagem que o agente vai usar.

	
< > Input
Python
from langchain.chat_models import init_chat_model
import os
import dotenv
dotenv.load_dotenv()
SCOPE_MODEL_NAME = "gpt-4.1"
SCOPE_MODEL_PROVIDER = "openai"
SCOPE_MODEL_BASE_URL = "https://api.openai.com/v1"
SCOPE_MODEL_TEMPERATURE = 0.0
SCOPE_MODEL_PROVIDER_API_KEY = os.getenv("OPENAI_API_KEY")
scope_model = init_chat_model(
model=SCOPE_MODEL_NAME,
model_provider=SCOPE_MODEL_PROVIDER,
api_key=SCOPE_MODEL_PROVIDER_API_KEY,
base_url=SCOPE_MODEL_BASE_URL,
temperature=SCOPE_MODEL_TEMPERATURE
)
Copied

Funções do agente Scopelink image 82

Como o agente terá de realizar duas ações, pedir mais informações ao utilizador, se necessário, e gerar o research_brief, vamos criar duas funções para cada uma das ações.

Começamos com a função para pedir mais informações ao usuário

  • Primeiro obtemos a última mensagem do histórico de mensagens, que será a mensagem do usuário
  • Em seguida, fazemos uma chamada ao modelo forçando que ele retorne uma saída estruturada do tipo ClarifyWithUser* Se o LLM retornar que é necessária mais informação, enviamos uma mensagem ao usuário indicando que é necessária mais informação e o fluxo do agente é encerrado
  • Se o LLM devolver que não é necessária mais informação, enviamos uma mensagem ao usuário indicando que a investigação pode começar e, no fluxo do agente, é indicado que a função write_research_brief seja executada para gerar o research_brief
	
< > Input
Python
from typing_extensions import Literal
from alive_progress import alive_bar
import traceback
import sys
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, get_buffer_string
from langgraph.graph import START, END
from langgraph.types import Command
def clarify_with_user(state: AgentState) -&gt; Command[Literal["write_research_brief", "__end__"]]:
"""
Determine if the user's request contains sufficient information to proceed with research.
Uses structured output to make deterministic decisions and avoid hallucination.
Routes to either research brief generation or ends with a clarification question.
"""
try:
print("⏳ Scope agent:")
format_messages([state.get("messages", [])[-1]], title="Real Human Message", msg_subtype='RealHumanMessage')
# Set up structured output model
structured_output_model = scope_model.with_structured_output(ClarifyWithUser)
# Invoke the model with clarification instructions
with alive_bar(monitor=False, stats=False, title="", spinner='dots_waves', bar='blocks') as bar:
response = structured_output_model.invoke([
HumanMessage(content=clarify_with_user_instructions.format(
messages=get_buffer_string(messages=state["messages"]),
date=get_today_str()
))
])
bar()
# Format and display the research messages
format_messages([response], title="Scope Assistant - need clarification?")
# Route based on clarification need
if response.need_clarification:
# Create a System message to show the decision
routing_message = SystemMessage(
content="Necesita aclaración por parte del usuario. Enviando pregunta aclaratoria..."
)
format_messages([routing_message], title="Scope System Message")
return Command(
goto=END,
update={"messages": [AIMessage(content=response.question)]}
)
else:
# Create a System message to show the decision
routing_message = SystemMessage(
content="No necesita aclaración por parte del usuario. Enviando mensaje de verificación..."
)
format_messages([routing_message], title="Scope System Message")
return Command(
goto="write_research_brief",
update={"messages": [AIMessage(content=response.verification)]}
)
except Exception as e:
# Get the traceback information
exc_type, exc_obj, exc_tb = sys.exc_info()
line_number = exc_tb.tb_lineno
# Print detailed error information
print(f" ❌ Error in clarify_with_user function:")
print(f" Line number: {line_number}")
print(f" Error type: {type(e).__name__}")
print(f" Error message: {str(e)}")
print(f" Full traceback:")
traceback.print_exc()
# Re-raise the exception to let the caller handle it
raise
Copied

Escrevemos agora a função para gerar o research_brief

  • Primeiro obtemos a última mensagem do histórico de mensagens, que será
  • Em seguida, fazemos uma chamada ao modelo forçando que ele retorne uma saída estruturada do tipo ResearchQuestion
  • O fluxo do agente termina retornando o research_brief
	
< > Input
Python
def write_research_brief(state: AgentState):
"""
Transform the conversation history into a comprehensive research brief.
Uses structured output to ensure the brief follows the required format
and contains all necessary details for effective research.
"""
try:
# Set up structured output model
structured_output_model = scope_model.with_structured_output(ResearchQuestion)
print("⏳ Scope agent - Write research brief:")
format_messages([state.get("messages", [])[-1]], title="Scope Assistant - Write research brief")
# Generate research brief from conversation history
with alive_bar(monitor=False, stats=False, title="", spinner='dots_waves', bar='blocks') as bar:
response = structured_output_model.invoke([
HumanMessage(content=transform_messages_into_research_topic_prompt.format(
messages=get_buffer_string(state.get("messages", [])),
date=get_today_str()
))
])
bar()
# Format and display the research brief
format_messages([response], title="Scope Assistant - Research brief generated")
# Update state with generated research brief and pass it to the supervisor
return {
"research_brief": response.research_brief,
"supervisor_messages": [HumanMessage(content=f"{response.research_brief}.")]
}
except Exception as e:
# Get the traceback information
exc_type, exc_obj, exc_tb = sys.exc_info()
line_number = exc_tb.tb_lineno
# Print detailed error information
print(f" ❌ Error in write_research_brief function:")
print(f" Line number: {line_number}")
print(f" Error type: {type(e).__name__}")
print(f" Error message: {str(e)}")
print(f" Full traceback:")
traceback.print_exc()
# Re-raise the exception to let the caller handle it
raise
Copied

Gráfico do agente Scopelink image 83

Agora que temos tudo o necessário para construir o agente, vamos definir o grafo do agente.

Primeiro criamos o agente com o estado AgentState e a entrada AgentInputState como explicamos antes, para que apenas se possa modificar a mensagem do estado interno do agente e não seja possível modificar o restante das variáveis do estado.

	
< > Input
Python
from langgraph.graph import StateGraph
# Build the scoping workflow
scope_graph_builder = StateGraph(AgentState, input_schema=AgentInputState)
Copied

Adicionamos dois nós, cada um com as funções que definimos anteriormente

	
< > Input
Python
# Add workflow nodes
scope_graph_builder.add_node("clarify_with_user", clarify_with_user)
scope_graph_builder.add_node("write_research_brief", write_research_brief)
Copied
>_ Output
			
&lt;langgraph.graph.state.StateGraph at 0x10a3dc410&gt;

E finalmente adicionamos as arestas que conectam os nós. Começamos com o nó START e o conectamos com o nó clarify_with_user. Por outro lado, unimos o nó write_research_brief com o nó END. E é a função clarify_with_user que se encarrega de decidir se, com a mensagem do usuário, passa-se ao nó write_research_brief ou se é pedida mais informação ao usuário e se passa ao nó END.

	
< > Input
Python
from langgraph.graph import START, END
# Add workflow edges
scope_graph_builder.add_edge(START, "clarify_with_user")
scope_graph_builder.add_edge("write_research_brief", END)
Copied
>_ Output
			
&lt;langgraph.graph.state.StateGraph at 0x10a3dc410&gt;

Compilamos o grafo e representamos o diagrama de fluxo

	
< > Input
Python
from langgraph.checkpoint.memory import InMemorySaver
from IPython.display import Image, display
checkpointer = InMemorySaver()
scope_agent = scope_graph_builder.compile(checkpointer=checkpointer)
# Visualize the workflow
display(Image(scope_agent.get_graph(xray=True).draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Execução do agente Scopelink image 84

Criamos um thread para poder fazer várias chamadas ao agente, caso ele nos peça mais informações, e criamos a mensagem inicial do usuário com o que queremos que ele crie o research_brief

	
< > Input
Python
thread = {"configurable": {"thread_id": "1"}}
user_message = "Quiero investigar las mejores cafeterías de Madrid."
result = await scope_agent.ainvoke({"messages": [HumanMessage(content=f"{user_message}.")]}, config=thread)
Copied
>_ Output
			
⏳ Scope agent:
>_ Output
			
╭───────────────────────────────────────────── 🧑 Real Human Message ─────────────────────────────────────────────╮
│ Quiero investigar las mejores cafeterías de Madrid.. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 2.7s
>_ Output
			
╭─────────────────────────────────── 🤖 Scope Assistant - need clarification? ────────────────────────────────────╮
│ need_clarification=True question='¿Podrías especificar qué criterios te interesan para considerar una cafetería │
│ como "mejor"? Por ejemplo: calidad del café, ambiente, ubicación, precio, opciones veganas, etc. ¿O prefieres │
│ una lista general?' verification='' │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭──────────────────────────────────────────── 🔧 Scope System Message ────────────────────────────────────────────╮
│ Necesita aclaración por parte del usuario. Enviando pregunta aclaratoria... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Como se pode ver, o agente nos pede mais informações, por isso voltamos a invocar o agente com as informações que nos pede

	
< > Input
Python
user_message = "Examina la calidad del café para evaluar las mejores cafeterías de Madrid."
result = await scope_agent.ainvoke({"messages": [HumanMessage(content=f"{user_message}.")]}, config=thread)
Copied
>_ Output
			
⏳ Scope agent:
>_ Output
			
╭───────────────────────────────────────────── 🧑 Real Human Message ─────────────────────────────────────────────╮
│ Examina la calidad del café para evaluar las mejores cafeterías de Madrid.. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 2.3s
>_ Output
			
╭─────────────────────────────────── 🤖 Scope Assistant - need clarification? ────────────────────────────────────╮
│ need_clarification=False question='' verification='Gracias por la información. Entiendo que deseas un informe │
│ sobre las mejores cafeterías de Madrid, evaluadas específicamente por la calidad del café. Comenzaré la │
│ investigación basándome en este criterio principal. Ahora iniciaré el proceso de investigación.' │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭──────────────────────────────────────────── 🔧 Scope System Message ────────────────────────────────────────────╮
│ No necesita aclaración por parte del usuario. Enviando mensaje de verificación... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Scope agent - Write research brief:
>_ Output
			
╭─────────────────────────────────── 🤖 Scope Assistant - Write research brief ───────────────────────────────────╮
│ Gracias por la información. Entiendo que deseas un informe sobre las mejores cafeterías de Madrid, evaluadas │
│ específicamente por la calidad del café. Comenzaré la investigación basándome en este criterio principal. Ahora │
│ iniciaré el proceso de investigación. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 3.9s
>_ Output
			
╭───────────────────────────────── 🤖 Scope Assistant - Research brief generated ─────────────────────────────────╮
│ research_brief='Quiero investigar cuáles son las mejores cafeterías de Madrid evaluadas específicamente por la │
│ calidad del café. Para ello, me interesa identificar cafeterías que destaquen por la excelencia en el sabor, │
│ aroma, frescura y preparación del café, considerando factores como el origen de los granos, el método de │
│ tostado y las técnicas de extracción utilizadas. No tengo preferencias sobre el rango de precios, ubicación, │
│ ambiente, opciones alimenticias o servicios adicionales, por lo que estos aspectos pueden considerarse abiertos │
│ o secundarios. Solicito priorizar fuentes primarias y confiables, como los sitios web oficiales de las │
│ cafeterías, reseñas de expertos en café y plataformas reconocidas de opinión de usuarios en español.' │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Com a nova informação esclarecedora, o agente gerou o research_brief, que será o que enviaremos ao agente de investigação.

Pesquisadorlink image 85

Se nos lembrarmos de como seria a arquitetura do deep researcher, vemos que há um Research supervisor que se encarrega de atribuir pesquisas a diferentes Researchers

arquitetura do deep researcher

Então, agora vamos definir o Researcher, que será responsável por realizar a pesquisa.

Prompts para pesquisadoreslink image 86

Primeiro definimos o prompt do Researcher, ele é indicado

  • O que é um investigador com acesso a ferramentas
  • É-lhe dito que as duas ferramentas que tem são a de pesquisa na web e a de reflexão
  • São dadas algumas instruções sobre como deve realizar a investigação
  • Indica-se que você deve utilizar a ferramenta de reflexão após cada busca
  • É atribuído um orçamento de chamadas a ferramentas
	
< > Input
Python
research_agent_prompt = """Eres un asistente de investigación que lleva a cabo una investigación sobre el tema introducido por el usuario. Para contextualizar, hoy es {date}.
&lt;Task&gt;
Tu trabajo consiste en utilizar herramientas para recopilar información sobre el tema introducido por el usuario.
Puedes utilizar cualquiera de las herramientas que se te proporcionan para encontrar recursos que te ayuden a responder a la pregunta de investigación. Puedes utilizar estas herramientas en serie o en paralelo, tu investigación se lleva a cabo en un bucle de llamada de herramientas.
&lt;/Task&gt;
&lt;Available Tools&gt;
Tienes acceso a dos herramientas principales:
1. **tavily_search**: para realizar búsquedas en la web con el fin de recopilar información.
2. **think_tool**: para la reflexión y la planificación estratégica durante la investigación.
**IMPORTANTE: utiliza think_tool después de cada búsqueda para reflexionar sobre los resultados y planificar los siguientes pasos**.
**CRÍTICO: Siempre debes proporcionar el parámetro 'reflection' con contenido detallado cuando llames a think_tool. Nunca llames a esta herramienta sin argumentos**.
&lt;/Available Tools&gt;
&lt;Instructions&gt;
Piensa como un investigador humano con tiempo limitado. Sigue estos pasos:
1. **Lee la pregunta con atención**: ¿qué información específica necesita el usuario?
2. **Empieza con búsquedas más amplias**: utiliza primero consultas amplias y exhaustivas.
3. **Después de cada búsqueda, haz una pausa y evalúa**: ¿tengo suficiente para responder? ¿Qué me falta?
4. **Realiza búsquedas más específicas a medida que recopilas información**: completa los huecos.
5. **Detente cuando puedas responder con seguridad**: no sigas buscando la perfección.
&lt;/Instructions&gt;
&lt;Hard Limits&gt;
**Presupuestos de llamadas a herramientas** (evita búsquedas excesivas):
- **Consultas simples**: utiliza un máximo de 2-3 llamadas a herramientas de búsqueda.
- **Consultas complejas**: utiliza un máximo de 5 llamadas a herramientas de búsqueda
- **Detente siempre**: después de 5 llamadas a herramientas de búsqueda si no puedes encontrar las fuentes adecuadas
**Detente inmediatamente cuando**:
- Puedas responder a la pregunta del usuario de forma exhaustiva
- Tengas más de 3 ejemplos/fuentes relevantes para la pregunta
- Tus últimas 2 búsquedas hayan devuelto información similar
&lt;/Hard Limits&gt;
&lt;Show Your Thinking&gt;
Después de cada llamada a la herramienta de búsqueda, utiliza think_tool para analizar los resultados:
- ¿Qué información clave he encontrado?
- ¿Qué falta?
- ¿Tengo suficiente para responder a la pregunta de forma exhaustiva?
- ¿Debo seguir buscando o dar mi respuesta?
&lt;/Show Your Thinking&gt;
"""
Copied

Agora definimos o prompt de um LLM que resume conteúdo de uma página web.

  • É fornecido o conteúdo de uma página web
  • São dadas algumas instruções sobre como deve realizar o resumo* É fornecido um formato no qual deve devolver o resumo
  • É dada a data atual
	
< > Input
Python
summarize_webpage_prompt = """Tu tarea consiste en resumir el contenido sin procesar de una página web obtenida mediante una búsqueda en Internet. Tu objetivo es crear un resumen que conserve la información más importante de la página web original. Este resumen será utilizado por un agente de investigación posterior, por lo que es fundamental mantener los detalles clave sin perder información esencial.
Este es el contenido sin procesar de la página web:
&lt;webpage_content&gt;
{webpage_content}
&lt;/webpage_content&gt;
Sigue estas pautas para crear tu resumen:
1. Identifica y conserva el tema principal o el propósito de la página web.
2. Conserva los datos, estadísticas y puntos clave que son fundamentales para el mensaje del contenido.
3. Conserva las citas importantes de fuentes o expertos fiables.
4. Mantén el orden cronológico de los acontecimientos si el contenido es sensible al tiempo o histórico.
5. Conserva las listas o instrucciones paso a paso, si las hay.
6. Incluye fechas, nombres y lugares relevantes que sean cruciales para comprender el contenido.
7. Resume las explicaciones largas manteniendo intacto el mensaje principal.
Cuando se trata de diferentes tipos de contenido:
- Para artículos de noticias: concéntrate en quién, qué, cuándo, dónde, por qué y cómo.
- Para contenido científico: conserva la metodología, los resultados y las conclusiones.
- Para artículos de opinión: mantén los argumentos principales y los puntos de apoyo.
- Para páginas de productos: mantén las características clave, las especificaciones y los puntos de venta únicos.
Tu resumen debe ser significativamente más corto que el contenido original, pero lo suficientemente completo como para constituir una fuente de información por tí mismo. Intenta que tenga entre un 25 % y un 30 % de la longitud original, a menos que el contenido ya sea conciso.
Presenta tu resumen en el siguiente formato:
```
{{
"summary": "Tu resumen aquí, estructurado con párrafos o viñetas adecuados según sea necesario",
"key_excerpts": "Primera cita o extracto importante, Segunda cita o extracto importante, Tercer cita o extracto importante, ...Añade más extractos según sea necesario, hasta un máximo de 5"
}}
```
Aquí hay dos ejemplos de buenos resúmenes:
Ejemplo 1 (para un artículo de noticias):
```json
{{
"summary": "El 15 de julio de 2023, la NASA lanzó con éxito la misión Artemis II desde el Centro Espacial Kennedy. Se trata de la primera misión tripulada a la Luna desde el Apolo 17 en 1972. La tripulación de cuatro personas, dirigida por la comandante Jane Smith, orbitará la Luna durante 10 días antes de regresar a la Tierra. Esta misión es un paso crucial en los planes de la NASA para establecer una presencia humana permanente en la Luna para 2030.",
"key_excerpts": "Artemis II representa una nueva era en la exploración espacial, dijo el administrador de la NASA, John Doe. La misión pondrá a prueba sistemas críticos para futuras estancias de larga duración en la Luna, explicó la ingeniera jefe Sarah Johnson. No solo vamos a volver a la Luna, vamos a avanzar hacia la Luna, declaró la comandante Jane Smith durante la rueda de prensa previa al lanzamiento"
}}
```
Ejemplo 2 (para un artículo científico):
```json
{{
"summary": "Un nuevo estudio publicado en Nature Climate Change revela que el nivel del mar está subiendo más rápido de lo que se pensaba. Los investigadores analizaron datos satelitales de 1993 a 2022 y descubrieron que la tasa de aumento del nivel del mar se ha acelerado en 0,08 mm/año² durante las últimas tres décadas. Esta aceleración se atribuye principalmente al derretimiento de los casquetes polares de Groenlandia y la Antártida. El estudio prevé que, si las tendencias actuales continúan, el nivel global del mar podría aumentar hasta 2 metros para 2100, lo que supondría un riesgo significativo para las comunidades costeras de todo el mundo.",
"key_excerpts": "Nuestros hallazgos indican una clara aceleración en el aumento del nivel del mar, lo que tiene importantes implicaciones para la planificación costera y las estrategias de adaptación», afirmó la autora principal, la Dra. Emily Brown. La velocidad de deshielo de las capas de hielo de Groenlandia y la Antártida se ha triplicado desde la década de 1990, según el estudio. Sin una reducción inmediata y sustancial de las emisiones de gases de efecto invernadero, nos enfrentamos a un aumento potencialmente catastrófico del nivel del mar a finales de este siglo, advirtió el coautor, el profesor Michael Green."
}}
```
Recuerda que tu objetivo es crear un resumen que pueda ser fácilmente comprendido y utilizado por un agente de investigación posterior, conservando al mismo tiempo la información más importante de la página web original.
La fecha de hoy es {date}.
"""
Copied

Uma vez que o conteúdo da página web foi resumido, chama-se outro LLM para limpar os resultados da pesquisa e do raciocínio interno ao resumir o conteúdo.

  • É fornecido conteúdo da pesquisa
  • São dadas algumas instruções de como deve realizar o resumo
  • É fornecido um formato no qual deve devolver o resumo* É fornecida a data atual
  • É indicado que deve conservar todas as declarações e informações relevantes recolhidas pelo investigador
  • É indicado que deve repetir a informação-chave textualmente

Este prompt é importante, porque se pede que crie uma lista numerada com as fontes utilizadas. Isso é útil para que, em pesquisas futuras, não sejam usadas novamente as mesmas fontes. Mas, sobretudo, para que no relatório final ao usuário seja possível mostrar as fontes utilizadas para cada afirmação.

	
< > Input
Python
compress_research_system_prompt = """Eres un asistente de investigación que ha investigado un tema utilizando diversas herramientas y búsquedas en Internet. Tu trabajo ahora consiste en limpiar los resultados, conservando todas las declaraciones e información relevantes recopiladas por el investigador. Para contextualizar, hoy es {date}.
&lt;Task&gt;
Debes limpiar la información recopilada de las herramientas y búsquedas en Internet en los mensajes existentes.
Toda la información relevante debe repetirse y reescribirse textualmente, pero en un formato más limpio.
El objetivo de este paso es simplemente eliminar cualquier información obviamente irrelevante o duplicada.
Por ejemplo, si tres fuentes dicen «X», podrías decir «Estas tres fuentes afirman X».
Solo estos resultados completos y limpios se devolverán al usuario, por lo que es esencial que no pierdas ninguna información de los mensajes sin procesar.
&lt;/Task&gt;
&lt;Tool Call Filtering&gt;
**IMPORTANTE**: Al procesar los mensajes de investigación, céntrate solo en el contenido sustantivo de la investigación:
- **Incluir**: Todos los resultados de tavily_search y los hallazgos de la búsqueda web
- **Excluir**: las llamadas y respuestas de think_tool, ya que son reflexiones internas del agente para la toma de decisiones y no deben incluirse en el informe final de la investigación
- **Céntrate en**: la información real recopilada de fuentes externas, no en el proceso de razonamiento interno del agente.
Las llamadas a think_tool contienen reflexiones estratégicas y notas sobre la toma de decisiones que son internas al proceso de investigación, pero no contienen información factual que deba conservarse en el informe final.
&lt;/Tool Call Filtering&gt;
&lt;Guidelines&gt;
1. Los resultados deben ser exhaustivos e incluir TODA la información y las fuentes que el investigador haya recopilado a partir de las llamadas a herramientas y las búsquedas en la web. Se espera que repita la información clave textualmente.
2. Este informe puede tener la extensión necesaria para incluir TODA la información que el investigador haya recopilado.
3. En tu informe, debes incluir citas en línea para cada fuente que el investigador haya encontrado.
4. Debes incluir una sección «Fuentes» al final del informe en la que se enumeran todas las fuentes que el investigador ha encontrado con las citas correspondientes, referenciadas en relación con las afirmaciones del informe.
5. Asegúrese de incluir TODAS las fuentes recopiladas por el investigador en el informe y cómo se utilizaron para responder a la pregunta.
6. Es muy importante no perder ninguna fuente. Más adelante se utilizará un LLM para fusionar este informe con otros, por lo que es esencial disponer de todas las fuentes.
&lt;/Guidelines&gt;
&lt;Output Format&gt;
El informe debe estructurarse de la siguiente manera:
**Lista de consultas y llamadas a herramientas realizadas**
**Resultados completos**
**Lista de todas las fuentes relevantes (con citas en el informe)**
&lt;/Output Format&gt;
&lt;Citation Rules&gt;
- Asigna a cada URL única un número de citación único en tu texto.
- Termina con ### Fuentes, enumerando cada fuente con los números correspondientes.
- IMPORTANTE: Numera las fuentes de forma secuencial sin saltos (1, 2, 3, 4...) en la lista final, independientemente de las fuentes que elijas.
- Ejemplo de formato:
[1] Título de la fuente: URL
[2] Título de la fuente: URL
&lt;/Citation Rules&gt;
Recordatorio importante: Es extremadamente importante que cualquier información que sea remotamente relevante para el tema de investigación del usuario se conserve textualmente (es decir, no la reescribas, no la resumas, no la parafrasees).
"""
Copied

Por fim, definimos o prompt de uma LLM encarregada de depurar os resultados da investigação para a pergunta específica que lhe foi atribuída.

  • É fornecido o tema de pesquisa* São fornecidas algumas instruções sobre como deve realizar o resumo
	
< > Input
Python
compress_research_human_message = """Todos los mensajes anteriores se refieren a la investigación realizada por un investigador de IA sobre el siguiente tema de investigación:
TEMA DE INVESTIGACIÓN: {research_topic}
Tu tarea consiste en depurar los resultados de esta investigación conservando TODA la información relevante para responder a esta pregunta de investigación específica.
REQUISITOS FUNDAMENTALES:
- NO resumas ni parafrasees la información: consérvala textualmente.
- NO pierdas ningún detalle, dato, nombre, número o hallazgo específico
- NO filtres la información que parezca relevante para el tema de investigación
- Organiza la información en un formato más claro, pero mantén todo el contenido
- Incluye TODAS las fuentes y citas encontradas durante la investigación
- Recuerda que esta investigación se llevó a cabo para responder a la pregunta específica anterior
Los resultados depurados se utilizarán para la elaboración del informe final, por lo que es fundamental que sean exhaustivos."""
Copied

Estado del investigadorlink image 87

Agora temos algo semelhante ao estado de Scope, temos dois estados ResearcherState e ResearcherInputState, vamos vê-los e depois explicamos

	
< > Input
Python
from typing_extensions import TypedDict, Annotated, List, Sequence
class ResearcherState(TypedDict):
"""
State for the research agent containing message history and research metadata.
This state tracks the researcher's conversation, iteration count for limiting
tool calls, the research topic being investigated, compressed findings,
and raw research notes for detailed analysis.
"""
researcher_messages: Annotated[Sequence[BaseMessage], add_messages]
tool_call_iterations: int
research_topic: str
compressed_research: str
raw_notes: Annotated[List[str], operator.add]
class ResearcherOutputState(TypedDict):
"""
Output state for the research agent containing final research results.
This represents the final output of the research process with compressed
research findings and all raw notes from the research process.
"""
compressed_research: str
raw_notes: Annotated[List[str], operator.add]
researcher_messages: Annotated[Sequence[BaseMessage], add_messages]
Copied

Pode-se ver que todas as variáveis de ResearcherOutputState (compressed_research, raw_notes e researcher_messages) estão presentes em ResearcherState, mas as variáveis tool_call_iterations e research_topic de ResearcherState não estão em ResearcherOutputState.

Isso ocorre porque são variáveis que o agente de pesquisa precisa, mas não são relevantes para o usuário final, que neste caso será o Supervisor.

Saída estruturada do Researcherlink image 88

Vamos definir como queremos que seja a saída do Researcher.

	
< > Input
Python
class Summary(BaseModel):
"""Schema for webpage content summarization."""
summary: str = Field(description="Concise summary of the webpage content")
key_excerpts: str = Field(description="Important quotes and excerpts from the content")
Copied

Vemos que queremos um resumo e as citações-chave da pesquisa.

Ferramentas do Researcherlink image 89

Quando explicamos o agente Scope, explicamos as funções do agente, enquanto agora vamos definir as tools; qual é a diferença?

As funções foram implementadas por nós e serão executadas sempre que o código seguir um fluxo que definimos. No entanto, as tools são ferramentas que o agente possui e é ele quem decide se vai utilizá-las ou não, e quando utilizá-las.

Ferramenta de busca na weblink image 90

Para pesquisar na internet, vamos utilizar a ferramenta tavily_search que já importamos anteriormente.

Antes de definir a ferramenta, primeiro definimos algumas funções auxiliares que nos vão ajudar a definir a ferramenta.

Primeiro definimos uma função para realizar várias buscas em paralelo.

	
< > Input
Python
def tavily_search_multiple(
search_queries: List[str],
max_results: int = 3,
topic: Literal["general", "news", "finance"] = "general",
include_raw_content: bool = True,
) -&gt; List[dict]:
"""Perform search using Tavily API for multiple queries.
Args:
search_queries: List of search queries to execute
max_results: Maximum number of results per query
topic: Topic filter for search results
include_raw_content: Whether to include raw webpage content
Returns:
List of search result dictionaries
"""
# Execute searches sequentially. Note: yon can use AsyncTavilyClient to parallelize this step.
search_docs = []
for query in search_queries:
result = tavily_client.search(
query,
max_results=max_results,
include_raw_content=include_raw_content,
topic=topic
)
search_docs.append(result)
return search_docs
Copied

Como fazemos várias buscas em paralelo, pode ser que essas buscas tenham encontrado dados nas mesmas fontes, por isso descartamos as buscas duplicadas com uma função

	
< > Input
Python
def deduplicate_search_results(search_results: List[dict]) -&gt; dict:
"""Deduplicate search results by URL to avoid processing duplicate content.
Args:
search_results: List of search result dictionaries
Returns:
Dictionary mapping URLs to unique results
"""
unique_results = {}
for response in search_results:
for result in response['results']:
url = result['url']
if url not in unique_results:
unique_results[url] = result
return unique_results
Copied

Agora definimos uma função para resumir o conteúdo de uma página web; para isso vamos usar o prompt summarize_webpage_prompt

	
< > Input
Python
def summarize_webpage_content(webpage_content: str) -&gt; str:
"""Summarize webpage content using the configured summarization model.
Args:
webpage_content: Raw webpage content to summarize
Returns:
Formatted summary with key excerpts
"""
try:
# Set up structured output model for summarization
structured_model = summarization_model.with_structured_output(Summary)
# Generate summary
summary = structured_model.invoke([
HumanMessage(content=summarize_webpage_prompt.format(
webpage_content=webpage_content,
date=get_today_str()
))
])
# Format summary with clear structure
formatted_summary = (
f"&lt;summary&gt; {summary.summary} &lt;/summary&gt; "
f"&lt;key_excerpts&gt; {summary.key_excerpts} &lt;/key_excerpts&gt;"
)
return formatted_summary
except Exception as e:
print(f"Failed to summarize webpage: {str(e)}")
return webpage_content[:1000] + "..." if len(webpage_content) &gt; 1000 else webpage_content
Copied

Com o conteúdo da busca resumido, criamos uma função para limpar os resultados da investigação.

	
< > Input
Python
def process_search_results(unique_results: dict) -&gt; dict:
"""Process search results by summarizing content where available.
Args:
unique_results: Dictionary of unique search results
Returns:
Dictionary of processed results with summaries
"""
summarized_results = {}
for url, result in unique_results.items():
# Use existing content if no raw content for summarization
if not result.get("raw_content"):
content = result['content']
else:
# Summarize raw content for better processing
content = summarize_webpage_content(result['raw_content'])
summarized_results[url] = {
'title': result['title'],
'content': content
}
return summarized_results
Copied

E, por último, criamos uma função para dar um formato específico ao conteúdo da investigação.

	
< > Input
Python
def format_search_output(summarized_results: dict) -&gt; str:
"""Format search results into a well-structured string output.
Args:
summarized_results: Dictionary of processed search results
Returns:
Formatted string of search results with clear source separation
"""
if not summarized_results:
return "No valid search results found. Please try different search queries or use a different search API."
formatted_output = "Search results: "
for i, (url, result) in enumerate(summarized_results.items(), 1):
formatted_output += f" --- SOURCE {i}: {result['title']} --- "
formatted_output += f"URL: {url} "
formatted_output += f"SUMMARY: {result['content']} "
formatted_output += "-" * 80 + " "
return formatted_output
Copied

Com todas estas funções auxiliares, já podemos definir a ferramenta que vai usar as funções que definimos. Primeiro, realiza várias buscas em paralelo, descarta os resultados duplicados, limpa os resultados e dá um formato concreto ao conteúdo da pesquisa.

	
< > Input
Python
from langchain_core.tools import tool, InjectedToolArg
@tool(parse_docstring=True)
def tavily_search(
query: str,
max_results: Annotated[int, InjectedToolArg] = 3,
topic: Annotated[Literal["general", "news", "finance"], InjectedToolArg] = "general",
) -&gt; str:
"""Fetch results from Tavily search API with content summarization.
Args:
query: A single search query to execute
max_results: Maximum number of results to return
topic: Topic to filter results by ('general', 'news', 'finance')
"""
# Execute search for single query
search_results = tavily_search_multiple(
[query], # Convert single query to list for the internal function
max_results=max_results,
topic=topic,
include_raw_content=True,
)
# Deduplicate results by URL to avoid processing duplicate content
unique_results = deduplicate_search_results(search_results)
# Process results with summarization
summarized_results = process_search_results(unique_results)
# Format output for consumption
return format_search_output(summarized_results)
Copied

Pode-se ver que usamos parse_docstring=True ao definir a ferramenta. Isso faz com que o docstring da função seja passado ao LLM para que ele tenha essa informação como contexto. Se não fizéssemos isso, o LLM receberia apenas como contexto

{
"query": str,
"max_results": int,
"tópico": Literal["general", "news", "finance"]
}

Ou seja, ele só saberia que tipo de argumentos a função espera, mas não saberia para que ela é usada. Mas, ao definir parse_docstring=True, o que o LLM recebe é algo assim:

Tenho um buscador que:
- Recebe:
- query (string): "Uma consulta de busca simples"
- max_results (int): "Número máximo de resultados a devolver"
- topic (Literal): "Categoria para filtrar ('general', 'news', 'finance')"

Embora haja outra coisa que fizemos que faz com que isso não seja realmente assim, as linhas

max_results: Annotated[int, InjectedToolArg] = 3,
tópico: Annotated[Literal["general", "news", "finance"], InjectedToolArg] = "general",

Usam InjectedToolArg, o que faz com que o valor desses argumentos fique fixo e não seja decidido pelo LLM. Imagine que o LLM decida que o número máximo de resultados seja 100; isso poderia fazer com que a busca demorasse demais.

Portanto, devido a isso, o que o LLM realmente recebe é algo assim:

Tenho um buscador que:
- Recebe:
- query (string): "Uma consulta de pesquisa simples"

Ferramenta de reflexãolink image 91

Como queremos que o agente reflita após cada busca, damos a ele uma ferramenta para isso

	
< > Input
Python
@tool(parse_docstring=True)
def think_tool(reflection: str = "") -&gt; str:
"""Record your strategic reflection on research progress (REQUIRED).
You MUST provide a detailed reflection analyzing your current findings,
gaps, and next steps. Never call this tool without providing reflection text.
Use this tool after each search to analyze results and plan next steps systematically.
This creates a deliberate pause in the research workflow for quality decision-making.
When to use:
- After receiving search results: What key information did I find?
- Before deciding next steps: Do I have enough to answer comprehensively?
- When assessing research gaps: What specific information am I still missing?
- Before concluding research: Can I provide a complete answer now?
Args:
reflection: (REQUIRED) Your detailed reflection on research progress, findings, gaps, and next steps.
Your reflection should include analysis of current findings, gap assessment,
quality evaluation, and strategic decision about next steps.
"""
if not reflection:
return "Reflection recorded: [No reflection provided]"
return f"Reflection recorded: {reflection}"
Copied

Assim como antes, usamos parse_docstring=True para que a docstring seja passada ao LLM como contexto.

Modelos de linguagem do researcherlink image 92

Agora que definimos as tools, vamos definir os diferentes modelos de linguagem que vamos usar, já que alguns deles vão usar as tools

Modelo de linguagem de resumo do researcherlink image 93

Primeiro, vamos criar o modelo de linguagem que vamos usar na função summarize_webpage_content que definimos antes

	
< > Input
Python
from langchain.chat_models import init_chat_model
import os
import dotenv
dotenv.load_dotenv()
SUMMARIZATION_MODEL_NAME = "gpt-4.1-mini"
SUMMARIZATION_MODEL_PROVIDER = "openai"
SUMMARIZATION_MODEL_BASE_URL = "https://api.openai.com/v1"
SUMMARIZATION_MODEL_TEMPERATURE = 0.0
SUMMARIZATION_MODEL_PROVIDER_API_KEY = os.getenv("OPENAI_API_KEY")
summarization_model = init_chat_model(
model=SUMMARIZATION_MODEL_NAME,
model_provider=SUMMARIZATION_MODEL_PROVIDER,
api_key=SUMMARIZATION_MODEL_PROVIDER_API_KEY,
base_url=SUMMARIZATION_MODEL_BASE_URL,
temperature=SUMMARIZATION_MODEL_TEMPERATURE
)
Copied

Modelo de linguagem de pesquisalink image 94

Agora vamos definir o modelo de linguagem que ficará encarregado da pesquisa

	
< > Input
Python
RESEARCH_MODEL_NAME = "anthropic:claude-sonnet-4-5-20250929"
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
RESEARCH_MODEL_MAX_TOKENS = 4096
research_model = init_chat_model(
model=RESEARCH_MODEL_NAME,
max_tokens=RESEARCH_MODEL_MAX_TOKENS
)
Copied

Agora damos ao modelo de linguagem de pesquisa acesso às ferramentas que definimos

	
< > Input
Python
researcher_tools = [tavily_search, think_tool]
model_with_tools = research_model.bind_tools(researcher_tools)
Copied

Modelo de linguagem de compressão da informaçãolink image 95

Por fim, definimos um modelo de linguagem que ficará encarregado de comprimir a informação da pesquisa

	
< > Input
Python
COMPRESS_MODEL_NAME = "gpt-4.1"
COMPRESS_MODEL_PROVIDER = "openai"
COMPRESS_MODEL_BASE_URL = "https://api.openai.com/v1"
COMPRESS_MODEL_TEMPERATURE = 0.0
COMPRESS_MODEL_PROVIDER_API_KEY = os.getenv("OPENAI_API_KEY")
compress_model = init_chat_model(
model=COMPRESS_MODEL_NAME,
model_provider=COMPRESS_MODEL_PROVIDER,
api_key=COMPRESS_MODEL_PROVIDER_API_KEY,
base_url=COMPRESS_MODEL_BASE_URL,
temperature=COMPRESS_MODEL_TEMPERATURE,
max_tokens=32000
)
Copied

Funções do agentelink image 96

Como vimos antes, por um lado estão as ferramentas do agente, que são as ferramentas que o LLM vai escolher quando e como usar, e por outro lado estão as funções do agente, que são as funções que serão executadas nos nós do grafo, e serão executadas no momento em que as definirmos por código.

Primeiro, criamos uma função que será responsável por chamar o modelo de linguagem de pesquisa com as ferramentas às quais ele tem acesso. O modelo decidirá se precisa usar uma tool ou não e, caso o faça, o controle será passado para a tool para que seja executada.

	
< > Input
Python
def llm_call(state: ResearcherState):
"""Analyze current state and decide on next actions.
The model analyzes the current conversation state and decides whether to:
1. Call search tools to gather more information
2. Provide a final answer based on gathered information
Returns updated state with the model's response.
"""
try:
# Show progress bar while waiting for LLM response
print("⏳ Researcher agent:")
if isinstance(state.get('researcher_messages', [])[-1], HumanMessage):
format_messages([state.get("researcher_messages", [])[-1]], title="Researcher Agent - Simulated Human Message")
with alive_bar(monitor=False, stats=False, title="", spinner='dots_waves', bar='blocks') as bar:
research_messages = model_with_tools.invoke(
[SystemMessage(content=research_agent_prompt)] + state["researcher_messages"]
)
bar() # Complete the progress bar
# Format and display the research messages
format_messages([research_messages], title="Researcher Agent response")
# Return the research messages
return {
"researcher_messages": [
research_messages
]
}
except Exception as e:
# Get the traceback information
exc_type, exc_obj, exc_tb = sys.exc_info()
line_number = exc_tb.tb_lineno
# Print detailed error information
print(f" ❌ Error in llm_call function:")
print(f" Line number: {line_number}")
print(f" Error type: {type(e).__name__}")
print(f" Error message: {str(e)}")
print(f" Full traceback:")
traceback.print_exc()
# Re-raise the exception to let the caller handle it
raise
Copied

Se o modelo de linguagem de pesquisa decidir que não precisa usar uma tool, o controle é passado para a função tool_node para que a tool escolhida pelo LLM seja executada.

	
< > Input
Python
from langchain_core.messages import ToolMessage, ToolCall
tools_by_name = {tool.name: tool for tool in researcher_tools}
def tool_node(state: ResearcherState):
"""Execute all tool calls from the previous LLM response.
Executes all tool calls from the previous LLM responses.
Returns updated state with tool execution results.
"""
try:
# Get the tool calls
tool_calls = state["researcher_messages"][-1].tool_calls
# Format the tool calls
format_messages([ToolCall(
name="Tool Calls",
args=tool_calls,
id="tool_call_id"
)], title="Researcher Agent - Tool calls")
# Execute all tool calls
observations = []
for tool_call in tool_calls:
with alive_bar(monitor=False, stats=False, title=f"", spinner='dots_waves', bar='blocks') as bar:
tool = tools_by_name[tool_call["name"]]
observation = tool.invoke(tool_call["args"])
observations.append(observation)
bar()
# Format and display the result immediately
format_messages([ToolMessage(
content=observation,
name=tool_call["name"],
tool_call_id=tool_call["id"]
)], title="Researcher Agent - Tool calls result")
# Create tool message outputs
tool_outputs = [
ToolMessage(
content=observation,
name=tool_call["name"],
tool_call_id=tool_call["id"]
) for observation, tool_call in zip(observations, tool_calls)
]
# Return the tool outputs
return {"researcher_messages": tool_outputs}
except Exception as e:
# Get the traceback information
exc_type, exc_obj, exc_tb = sys.exc_info()
line_number = exc_tb.tb_lineno
# Print detailed error information
print(f" ❌ Error in tool_node function:")
print(f" Line number: {line_number}")
print(f" Error type: {type(e).__name__}")
print(f" Error message: {str(e)}")
print(f" Full traceback:")
traceback.print_exc()
# Re-raise the exception to let the caller handle it
raise
Copied

Agora vamos definir a função que será encarregada de comprimir a informação da investigação.

	
< > Input
Python
from langchain_core.messages import SystemMessage, HumanMessage, filter_messages
def compress_research(state: ResearcherState) -&gt; dict:
"""Compress research findings into a concise summary.
Takes all the research messages and tool outputs and creates
a compressed summary suitable for the supervisor's decision-making.
"""
try:
system_message = compress_research_system_prompt.format(date=get_today_str())
messages = [SystemMessage(content=system_message)] + state.get("researcher_messages", []) + [HumanMessage(content=compress_research_human_message)]
print("⏳ Compressing Research:")
with alive_bar(monitor=False, stats=False, title="", spinner='dots_waves', bar='blocks') as bar:
response = compress_model.invoke(messages)
bar()
# Format and display the compressed research
format_messages([response], title="Researcher Agent - Compressed Research")
# Extract raw notes from tool and AI messages
raw_notes = [
str(m.content) for m in filter_messages(
state["researcher_messages"],
include_types=["tool", "ai"]
)
]
# Return the compressed research and raw notes
return {
"compressed_research": str(response.content),
"raw_notes": [" ".join(raw_notes)]
}
except Exception as e:
# Get the traceback information
exc_type, exc_obj, exc_tb = sys.exc_info()
line_number = exc_tb.tb_lineno
# Print detailed error information
print(f" ❌ Error in compress_research function:")
print(f" Line number: {line_number}")
print(f" Error type: {type(e).__name__}")
print(f" Error message: {str(e)}")
print(f" Full traceback:")
traceback.print_exc()
# Re-raise the exception to let the caller handle it
raise
Copied

Por fim, vamos definir a função que será responsável por decidir se a investigação continua ou se é encerrada.

O que vai fazer é verificar se a última mensagem do modelo de linguagem de pesquisa contém chamadas de ferramenta e, nesse caso, isso quer dizer que o LLM decidiu usar uma ferramenta, pelo que o controlo é passado para a função tool_node para que seja executada a ferramenta que o LLM escolheu.

Caso contrário, se a última mensagem não contiver chamadas de ferramenta, isso significa que o LLM decidiu que já tem informações suficientes e o controle é passado para a função compress_research para que a informação da pesquisa seja compactada.

	
< > Input
Python
def should_continue(state: ResearcherState) -&gt; Literal["tool_node", "compress_research"]:
"""Determine whether to continue research or provide final answer.
Determines whether the agent should continue the research loop or provide
a final answer based on whether the LLM made tool calls.
Returns:
"tool_node": Continue to tool execution
"compress_research": Stop and compress research
"""
try:
messages = state["researcher_messages"]
last_message = messages[-1]
# If the LLM makes a tool call, continue to tool execution
if last_message.tool_calls:
# Create a System message to show the decision
decision_message = SystemMessage(
content="Last message contains tool calls. Continuing to tool execution..."
)
format_messages([decision_message], title="Researcher Agent - Should continue")
return "tool_node"
# Otherwise, we have a final answer
else:
# Create a System message to show the decision
decision_message = SystemMessage(
content="No tool calls found. Stopping research and compressing findings..."
)
format_messages([decision_message], title="Researcher Agent - Should compress research")
return "compress_research"
except Exception as e:
# Get the traceback information
exc_type, exc_obj, exc_tb = sys.exc_info()
line_number = exc_tb.tb_lineno
# Print detailed error information
print(f" ❌ Error in should_continue function:")
print(f" Line number: {line_number}")
print(f" Error type: {type(e).__name__}")
print(f" Error message: {str(e)}")
print(f" Full traceback:")
traceback.print_exc()
# Re-raise the exception to let the caller handle it
raise
Copied

Grafo do pesquisadorlink image 97

Já temos tudo o que é necessário para construir o grafo do agente researcher.

Primeiro criamos o grafo

	
< > Input
Python
# Build the agent workflow
researcher_agent_builder = StateGraph(ResearcherState, output_schema=ResearcherOutputState)
Copied

Agora criamos os nós que vão compor o grafo. Eles vão utilizar as funções que definimos antes.

	
< > Input
Python
# Add nodes to the graph
researcher_agent_builder.add_node("llm_call", llm_call)
researcher_agent_builder.add_node("tool_node", tool_node)
researcher_agent_builder.add_node("compress_research", compress_research)
Copied
>_ Output
			
&lt;langgraph.graph.state.StateGraph at 0x1112bddf0&gt;

Já temos os nós, nós os unimos com as arestas.

  • Do nó START, passa-se para o nó llm_call
  • Do nó llm_call, passa-se para o nó tool_node se a última mensagem contiver chamadas de ferramenta, ou para o nó compress_research se não contiver chamadas de ferramenta* Do nó tool_node passa-se para o nó llm_call para que o LLM possa decidir se continua investigando ou não
  • Do nó compress_research passa-se para o nó END
	
< > Input
Python
# Add edges to connect nodes
researcher_agent_builder.add_edge(START, "llm_call")
researcher_agent_builder.add_conditional_edges(
"llm_call",
should_continue,
{
"tool_node": "tool_node", # Continue research loop
"compress_research": "compress_research", # Provide final answer
},
)
researcher_agent_builder.add_edge("tool_node", "llm_call") # Loop back for more research
researcher_agent_builder.add_edge("compress_research", END)
Copied
>_ Output
			
&lt;langgraph.graph.state.StateGraph at 0x1112bddf0&gt;

Compilamos o grafo e representamos o diagrama de fluxo

	
< > Input
Python
from langgraph.checkpoint.memory import InMemorySaver
from IPython.display import Image, display
checkpointer = InMemorySaver()
researcher_agent = researcher_agent_builder.compile(checkpointer=checkpointer)
# Visualize the workflow
display(Image(researcher_agent.get_graph(xray=True).draw_mermaid_png()))
Copied
>_ Output
			
&lt;IPython.core.display.Image object&gt;

Execução do agente researcherlink image 98

Primeiro, vamos chamar o agente Scope para nos ajudar a definir o research_brief

Criamos um thread para poder fazer várias chamadas ao agente, caso ele nos peça mais informações

	
< > Input
Python
thread = {"configurable": {"thread_id": "2"}}
user_message = "Quiero investigar las mejores cafeterías de Madrid."
result = await scope_agent.ainvoke({"messages": [HumanMessage(content=f"{user_message}.")]}, config=thread)
Copied
>_ Output
			
⏳ Scope agent:
>_ Output
			
╭───────────────────────────────────────────── 🧑 Real Human Message ─────────────────────────────────────────────╮
│ Quiero investigar las mejores cafeterías de Madrid.. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 2.6s
>_ Output
			
╭─────────────────────────────────── 🤖 Scope Assistant - need clarification? ────────────────────────────────────╮
│ need_clarification=True question='¿Podrías especificar qué criterios te interesan para considerar una cafetería │
│ como "mejor"? Por ejemplo: calidad del café, ambiente, ubicación, opciones de comida, precio, etc. ¿Buscas │
│ cafeterías para trabajar, para socializar, o simplemente para disfrutar buen café?' verification='' │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭──────────────────────────────────────────── 🔧 Scope System Message ────────────────────────────────────────────╮
│ Necesita aclaración por parte del usuario. Enviando pregunta aclaratoria... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

O agente nos pede mais informações, por isso voltamos a invocar o agente com as informações que ele nos solicita

	
< > Input
Python
user_message = "Examina la calidad del café para evaluar las mejores cafeterías de Madrid."
result = await scope_agent.ainvoke({"messages": [HumanMessage(content=f"{user_message}.")]}, config=thread)
Copied
>_ Output
			
⏳ Scope agent:
>_ Output
			
╭───────────────────────────────────────────── 🧑 Real Human Message ─────────────────────────────────────────────╮
│ Examina la calidad del café para evaluar las mejores cafeterías de Madrid.. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 1.8s
>_ Output
			
╭─────────────────────────────────── 🤖 Scope Assistant - need clarification? ────────────────────────────────────╮
│ need_clarification=False question='' verification='Gracias por la información. Entiendo que deseas un informe │
│ sobre las mejores cafeterías de Madrid, evaluadas principalmente según la calidad del café. Comenzaré la │
│ investigación considerando este criterio como el principal. Ahora iniciaré el proceso de investigación.' │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭──────────────────────────────────────────── 🔧 Scope System Message ────────────────────────────────────────────╮
│ No necesita aclaración por parte del usuario. Enviando mensaje de verificación... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Scope agent - Write research brief:
>_ Output
			
╭─────────────────────────────────── 🤖 Scope Assistant - Write research brief ───────────────────────────────────╮
│ Gracias por la información. Entiendo que deseas un informe sobre las mejores cafeterías de Madrid, evaluadas │
│ principalmente según la calidad del café. Comenzaré la investigación considerando este criterio como el │
│ principal. Ahora iniciaré el proceso de investigación. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 5.5s
>_ Output
			
╭───────────────────────────────── 🤖 Scope Assistant - Research brief generated ─────────────────────────────────╮
│ research_brief='Quiero investigar cuáles son las mejores cafeterías de Madrid evaluadas principalmente según la │
│ calidad del café que ofrecen. Para ello, me interesa identificar y comparar cafeterías en Madrid considerando │
│ factores clave que influyen en la calidad del café, como el origen y la frescura de los granos, los métodos de │
│ tostado, las técnicas de preparación y la experiencia sensorial (sabor, aroma, cuerpo y acidez). No he │
│ especificado preferencias sobre otros aspectos como ambiente, precio, ubicación, opciones de comida o si las │
│ cafeterías son adecuadas para trabajar o socializar, por lo que estos factores pueden considerarse secundarios │
│ o abiertos. Solicito priorizar fuentes primarias y confiables, como sitios web oficiales de las cafeterías, │
│ reseñas de expertos en café y plataformas reconocidas de opinión de usuarios en español.' │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Já temos o research_brief, portanto podemos invocar o agente Researcher para que comece a investigação.

IMPORTANTE: Neste caso, o que fazemos é invocar o agente Researcher com o research_brief que já temos; para isso, passamos isso como a variável do estado researcher_messages e como a variável do estado research_topic

Criamos também um cliente da API da Tavily para poder fazer buscas na internet.

	
< > Input
Python
from tavily import TavilyClient
research_brief = result["research_brief"]
research_topic = str(research_brief)
tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
result = await researcher_agent.ainvoke({"researcher_messages": [HumanMessage(content=f"{research_brief}.")], "research_topic": research_topic}, config=thread)
Copied
>_ Output
			
⏳ Researcher agent:
>_ Output
			
╭───────────────────────────────── 🧑 Researcher Agent - Simulated Human Message ─────────────────────────────────╮
│ Quiero investigar cuáles son las mejores cafeterías de Madrid evaluadas principalmente según la calidad del │
│ café que ofrecen. Para ello, me interesa identificar y comparar cafeterías en Madrid considerando factores │
│ clave que influyen en la calidad del café, como el origen y la frescura de los granos, los métodos de tostado, │
│ las técnicas de preparación y la experiencia sensorial (sabor, aroma, cuerpo y acidez). No he especificado │
│ preferencias sobre otros aspectos como ambiente, precio, ubicación, opciones de comida o si las cafeterías son │
│ adecuadas para trabajar o socializar, por lo que estos factores pueden considerarse secundarios o abiertos. │
│ Solicito priorizar fuentes primarias y confiables, como sitios web oficiales de las cafeterías, reseñas de │
│ expertos en café y plataformas reconocidas de opinión de usuarios en español.. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 4.5s
>_ Output
			
╭───────────────────────────────────────── 🤖 Researcher Agent response ──────────────────────────────────────────╮
│ Voy a investigar las mejores cafeterías de Madrid enfocándome en la calidad del café. Comenzaré con búsquedas │
│ amplias para identificar las cafeterías más reconocidas por la calidad de su café. │
│ │
│ 🔧 Tool Call: tavily_search │
│ Args: { │
│ "query": "mejores cafeterías Madrid calidad café especialidad tostado granos" │
│ } │
│ ID: toolu_01RXjNzCsYFkycrbyZ9idSmz │
│ │
│ 🔧 Tool Call: tavily_search │
│ Args: { │
│ "query": "cafés de especialidad Madrid expertos baristas origen granos" │
│ } │
│ ID: toolu_01DTXgvZwXBdWnwAAvZczcNc │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮
│ Last message contains tool calls. Continuing to tool execution... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭─────────────────────────────────────── 🔧 Researcher Agent - Tool calls ────────────────────────────────────────╮
│ Tool Call Name: Tool Calls │
│ ID: tool_call_id │
│ │
│ Tool Call 1: │
│ Name: tavily_search │
│ ID: toolu_01RXjNzCsYFkycrbyZ9idSmz │
│ Type: tool_call │
│ Args: │
│ { │
│ "query": "mejores cafeterías Madrid calidad café especialidad tostado granos" │
│ } │
│ │
│ Tool Call 2: │
│ Name: tavily_search │
│ ID: toolu_01DTXgvZwXBdWnwAAvZczcNc │
│ Type: tool_call │
│ Args: │
│ { │
│ "query": "cafés de especialidad Madrid expertos baristas origen granos" │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 32.8s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Search results: │
│ │
│ │
│ │
│ --- SOURCE 1: Las 25 mejores cafeterías de especialidad de Madrid --- │
│ URL: https://madridsecreto.co/mejor-cafe-de-madrid/ │
│ │
│ SUMMARY: │
│ &lt;summary&gt; │
│ El artículo presenta una guía detallada de las 27 mejores cafeterías de especialidad en Madrid, destacando la │
│ evolución del café en la ciudad desde el predominio del café torrefacto hasta la actual fiebre por el café de │
│ especialidad o "speciality coffee". Cada cafetería se describe con su nombre, ubicación, características │
│ distintivas y especialidades, como tostado propio, café 100% arábica, opciones veganas, pastelería de alta │
│ calidad, y propuestas innovadoras en bebidas y alimentos. Entre las mencionadas se encuentran Bianchi Kiosko │
│ Caffé en Malasaña, Obrar en Chamberí, Ruda Café en La Latina, The Fix en Argüelles, Toma Café con varios │
│ locales, Pum Pum Café en Lavapiés, Hola Coffee con formación de baristas, y otras como 1000 Cups Café, Aroma │
│ and Bread, Solo Café, Rebel Café, Plántate Café, Cafelito, Monkee Koffee, Feliz Coffee, Acid Café, Santa │
│ Kafeina, Misión Café, Casa Neutrale, Syra Coffee, Caferama, Clima Cafecito, God Café, Café Angélica, HanSo, En │
│ Bruto y Borbeen. El artículo también menciona la importancia de la calidad del grano, la trazabilidad, y la │
...
│ condiciones óptimas.", "En Madrid, tostamos cafés de diferentes orígenes y procesos.", "Recientemente, el día │
│ 17 de junio de 2023 hemos ganado el título de 'mejor tostador de café de especialidad en Madrid'.", "Nuestra │
│ política de precios está hecha para llevar el café de alta calidad a muchos hogares.", "Mejorar la calidad de │
│ vida de todas las personas involucradas en la realización es fundamental para lograr que el café de │
│ especialidad continúe creciendo." │
│ &lt;/key_excerpts&gt; │
│ │
│ -------------------------------------------------------------------------------- │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 29.7s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Search results: │
│ │
│ │
│ │
│ --- SOURCE 1: Café de especialidad en Madrid y España: ¿Cómo comprar? --- │
│ URL: │
│ https://boconospecialtycoffee.com/cafe-de-especialidad-en-madrid/?srsltid=AfmBOoqmKvAdvfWSlCPk0RP95jHwpy9oxBk4T │
│ h5IiSWvBxpNbHDGdYZv │
│ │
│ SUMMARY: │
│ &lt;summary&gt; │
│ Boconó Specialty Coffee es una marca dedicada al tueste y venta de café de especialidad en Madrid, con orígenes │
│ en Venezuela desde 2006 y establecida en Madrid desde 2016. En junio de 2023, ganó el título de mejor tostador │
│ de café de especialidad en Madrid en el evento organizado por Naked Festival. La empresa ofrece cafés de │
│ diferentes orígenes y procesos, además de accesorios, maquinaria y cursos de barista. El café de especialidad │
│ se caracteriza por un proceso meticuloso desde el cultivo sostenible, cosecha manual y postcosecha cuidadosa, │
│ hasta un tostado controlado que preserva sus características únicas. En Madrid, el interés por el café de │
│ especialidad ha crecido notablemente desde 2016, con Lavapiés como uno de los barrios con más cafeterías │
│ especializadas. Boconó tuesta su café en el mismo local y ofrece degustaciones gratuitas, venta en tienda │
...
│ &lt;key_excerpts&gt; │
│ Compra online en nuestra tienda de café de especialidad y recíbelo en cualquier punto de Madrid en 24-48 h., │
│ Café puntuado por encima de 80 en la escala SCA, Tueste artesanal en pequeños lotes, Selección directa de │
│ fincas responsables, Frescura garantizada: tueste bajo pedido, El café de especialidad es un café de alta │
│ calidad, cultivado en condiciones óptimas y puntuado con más de 80 puntos en la escala SCA. │
│ &lt;/key_excerpts&gt; │
│ │
│ -------------------------------------------------------------------------------- │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Researcher agent:
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 12.4s
>_ Output
			
╭───────────────────────────────────────── 🤖 Researcher Agent response ──────────────────────────────────────────╮
│ │
│ 🔧 Tool Call: think_tool │
│ Args: { │
│ "reflection": " He encontrado información muy valiosa en estas primeras búsquedas: **Información │
│ clave obtenida:** 1. Hola Coffee aparece como referencia destacada - puesto 12 a nivel mundial según "The │
│ World's 100 Best Coffee Shops", fundada en 2016, tiene tostadero propio, su dueño fue Spanish Barista Champion │
│ 2016 2. Boconó Specialty Coffee - ganador del título "mejor tostador de café de especialidad en Madrid" en │
│ junio 2023 (Naked Festival), tiene origen venezolano, ubicado en Lavapiés, tuesta en el mismo local 3. Lista │
│ amplia de cafeterías mencionadas: Bianchi Kiosko Caffé, Obrar, Ruda Café, The Fix, Toma Café, Pum Pum Café, │
│ Rebel Café, Café Angélica, entre otras 4. Factores mencionados sobre calidad: granos 100% Arábica, tostado │
│ artesanal, trazabilidad, origen de países como Colombia, Etiopía, Guatemala, Burundi **Gaps │
│ identificados:** - Necesito información más específica sobre métodos de preparación (V60, Chemex, Aeropress, │
│ etc.) - Detalles sobre el perfil sensorial de cada cafetería (sabor, aroma, cuerpo, acidez) - Información de │
│ sitios web oficiales de las cafeterías principales - Opiniones de expertos y reseñas de usuarios en │
│ plataformas reconocidas - Comparación más detallada entre las cafeterías mencionadas **Próximos │
│ pasos:** Necesito buscar información más específica sobre las cafeterías top mencionadas (Hola Coffee, Boconó, │
│ Toma Café, Hanso, Syra Coffee) incluyendo sus sitios oficiales, métodos de preparación, y reseñas de │
│ expertos/usuarios. " │
│ } │
│ ID: toolu_01XneDofDVtm3u5g4CHA4Duf │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮
│ Last message contains tool calls. Continuing to tool execution... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭─────────────────────────────────────── 🔧 Researcher Agent - Tool calls ────────────────────────────────────────╮
│ Tool Call Name: Tool Calls │
│ ID: tool_call_id │
│ │
│ Tool Call 1: │
│ Name: think_tool │
│ ID: toolu_01XneDofDVtm3u5g4CHA4Duf │
│ Type: tool_call │
│ Args: │
│ { │
│ "reflection": " He encontrado información muy valiosa en estas primeras búsquedas: **Información │
│ clave obtenida:** 1. Hola Coffee aparece como referencia destacada - puesto 12 a nivel mundial según "The │
│ World's 100 Best Coffee Shops", fundada en 2016, tiene tostadero propio, su dueño fue Spanish Barista Champion │
│ 2016 2. Boconó Specialty Coffee - ganador del título "mejor tostador de café de especialidad en Madrid" en │
│ junio 2023 (Naked Festival), tiene origen venezolano, ubicado en Lavapiés, tuesta en el mismo local 3. Lista │
│ amplia de cafeterías mencionadas: Bianchi Kiosko Caffé, Obrar, Ruda Café, The Fix, Toma Café, Pum Pum Café, │
│ Rebel Café, Café Angélica, entre otras 4. Factores mencionados sobre calidad: granos 100% Arábica, tostado │
│ artesanal, trazabilidad, origen de países como Colombia, Etiopía, Guatemala, Burundi **Gaps │
│ identificados:** - Necesito información más específica sobre métodos de preparación (V60, Chemex, Aeropress, │
│ etc.) - Detalles sobre el perfil sensorial de cada cafetería (sabor, aroma, cuerpo, acidez) - Información de │
│ sitios web oficiales de las cafeterías principales - Opiniones de expertos y reseñas de usuarios en │
│ plataformas reconocidas - Comparación más detallada entre las cafeterías mencionadas **Próximos │
│ pasos:** Necesito buscar información más específica sobre las cafeterías top mencionadas (Hola Coffee, Boconó, │
│ Toma Café, Hanso, Syra Coffee) incluyendo sus sitios oficiales, métodos de preparación, y reseñas de │
│ expertos/usuarios. " │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 0.0s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Reflection recorded: │
│ He encontrado información muy valiosa en estas primeras búsquedas: │
│ │
│ **Información clave obtenida:** │
│ 1. Hola Coffee aparece como referencia destacada - puesto 12 a nivel mundial según "The World's 100 Best Coffee │
│ Shops", fundada en 2016, tiene tostadero propio, su dueño fue Spanish Barista Champion 2016 │
│ 2. Boconó Specialty Coffee - ganador del título "mejor tostador de café de especialidad en Madrid" en junio │
│ 2023 (Naked Festival), tiene origen venezolano, ubicado en Lavapiés, tuesta en el mismo local │
│ 3. Lista amplia de cafeterías mencionadas: Bianchi Kiosko Caffé, Obrar, Ruda Café, The Fix, Toma Café, Pum Pum │
│ Café, Rebel Café, Café Angélica, entre otras │
│ 4. Factores mencionados sobre calidad: granos 100% Arábica, tostado artesanal, trazabilidad, origen de países │
│ como Colombia, Etiopía, Guatemala, Burundi │
│ │
│ **Gaps identificados:** │
│ - Necesito información más específica sobre métodos de preparación (V60, Chemex, Aeropress, etc.) │
│ - Detalles sobre el perfil sensorial de cada cafetería (sabor, aroma, cuerpo, acidez) │
│ - Información de sitios web oficiales de las cafeterías principales │
│ - Opiniones de expertos y reseñas de usuarios en plataformas reconocidas │
│ - Comparación más detallada entre las cafeterías mencionadas │
│ │
│ **Próximos pasos:** │
│ Necesito buscar información más específica sobre las cafeterías top mencionadas (Hola Coffee, Boconó, Toma │
│ Café, Hanso, Syra Coffee) incluyendo sus sitios oficiales, métodos de preparación, y reseñas de │
│ expertos/usuarios. │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Researcher agent:
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 5.1s
>_ Output
			
╭───────────────────────────────────────── 🤖 Researcher Agent response ──────────────────────────────────────────╮
│ │
│ 🔧 Tool Call: tavily_search │
│ Args: { │
│ "query": "Hola Coffee Madrid sitio oficial métodos preparación barista champion tostadero" │
│ } │
│ ID: toolu_01CQ7xbd5KQM9GoxmXdA4PSd │
│ │
│ 🔧 Tool Call: tavily_search │
│ Args: { │
│ "query": "Toma Café HanSo Syra Coffee Madrid reseñas expertos calidad café métodos extracción" │
│ } │
│ ID: toolu_01Ncd4CFxzPYqQfWpuo3Ev4A │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮
│ Last message contains tool calls. Continuing to tool execution... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭─────────────────────────────────────── 🔧 Researcher Agent - Tool calls ────────────────────────────────────────╮
│ Tool Call Name: Tool Calls │
│ ID: tool_call_id │
│ │
│ Tool Call 1: │
│ Name: tavily_search │
│ ID: toolu_01CQ7xbd5KQM9GoxmXdA4PSd │
│ Type: tool_call │
│ Args: │
│ { │
│ "query": "Hola Coffee Madrid sitio oficial métodos preparación barista champion tostadero" │
│ } │
│ │
│ Tool Call 2: │
│ Name: tavily_search │
│ ID: toolu_01Ncd4CFxzPYqQfWpuo3Ev4A │
│ Type: tool_call │
│ Args: │
│ { │
│ "query": "Toma Café HanSo Syra Coffee Madrid reseñas expertos calidad café métodos extracción" │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 10.2s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Search results: │
│ │
│ │
│ │
│ --- SOURCE 1: En Madrid, Hola Coffee Lagasca se distingue por un diseño ... --- │
│ URL: https://www.instagram.com/reel/DNjXa-9yr7j/ │
│ │
│ SUMMARY: │
│ ✨ Como flagship de la marca tostadora, ofrecen cafés filtrados y bebidas de autor que destacan por su │
│ innovación y calidad, siempre acompañados │
│ │
│ -------------------------------------------------------------------------------- │
│ │
│ │
│ --- SOURCE 2: Hola Coffee Academy --- │
│ URL: │
│ https://hola.coffee/collections/cursos-barista?srsltid=AfmBOopR-kQ7PjyThxry5og5Cvmqc3LPmQfBGCETNBx64KK4NanajSey │
│ │
│ SUMMARY: │
...
│ --- SOURCE 3: Hola Coffee Roasters (@holacoffee) --- │
│ URL: https://www.instagram.com/holacoffee/?hl=en │
│ │
│ SUMMARY: │
│ 72K followers · 1.6K+ following · 725 posts · @holacoffee: “Café de especialidad tostado en Madrid Tostador / │
│ Academia / Cafeterías Calle Doctor Fourquet, │
│ │
│ -------------------------------------------------------------------------------- │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 9.4s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Search results: │
│ │
│ │
│ │
│ --- SOURCE 1: ▷ SYRA Coffee Madrid《 Cafés, Carta y Opiniones 》 --- │
│ URL: https://myspecialtycoffee.es/madrid/syra-coffee/ │
│ │
│ SUMMARY: │
│ &lt;summary&gt; │
│ Syra Coffee es una cadena de cafés de especialidad establecida en Madrid desde 2022, reconocida por ofrecer una │
│ experiencia cafetera de alta calidad con un enfoque en el servicio para llevar. Cuenta con múltiples │
│ ubicaciones en Madrid, todas con horario de lunes a domingo de 8:00 a 20:00, y un diseño minimalista que │
│ prioriza la rapidez y comodidad. Su oferta incluye una variedad de bebidas como Flat White, Latte, Espresso, │
│ café filtrado (V60, Aeropress), además de opciones descafeinadas, tés y repostería artesanal. Destacan la venta │
│ de café en grano de orígenes seleccionados (Etiopía, Colombia, Brasil) y accesorios para preparar café en casa, │
│ así como un programa de fidelización mediante una app móvil que permite acumular puntos y realizar pedidos │
│ anticipados. Syra Coffee también organiza catas de café en colaboración con expertos para profundizar en el │
│ conocimiento del café de especialidad. La marca mantiene una activa presencia en redes sociales (Instagram, │
│ Facebook) y su página web. Los clientes valoran positivamente la calidad del café y la atención, con una │
...
│ https://syra.coffee/pages/opiniones-de-nuestros-clientes?srsltid=AfmBOopS-jJ3HErXYCtp9whNqifE44V062Og1ynxGHmKd0 │
│ OVAPRtdGql │
│ │
│ SUMMARY: │
│ Los clientes saben más. Opiniones de clientes sobre nuestra experiencia de compra en línea. Los clientes │
│ confirman nuestro excelente servicio y calidad. │
│ │
│ -------------------------------------------------------------------------------- │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Researcher agent:
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 9.2s
>_ Output
			
╭───────────────────────────────────────── 🤖 Researcher Agent response ──────────────────────────────────────────╮
│ │
│ 🔧 Tool Call: think_tool │
│ Args: { │
│ "reflection": " **Nueva información obtenida:** 1. Hola Coffee: Tiene academia de baristas con │
│ certificación SCA, tostadora propia en C/ Sepúlveda 109, múltiples cafeterías, más de 10 años de experiencia, │
│ ofrece cursos desde iniciación a avanzado, cafés filtrados y bebidas de autor 2. Syra Coffee: Establecida en │
│ 2022, ofrece métodos de preparación específicos (V60, Aeropress), cafés de origen (Etiopía, Colombia, Brasil), │
│ más de 4.5 estrellas en Google Maps con más de 3000 opiniones, múltiples ubicaciones, catas con expertos 3. │
│ HanSo: Aparece en TripAdvisor pero con información limitada **Información todavía necesaria:** - Detalles │
│ sobre Toma Café (que apareció en la primera búsqueda pero sin profundizar) - Información sobre otras │
│ cafeterías top mencionadas: Bianchi Kiosko Caffé, Obrar, Café Angélica - Reseñas más específicas sobre │
│ perfiles sensoriales - Información oficial de sitios web de las cafeterías **Evaluación:** Tengo │
│ información sólida sobre algunas cafeterías principales, pero necesito una búsqueda más para completar el │
│ panorama con información sobre las otras cafeterías destacadas y obtener más detalles sobre métodos de tostado │
│ y perfiles sensoriales específicos. " │
│ } │
│ ID: toolu_01DKcZGkQjzUYcjB4NQPYRfB │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮
│ Last message contains tool calls. Continuing to tool execution... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭─────────────────────────────────────── 🔧 Researcher Agent - Tool calls ────────────────────────────────────────╮
│ Tool Call Name: Tool Calls │
│ ID: tool_call_id │
│ │
│ Tool Call 1: │
│ Name: think_tool │
│ ID: toolu_01DKcZGkQjzUYcjB4NQPYRfB │
│ Type: tool_call │
│ Args: │
│ { │
│ "reflection": " **Nueva información obtenida:** 1. Hola Coffee: Tiene academia de baristas con │
│ certificación SCA, tostadora propia en C/ Sepúlveda 109, múltiples cafeterías, más de 10 años de experiencia, │
│ ofrece cursos desde iniciación a avanzado, cafés filtrados y bebidas de autor 2. Syra Coffee: Establecida en │
│ 2022, ofrece métodos de preparación específicos (V60, Aeropress), cafés de origen (Etiopía, Colombia, Brasil), │
│ más de 4.5 estrellas en Google Maps con más de 3000 opiniones, múltiples ubicaciones, catas con expertos 3. │
│ HanSo: Aparece en TripAdvisor pero con información limitada **Información todavía necesaria:** - Detalles │
│ sobre Toma Café (que apareció en la primera búsqueda pero sin profundizar) - Información sobre otras │
│ cafeterías top mencionadas: Bianchi Kiosko Caffé, Obrar, Café Angélica - Reseñas más específicas sobre │
│ perfiles sensoriales - Información oficial de sitios web de las cafeterías **Evaluación:** Tengo │
│ información sólida sobre algunas cafeterías principales, pero necesito una búsqueda más para completar el │
│ panorama con información sobre las otras cafeterías destacadas y obtener más detalles sobre métodos de tostado │
│ y perfiles sensoriales específicos. " │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 0.0s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Reflection recorded: │
│ **Nueva información obtenida:** │
│ 1. Hola Coffee: Tiene academia de baristas con certificación SCA, tostadora propia en C/ Sepúlveda 109, │
│ múltiples cafeterías, más de 10 años de experiencia, ofrece cursos desde iniciación a avanzado, cafés filtrados │
│ y bebidas de autor │
│ 2. Syra Coffee: Establecida en 2022, ofrece métodos de preparación específicos (V60, Aeropress), cafés de │
│ origen (Etiopía, Colombia, Brasil), más de 4.5 estrellas en Google Maps con más de 3000 opiniones, múltiples │
│ ubicaciones, catas con expertos │
│ 3. HanSo: Aparece en TripAdvisor pero con información limitada │
│ │
│ **Información todavía necesaria:** │
│ - Detalles sobre Toma Café (que apareció en la primera búsqueda pero sin profundizar) │
│ - Información sobre otras cafeterías top mencionadas: Bianchi Kiosko Caffé, Obrar, Café Angélica │
│ - Reseñas más específicas sobre perfiles sensoriales │
│ - Información oficial de sitios web de las cafeterías │
│ │
│ **Evaluación:** │
│ Tengo información sólida sobre algunas cafeterías principales, pero necesito una búsqueda más para completar el │
│ panorama con información sobre las otras cafeterías destacadas y obtener más detalles sobre métodos de tostado │
│ y perfiles sensoriales específicos. │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Researcher agent:
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 4.0s
>_ Output
			
╭───────────────────────────────────────── 🤖 Researcher Agent response ──────────────────────────────────────────╮
│ │
│ 🔧 Tool Call: tavily_search │
│ Args: { │
│ "query": ""Toma Café" Madrid café especialidad tostado origen granos métodos preparación" │
│ } │
│ ID: toolu_01CnmUWvcWSV8QAvXC8jEnLw │
│ │
│ 🔧 Tool Call: tavily_search │
│ Args: { │
│ "query": "opiniones expertos mejores cafeterías Madrid 2024 2025 calidad baristas perfil sensorial" │
│ } │
│ ID: toolu_01Ec2DAoUYTbkYsLEtdy3US6 │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮
│ Last message contains tool calls. Continuing to tool execution... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭─────────────────────────────────────── 🔧 Researcher Agent - Tool calls ────────────────────────────────────────╮
│ Tool Call Name: Tool Calls │
│ ID: tool_call_id │
│ │
│ Tool Call 1: │
│ Name: tavily_search │
│ ID: toolu_01CnmUWvcWSV8QAvXC8jEnLw │
│ Type: tool_call │
│ Args: │
│ { │
│ "query": ""Toma Café" Madrid café especialidad tostado origen granos métodos preparación" │
│ } │
│ │
│ Tool Call 2: │
│ Name: tavily_search │
│ ID: toolu_01Ec2DAoUYTbkYsLEtdy3US6 │
│ Type: tool_call │
│ Args: │
│ { │
│ "query": "opiniones expertos mejores cafeterías Madrid 2024 2025 calidad baristas perfil sensorial" │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 29.5s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Search results: │
│ │
│ │
│ │
│ --- SOURCE 1: Archivo de la etiqueta: Cafés de orígen --- │
│ URL: https://educafes.com/tag/cafes-de-origen/ │
│ │
│ SUMMARY: │
│ &lt;summary&gt; │
│ La página web Educafes ofrece una amplia gama de contenidos especializados sobre café, destacando temas como la │
│ combinación innovadora de café y queso, la capacitación en manejo y tostión de cafés de origen, y la │
│ importancia del análisis sensorial para valorar la calidad y el sabor del café. Se resalta el emprendimiento │
│ familiar Constantine Café, que promueve la cultura cafetera sostenible en Colombia. Además, se profundiza en la │
│ creciente popularidad del cold brew, su proceso de producción, beneficios, métodos de preparación y su impacto │
│ en el mercado, especialmente entre los millennials. También se aborda la frescura del café como factor clave │
│ para la calidad, explicando los procesos químicos que afectan el sabor y cómo conservarlo. Finalmente, se │
│ menciona a Varietale como un referente en calidad y servicio en Bogotá. │
│ &lt;/summary&gt; │
│ │
...
│ caficultores, reduciendo intermediarios y pagando un precio justo. Hola Coffee ofrece cafés especiales de 80 │
│ puntos en cata, estacionales y de pequeños productores que reciben un precio cuatro veces superior al │
│ comercial. Cafés Tornasol ofrece catas de café bajo reserva para profundizar en el mundo del café de │
│ especialidad sin salir de Madrid. Naji Speciality Coffee destaca por su Naji Pistachio Latte, elaborado con una │
│ mezcla secreta y artesanal de pistacho. │
│ &lt;/key_excerpts&gt; │
│ │
│ -------------------------------------------------------------------------------- │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 11.7s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Search results: │
│ │
│ │
│ │
│ --- SOURCE 1: TOP 10 BEST Cafeteria in Madrid, Spain - Updated 2025 - Yelp --- │
│ URL: https://www.yelp.com/search?find_desc=Cafeteria&amp;find_loc=Madrid │
│ │
│ SUMMARY: │
│ 1. Hanso Café. 4.4 (141 reviews). 0.4 mi. Calle del Pez, 20, 28004 Madrid Spain. 911 375 429 · 2. Lolina │
│ Vintage Café. 4.0 (68 reviews). 0.3 mi. Calle del │
│ │
│ -------------------------------------------------------------------------------- │
│ │
│ │
│ --- SOURCE 2: Las 10 mejores cafeterías de autor en Madrid (actualizado 2025) --- │
│ URL: https://cafecomprar.es/las-10-mejores-cafeterias-de-autor-en-madrid-actualizado-2025-cafecomprar/ │
│ │
│ SUMMARY: │
│ &lt;summary&gt; │
...
│ --- SOURCE 3: Las mejores cafeterías de especialidad de España en 2025 --- │
│ URL: https://www.gastronomistas.com/las-mejores-cafeterias-de-especialidad-de-espana-en-2025/ │
│ │
│ SUMMARY: │
│ Madrid, con 20 locales, y Barcelona, con 17, lideran el listado de mejores cafeterías de especialidad BEST │
│ COFFEE SHOPS de España 2025. │
│ │
│ -------------------------------------------------------------------------------- │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Researcher agent:
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 12.2s
>_ Output
			
╭───────────────────────────────────────── 🤖 Researcher Agent response ──────────────────────────────────────────╮
│ │
│ 🔧 Tool Call: think_tool │
│ Args: { │
│ "reflection": " **Nueva información relevante obtenida:** 1. Toma Café aparece mencionada entre las │
│ mejores cafeterías de autor en Madrid, reconocida por calidad y métodos artesanales 2. Confirmación sobre │
│ HanSo Café con 4.4 estrellas en Yelp (141 reseñas), ubicación en Calle del Pez 20 3. Hola Coffee: ofrece cafés │
│ de 80+ puntos en cata, trabaja con pequeños productores pagando 4 veces el precio comercial 4. Cafés Tornasol │
│ mencionado por ofrecer catas bajo reserva 5. Naji Specialty Coffee destacado por su Naji Pistachio Latte │
│ artesanal 6. Madrid lidera con 20 cafeterías en el ranking Best Coffee Shops España 2025 **Información │
│ sobre calidad del café obtenida:** - Escala SCA: 80-84.99 (muy bueno), 85-89.99 (excelente), 90-100 │
│ (excepcional) - Métodos de preparación mencionados: pour over, espresso, V60, Aeropress, cold brew - Factores │
│ de calidad: compra directa a productores, trazabilidad, tostado artesanal, frescura - Importancia del perfil │
│ sensorial consistente **Evaluación de suficiencia:** Tengo información sólida sobre: - Las principales │
│ cafeterías (Hola Coffee, Syra Coffee, Boconó, Toma Café, HanSo, Naji, Cafés Tornasol) - Factores de calidad │
│ del café (origen, tostado, preparación, puntuación SCA) - Reconocimientos y premios - Métodos de │
│ preparación - Reseñas y opiniones Creo que tengo suficiente información para proporcionar una respuesta │
│ completa. Podría hacer una búsqueda final sobre alguna cafetería específica adicional mencionada (como Bianchi, │
│ Obrar, Café Angélica) pero ya tengo un panorama bastante completo con múltiples opciones bien │
│ fundamentadas. **Decisión:** Realizar una última búsqueda enfocada en detalles específicos sobre perfiles │
│ sensoriales y métodos de tostado de algunas cafeterías top para enriquecer la comparación. " │
│ } │
│ ID: toolu_015YRizf4hcP52akBFMoPk9x │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮
│ Last message contains tool calls. Continuing to tool execution... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭─────────────────────────────────────── 🔧 Researcher Agent - Tool calls ────────────────────────────────────────╮
│ Tool Call Name: Tool Calls │
│ ID: tool_call_id │
│ │
│ Tool Call 1: │
│ Name: think_tool │
│ ID: toolu_015YRizf4hcP52akBFMoPk9x │
│ Type: tool_call │
│ Args: │
│ { │
│ "reflection": " **Nueva información relevante obtenida:** 1. Toma Café aparece mencionada entre las │
│ mejores cafeterías de autor en Madrid, reconocida por calidad y métodos artesanales 2. Confirmación sobre │
│ HanSo Café con 4.4 estrellas en Yelp (141 reseñas), ubicación en Calle del Pez 20 3. Hola Coffee: ofrece cafés │
│ de 80+ puntos en cata, trabaja con pequeños productores pagando 4 veces el precio comercial 4. Cafés Tornasol │
│ mencionado por ofrecer catas bajo reserva 5. Naji Specialty Coffee destacado por su Naji Pistachio Latte │
│ artesanal 6. Madrid lidera con 20 cafeterías en el ranking Best Coffee Shops España 2025 **Información │
│ sobre calidad del café obtenida:** - Escala SCA: 80-84.99 (muy bueno), 85-89.99 (excelente), 90-100 │
│ (excepcional) - Métodos de preparación mencionados: pour over, espresso, V60, Aeropress, cold brew - Factores │
│ de calidad: compra directa a productores, trazabilidad, tostado artesanal, frescura - Importancia del perfil │
│ sensorial consistente **Evaluación de suficiencia:** Tengo información sólida sobre: - Las principales │
│ cafeterías (Hola Coffee, Syra Coffee, Boconó, Toma Café, HanSo, Naji, Cafés Tornasol) - Factores de calidad │
│ del café (origen, tostado, preparación, puntuación SCA) - Reconocimientos y premios - Métodos de │
│ preparación - Reseñas y opiniones Creo que tengo suficiente información para proporcionar una respuesta │
│ completa. Podría hacer una búsqueda final sobre alguna cafetería específica adicional mencionada (como Bianchi, │
│ Obrar, Café Angélica) pero ya tengo un panorama bastante completo con múltiples opciones bien │
│ fundamentadas. **Decisión:** Realizar una última búsqueda enfocada en detalles específicos sobre perfiles │
│ sensoriales y métodos de tostado de algunas cafeterías top para enriquecer la comparación. " │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 0.0s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Reflection recorded: │
│ **Nueva información relevante obtenida:** │
│ 1. Toma Café aparece mencionada entre las mejores cafeterías de autor en Madrid, reconocida por calidad y │
│ métodos artesanales │
│ 2. Confirmación sobre HanSo Café con 4.4 estrellas en Yelp (141 reseñas), ubicación en Calle del Pez 20 │
│ 3. Hola Coffee: ofrece cafés de 80+ puntos en cata, trabaja con pequeños productores pagando 4 veces el precio │
│ comercial │
│ 4. Cafés Tornasol mencionado por ofrecer catas bajo reserva │
│ 5. Naji Specialty Coffee destacado por su Naji Pistachio Latte artesanal │
│ 6. Madrid lidera con 20 cafeterías en el ranking Best Coffee Shops España 2025 │
│ │
│ **Información sobre calidad del café obtenida:** │
│ - Escala SCA: 80-84.99 (muy bueno), 85-89.99 (excelente), 90-100 (excepcional) │
│ - Métodos de preparación mencionados: pour over, espresso, V60, Aeropress, cold brew │
│ - Factores de calidad: compra directa a productores, trazabilidad, tostado artesanal, frescura │
│ - Importancia del perfil sensorial consistente │
│ │
│ **Evaluación de suficiencia:** │
│ Tengo información sólida sobre: │
│ - Las principales cafeterías (Hola Coffee, Syra Coffee, Boconó, Toma Café, HanSo, Naji, Cafés Tornasol) │
│ - Factores de calidad del café (origen, tostado, preparación, puntuación SCA) │
│ - Reconocimientos y premios │
│ - Métodos de preparación │
│ - Reseñas y opiniones │
│ │
│ Creo que tengo suficiente información para proporcionar una respuesta completa. Podría hacer una búsqueda final │
│ sobre alguna cafetería específica adicional mencionada (como Bianchi, Obrar, Café Angélica) pero ya tengo un │
│ panorama bastante completo con múltiples opciones bien fundamentadas. │
│ │
│ **Decisión:** Realizar una última búsqueda enfocada en detalles específicos sobre perfiles sensoriales y │
│ métodos de tostado de algunas cafeterías top para enriquecer la comparación. │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Researcher agent:
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 3.2s
>_ Output
			
╭───────────────────────────────────────── 🤖 Researcher Agent response ──────────────────────────────────────────╮
│ │
│ 🔧 Tool Call: tavily_search │
│ Args: { │
│ "query": ""Café Angélica" "Obrar" "Bianchi" Madrid café especialidad tostado perfil sensorial │
│ Colombia Etiopía" │
│ } │
│ ID: toolu_017pXguKFjDdb3cWhbwQ7ugp │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮
│ Last message contains tool calls. Continuing to tool execution... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭─────────────────────────────────────── 🔧 Researcher Agent - Tool calls ────────────────────────────────────────╮
│ Tool Call Name: Tool Calls │
│ ID: tool_call_id │
│ │
│ Tool Call 1: │
│ Name: tavily_search │
│ ID: toolu_017pXguKFjDdb3cWhbwQ7ugp │
│ Type: tool_call │
│ Args: │
│ { │
│ "query": ""Café Angélica" "Obrar" "Bianchi" Madrid café especialidad tostado perfil sensorial │
│ Colombia Etiopía" │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 31.9s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Search results: │
│ │
│ │
│ │
│ --- SOURCE 1: Vogue Enhorabuena cafeteros, tenéis un nuevo templo en Madrid: Café Angélica --- │
│ URL: https://www.vogue.es/living/articulos/cafe-angelica-madrid-hermanos-zamora-san-bernardo/29259 │
│ │
│ SUMMARY: │
│ &lt;summary&gt; │
│ Café Angélica es un nuevo café en Madrid ubicado en la calle San Bernardo, 24, que ha recuperado un local │
│ histórico que fue la primera tienda de hierbas aromáticas de la ciudad, fundada en 1948 por Francisco. Carlos y │
│ Lucía Zamora, responsables del grupo de restauración Deluz, adquirieron el espacio tras el cierre por aumento │
│ de alquiler y lo transformaron en un café para amantes del café de especialidad, que ellos mismos tuestan con │
│ una tostadora Probat alemana y sirven con una máquina La Marzocco. Ofrecen desayunos con productos ecológicos │
│ como queso fresco pasiego, bocadillos con roast beef de Cantabria, ensaladas con pollo ecológico de Segovia y │
│ vinos seleccionados. Además, venden hierbas naturales, especias, tés especiales, frutos secos y cafés de │
│ pequeños agricultores ecológicos de Colombia, México, Etiopía y Kenia. La iniciativa nació tras ver el │
│ documental "Black Gold" y busca apoyar a pequeños productores y un proyecto de cooperación en Costa de Marfil │
│ liderado por Philo, colaboradora cercana de los Zamora. Café Angélica combina tradición, sostenibilidad y │
...
│ sostenible en el que cuidan a todas las personas implicadas en el desarrollo, desde su recolección grano a │
│ grano, hasta la fase de tostado., Estos cafés proceden de pequeñas cooperativas de Guatemala, Etiopía, Ruanda o │
│ Colombia, entre otros. Todos, sin excepción, son cafés con una historia social detrás y eso, además de sus nota │
│ de cata, los hace si cabe todavía más exquisitos., Organizan cursos y catas todos los jueves a partir de las │
│ 20pm, allí mismo, en Café Angélica. │
│ &lt;/key_excerpts&gt; │
│ │
│ -------------------------------------------------------------------------------- │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Researcher agent:
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 16.6s
>_ Output
			
╭───────────────────────────────────────── 🤖 Researcher Agent response ──────────────────────────────────────────╮
│ │
│ 🔧 Tool Call: think_tool │
│ Args: { │
│ "reflection": " **Excelente información adicional obtenida sobre Café Angélica:** - Ubicación: C/ San │
│ Bernardo 24, local histórico desde 1948 - Tuestan su propio café con tostadora Probat alemana - Máquina La │
│ Marzocco para preparación - Cafés de comercio justo de Colombia, México, Etiopía, Kenia, Guatemala, Ruanda - │
│ Organizan catas todos los jueves a las 20:00h - Proyecto sostenible y con enfoque social (apoyo a pequeños │
│ productores) - Recomendado por Vogue y otras publicaciones especializadas - Grupo Deluz (Carlos y Lucía │
│ Zamora) **Resumen completo de información recopilada:** He conseguido información detallada sobre │
│ múltiples cafeterías de especialidad en Madrid con enfoque en calidad del café: 1. **Hola Coffee** - Puesto │
│ 12 mundial, tostadora propia, academia SCA, Spanish Barista Champion 2016 2. **Boconó Specialty Coffee** - │
│ Mejor tostador Madrid 2023, tuesta en local, Lavapiés 3. **Syra Coffee** - Métodos V60 y Aeropress, +4.5 │
│ estrellas Google Maps (3000+ reseñas), cafés de Etiopía/Colombia/Brasil 4. **Café Angélica** - Tostadora │
│ Probat, máquina La Marzocco, cafés de comercio justo, catas semanales 5. **Toma Café** - Reconocida entre │
│ cafeterías de autor, métodos artesanales 6. **HanSo Café** - 4.4 estrellas Yelp, 141 reseñas 7. **Naji │
│ Specialty Coffee** - Especialidad en Pistachio Latte artesanal 8. **Cafés Tornasol** - Catas bajo │
│ reserva También tengo información sobre: - Factores de calidad: escala SCA (80+ puntos), origen de granos, │
│ tostado artesanal, métodos de preparación - Métodos: pour over, V60, Aeropress, espresso, cold brew - │
│ Orígenes principales: Colombia, Etiopía, Guatemala, Brasil, Kenia, Ruanda, México **Evaluación │
│ final:** Tengo información muy completa y de fuentes confiables (Vogue, rankings internacionales, sitios │
│ especializados, sitios oficiales, reseñas de usuarios). Puedo proporcionar una respuesta exhaustiva comparando │
│ las mejores cafeterías según calidad del café. He realizado 5 búsquedas que es el límite para consultas │
│ complejas. **Decisión:** Proceder a elaborar la respuesta final con toda la información recopilada. " │
│ } │
│ ID: toolu_01W3EfSjdzUbPcy5TrCGSVNX │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮
│ Last message contains tool calls. Continuing to tool execution... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭─────────────────────────────────────── 🔧 Researcher Agent - Tool calls ────────────────────────────────────────╮
│ Tool Call Name: Tool Calls │
│ ID: tool_call_id │
│ │
│ Tool Call 1: │
│ Name: think_tool │
│ ID: toolu_01W3EfSjdzUbPcy5TrCGSVNX │
│ Type: tool_call │
│ Args: │
│ { │
│ "reflection": " **Excelente información adicional obtenida sobre Café Angélica:** - Ubicación: C/ San │
│ Bernardo 24, local histórico desde 1948 - Tuestan su propio café con tostadora Probat alemana - Máquina La │
│ Marzocco para preparación - Cafés de comercio justo de Colombia, México, Etiopía, Kenia, Guatemala, Ruanda - │
│ Organizan catas todos los jueves a las 20:00h - Proyecto sostenible y con enfoque social (apoyo a pequeños │
│ productores) - Recomendado por Vogue y otras publicaciones especializadas - Grupo Deluz (Carlos y Lucía │
│ Zamora) **Resumen completo de información recopilada:** He conseguido información detallada sobre │
│ múltiples cafeterías de especialidad en Madrid con enfoque en calidad del café: 1. **Hola Coffee** - Puesto │
│ 12 mundial, tostadora propia, academia SCA, Spanish Barista Champion 2016 2. **Boconó Specialty Coffee** - │
│ Mejor tostador Madrid 2023, tuesta en local, Lavapiés 3. **Syra Coffee** - Métodos V60 y Aeropress, +4.5 │
│ estrellas Google Maps (3000+ reseñas), cafés de Etiopía/Colombia/Brasil 4. **Café Angélica** - Tostadora │
│ Probat, máquina La Marzocco, cafés de comercio justo, catas semanales 5. **Toma Café** - Reconocida entre │
│ cafeterías de autor, métodos artesanales 6. **HanSo Café** - 4.4 estrellas Yelp, 141 reseñas 7. **Naji │
│ Specialty Coffee** - Especialidad en Pistachio Latte artesanal 8. **Cafés Tornasol** - Catas bajo │
│ reserva También tengo información sobre: - Factores de calidad: escala SCA (80+ puntos), origen de granos, │
│ tostado artesanal, métodos de preparación - Métodos: pour over, V60, Aeropress, espresso, cold brew - │
│ Orígenes principales: Colombia, Etiopía, Guatemala, Brasil, Kenia, Ruanda, México **Evaluación │
│ final:** Tengo información muy completa y de fuentes confiables (Vogue, rankings internacionales, sitios │
│ especializados, sitios oficiales, reseñas de usuarios). Puedo proporcionar una respuesta exhaustiva comparando │
│ las mejores cafeterías según calidad del café. He realizado 5 búsquedas que es el límite para consultas │
│ complejas. **Decisión:** Proceder a elaborar la respuesta final con toda la información recopilada. " │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 0.0s
>_ Output
			
╭──────────────────────────────────── 🔧 Researcher Agent - Tool calls result ────────────────────────────────────╮
│ Reflection recorded: │
│ **Excelente información adicional obtenida sobre Café Angélica:** │
│ - Ubicación: C/ San Bernardo 24, local histórico desde 1948 │
│ - Tuestan su propio café con tostadora Probat alemana │
│ - Máquina La Marzocco para preparación │
│ - Cafés de comercio justo de Colombia, México, Etiopía, Kenia, Guatemala, Ruanda │
│ - Organizan catas todos los jueves a las 20:00h │
│ - Proyecto sostenible y con enfoque social (apoyo a pequeños productores) │
│ - Recomendado por Vogue y otras publicaciones especializadas │
│ - Grupo Deluz (Carlos y Lucía Zamora) │
│ │
│ **Resumen completo de información recopilada:** │
│ │
│ He conseguido información detallada sobre múltiples cafeterías de especialidad en Madrid con enfoque en calidad │
│ del café: │
│ │
│ 1. **Hola Coffee** - Puesto 12 mundial, tostadora propia, academia SCA, Spanish Barista Champion 2016 │
│ 2. **Boconó Specialty Coffee** - Mejor tostador Madrid 2023, tuesta en local, Lavapiés │
│ 3. **Syra Coffee** - Métodos V60 y Aeropress, +4.5 estrellas Google Maps (3000+ reseñas), cafés de │
│ Etiopía/Colombia/Brasil │
│ 4. **Café Angélica** - Tostadora Probat, máquina La Marzocco, cafés de comercio justo, catas semanales │
│ 5. **Toma Café** - Reconocida entre cafeterías de autor, métodos artesanales │
│ 6. **HanSo Café** - 4.4 estrellas Yelp, 141 reseñas │
│ 7. **Naji Specialty Coffee** - Especialidad en Pistachio Latte artesanal │
│ 8. **Cafés Tornasol** - Catas bajo reserva │
│ │
│ También tengo información sobre: │
│ - Factores de calidad: escala SCA (80+ puntos), origen de granos, tostado artesanal, métodos de preparación │
│ - Métodos: pour over, V60, Aeropress, espresso, cold brew │
│ - Orígenes principales: Colombia, Etiopía, Guatemala, Brasil, Kenia, Ruanda, México │
│ │
│ **Evaluación final:** │
│ Tengo información muy completa y de fuentes confiables (Vogue, rankings internacionales, sitios especializados, │
│ sitios oficiales, reseñas de usuarios). Puedo proporcionar una respuesta exhaustiva comparando las mejores │
│ cafeterías según calidad del café. He realizado 5 búsquedas que es el límite para consultas complejas. │
│ │
│ **Decisión:** Proceder a elaborar la respuesta final con toda la información recopilada. │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Researcher agent:
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 53.0s
>_ Output
			
╭───────────────────────────────────────── 🤖 Researcher Agent response ──────────────────────────────────────────╮
│ Basándome en mi investigación exhaustiva, te presento un análisis detallado de las **mejores cafeterías de │
│ Madrid evaluadas según la calidad del café**, priorizando los factores clave que mencionaste: │
│ │
│ --- │
│ │
│ ## 🏆 **LAS MEJORES CAFETERÍAS DE ESPECIALIDAD EN MADRID** │
│ │
│ ### **1. HOLA COFFEE** ⭐ Top 1 en Madrid │
│ **Reconocimientos:** │
│ - **Puesto 12 mundial** en el ranking "The World's 100 Best Coffee Shops" 2025 │
│ - Fundador nombrado **Spanish Barista Champion 2016** │
│ - Madrid lidera en España con 20 cafeterías en el ranking nacional │
│ │
│ **Calidad del Café:** │
│ - **Origen y frescura:** Cafés de pequeños productores con puntuación **superior a 80 puntos** en la escala SCA │
│ (Specialty Coffee Association) │
│ - **Tostado:** Tostadora propia ubicada en Calle Sepúlveda 109, Madrid │
│ - **Métodos de preparación:** Cafés filtrados, espresso y bebidas de autor │
│ - **Trazabilidad:** Pagan hasta **4 veces el precio comercial** a productores para garantizar calidad ética │
...
│ - **Plataformas de reseñas:** Google Maps (3,000+ opiniones Syra Coffee), Yelp (141 reseñas HanSo) │
│ - **Organizaciones certificadoras:** Specialty Coffee Association (SCA), Naked Festival │
│ │
│ --- │
│ │
│ Todas estas cafeterías cumplen con los más altos estándares de calidad en café de especialidad, con │
│ puntuaciones superiores a 80 en la escala SCA, trazabilidad completa, métodos de preparación profesionales y un │
│ compromiso genuino con la excelencia sensorial. La elección entre ellas dependerá de tu ubicación preferida en │
│ Madrid y si valoras más el reconocimiento internacional, el proyecto sostenible o la experiencia educativa. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
╭──────────────────────────────── 🔧 Researcher Agent - Should compress research ─────────────────────────────────╮
│ No tool calls found. Stopping research and compressing findings... │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
>_ Output
			
⏳ Compressing Research:
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 54.6s
>_ Output
			
╭─────────────────────────────────── 🤖 Researcher Agent - Compressed Research ───────────────────────────────────╮
│ **Lista de consultas y llamadas a herramientas realizadas** │
│ │
│ 1. tavily_search: "mejores cafeterías Madrid calidad café especialidad tostado granos" │
│ 2. tavily_search: "cafés de especialidad Madrid expertos baristas origen granos" │
│ 3. tavily_search: "Hola Coffee Madrid sitio oficial métodos preparación barista champion tostadero" │
│ 4. tavily_search: "Toma Café HanSo Syra Coffee Madrid reseñas expertos calidad café métodos extracción" │
│ 5. tavily_search: "Toma Café Madrid café especialidad tostado origen granos métodos preparación" │
│ 6. tavily_search: "opiniones expertos mejores cafeterías Madrid 2024 2025 calidad baristas perfil sensorial" │
│ 7. tavily_search: ""Café Angélica" "Obrar" "Bianchi" Madrid café especialidad tostado perfil sensorial │
│ Colombia Etiopía" │
│ │
│ --- │
│ │
│ **Resultados completos** │
│ │
│ --- SOURCE 1: Las 25 mejores cafeterías de especialidad de Madrid --- │
│ URL: https://madridsecreto.co/mejor-cafe-de-madrid/ │
│ │
│ SUMMARY: │
...
│ [9] TOP 10 BEST Cafeteria in Madrid, Spain - Updated 2025 - Yelp: │
│ https://www.yelp.com/search?find_desc=Cafeteria&amp;find_loc=Madrid │
│ [10] Las 10 mejores cafeterías de autor en Madrid (actualizado 2025): │
│ https://cafecomprar.es/las-10-mejores-cafeterias-de-autor-en-madrid-actualizado-2025-cafecomprar/ │
│ [11] Vogue Enhorabuena cafeteros, tenéis un nuevo templo en Madrid: Café Angélica: │
│ https://www.vogue.es/living/articulos/cafe-angelica-madrid-hermanos-zamora-san-bernardo/29259 │
│ [12] Cafeangelica Café Angélica – Tienda de Café, tes y especias en Madrid: https://cafeangelica.es/ │
│ [13] Con El Morro Fino Café Angélica, templo del café, té y especias | Con El Morro Fino: │
│ https://conelmorrofino.com/cafe-angelica-madrid/ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Como podemos ver, o agente Researcher usou várias vezes a ferramenta tavily_search para obter informações da internet e a ferramenta think_tool para refletir sobre as informações que possui. Por fim, ele compactou a informação

---

➡️ **Continua na Parte 2: Research Supervisor multiagente**, onde coordenaremos vários agentes Researcher em paralelo.

Continuar lendo

Últimos posts -->

Você viu esses projetos?

Gymnasia

Gymnasia Gymnasia
React Native
Expo
TypeScript
FastAPI
Next.js
OpenAI
Anthropic

Aplicativo móvel de treino pessoal com assistente de IA, biblioteca de exercícios, acompanhamento de rotinas, dieta e medidas corporais

Horeca chatbot

Horeca chatbot Horeca chatbot
Python
LangChain
PostgreSQL
PGVector
React
Kubernetes
Docker
GitHub Actions

Chatbot conversacional para cozinheiros de hotéis e restaurantes. Um cozinheiro, gerente de cozinha ou serviço de quarto de um hotel ou restaurante pode falar com o chatbot para obter informações sobre receitas e menus. Mas também implementa agentes, com os quais pode editar ou criar novas receitas ou menus

Naviground

Naviground Naviground
Ver todos os projetos -->
>_ Disponível para projetos

Tem um projeto com IA?

Vamos conversar.

maximofn@gmail.com

Especialista em Machine Learning e Inteligência Artificial. Desenvolvo soluções com IA generativa, agentes inteligentes e modelos personalizados.

Quer assistir alguma palestra?

Últimas palestras -->

Quer melhorar com essas dicas?

Últimos tips -->

Use isso localmente

Os espaços do Hugging Face nos permitem executar modelos com demos muito simples, mas e se a demo quebrar? Ou se o usuário a deletar? Por isso, criei contêineres docker com alguns espaços interessantes, para poder usá-los localmente, aconteça o que acontecer. Na verdade, se você clicar em qualquer botão de visualização de projeto, ele pode levá-lo a um espaço que não funciona.

Flow edit

Flow edit Flow edit

Edite imagens com este modelo de Flow. Baseado em SD3 ou FLUX, você pode editar qualquer imagem e gerar novas

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
Ver todos os contêineres -->
>_ Disponível para projetos

Tem um projeto com IA?

Vamos conversar.

maximofn@gmail.com

Especialista em Machine Learning e Inteligência Artificial. Desenvolvo soluções com IA generativa, agentes inteligentes e modelos personalizados.

Você quer treinar seu modelo com esses datasets?

short-jokes-dataset

HuggingFace

Dataset com piadas em inglês

Uso: Fine-tuning de modelos de geração de texto humorístico

231K linhas 2 colunas 45 MB
Ver no HuggingFace →

opus100

HuggingFace

Dataset com traduções de inglês para espanhol

Uso: Treinamento de modelos de tradução inglês-espanhol

1M linhas 2 colunas 210 MB
Ver no HuggingFace →

netflix_titles

HuggingFace

Dataset com filmes e séries da Netflix

Uso: Análise de catálogo Netflix e sistemas de recomendação

8.8K linhas 12 colunas 3.5 MB
Ver no HuggingFace →
Ver mais datasets -->