Files

128 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<|channel|>final code<|message|>import os
from typing import TypedDict, List
import questionary
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.constants import START
from langgraph.graph import StateGraph
from langgraph.types import interrupt, Command
# ---------- 1. Состояние графа ----------
class GraphState(TypedDict):
"""Состояние графа."""
# Текст истории, генерируемый LLM
story: str
# Ответ пользователя после прерывания
human_value: str | None
# Внутренний токен для идентификации потока (необязательно)
thread_id: str
# ---------- 2. Узел с прерыванием ----------
def generate_story(state: GraphState) -> GraphState:
"""
Генерирует начало истории и предлагает варианты действий.
После генерации вызывается interrupt, чтобы пользователь выбрал вариант.
"""
# Если история уже есть, просто возвращаем состояние
if state.get("story"):
return state
llm = ChatOpenAI(temperature=0.7)
prompt = (
"Создай короткую интригующую историю о герое, который стоит перед выбором.\n"
"В конце предложи 3 варианта действий героя (A, B, C)."
)
response = llm.invoke(prompt)
story_text = response.content.strip()
# Сохраняем сгенерированную часть истории
state["story"] = story_text
# Подготавливаем payload для прерывания
interrupt_payload = {
"type": "choice",
"question": "Какое действие выберет герой?",
"allow_responds": ["A", "B", "C"],
}
# Вызываем прерывание: выполнение остановится до возобновления
interrupt(interrupt_payload)
# После возобновления сюда вернётся объект с ответом пользователя
return state
def finish_story(state: GraphState) -> GraphState:
"""
Завершающий узел, который получает выбор пользователя и дописывает концовку.
"""
llm = ChatOpenAI(temperature=0.7)
choice = state["human_value"]
prompt = (
f"История:\n{state['story']}\n\n"
f"Герой выбрал вариант {choice}. Напиши короткую концовку истории."
)
response = llm.invoke(prompt)
state["story"] += "\n\n" + response.content.strip()
return state
# ---------- 3. Сборка графа ----------
builder = StateGraph(GraphState)
# Добавляем узлы
builder.add_node("generate", generate_story)
builder.add_node("finish", finish_story)
# Определяем переходы
builder.set_entry_point("generate")
builder.add_edge(START, "generate")
builder.add_edge("generate", "finish")
# Компилируем граф с чекпоинтером
graph = builder.compile(checkpointer=InMemorySaver())
# ---------- 4. Цикл запуска ----------
def main() -> None:
# Уникальный идентификатор потока (можно генерировать случайным образом)
thread_id = "story_thread_1"
config = {"configurable": {"thread_id": thread_id}}
# Запускаем граф до первого прерывания
stream = graph.stream(Command(state={}), config=config)
for chunk in stream:
# Если в чанке есть прерывание, обрабатываем его
if "__interrupt__" in chunk:
interrupt_obj = chunk["__interrupt__"][0].value # payload dict
print("\n=== Прерывание ===")
print(f"{interrupt_obj['question']}")
answer = questionary.select(
"Выберите вариант:",
choices=interrupt_obj["allow_responds"],
).ask()
if not answer:
raise SystemExit("Отмена пользователем")
# Добавляем ответ в payload и возобновляем граф
interrupt_obj["answer"] = answer
stream = graph.stream(Command(resume=interrupt_obj), config=config)
continue
# Выводим обычные сообщения (если нужно)
if "story" in chunk:
print("\nИстория:")
print(chunk["story"])
# После завершения выводим итоговую историю
final_state = stream.final_state()
print("\n=== Итоговая история ===")
print(final_state["story"])
if __name__ == "__main__":
main()