From 2f9873503c33ec95b8d269952243cb752141c22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=92=D0=B8=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= Date: Tue, 30 Jun 2026 16:07:59 +0000 Subject: [PATCH] =?UTF-8?q?add:=20main.py=20=E2=80=94=20=D0=9F=D0=BE=D0=B2?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BD=D1=8B=D0=B9=20=D1=8D=D0=BA=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD:=20=D0=98=D1=81=D1=81=D0=BB=D0=B5=D0=B4?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=B1=D1=80=D0=B8=D1=84=20(=D0=BF=D0=BB=D0=B0=D0=BD?= =?UTF-8?q?=20=E2=86=92=20=D1=88=D0=B0=D0=B3=D0=B8=20=E2=86=92=20=D1=81?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D0=BA=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..ebd6fd2 --- /dev/null +++ b/main.py @@ -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()) \ No newline at end of file