Solution published: add main.py
This commit is contained in:
@@ -0,0 +1,102 @@
|
|||||||
|
"""LangGraph research brief agent.
|
||||||
|
|
||||||
|
This script implements a LangGraph agent that, given a topic, produces a short research brief.
|
||||||
|
The brief consists of:
|
||||||
|
1. An outline of 4–5 research points.
|
||||||
|
2. For each point, a single web search via Tavily and a short note.
|
||||||
|
3. A final synthesis of all notes into a coherent brief.
|
||||||
|
|
||||||
|
The agent uses LangGraph's StateGraph and LangChain's Tavily and OpenAI LLM.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import TypedDict, List
|
||||||
|
|
||||||
|
from langgraph.graph import StateGraph, END
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
from langchain_tavily import TavilySearchResults
|
||||||
|
from langchain_core.messages import HumanMessage
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# --- State definition -----------------------------------------------------
|
||||||
|
class BriefState(TypedDict):
|
||||||
|
topic: str
|
||||||
|
outline: List[str] | None
|
||||||
|
step_index: int
|
||||||
|
notes: List[str]
|
||||||
|
final_brief: str | None
|
||||||
|
|
||||||
|
# --- LLM and Tavily -------------------------------------------------------
|
||||||
|
llm = ChatOpenAI(temperature=0.2)
|
||||||
|
search = TavilySearchResults(max_results=1)
|
||||||
|
|
||||||
|
# --- Node functions -------------------------------------------------------
|
||||||
|
async def outline_node(state: BriefState) -> BriefState:
|
||||||
|
"""Generate an outline of 4–5 research points for the topic."""
|
||||||
|
prompt = (
|
||||||
|
f"Generate a concise outline of 4–5 research points for the following topic. "
|
||||||
|
f"Return the points as a numbered list, one per line. Topic: {state['topic']}"
|
||||||
|
)
|
||||||
|
response = await llm.agenerate([HumanMessage(content=prompt)])
|
||||||
|
outline_text = response.generations[0][0].text.strip()
|
||||||
|
outline = [line.strip() for line in outline_text.splitlines() if line.strip()]
|
||||||
|
return {"outline": outline, "step_index": 0, "notes": []}
|
||||||
|
|
||||||
|
async def research_step_node(state: BriefState) -> BriefState:
|
||||||
|
"""Perform a single web search for the current outline point and store a short note."""
|
||||||
|
idx = state["step_index"]
|
||||||
|
point = state["outline"][idx]
|
||||||
|
# Search via Tavily
|
||||||
|
search_results = await search.ainvoke(point)
|
||||||
|
snippet = search_results[0].snippet if search_results else "No snippet found."
|
||||||
|
# Summarize snippet with LLM
|
||||||
|
prompt = (
|
||||||
|
f"You are a concise researcher. Based on the following snippet, write a 5–8 sentence note summarizing the key information. "
|
||||||
|
f"Snippet: {snippet}"
|
||||||
|
)
|
||||||
|
note_resp = await llm.agenerate([HumanMessage(content=prompt)])
|
||||||
|
note = note_resp.generations[0][0].text.strip()
|
||||||
|
notes = state["notes"] + [note]
|
||||||
|
return {"notes": notes, "step_index": idx + 1}
|
||||||
|
|
||||||
|
async def synthesize_node(state: BriefState) -> BriefState:
|
||||||
|
"""Combine all notes into a coherent brief with headings."""
|
||||||
|
outline = state["outline"]
|
||||||
|
notes = state["notes"]
|
||||||
|
sections = [f"**{point}**\n{note}" for point, note in zip(outline, notes)]
|
||||||
|
brief = "\n\n".join(sections)
|
||||||
|
return {"final_brief": brief}
|
||||||
|
|
||||||
|
# --- Graph construction ---------------------------------------------------
|
||||||
|
workflow = StateGraph(BriefState)
|
||||||
|
workflow.add_node("outline", outline_node)
|
||||||
|
workflow.add_node("research_step", research_step_node)
|
||||||
|
workflow.add_node("synthesize", synthesize_node)
|
||||||
|
|
||||||
|
workflow.set_entry_point("outline")
|
||||||
|
workflow.add_conditional_edges(
|
||||||
|
"outline",
|
||||||
|
lambda state: "research_step" if state["outline"] else END,
|
||||||
|
)
|
||||||
|
workflow.add_conditional_edges(
|
||||||
|
"research_step",
|
||||||
|
lambda state: "research_step" if state["step_index"] < len(state["outline"]) else "synthesize",
|
||||||
|
)
|
||||||
|
workflow.add_edge("synthesize", END)
|
||||||
|
|
||||||
|
graph = workflow.compile()
|
||||||
|
|
||||||
|
# --- Runner ----------------------------------------------------------------
|
||||||
|
async def run_brief(topic: str) -> str:
|
||||||
|
state: BriefState = {"topic": topic, "outline": None, "step_index": 0, "notes": [], "final_brief": None}
|
||||||
|
result = await graph.ainvoke(state)
|
||||||
|
return result["final_brief"]
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import asyncio
|
||||||
|
topic = os.getenv("TOPIC", "Как студенту безопасно подключать MCP к LangChain")
|
||||||
|
brief = asyncio.run(run_brief(topic))
|
||||||
|
print("\n=== Brief ===")
|
||||||
|
print(brief)
|
||||||
Reference in New Issue
Block a user