add: main.py — Повторный экзамен: Исследовательский бриф (план → шаги → сводка)
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import os
|
||||
import json
|
||||
import asyncio
|
||||
from dotenv import load_dotenv
|
||||
from typing import TypedDict, List, Optional
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_tavily import TavilySearchResults
|
||||
from langchain.tools import tool
|
||||
|
||||
from deepagents import create_deep_agent
|
||||
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
|
||||
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# LLM configuration
|
||||
llm = ChatOpenAI(
|
||||
model="openai/gpt-oss-20b:free",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
temperature=0.0,
|
||||
)
|
||||
|
||||
# Backend for deepagents
|
||||
backend = CompositeBackend([
|
||||
LocalShellBackend(workspace_dir="./workspace"),
|
||||
FilesystemBackend(),
|
||||
])
|
||||
|
||||
# TypedDict for graph state
|
||||
class BriefState(TypedDict):
|
||||
topic: str
|
||||
outline: List[str] | None
|
||||
step_index: int
|
||||
notes: List[str]
|
||||
final_brief: str | None
|
||||
|
||||
# Tool: Web search using Tavily
|
||||
@tool
|
||||
def tavily_search(query: str) -> str:
|
||||
"""Search the web for the query and return results."""
|
||||
search = TavilySearchResults()
|
||||
results = search.run(query)
|
||||
return results
|
||||
|
||||
# Outline node: generate research plan
|
||||
def outline_node(state: BriefState) -> BriefState:
|
||||
if state["outline"] is None:
|
||||
prompt = (
|
||||
f"Generate 4-5 bullet points outlining a research plan for the topic: "
|
||||
f"{state['topic']}. Return as a JSON array of strings."
|
||||
)
|
||||
response = llm.invoke(prompt)
|
||||
try:
|
||||
outline = json.loads(response.content)
|
||||
if isinstance(outline, list):
|
||||
state["outline"] = outline
|
||||
except Exception:
|
||||
state["outline"] = []
|
||||
return state
|
||||
|
||||
# Research step node: one search per iteration
|
||||
def research_step_node(state: BriefState) -> BriefState:
|
||||
if state["step_index"] < len(state["outline"] or []):
|
||||
current_step = state["outline"][state["step_index"]]
|
||||
# Perform web search
|
||||
search_results = tavily_search(current_step)
|
||||
# Summarize results
|
||||
summary_prompt = (
|
||||
f"Summarize the following search results into 5-8 sentences:\n{search_results}"
|
||||
)
|
||||
summary = llm.invoke(summary_prompt).content.strip()
|
||||
state["notes"].append(summary)
|
||||
state["step_index"] += 1
|
||||
return state
|
||||
|
||||
# Synthesize node: combine notes into final brief
|
||||
def synthesize_node(state: BriefState) -> BriefState:
|
||||
if state["step_index"] >= len(state["outline"] or []):
|
||||
notes_text = "\n\n".join(state["notes"])
|
||||
synth_prompt = (
|
||||
f"Combine the following notes into a concise research brief (about one page). "
|
||||
f"Use headings for each point.\n\n{notes_text}"
|
||||
)
|
||||
brief = llm.invoke(synth_prompt).content.strip()
|
||||
state["final_brief"] = brief
|
||||
return state
|
||||
|
||||
# Build the LangGraph
|
||||
graph = StateGraph(BriefState)
|
||||
graph.add_node("outline", outline_node)
|
||||
graph.add_node("research_step", research_step_node)
|
||||
graph.add_node("synthesize", synthesize_node)
|
||||
|
||||
graph.set_entry_point("outline")
|
||||
graph.add_conditional_edges(
|
||||
"outline",
|
||||
lambda state: "research_step" if state["outline"] is not None else END,
|
||||
)
|
||||
graph.add_conditional_edges(
|
||||
"research_step",
|
||||
lambda state: (
|
||||
"research_step"
|
||||
if state["step_index"] < len(state["outline"] or [])
|
||||
else "synthesize"
|
||||
),
|
||||
)
|
||||
graph.add_edge("synthesize", END)
|
||||
|
||||
compiled_graph = graph.compile()
|
||||
|
||||
# Tool: Generate brief using the graph
|
||||
@tool
|
||||
def generate_brief(topic: str) -> str:
|
||||
"""Generate a research brief for the given topic."""
|
||||
initial_state: BriefState = {
|
||||
"topic": topic,
|
||||
"outline": None,
|
||||
"step_index": 0,
|
||||
"notes": [],
|
||||
"final_brief": None,
|
||||
}
|
||||
final_state = compiled_graph.invoke(initial_state)
|
||||
return final_state["final_brief"] or ""
|
||||
|
||||
# Create deepagents agent
|
||||
agent = create_deep_agent(
|
||||
model=llm,
|
||||
tools=[generate_brief, tavily_search],
|
||||
backend=backend,
|
||||
system_prompt="You are a helpful research assistant. Use the provided tools to generate a research brief.",
|
||||
)
|
||||
|
||||
# Demo execution
|
||||
async def main():
|
||||
topic = "Как студенту безопасно подключать MCP к LangChain"
|
||||
result = await agent.ainvoke(
|
||||
{"messages": [HumanMessage(content=f"Generate a brief on: {topic}")]},
|
||||
{"configurable": {"thread_id": "session-1"}},
|
||||
)
|
||||
print(result["messages"][-1].content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user