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?
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 assistente
Para realizar o assistente vamos usar a seguinte arquitetura:
Escopo
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 pesquisador
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.
Escritor
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 bibliotecas
Vamos instalar todas as bibliotecas que vamos precisar para o desenvolvimento do assistente.
Primeiro, criamos um ambiente uv
InputPython!uv init deepresearcherCopied
error: Project is already initialized in `/Users/macm1/Documents/web/portafolio/posts/deepresearcher` (`pyproject.toml` file exists)
Nós sincronizamos isso
InputPython!cd deepresearcher && uv syncCopied
Resolved 88 packages in 9msAudited 84 packages in 0.38ms
Nós o ativamos
InputPython!cd deepresearcher && source .venv/bin/activateCopied
Instalamos as bibliotecas necessárias
InputPython!cd deepresearcher && source .venv/bin/activate && uv add "alive-progress>=3.3.0" "dotenv>=0.9.9" "grandalf>=0.8" "ipykernel>=6.30.1" "langchain>=0.3.27" "langchain-anthropic>=0.3.21" "langchain-community>=0.3.29" "langchain-mcp-adapters>=0.1.11" "langchain-openai>=0.3.33" "langgraph>=0.6.7" "rich>=14.1.0" "tavily-python>=0.7.12"Copied
Resolved 88 packages in 0.59msAudited 84 packages in 0.01ms
Vamos analisar o que instalamos
- alive-progress - Para mostrar uma barra de progresso.
- dotenv - Para carregar as variáveis de ambiente.
- grandalf - Para mostrar a arquitetura do assistente.
- langchain - Para a cadeia de pensamento.* langchain-anthropic - Para o modelo de linguagem Anthropic.
- langchain-community - Para a comunidade do LangChain.
- langchain-mcp-adapters - Para os adaptadores de MCP.
- langchain-openai - Para o modelo de linguagem OpenAI.
Utilitários
Antes de entrarmos no código, vamos criar algumas funções que nos serão úteis para o desenvolvimento do assistente.
Diretório
Em primeiro lugar, criamos uma função que nos permite obter o diretório atual.
InputPythonfrom pathlib import Pathdef get_current_dir() -> 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().parentexcept NameError: # __file__ is not definedreturn Path.cwd()Copied
Data
Agora criamos uma função que nos permite obter a data atual.
InputPythonfrom datetime import datetimedef get_today_str() -> str:"""Get current date in a human-readable format."""return datetime.now().strftime("%a %b %-d, %Y")Copied
Mensagens
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
InputPythonfrom rich.console import Consolefrom rich.panel import Panelfrom rich.text import Textimport jsonimport astconsole = Console()def format_tool_outputs(tool_outputs_list: list) -> 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 stringsif 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 outputsif len(content) > 500:content_preview = content[:500] + f"... ({len(content)} chars total)"else:content_preview = contentformatted_parts.append(f" Content:")# Indent each line of contentfor 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) -> 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 listif 'Tool outputs:' in content:try:# Find the position where the tool outputs list startstool_outputs_index = content.index('Tool outputs:')prefix = content[:tool_outputs_index + len('Tool outputs:')]# Extract the part that should be a listpotential_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 nicelyif 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 checkpass# Check if the string contains 'Tool calls:' followed by a listif 'Tool calls:' in content:try:# Find the position where the tool calls list startstool_calls_index = content.index('Tool calls:')prefix = content[:tool_calls_index + len('Tool calls:')]# Extract the part that should be a listpotential_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 nicelyif 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 contentpass# Try to detect if the entire content is a JSON/dict/listtry:# Try JSON firstparsed = 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 contentreturn contentdef 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 dictionaryparts.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' attributeif not hasattr(message, 'content'):# If it's a dict-like object, try to access it as a dictif 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 stringformatted_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 = Trueelse:parts.append(str(message.content))# Handle tool calls attached to the message (OpenAI format) - only if not already processedif 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 formattingArgs:messages: List of messages to formattitle: Title of the panelborder_style: Border style of the panelmsg_subtype: Subtype of the message"""# Check if messages is a listif isinstance(messages, list):for m in messages:# Handle different message typesif isinstance(m, dict):# Check if it's a ToolCall dictif '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")) # Blueelse:if title == "":title = "🧑 Simulated Human Message"else:title = f"🧑 {title}"console.print(Panel(content, title=title, border_style="#1A64DB")) # Blueelif msg_type == 'AI':if title == "":title = "🤖 Assistant"else:title = f"🤖 {title}"console.print(Panel(content, title=title, border_style="#24FA00")) # Greenelif msg_type == 'ClarifyWithUser':if title == "":title = "🤖 Clarify With User"else:title = f"🤖 {title}"console.print(Panel(content, title=title, border_style="#37DB1A")) # Greenelif msg_type == 'ResearchQuestion':if title == "":title = "🤖 Research brief generated"else:title = f"🤖 {title}"console.print(Panel(content, title=title, border_style="#37DB1A")) # Greenelif 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
Escopo
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 escopo
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
InputPythonclarify_with_user_instructions="""Estos son los mensajes que se han intercambiado hasta ahora con el usuario que solicita el informe:<Messages>{messages}</Messages>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": "<pregunta para pedir al usuario que aclare el alcance del informe>","verification": "<mensaje de verificación de que comenzaremos la investigación>"Si necesitas hacer una pregunta aclaratoria, devuelve:"need_clarification": true,"question": "<tu pregunta aclaratoria>","verification": ""Si no necesitas hacer una pregunta aclaratoria, devuelve:"need_clarification": false,"question": "","verification": "<Mensaje de confirmación de que ahora comenzará la investigación basándose en la información proporcionada.>"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
InputPythontransform_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:<Messages>{messages}</Messages>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 escopo
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
InputPythonimport operatorfrom typing_extensions import Optional, Annotated, Sequencefrom langchain_core.messages import BaseMessagefrom langgraph.graph import MessagesStatefrom langgraph.graph.message import add_messagesclass AgentInputState(MessagesState):"""Input state for the full agent - only contains messages from user input."""passclass 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 properstate management between subgraphs and the main workflow."""# Research brief generated from user conversation historyresearch_brief: Optional[str]# Messages exchanged with the supervisor agent for coordinationsupervisor_messages: Annotated[Sequence[BaseMessage], add_messages]# Raw unprocessed research notes collected during the research phaseraw_notes: Annotated[list[str], operator.add] = []# Processed and structured notes ready for report generationnotes: Annotated[list[str], operator.add] = []# Final formatted research reportfinal_report: strCopied
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 Scope
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).
InputPythonfrom pydantic import BaseModel, Fieldclass 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 Scope
Agora que temos os prompts, definimos o modelo de linguagem que o agente vai usar.
InputPythonfrom langchain.chat_models import init_chat_modelimport osimport dotenvdotenv.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.0SCOPE_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 Scope
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_briefseja executada para gerar oresearch_brief
InputPythonfrom typing_extensions import Literalfrom alive_progress import alive_barimport tracebackimport sysfrom langchain_core.messages import HumanMessage, AIMessage, SystemMessage, get_buffer_stringfrom langgraph.graph import START, ENDfrom langgraph.types import Commanddef clarify_with_user(state: AgentState) -> 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 modelstructured_output_model = scope_model.with_structured_output(ClarifyWithUser)# Invoke the model with clarification instructionswith 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 messagesformat_messages([response], title="Scope Assistant - need clarification?")# Route based on clarification needif response.need_clarification:# Create a System message to show the decisionrouting_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 decisionrouting_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 informationexc_type, exc_obj, exc_tb = sys.exc_info()line_number = exc_tb.tb_lineno# Print detailed error informationprint(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 itraiseCopied
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
InputPythondef write_research_brief(state: AgentState):"""Transform the conversation history into a comprehensive research brief.Uses structured output to ensure the brief follows the required formatand contains all necessary details for effective research."""try:# Set up structured output modelstructured_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 historywith 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 briefformat_messages([response], title="Scope Assistant - Research brief generated")# Update state with generated research brief and pass it to the supervisorreturn {"research_brief": response.research_brief,"supervisor_messages": [HumanMessage(content=f"{response.research_brief}.")]}except Exception as e:# Get the traceback informationexc_type, exc_obj, exc_tb = sys.exc_info()line_number = exc_tb.tb_lineno# Print detailed error informationprint(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 itraiseCopied
Gráfico do agente Scope
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.
InputPythonfrom langgraph.graph import StateGraph# Build the scoping workflowscope_graph_builder = StateGraph(AgentState, input_schema=AgentInputState)Copied
Adicionamos dois nós, cada um com as funções que definimos anteriormente
InputPython# Add workflow nodesscope_graph_builder.add_node("clarify_with_user", clarify_with_user)scope_graph_builder.add_node("write_research_brief", write_research_brief)Copied
<langgraph.graph.state.StateGraph at 0x10a3dc410>
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.
InputPythonfrom langgraph.graph import START, END# Add workflow edgesscope_graph_builder.add_edge(START, "clarify_with_user")scope_graph_builder.add_edge("write_research_brief", END)Copied
<langgraph.graph.state.StateGraph at 0x10a3dc410>
Compilamos o grafo e representamos o diagrama de fluxo
InputPythonfrom langgraph.checkpoint.memory import InMemorySaverfrom IPython.display import Image, displaycheckpointer = InMemorySaver()scope_agent = scope_graph_builder.compile(checkpointer=checkpointer)# Visualize the workflowdisplay(Image(scope_agent.get_graph(xray=True).draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Execução do agente Scope
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
InputPythonthread = {"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
⏳ Scope agent:
╭───────────────────────────────────────────── 🧑 Real Human Message ─────────────────────────────────────────────╮│ Quiero investigar las mejores cafeterías de Madrid.. │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 2.7s
╭─────────────────────────────────── 🤖 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='' │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── 🔧 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
InputPythonuser_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
⏳ Scope agent:
╭───────────────────────────────────────────── 🧑 Real Human Message ─────────────────────────────────────────────╮│ Examina la calidad del café para evaluar las mejores cafeterías de Madrid.. │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 2.3s
╭─────────────────────────────────── 🤖 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.' │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── 🔧 Scope System Message ────────────────────────────────────────────╮│ No necesita aclaración por parte del usuario. Enviando mensaje de verificación... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Scope agent - Write research brief:
╭─────────────────────────────────── 🤖 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. │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 3.9s
╭───────────────────────────────── 🤖 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.
Pesquisador
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
Então, agora vamos definir o Researcher, que será responsável por realizar a pesquisa.
Prompts para pesquisadores
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
InputPythonresearch_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}.<Task>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.</Task><Available Tools>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**.</Available Tools><Instructions>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.</Instructions><Hard Limits>**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</Hard Limits><Show Your Thinking>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?</Show Your Thinking>"""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
InputPythonsummarize_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:<webpage_content>{webpage_content}</webpage_content>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.
InputPythoncompress_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}.<Task>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.</Task><Tool Call Filtering>**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.</Tool Call Filtering><Guidelines>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.</Guidelines><Output Format>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)**</Output Format><Citation Rules>- 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</Citation Rules>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
InputPythoncompress_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 anteriorLos resultados depurados se utilizarán para la elaboración del informe final, por lo que es fundamental que sean exhaustivos."""Copied
Estado del investigador
Agora temos algo semelhante ao estado de Scope, temos dois estados ResearcherState e ResearcherInputState, vamos vê-los e depois explicamos
InputPythonfrom typing_extensions import TypedDict, Annotated, List, Sequenceclass ResearcherState(TypedDict):"""State for the research agent containing message history and research metadata.This state tracks the researcher's conversation, iteration count for limitingtool 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: intresearch_topic: strcompressed_research: strraw_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 compressedresearch findings and all raw notes from the research process."""compressed_research: strraw_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 Researcher
Vamos definir como queremos que seja a saída do Researcher.
InputPythonclass 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 Researcher
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 web
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.
InputPythondef tavily_search_multiple(search_queries: List[str],max_results: int = 3,topic: Literal["general", "news", "finance"] = "general",include_raw_content: bool = True,) -> List[dict]:"""Perform search using Tavily API for multiple queries.Args:search_queries: List of search queries to executemax_results: Maximum number of results per querytopic: Topic filter for search resultsinclude_raw_content: Whether to include raw webpage contentReturns: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_docsCopied
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
InputPythondef deduplicate_search_results(search_results: List[dict]) -> dict:"""Deduplicate search results by URL to avoid processing duplicate content.Args:search_results: List of search result dictionariesReturns: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] = resultreturn unique_resultsCopied
Agora definimos uma função para resumir o conteúdo de uma página web; para isso vamos usar o prompt summarize_webpage_prompt
InputPythondef summarize_webpage_content(webpage_content: str) -> str:"""Summarize webpage content using the configured summarization model.Args:webpage_content: Raw webpage content to summarizeReturns:Formatted summary with key excerpts"""try:# Set up structured output model for summarizationstructured_model = summarization_model.with_structured_output(Summary)# Generate summarysummary = structured_model.invoke([HumanMessage(content=summarize_webpage_prompt.format(webpage_content=webpage_content,date=get_today_str()))])# Format summary with clear structureformatted_summary = (f"<summary> {summary.summary} </summary> "f"<key_excerpts> {summary.key_excerpts} </key_excerpts>")return formatted_summaryexcept Exception as e:print(f"Failed to summarize webpage: {str(e)}")return webpage_content[:1000] + "..." if len(webpage_content) > 1000 else webpage_contentCopied
Com o conteúdo da busca resumido, criamos uma função para limpar os resultados da investigação.
InputPythondef process_search_results(unique_results: dict) -> dict:"""Process search results by summarizing content where available.Args:unique_results: Dictionary of unique search resultsReturns:Dictionary of processed results with summaries"""summarized_results = {}for url, result in unique_results.items():# Use existing content if no raw content for summarizationif not result.get("raw_content"):content = result['content']else:# Summarize raw content for better processingcontent = summarize_webpage_content(result['raw_content'])summarized_results[url] = {'title': result['title'],'content': content}return summarized_resultsCopied
E, por último, criamos uma função para dar um formato específico ao conteúdo da investigação.
InputPythondef format_search_output(summarized_results: dict) -> str:"""Format search results into a well-structured string output.Args:summarized_results: Dictionary of processed search resultsReturns: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_outputCopied
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.
InputPythonfrom 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",) -> str:"""Fetch results from Tavily search API with content summarization.Args:query: A single search query to executemax_results: Maximum number of results to returntopic: Topic to filter results by ('general', 'news', 'finance')"""# Execute search for single querysearch_results = tavily_search_multiple([query], # Convert single query to list for the internal functionmax_results=max_results,topic=topic,include_raw_content=True,)# Deduplicate results by URL to avoid processing duplicate contentunique_results = deduplicate_search_results(search_results)# Process results with summarizationsummarized_results = process_search_results(unique_results)# Format output for consumptionreturn 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ão
Como queremos que o agente reflita após cada busca, damos a ele uma ferramenta para isso
InputPython@tool(parse_docstring=True)def think_tool(reflection: str = "") -> 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 researcher
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 researcher
Primeiro, vamos criar o modelo de linguagem que vamos usar na função summarize_webpage_content que definimos antes
InputPythonfrom langchain.chat_models import init_chat_modelimport osimport dotenvdotenv.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.0SUMMARIZATION_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 pesquisa
Agora vamos definir o modelo de linguagem que ficará encarregado da pesquisa
InputPythonRESEARCH_MODEL_NAME = "anthropic:claude-sonnet-4-5-20250929"ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")RESEARCH_MODEL_MAX_TOKENS = 4096research_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
InputPythonresearcher_tools = [tavily_search, think_tool]model_with_tools = research_model.bind_tools(researcher_tools)Copied
Modelo de linguagem de compressão da informação
Por fim, definimos um modelo de linguagem que ficará encarregado de comprimir a informação da pesquisa
InputPythonCOMPRESS_MODEL_NAME = "gpt-4.1"COMPRESS_MODEL_PROVIDER = "openai"COMPRESS_MODEL_BASE_URL = "https://api.openai.com/v1"COMPRESS_MODEL_TEMPERATURE = 0.0COMPRESS_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 agente
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.
InputPythondef 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 information2. Provide a final answer based on gathered informationReturns updated state with the model's response."""try:# Show progress bar while waiting for LLM responseprint("⏳ 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 messagesformat_messages([research_messages], title="Researcher Agent response")# Return the research messagesreturn {"researcher_messages": [research_messages]}except Exception as e:# Get the traceback informationexc_type, exc_obj, exc_tb = sys.exc_info()line_number = exc_tb.tb_lineno# Print detailed error informationprint(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 itraiseCopied
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.
InputPythonfrom langchain_core.messages import ToolMessage, ToolCalltools_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 callstool_calls = state["researcher_messages"][-1].tool_calls# Format the tool callsformat_messages([ToolCall(name="Tool Calls",args=tool_calls,id="tool_call_id")], title="Researcher Agent - Tool calls")# Execute all tool callsobservations = []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 immediatelyformat_messages([ToolMessage(content=observation,name=tool_call["name"],tool_call_id=tool_call["id"])], title="Researcher Agent - Tool calls result")# Create tool message outputstool_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 outputsreturn {"researcher_messages": tool_outputs}except Exception as e:# Get the traceback informationexc_type, exc_obj, exc_tb = sys.exc_info()line_number = exc_tb.tb_lineno# Print detailed error informationprint(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 itraiseCopied
Agora vamos definir a função que será encarregada de comprimir a informação da investigação.
InputPythonfrom langchain_core.messages import SystemMessage, HumanMessage, filter_messagesdef compress_research(state: ResearcherState) -> dict:"""Compress research findings into a concise summary.Takes all the research messages and tool outputs and createsa 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 researchformat_messages([response], title="Researcher Agent - Compressed Research")# Extract raw notes from tool and AI messagesraw_notes = [str(m.content) for m in filter_messages(state["researcher_messages"],include_types=["tool", "ai"])]# Return the compressed research and raw notesreturn {"compressed_research": str(response.content),"raw_notes": [" ".join(raw_notes)]}except Exception as e:# Get the traceback informationexc_type, exc_obj, exc_tb = sys.exc_info()line_number = exc_tb.tb_lineno# Print detailed error informationprint(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 itraiseCopied
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.
InputPythondef should_continue(state: ResearcherState) -> Literal["tool_node", "compress_research"]:"""Determine whether to continue research or provide final answer.Determines whether the agent should continue the research loop or providea 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 executionif last_message.tool_calls:# Create a System message to show the decisiondecision_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 answerelse:# Create a System message to show the decisiondecision_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 informationexc_type, exc_obj, exc_tb = sys.exc_info()line_number = exc_tb.tb_lineno# Print detailed error informationprint(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 itraiseCopied
Grafo do pesquisador
Já temos tudo o que é necessário para construir o grafo do agente researcher.
Primeiro criamos o grafo
InputPython# Build the agent workflowresearcher_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.
InputPython# Add nodes to the graphresearcher_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
<langgraph.graph.state.StateGraph at 0x1112bddf0>
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_nodese a última mensagem contiver chamadas de ferramenta, ou para o nócompress_researchse não contiver chamadas de ferramenta* Do nótool_nodepassa-se para o nóllm_callpara que o LLM possa decidir se continua investigando ou não - Do nó
compress_researchpassa-se para o nóEND
InputPython# Add edges to connect nodesresearcher_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 researchresearcher_agent_builder.add_edge("compress_research", END)Copied
<langgraph.graph.state.StateGraph at 0x1112bddf0>
Compilamos o grafo e representamos o diagrama de fluxo
InputPythonfrom langgraph.checkpoint.memory import InMemorySaverfrom IPython.display import Image, displaycheckpointer = InMemorySaver()researcher_agent = researcher_agent_builder.compile(checkpointer=checkpointer)# Visualize the workflowdisplay(Image(researcher_agent.get_graph(xray=True).draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Execução do agente researcher
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
InputPythonthread = {"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
⏳ Scope agent:
╭───────────────────────────────────────────── 🧑 Real Human Message ─────────────────────────────────────────────╮│ Quiero investigar las mejores cafeterías de Madrid.. │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 2.6s
╭─────────────────────────────────── 🤖 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='' │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── 🔧 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
InputPythonuser_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
⏳ Scope agent:
╭───────────────────────────────────────────── 🧑 Real Human Message ─────────────────────────────────────────────╮│ Examina la calidad del café para evaluar las mejores cafeterías de Madrid.. │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 1.8s
╭─────────────────────────────────── 🤖 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.' │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────── 🔧 Scope System Message ────────────────────────────────────────────╮│ No necesita aclaración por parte del usuario. Enviando mensaje de verificación... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Scope agent - Write research brief:
╭─────────────────────────────────── 🤖 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. │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 5.5s
╭───────────────────────────────── 🤖 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
Researchercom oresearch_briefque já temos; para isso, passamos isso como a variável do estadoresearcher_messagese como a variável do estadoresearch_topic
Criamos também um cliente da API da Tavily para poder fazer buscas na internet.
InputPythonfrom tavily import TavilyClientresearch_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
⏳ Researcher agent:
╭───────────────────────────────── 🧑 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.. │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 4.5s
╭───────────────────────────────────────── 🤖 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 │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮│ Last message contains tool calls. Continuing to tool execution... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── 🔧 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" ││ } │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 32.8s
╭──────────────────────────────────── 🔧 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: ││ <summary> ││ 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." ││ </key_excerpts> ││ ││ -------------------------------------------------------------------------------- ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 29.7s
╭──────────────────────────────────── 🔧 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: ││ <summary> ││ 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 │...│ <key_excerpts> ││ 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. ││ </key_excerpts> ││ ││ -------------------------------------------------------------------------------- ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Researcher agent:|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 12.4s
╭───────────────────────────────────────── 🤖 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 │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮│ Last message contains tool calls. Continuing to tool execution... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── 🔧 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. " ││ } │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 0.0s
╭──────────────────────────────────── 🔧 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. ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Researcher agent:|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 5.1s
╭───────────────────────────────────────── 🤖 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 │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮│ Last message contains tool calls. Continuing to tool execution... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── 🔧 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" ││ } │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 10.2s
╭──────────────────────────────────── 🔧 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, ││ ││ -------------------------------------------------------------------------------- ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 9.4s
╭──────────────────────────────────── 🔧 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: ││ <summary> ││ 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. ││ ││ -------------------------------------------------------------------------------- ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Researcher agent:|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 9.2s
╭───────────────────────────────────────── 🤖 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 │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮│ Last message contains tool calls. Continuing to tool execution... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── 🔧 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. " ││ } │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 0.0s
╭──────────────────────────────────── 🔧 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. ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Researcher agent:|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 4.0s
╭───────────────────────────────────────── 🤖 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 │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮│ Last message contains tool calls. Continuing to tool execution... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── 🔧 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" ││ } │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 29.5s
╭──────────────────────────────────── 🔧 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: ││ <summary> ││ 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á. ││ </summary> ││ │...│ 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. ││ </key_excerpts> ││ ││ -------------------------------------------------------------------------------- ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 11.7s
╭──────────────────────────────────── 🔧 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&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: ││ <summary> │...│ --- 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. ││ ││ -------------------------------------------------------------------------------- ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Researcher agent:|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 12.2s
╭───────────────────────────────────────── 🤖 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 │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮│ Last message contains tool calls. Continuing to tool execution... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── 🔧 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. " ││ } │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 0.0s
╭──────────────────────────────────── 🔧 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. ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Researcher agent:|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 3.2s
╭───────────────────────────────────────── 🤖 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 │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮│ Last message contains tool calls. Continuing to tool execution... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── 🔧 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" ││ } │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 31.9s
╭──────────────────────────────────── 🔧 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: ││ <summary> ││ 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. ││ </key_excerpts> ││ ││ -------------------------------------------------------------------------------- ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Researcher agent:|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 16.6s
╭───────────────────────────────────────── 🤖 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 │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────── 🔧 Researcher Agent - Should continue ─────────────────────────────────────╮│ Last message contains tool calls. Continuing to tool execution... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────── 🔧 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. " ││ } │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 0.0s
╭──────────────────────────────────── 🔧 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. ││ │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Researcher agent:|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 53.0s
╭───────────────────────────────────────── 🤖 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. │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────── 🔧 Researcher Agent - Should compress research ─────────────────────────────────╮│ No tool calls found. Stopping research and compressing findings... │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⏳ Compressing Research:|▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉| in 54.6s
╭─────────────────────────────────── 🤖 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&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.