LevinAleksey commited on
Commit
ae61a39
·
verified ·
1 Parent(s): fbeb103

Update graph.py

Browse files
Files changed (1) hide show
  1. graph.py +87 -21
graph.py CHANGED
@@ -1,35 +1,101 @@
1
- from typing import Annotated, Sequence, TypedDict
 
 
 
 
2
  from langchain_openai import ChatOpenAI
3
  from langgraph.graph import StateGraph, START, END
4
- from langchain_core.messages import BaseMessage
5
- import os
6
 
7
- # Состояние нашего чата
 
 
 
 
 
 
 
8
  class AgentState(TypedDict):
9
- messages: Annotated[Sequence[BaseMessage], "add_messages"]
10
- next_node: str # Кто ходит следующим
 
 
11
 
12
- # 1. Настройка модели через OpenRouter
 
 
13
  llm = ChatOpenAI(
14
- model="google/gemini-2.0-flash-001", # Быстрая и дешевая для логики
15
  openai_api_key=os.getenv("OPENROUTER_API_KEY"),
16
- openai_api_base="https://openrouter.ai/api/v1"
 
 
 
 
 
 
 
 
 
 
 
17
  )
18
 
19
- # 2. Узел Супервайзера (Директор)
20
- def supervisor_node(state: AgentState):
21
- # Промпт: реши, кому передать задачу: SALES, ADV или закончить (FINISH)
22
- # Здесь логика выбора на основе текста пользователя
23
- return {"next_node": "SALES_AGENT"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- # Собираем граф
 
 
26
  workflow = StateGraph(AgentState)
27
- # Добавляем узлы (Nodes)
28
- workflow.add_node("SUPERVISOR", supervisor_node)
29
- # Добавим сюда узлы для SALES и ADV...
30
 
31
- workflow.add_edge(START, "SUPERVISOR")
32
- # ... логика переходов
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- # Компилируем с нашей базой данных!
 
 
 
35
  graph = workflow.compile(checkpointer=checkpointer)
 
1
+ import os
2
+ from typing import Annotated, Literal, TypedDict
3
+ from pydantic import BaseModel
4
+
5
+ from langchain_core.messages import BaseMessage, SystemMessage
6
  from langchain_openai import ChatOpenAI
7
  from langgraph.graph import StateGraph, START, END
8
+ from langgraph.graph.message import add_messages
9
+ from langgraph.prebuilt import create_react_agent
10
 
11
+ # Импорты из соседних файлов нашего проекта
12
+ from tools import ads_tools
13
+ from prompts import SUPERVISOR_PROMPT, ADS_ANALYST_PROMPT
14
+ from database import checkpointer
15
+
16
+ # ==========================================
17
+ # 1. СТРУКТУРА СОСТОЯНИЯ (STATE)
18
+ # ==========================================
19
  class AgentState(TypedDict):
20
+ # Указываем, что новые сообщения будут добавляться к старым (склеиваться)
21
+ messages: Annotated[list[BaseMessage], add_messages]
22
+ # Переменная для хранения решения Супервайзера (кому передать ход)
23
+ next_node: str
24
 
25
+ # ==========================================
26
+ # 2. ИНИЦИАЛИЗАЦИЯ LLM (OpenRouter)
27
+ # ==========================================
28
  llm = ChatOpenAI(
29
+ model="google/gemini-2.0-flash-001", # Можно поменять на claude-3-5-sonnet
30
  openai_api_key=os.getenv("OPENROUTER_API_KEY"),
31
+ openai_api_base="https://openrouter.ai/api/v1",
32
+ temperature=0.2 # Низкая температура для аналитики, чтобы модель меньше фантазировала
33
+ )
34
+
35
+ # ==========================================
36
+ # 3. УЗЕЛ: АНАЛИТИК РЕКЛАМЫ (Суб-агент)
37
+ # ==========================================
38
+ # Используем готовый шаблон ReAct-агента, который умеет вызывать Tools
39
+ ads_agent_runnable = create_react_agent(
40
+ model=llm,
41
+ tools=ads_tools,
42
+ state_modifier=ADS_ANALYST_PROMPT # Передаем огромный промпт из файла
43
  )
44
 
45
+ async def ads_node(state: AgentState):
46
+ """
47
+ Агент по рекламе берет историю диалога, вызывает нужное API (tools)
48
+ и формирует итоговый аналитический ответ.
49
+ """
50
+ # Вызываем агента и передаем ему текущую историю сообщений
51
+ result = await ads_agent_runnable.ainvoke({"messages": state["messages"]})
52
+
53
+ # Возвращаем только последнее сгенерированное сообщение,
54
+ # чтобы функция add_messages корректно добавила его в State
55
+ return {"messages": [result["messages"][-1]]}
56
+
57
+ # ==========================================
58
+ # 4. УЗЕЛ: СУПЕРВАЙЗЕР (Оркестратор)
59
+ # ==========================================
60
+ class RouterOutput(BaseModel):
61
+ # Строго ограничиваем варианты ответа Супервайзера
62
+ next_node: Literal["Ads_Analyst", "FINISH"]
63
+
64
+ async def supervisor_node(state: AgentState):
65
+ """
66
+ Супервайзер читает весь диалог и решает, кому передать задачу.
67
+ Он возвращает только JSON с названием следующего узла.
68
+ """
69
+ # Собираем промпт + историю
70
+ messages = [SystemMessage(content=SUPERVISOR_PROMPT)] + state["messages"]
71
+
72
+ # Заставляем LLM ответить строго по структуре RouterOutput
73
+ response = await llm.with_structured_output(RouterOutput).ainvoke(messages)
74
+
75
+ return {"next_node": response.next_node}
76
 
77
+ # ==========================================
78
+ # 5. СБОРКА ГРАФА СОСТОЯНИЙ (LangGraph)
79
+ # ==========================================
80
  workflow = StateGraph(AgentState)
 
 
 
81
 
82
+ # Добавляем узлы в граф
83
+ workflow.add_node("Supervisor", supervisor_node)
84
+ workflow.add_node("Ads_Analyst", ads_node)
85
+
86
+ # Логика условного перехода: куда идти после Супервайзера?
87
+ def router(state: AgentState) -> str:
88
+ if state["next_node"] == "FINISH":
89
+ return END
90
+ return state["next_node"]
91
+
92
+ # Строим связи (ребра графа)
93
+ workflow.add_edge(START, "Supervisor") # Старт всегда направляется к Супервайзеру
94
+ workflow.add_conditional_edges("Supervisor", router) # От Супервайзера - по условию
95
+ workflow.add_edge("Ads_Analyst", "Supervisor") # После того как Аналитик ответил, возвращаем ход Супервайзеру
96
 
97
+ # ==========================================
98
+ # 6. КОМПИЛЯЦИЯ И ПОДКЛЮЧЕНИЕ ПАМЯТИ
99
+ # ==========================================
100
+ # Компилируем граф, передавая checkpointer (Supabase PostgresSaver)
101
  graph = workflow.compile(checkpointer=checkpointer)