Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- agente_functions.py +94 -0
- main.py +97 -0
- requirements.txt +11 -0
agente_functions.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# agente_functions.py
|
| 2 |
+
|
| 3 |
+
from typing import TypedDict, List
|
| 4 |
+
from langchain_core.tools import tool
|
| 5 |
+
from langchain_community.tools.tavily_search import TavilySearchResults
|
| 6 |
+
from langchain.agents import load_tools
|
| 7 |
+
from langgraph.prebuilt import create_react_agent
|
| 8 |
+
|
| 9 |
+
# --- Define a estrutura de estado do grafo ---
|
| 10 |
+
class AgentState(TypedDict):
|
| 11 |
+
user_query: str
|
| 12 |
+
router_decision: str
|
| 13 |
+
web_answer: str
|
| 14 |
+
scientific_answer: str
|
| 15 |
+
final_answer: str
|
| 16 |
+
|
| 17 |
+
# --- Ferramentas e Funções dos Agentes ---
|
| 18 |
+
|
| 19 |
+
@tool
|
| 20 |
+
def busca_web(query: str) -> list:
|
| 21 |
+
"""
|
| 22 |
+
Busca na web por um termo específico usando Tavily Search.
|
| 23 |
+
"""
|
| 24 |
+
tavily_search = TavilySearchResults(max_results=2, search_depth='advanced', max_tokens=1000)
|
| 25 |
+
resultado_busca = tavily_search.invoke(query)
|
| 26 |
+
return resultado_busca
|
| 27 |
+
|
| 28 |
+
def funcao_agente_web(state: AgentState, llm, tools: List) -> dict:
|
| 29 |
+
"""
|
| 30 |
+
Executa um agente ReAct que realiza uma busca na web.
|
| 31 |
+
"""
|
| 32 |
+
system_prompt = """Atue como um assistente útil.
|
| 33 |
+
Use as ferramentas fornecidas para responder às perguntas do usuário.
|
| 34 |
+
- busca_web: Retorna os resultados de uma busca na web.
|
| 35 |
+
Use a busca_web sempre que o usuário fizer uma pergunta sobre um tema específico e retorne o link dos artigos na resposta.
|
| 36 |
+
"""
|
| 37 |
+
agente_web = create_react_agent(model=llm, tools=tools, prompt=system_prompt)
|
| 38 |
+
resultado = agente_web.invoke({"messages": [("user", state["user_query"])]})
|
| 39 |
+
resposta_final = resultado['messages'][-1].content
|
| 40 |
+
return {"web_answer": resposta_final}
|
| 41 |
+
|
| 42 |
+
def funcao_agente_cientifico(state: AgentState, llm, tools: List) -> dict:
|
| 43 |
+
"""
|
| 44 |
+
Executa um agente ReAct que realiza uma busca no Arxiv.
|
| 45 |
+
"""
|
| 46 |
+
system_prompt = """Atue como um assistente útil.
|
| 47 |
+
Use as ferramentas fornecidas para responder às perguntas do usuário.
|
| 48 |
+
- tool_cientifica: Retorna os resultados de uma busca no arxiv.
|
| 49 |
+
Use a tool_cientifica sempre que o usuário fizer uma pergunta sobre um tema específico e retorne o título dos artigos na resposta.
|
| 50 |
+
"""
|
| 51 |
+
agente_cientifico = create_react_agent(llm, tools, prompt=system_prompt)
|
| 52 |
+
resultado = agente_cientifico.invoke({"messages": [("user", state["user_query"])]})
|
| 53 |
+
resposta_final = resultado['messages'][-1].content
|
| 54 |
+
return {"scientific_answer": resposta_final}
|
| 55 |
+
|
| 56 |
+
def router_agent(state: AgentState, llm) -> dict:
|
| 57 |
+
"""
|
| 58 |
+
Agente roteador que decide qual ferramenta usar com base na query do usuário.
|
| 59 |
+
"""
|
| 60 |
+
router_prompt_template = """Você é um agente roteador. Sua tarefa é decidir qual o agente é mais apropriado para responder à pergunta do usuário.
|
| 61 |
+
Se a pergunta mencionar explicitamente "fontes da web", "notícias" ou for de natureza geral, escolha 'web_search'.
|
| 62 |
+
Se a pergunta mencionar "artigos científicos", "pesquisas", "estudos" ou for de natureza técnica/acadêmica, escolha 'scientific_search'.
|
| 63 |
+
Para perguntas gerais sem especificação, prefira 'web_search'.
|
| 64 |
+
|
| 65 |
+
Pergunta do usuário: {user_query}
|
| 66 |
+
|
| 67 |
+
Responda APENAS com o nome da opção escolhida: 'web_search' ou 'scientific_search'.
|
| 68 |
+
"""
|
| 69 |
+
prompt = router_prompt_template.format(user_query=state["user_query"])
|
| 70 |
+
response = llm.invoke(prompt)
|
| 71 |
+
decision = response.content.strip().lower()
|
| 72 |
+
|
| 73 |
+
if "scientific_search" in decision:
|
| 74 |
+
return {"router_decision": "scientific_search"}
|
| 75 |
+
else:
|
| 76 |
+
return {"router_decision": "web_search"}
|
| 77 |
+
|
| 78 |
+
def supervisor_node(state: AgentState) -> dict:
|
| 79 |
+
"""
|
| 80 |
+
Combina os resultados dos outros nós e formata a resposta final.
|
| 81 |
+
"""
|
| 82 |
+
web_results = state.get("web_answer", "Não foi realizada pesquisa na Web.")
|
| 83 |
+
scientific_results = state.get("scientific_answer", "Não foi realizada pesquisa por artigos científicos.")
|
| 84 |
+
|
| 85 |
+
# Constrói a resposta final baseada nos resultados disponíveis
|
| 86 |
+
final_answer = "## Resultados da Pesquisa\n\n"
|
| 87 |
+
if state.get("web_answer"):
|
| 88 |
+
final_answer += "### Resultados da Pesquisa na Web\n"
|
| 89 |
+
final_answer += web_results
|
| 90 |
+
if state.get("scientific_answer"):
|
| 91 |
+
final_answer += "\n\n### Resultados de Artigos Científicos\n"
|
| 92 |
+
final_answer += scientific_results
|
| 93 |
+
|
| 94 |
+
return {"final_answer": final_answer}
|
main.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# main.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import gradio as gr
|
| 5 |
+
from functools import partial
|
| 6 |
+
|
| 7 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 8 |
+
from langchain.agents import load_tools
|
| 9 |
+
from langgraph.graph import StateGraph, START, END
|
| 10 |
+
|
| 11 |
+
# Importa as funções e a classe de estado do outro arquivo
|
| 12 |
+
from agente_functions import (
|
| 13 |
+
AgentState,
|
| 14 |
+
busca_web,
|
| 15 |
+
funcao_agente_web,
|
| 16 |
+
funcao_agente_cientifico,
|
| 17 |
+
supervisor_node,
|
| 18 |
+
router_agent
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
# --- 1. Configuração do Ambiente ---
|
| 22 |
+
# É recomendado carregar as chaves de um arquivo .env para segurança
|
| 23 |
+
# from dotenv import load_dotenv
|
| 24 |
+
# load_dotenv()
|
| 25 |
+
# api_key = os.getenv("GOOGLE_API_KEY")
|
| 26 |
+
# tavily_api_key = os.getenv("TAVILY_API_KEY")
|
| 27 |
+
|
| 28 |
+
# Para este exemplo, vamos supor que as chaves estão como variáveis de ambiente
|
| 29 |
+
os.environ["GOOGLE_API_KEY"] = "SUA_CHAVE_API_GEMINI_AQUI"
|
| 30 |
+
os.environ["TAVILY_API_KEY"] = "SUA_CHAVE_API_TAVILY_AQUI"
|
| 31 |
+
|
| 32 |
+
if not os.environ.get("GOOGLE_API_KEY") or not os.environ.get("TAVILY_API_KEY"):
|
| 33 |
+
print("Por favor, defina as variáveis de ambiente GOOGLE_API_KEY e TAVILY_API_KEY.")
|
| 34 |
+
exit()
|
| 35 |
+
|
| 36 |
+
# --- 2. Inicialização do Modelo e Ferramentas ---
|
| 37 |
+
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash")
|
| 38 |
+
web_tools = [busca_web]
|
| 39 |
+
scientific_tools = load_tools(["arxiv"])
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
# --- 3. Construção do Grafo com LangGraph ---
|
| 43 |
+
workflow = StateGraph(AgentState)
|
| 44 |
+
|
| 45 |
+
# Usando functools.partial para passar argumentos fixos (llm, tools) para as funções dos nós
|
| 46 |
+
web_search_node = partial(funcao_agente_web, llm=llm, tools=web_tools)
|
| 47 |
+
scientific_search_node = partial(funcao_agente_cientifico, llm=llm, tools=scientific_tools)
|
| 48 |
+
router_node = partial(router_agent, llm=llm)
|
| 49 |
+
|
| 50 |
+
# Adiciona os nós ao grafo
|
| 51 |
+
workflow.add_node("router", router_node)
|
| 52 |
+
workflow.add_node("web_search", web_search_node)
|
| 53 |
+
workflow.add_node("scientific_search", scientific_search_node)
|
| 54 |
+
workflow.add_node("supervisor", supervisor_node)
|
| 55 |
+
|
| 56 |
+
# Define as arestas (fluxo de trabalho)
|
| 57 |
+
workflow.add_edge(START, "router")
|
| 58 |
+
workflow.add_conditional_edges(
|
| 59 |
+
"router",
|
| 60 |
+
lambda state: state['router_decision'],
|
| 61 |
+
{
|
| 62 |
+
"web_search": "web_search",
|
| 63 |
+
"scientific_search": "scientific_search"
|
| 64 |
+
}
|
| 65 |
+
)
|
| 66 |
+
workflow.add_edge("web_search", "supervisor")
|
| 67 |
+
workflow.add_edge("scientific_search", "supervisor")
|
| 68 |
+
workflow.add_edge("supervisor", END)
|
| 69 |
+
|
| 70 |
+
# Compila o grafo para torná-lo executável
|
| 71 |
+
app = workflow.compile()
|
| 72 |
+
|
| 73 |
+
# --- 4. Função para Interface do Gradio ---
|
| 74 |
+
def run_graph(user_query: str) -> str:
|
| 75 |
+
"""
|
| 76 |
+
Executa o grafo do LangGraph com a pergunta do usuário e retorna a resposta final.
|
| 77 |
+
"""
|
| 78 |
+
try:
|
| 79 |
+
result = app.invoke({"user_query": user_query})
|
| 80 |
+
# Retorna a resposta final do supervisor, com um fallback
|
| 81 |
+
return result.get("final_answer", "Não foi possível obter uma resposta final.")
|
| 82 |
+
except Exception as e:
|
| 83 |
+
return f"Ocorreu um erro: {e}"
|
| 84 |
+
|
| 85 |
+
# --- 5. Lançamento da Aplicação com Gradio ---
|
| 86 |
+
if __name__ == "__main__":
|
| 87 |
+
print("Iniciando a interface do Gradio...")
|
| 88 |
+
iface = gr.Interface(
|
| 89 |
+
fn=run_graph,
|
| 90 |
+
inputs=gr.Textbox(lines=2, label="Digite sua pergunta:", placeholder="Ex: Qual o impacto da IA na medicina?"),
|
| 91 |
+
outputs=gr.Markdown(label="Resposta do Agente"),
|
| 92 |
+
title="🤖 Agente de Pesquisa com LangGraph e Gemini",
|
| 93 |
+
description="Faça uma pergunta e o sistema decidirá se busca na web, em artigos científicos, ou ambos, para construir a melhor resposta.",
|
| 94 |
+
allow_flagging="never"
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
iface.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# requirements.txt
|
| 2 |
+
|
| 3 |
+
google-ai-generativelanguage==0.6.15
|
| 4 |
+
langchain-google-genai
|
| 5 |
+
langchain
|
| 6 |
+
langchain-pinecone
|
| 7 |
+
langchain-community
|
| 8 |
+
langgraph
|
| 9 |
+
arxiv
|
| 10 |
+
tavily-client
|
| 11 |
+
gradio
|