Повторный экзамен: Исследовательский бриф (план → шаги → сводка): solution.py

This commit is contained in:
2026-06-02 06:07:23 +00:00
parent 6bce419b95
commit 95957da305
+106
View File
@@ -0,0 +1,106 @@
import os
from typing import TypedDict, List, Optional, Dict, Any
from langchain_openai import ChatOpenAI
from langchain_tavily import TavilySearchResults
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langgraph.prebuilt import create_react_agent
from dotenv import load_dotenv
load_dotenv()
# ---------- 1. Состояние ----------
class BriefState(TypedDict):
topic: str
outline: List[str] | None
step_index: int
notes: List[str]
final_brief: str | None
# ---------- 2. Инструменты ----------
search_tool = TavilySearchResults()
# ---------- 3. Узлы ----------
def outline_node(state: BriefState) -> Dict[str, Any]:
"""Создаёт план из 4–5 пунктов по теме."""
llm = ChatOpenAI(model="gpt-4o-mini")
prompt = (
f"Составь план исследования по теме: {state['topic']}. "
"План должен содержать 4–5 пунктов, каждый пункт – заголовок без описания."
)
plan = llm.invoke(prompt).content.strip()
outline = [line.strip("- ").strip() for line in plan.splitlines() if line.strip()]
return {"outline": outline, "step_index": 0, "notes": []}
def research_step_node(state: BriefState) -> Dict[str, Any]:
"""Для текущего пункта плана делает поиск и формирует заметку."""
llm = ChatOpenAI(model="gpt-4o-mini")
current_index = state["step_index"]
if state["outline"] is None or current_index >= len(state["outline"]):
return {"step_index": current_index, "notes": state["notes"]}
topic = state["outline"][current_index]
# Поиск
search_query = f"{state['topic']} - {topic}"
results = search_tool.invoke({"query": search_query})
# Сводка
summary_prompt = (
f"Сделай краткую заметку (5–8 предложений) по теме '{topic}'. "
f"Используй найденные источники: {results}"
)
note = llm.invoke(summary_prompt).content.strip()
notes = state["notes"] + [f"**{topic}**\n{note}"]
return {"step_index": current_index + 1, "notes": notes}
def synthesize_node(state: BriefState) -> Dict[str, Any]:
"""Объединяет все заметки в связный бриф."""
llm = ChatOpenAI(model="gpt-4o-mini")
combined = "\n\n".join(state["notes"])
prompt = (
f"Собери из следующих заметок связный исследовательский бриф (½–1 страница):\n{combined}"
)
brief = llm.invoke(prompt).content.strip()
return {"final_brief": brief}
# ---------- 4. Граф ----------
def build_graph() -> StateGraph[BriefState]:
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_edge("outline", "research_step")
graph.add_conditional_edges(
"research_step",
lambda state: "synthesize" if state["step_index"] >= len(state["outline"] or []) else "research_step",
{
"synthesize": "synthesize",
"research_step": "research_step",
},
)
graph.add_edge("synthesize", END)
return graph
# ---------- 5. Запуск ----------
if __name__ == "__main__":
topic = input("Введите тему исследования: ").strip()
initial_state: BriefState = {
"topic": topic,
"outline": None,
"step_index": 0,
"notes": [],
"final_brief": None,
}
graph = build_graph()
chain = graph.compile()
result = chain.invoke(initial_state)
print("\n=== Итоговый исследовательский бриф ===\n")
print(result["final_brief"])