commit 3578db86880df03a39e2f240709d59846797a54d Author: Илья 5f1b81b8-4f5d-11e8-9c2d-fa7ae01bbebc Date: Sat Jun 27 13:47:34 2026 +0000 add: main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..25792f2 --- /dev/null +++ b/main.py @@ -0,0 +1,162 @@ +import os +import json +import asyncio +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# LLM configuration – OpenRouter via langchain_openai +from langchain_openai import ChatOpenAI + +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, +) + +# Tavily search tool +from langchain_tavily import TavilySearchResults + +tavily = TavilySearchResults(tavily_api_key=os.getenv("TAVILY_API_KEY")) + +# LangGraph imports +from langgraph.graph import StateGraph, START, END +from typing import TypedDict, Annotated, List +from langgraph.graph.message import add_messages + +# Define state +class BriefState(TypedDict): + topic: str + outline: List[str] | None + step_index: int + notes: List[str] + final_brief: str | None + +# Outline node – generate 4‑5 bullet points +async def outline_node(state: BriefState) -> BriefState: + prompt = ( + f"Given the research topic: {state['topic']}\n" + "Provide a concise outline with 4–5 bullet points. Return the outline as a JSON array of strings." + ) + response = await llm.ainvoke(prompt) + try: + outline = json.loads(response) + if not isinstance(outline, list): + raise ValueError + except Exception: + # Fallback: split lines + outline = [line.strip('- • ') for line in response.splitlines() if line.strip()] + return { + "topic": state['topic'], + "outline": outline, + "step_index": 0, + "notes": [], + "final_brief": None, + } + +# Research step node – one web search per step +async def research_step_node(state: BriefState) -> BriefState: + step = state['outline'][state['step_index']] + query = f"{state['topic']} {step}" + search_results = tavily.run(query) + prompt = ( + f"Using the following search results, write a concise note (5–8 sentences) about the step: {step}.\n" + f"Search results:\n{search_results}\n" + "Note:") + note = await llm.ainvoke(prompt) + notes = state['notes'] + [note] + step_index = state['step_index'] + 1 + return { + "topic": state['topic'], + "outline": state['outline'], + "step_index": step_index, + "notes": notes, + "final_brief": None, + } + +# Synthesize node – produce final brief +async def synthesize_node(state: BriefState) -> BriefState: + notes_text = "\n\n".join(state['notes']) + prompt = ( + f"Based on the following notes, write a cohesive research brief about the topic: {state['topic']}\n" + "Include headings for each point and keep the brief ½–1 page long.\n" + f"Notes:\n{notes_text}\n" + "Final brief:") + final = await llm.ainvoke(prompt) + return { + "topic": state['topic'], + "outline": state['outline'], + "step_index": state['step_index'], + "notes": state['notes'], + "final_brief": final, + } + +# Build the graph +graph = StateGraph(BriefState) +graph.add_node("outline", outline_node) +graph.add_node("research_step", research_step_node) +graph.add_node("synthesize", synthesize_node) + +# Conditional edge after research_step +def condition(state: BriefState): + if state['step_index'] < len(state['outline']): + return "research_step" + return "synthesize" + +graph.add_edge(START, "outline") +graph.add_edge("outline", "research_step") +graph.add_conditional_edges("research_step", condition) +graph.add_edge("synthesize", END) + +# Compile the graph into a function +compiled_graph = graph.compile() + +# Function to run the whole brief generation +async def run_brief(topic: str) -> str: + init_state: BriefState = { + "topic": topic, + "outline": None, + "step_index": 0, + "notes": [], + "final_brief": None, + } + final_state = await compiled_graph.ainvoke(init_state) + return final_state["final_brief"] + +# DeepAgents integration +from deepagents import create_deep_agent +from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend +from langchain.tools import tool +from langchain_core.messages import HumanMessage + +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +@tool +async def generate_brief(topic: str) -> str: + """Generate a research brief for the given topic.""" + return await run_brief(topic) + +agent = create_deep_agent( + model=llm, + tools=[generate_brief], + backend=backend, + system_prompt="You are a research assistant. Use the provided tools to generate briefs.", +) + +async def main(): + default_topic = "Как студенту безопасно подключать MCP к LangChain" + response = await agent.ainvoke( + {"messages": [HumanMessage(content=f"Generate brief on '{default_topic}'")], + "configurable": {"thread_id": "session-1"}}, + ) + # The tool output will be in the last message + print("\n=== Research Brief ===\n") + print(response["messages"][-1].content) + +if __name__ == "__main__": + asyncio.run(main())