From 3c13f5da65387396c63d98068e4fccefd049cec5 Mon Sep 17 00:00:00 2001 From: Danil Parunin 5f1b81b8-4f5d-11e8-9c2d-fa7ae01bbebc Date: Mon, 15 Jun 2026 12:14:04 +0000 Subject: [PATCH] add: main.py --- main.py | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..cb7ff4a --- /dev/null +++ b/main.py @@ -0,0 +1,128 @@ +import os +import asyncio +from typing import TypedDict, Annotated, List + +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage +from langchain.tools import tool +from langchain_tavily import TavilySearchResults +from langgraph.graph import StateGraph, START, END +from langgraph.graph.message import add_messages +from deepagents import create_deep_agent +from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend + +# ----------------- LLM ----------------- +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 ----------------- +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +# ----------------- Tavily Tool ----------------- +search_tool = TavilySearchResults(max_results=3) + +# ----------------- State ----------------- +class BriefState(TypedDict): + topic: str + outline: List[str] | None + step_index: int + notes: List[str] + final_brief: str | None + +# ----------------- Nodes ----------------- +async def outline_node(state: BriefState) -> BriefState: + prompt = f"""Create a concise outline of 4–5 research steps for the topic: {state['topic']}. Return a JSON array of strings.""" + response = await llm.ainvoke([HumanMessage(content=prompt)]) + # Parse JSON array + import json + try: + outline = json.loads(response.content) + if not isinstance(outline, list): + raise ValueError + except Exception: + outline = ["Step 1: Define scope", "Step 2: Search", "Step 3: Analyze", "Step 4: Summarize"] + state.update(outline=outline, step_index=0, notes=[], final_brief=None) + return state + +async def research_step_node(state: BriefState) -> BriefState: + step = state['outline'][state['step_index']] + # Perform web search via Tavily + results = await search_tool.ainvoke(step) + # Summarize results into 5–8 sentences + summary_prompt = f"""Summarize the following search results into 5–8 concise sentences for the research step: {step}. + +Results: +{results}""" + summary = await llm.ainvoke([HumanMessage(content=summary_prompt)]) + state['notes'].append(f"{step}\n{summary.content}") + state['step_index'] += 1 + return state + +async def synthesize_node(state: BriefState) -> BriefState: + # Combine notes into a coherent brief with headings + heading_prompt = """Combine the following notes into a ½–1 page research brief. Use the step titles as headings and write in a clear, academic style. + +Notes: +""" + "\n\n".join(state['notes']) + brief = await llm.ainvoke([HumanMessage(content=heading_prompt)]) + state['final_brief'] = brief.content + return state + +# ----------------- Graph ----------------- +graph = StateGraph(BriefState) +graph.add_node("outline", outline_node) +graph.add_node("research_step", research_step_node) +graph.add_node("synthesize", synthesize_node) + +# Entry point +graph.set_entry_point("outline") + +# Conditional edges +graph.add_conditional_edges( + "outline", + lambda _: "research_step", +) + +graph.add_conditional_edges( + "research_step", + lambda state: "synthesize" if state['step_index'] >= len(state['outline']) else "research_step", +) + +graph.add_edge("synthesize", END) + +app = graph.compile() + +# ----------------- DeepAgent ----------------- +agent = create_deep_agent( + model=llm, + tools=[search_tool], + backend=backend, + system_prompt="You are a research assistant that builds a brief based on a topic.", +) + +# ----------------- Main ----------------- +async def main(): + topic = os.getenv("DEFAULT_TOPIC", "Как студенту безопасно подключать MCP к LangChain") + # Run graph to get outline and notes + state = await app.ainvoke({"topic": topic, "outline": None, "step_index": 0, "notes": [], "final_brief": None}) + # Print outline + print("\n=== Outline ===") + for i, step in enumerate(state['outline'], 1): + print(f"{i}. {step}") + # Print each research step note + for i, note in enumerate(state['notes'], 1): + print(f"\n[Step {i}] {note.splitlines()[0]}") + print(note.splitlines()[1]) + # Print final brief + print("\n=== Final Brief ===") + print(state['final_brief']) + +if __name__ == "__main__": + asyncio.run(main())