diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..72625d3 --- /dev/null +++ b/.env.example @@ -0,0 +1,55 @@ +# Скопируй этот файл в .env и заполни реальными значениями: +# cp .env.example .env +# # открой .env и заполни OPENAI_API_KEY и другие +# +# Реальный .env НЕ коммитится (см. .gitignore). +# Если у тебя есть рабочий .env в bro-js/agents/teacher/assistant/.env -- +# можешь сделать symlink или просто скопировать его сюда. + +# ============================================================================ +# LLM Provider (используется во всех 122 example-скриптах) +# ============================================================================ + +# Endpoint by default: https://llm.brojs.ru/v1 +# OpenAI-compatible, можно переопределить через OPENAI_BASE_URL +OPENAI_BASE_URL=https://llm.brojs.ru/v1 +OPENAI_API_KEY= + +# Альтернативные провайдеры (раскомментируй нужный, скрипты автоматически подхватят): +# MINIMAX_API_KEY=sk-cp-... +# GIGACHAT_CREDENTIALS=... +# GIGACHAT_SCOPE=GIGACHAT_API_B2B +# GIGACHAT_MODEL=GigaChat-MAX + +# ============================================================================ +# Observability (LangSmith) -- опционально, для tracing +# ============================================================================ + +# LANGSMITH_API_KEY=lsv2_pt_... +# LANGSMITH_ENDPOINT=https://api.smith.langchain.com +# LANGSMITH_TRACING_V2=true +# LANGSMITH_PROJECT=langchain-evolution-deck + +# ============================================================================ +# Search (Tavily) -- нужен для RAG-слайдов про retrievers +# ============================================================================ + +# TAVILY_API_KEY=tvly-dev-... + +# ============================================================================ +# Embeddings (HuggingFace) -- опционально для RAG +# ============================================================================ + +# HUGGINGFACEHUB_API_KEY=hf_... + +# ============================================================================ +# Не используется в этой колоде, но есть в teacher/assistant/.env +# ============================================================================ + +# SMTP_CONFIG='["mail.itpark.tech", 25, false]' +# SMTP_MAIL_LOGIN=academydevops@itpark.tech +# SMTP_MAIL_PASSWORD=... +# MAIL_TO_1=primakovpro@gmail.com +# GITHUB_TOKEN=ghp_... +# GITEA_TOKEN=... +# JOURNAL_MCP_PAT=jrnl_... diff --git a/.gitignore b/.gitignore index 22c1010..35997d1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ node_modules/ # Python __pycache__/ *.pyc +.env + +# Parser script and intermediate +parse_slides.py diff --git a/examples/slide-001/script.py b/examples/slide-001/script.py new file mode 100644 index 0000000..0f77c70 --- /dev/null +++ b/examples/slide-001/script.py @@ -0,0 +1,39 @@ +""" +Slide 1: Untitled +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-01.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-001/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 1: Untitled') diff --git a/examples/slide-002/script.py b/examples/slide-002/script.py new file mode 100644 index 0000000..c0de140 --- /dev/null +++ b/examples/slide-002/script.py @@ -0,0 +1,39 @@ +""" +Slide 2: Что такое LangChain в 2025+ +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-02.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-002/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 2: Что такое LangChain в 2025+') diff --git a/examples/slide-003/script.py b/examples/slide-003/script.py new file mode 100644 index 0000000..c53fecd --- /dev/null +++ b/examples/slide-003/script.py @@ -0,0 +1,49 @@ +""" +Slide 3: pip install -- и сразу в дело +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-03.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-003/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 3 === +# 1. Core -- обязательно для всех остальных пакетов +pip install langchain + +# 2. Provider-интеграции -- ставим только те, что нужны +pip install langchain-openai +pip install langchain-anthropic +pip install langchain-google + +# 3. (опционально) дополнительные интеграции +pip install langchain-community # ~700 community-пакетов +pip install langchain-classic # legacy chains/AgentExecutor diff --git a/examples/slide-004/script.py b/examples/slide-004/script.py new file mode 100644 index 0000000..785a977 --- /dev/null +++ b/examples/slide-004/script.py @@ -0,0 +1,49 @@ +""" +Slide 4: Первый вызов модели +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-04.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-004/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 4 === +# Один универсальный инициализатор для всех провайдеров +from langchain.chat_models import init_chat_model + +# Формат: ":" +model = init_chat_model("openai:gpt-4.1-mini") + +# invoke -> BaseMessage; нам нужен .content +result = model.invoke("Say hello in one sentence") +print(result.content) + +# >>> "Hello! How can I help you today?" diff --git a/examples/slide-005/script.py b/examples/slide-005/script.py new file mode 100644 index 0000000..43a4f8c --- /dev/null +++ b/examples/slide-005/script.py @@ -0,0 +1,52 @@ +""" +Slide 5: Переключение провайдера -- одна строка +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-05.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-005/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 5 === +from langchain.chat_models import init_chat_model + +# OpenAI +m_openai = init_chat_model("openai:gpt-4.1-mini") + +# Anthropic +m_anthropic = init_chat_model("anthropic:claude-3-7-sonnet-latest") + +# Google Vertex AI +m_google = init_chat_model("google_vertexai:gemini-2.0-flash") + +# Все три -- объекты BaseChatModel. Один и тот же .invoke() +for m in [m_openai, m_anthropic, m_google]: + print(type(m).__name__, ":", m.invoke("ping").content[:30]) diff --git a/examples/slide-006/script.py b/examples/slide-006/script.py new file mode 100644 index 0000000..22610b9 --- /dev/null +++ b/examples/slide-006/script.py @@ -0,0 +1,54 @@ +""" +Slide 6: Стандартизированные сообщения +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-06.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-006/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 6 === +from langchain.messages import ( + HumanMessage, AIMessage, SystemMessage, ToolMessage, +) +from langchain.chat_models import init_chat_model + +model = init_chat_model("openai:gpt-4.1-mini") + +messages = [ + SystemMessage(content="You are a concise assistant."), + HumanMessage(content="What is LCEL?"), + AIMessage(content="LCEL = pipe-based composition in LangChain."), + HumanMessage(content="Show a one-line example."), +] + +response = model.invoke(messages) +print(response.content) diff --git a/examples/slide-007/script.py b/examples/slide-007/script.py new file mode 100644 index 0000000..1d8d70b --- /dev/null +++ b/examples/slide-007/script.py @@ -0,0 +1,55 @@ +""" +Slide 7: Потоковый вывод -- token за токеном +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-07.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-007/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 7 === +from langchain.chat_models import init_chat_model + +model = init_chat_model("openai:gpt-4.1-mini") + +# .stream() -> итератор по AIMessageChunk +for chunk in model.stream("Write a haiku about Python"): + # У каждого chunk-а есть .content (текст) и .response_metadata + print(chunk.content, end="", flush=True) + +print() # перевод строки после потока + +# Async-вариант: model.astream() -- то же самое в async-контексте +import asyncio + +async def main(): + async for chunk in model.astream("Write a haiku about async"): + print(chunk.content, end="", flush=True) diff --git a/examples/slide-008/script.py b/examples/slide-008/script.py new file mode 100644 index 0000000..fa62b75 --- /dev/null +++ b/examples/slide-008/script.py @@ -0,0 +1,53 @@ +""" +Slide 8: ChatPromptTemplate -- декларативно +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-08.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-008/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 8 === +from langchain_core.prompts import ChatPromptTemplate +from langchain.chat_models import init_chat_model + +prompt = ChatPromptTemplate.from_messages([ + ("system", "Translate the following text to {language}."), + ("human", "{text}"), +]) + +model = init_chat_model("openai:gpt-4.1-mini") + +# .invoke() принимает dict и подставляет переменные +messages = prompt.invoke({"language": "French", "text": "Hello world"}) +print(messages.to_messages()) + +# -> [SystemMessage(...), HumanMessage(...)] -- готов к model.invoke() diff --git a/examples/slide-009/script.py b/examples/slide-009/script.py new file mode 100644 index 0000000..88cf243 --- /dev/null +++ b/examples/slide-009/script.py @@ -0,0 +1,62 @@ +""" +Slide 9: MessagesPlaceholder + few-shot +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-09.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-009/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 9 === +from langchain_core.prompts import ( + ChatPromptTemplate, MessagesPlaceholder, FewShotChatMessagePromptTemplate, +) + +# Few-shot блок: примеры "вопрос -> ответ" +examples = [ + {"input": "2+2", "output": "4"}, + {"input": "3*3", "output": "9"}, +] +example_prompt = ChatPromptTemplate.from_messages([ + ("human", "{input}"), + ("ai", "{output}"), +]) +few_shot = FewShotChatMessagePromptTemplate( + example_prompt=example_prompt, examples=examples, +) + +# Сборка: system + few-shot + история + текущий вопрос +prompt = ChatPromptTemplate.from_messages([ + ("system", "You are a math assistant."), + few_shot, + MessagesPlaceholder("history"), + ("human", "{question}"), +]) diff --git a/examples/slide-010/script.py b/examples/slide-010/script.py new file mode 100644 index 0000000..550623a --- /dev/null +++ b/examples/slide-010/script.py @@ -0,0 +1,55 @@ +""" +Slide 10: StrOutputParser -- самый частый +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-10.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-010/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 10 === +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.output_parsers import StrOutputParser +from langchain.chat_models import init_chat_model + +prompt = ChatPromptTemplate.from_messages([ + ("system", "Translate to French."), + ("human", "{text}"), +]) +model = init_chat_model("openai:gpt-4.1-mini") + +# Склеиваем через LCEL: prompt | model | parser +chain = prompt | model | StrOutputParser() + +# Теперь на выходе str, а не AIMessage +result = chain.invoke({"text": "Hello world"}) +print(type(result).__name__, "->", result) +# >>> str -> "Bonjour le monde" diff --git a/examples/slide-011/script.py b/examples/slide-011/script.py new file mode 100644 index 0000000..bdfc5e2 --- /dev/null +++ b/examples/slide-011/script.py @@ -0,0 +1,58 @@ +""" +Slide 11: PydanticOutputParser -- типизированный выход +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-11.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-011/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 11 === +from pydantic import BaseModel, Field +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.output_parsers import PydanticOutputParser +from langchain.chat_models import init_chat_model + +class MovieReview(BaseModel): + title: str = Field(description="Movie title") + rating: int = Field(description="Rating from 1 to 10") + summary: str = Field(description="One-sentence summary") + +parser = PydanticOutputParser(pydantic_object=MovieReview) + +prompt = ChatPromptTemplate.from_messages([ + ("system", "Extract review fields.\n{format_instructions}"), + ("human", "{review_text}"), +]).partial(format_instructions=parser.get_format_instructions()) + +chain = prompt | init_chat_model("openai:gpt-4.1-mini") | parser +review = chain.invoke({"review_text": "Inception was brilliant. 9/10."}) +print(review.title, review.rating, review.summary) diff --git a/examples/slide-012/script.py b/examples/slide-012/script.py new file mode 100644 index 0000000..073dccf --- /dev/null +++ b/examples/slide-012/script.py @@ -0,0 +1,55 @@ +""" +Slide 12: with_structured_output -- рекомендованный путь +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-12.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-012/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 12 === +from pydantic import BaseModel, Field +from langchain.chat_models import init_chat_model + +class Weather(BaseModel): + city: str = Field(description="City name") + temperature_c: float = Field(description="Temperature in Celsius") + conditions: str = Field(description="Weather summary") + +# Один вызов -- и модель возвращает типизированный Pydantic-объект +model = init_chat_model("openai:gpt-4.1-mini") +structured = model.with_structured_output(Weather) + +result: Weather = structured.invoke("Weather in Paris?") +print(result.city, result.temperature_c, result.conditions) + +# method="json_mode" -- если провайдер не поддерживает tool calling +# structured_json = model.with_structured_output(Weather, method="json_mode") diff --git a/examples/slide-013/script.py b/examples/slide-013/script.py new file mode 100644 index 0000000..2c48098 --- /dev/null +++ b/examples/slide-013/script.py @@ -0,0 +1,57 @@ +""" +Slide 13: | -- декларативная композиция +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-13.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-013/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 13 === +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.output_parsers import StrOutputParser +from langchain.chat_models import init_chat_model + +prompt = ChatPromptTemplate.from_messages([ + ("system", "You are a {style} assistant."), + ("human", "{question}"), +]) +model = init_chat_model("openai:gpt-4.1-mini") + +# prompt, model, parser -- все три реализуют Runnable +# Оператор | склеивает их в один chain +chain = prompt | model | StrOutputParser() + +# type(chain) -> RunnableSequence +print(type(chain).__name__) + +# invoke -> dict на вход, str на выход +print(chain.invoke({"style": "concise", "question": "What is LCEL?"})) diff --git a/examples/slide-014/script.py b/examples/slide-014/script.py new file mode 100644 index 0000000..b58745b --- /dev/null +++ b/examples/slide-014/script.py @@ -0,0 +1,54 @@ +""" +Slide 14: invoke / batch / stream -- без смены кода +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-14.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-014/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 14 === +chain = prompt | model | StrOutputParser() + +# 1. invoke -- один вход, один выход +out_one = chain.invoke({"language": "French", "text": "Good morning"}) + +# 2. batch -- список входов, список выходов (параллельно) +out_many = chain.batch([ + {"language": "French", "text": "Good morning"}, + {"language": "German", "text": "Good morning"}, + {"language": "Spanish", "text": "Good morning"}, +]) +print(out_many) # ["Bonjour", "Guten Morgen", "Buenos dias"] + +# 3. stream -- итератор по чанкам (стримит последний Runnable) +for chunk in chain.stream({"language": "French", "text": "Stream me"}): + print(chunk, end="", flush=True) diff --git a/examples/slide-015/script.py b/examples/slide-015/script.py new file mode 100644 index 0000000..9dacd79 --- /dev/null +++ b/examples/slide-015/script.py @@ -0,0 +1,59 @@ +""" +Slide 15: async + config -- полный контроль +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-15.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-015/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 15 === +import asyncio +from langchain_core.runnables import ConfigurableField + +# Любой Runnable имеет ainvoke / astream / abatch +async def main(): + # Один async-вызов + result = await chain.ainvoke({"language": "French", "text": "Hi"}) + + # Async stream + async for chunk in chain.astream({"language": "French", "text": "Hi"}): + print(chunk, end="", flush=True) + +asyncio.run(main()) + +# configurable_fields -- параметры, которые можно переопределять +# на лету через config={"configurable": {...}} +configurable_chain = init_chat_model("openai:gpt-4.1-mini", + temperature=0.7).configurable_fields( + temperature=ConfigurableField(id="temperature"), +) +# configurable_chain.invoke(messages, config={"configurable": {"temperature": 0.0}}) diff --git a/examples/slide-016/script.py b/examples/slide-016/script.py new file mode 100644 index 0000000..bcebde3 --- /dev/null +++ b/examples/slide-016/script.py @@ -0,0 +1,57 @@ +""" +Slide 16: RunnableLambda + RunnablePassthrough +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-16.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-016/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 16 === +from langchain_core.runnables import RunnableLambda, RunnablePassthrough + +# RunnableLambda -- оборачивает произвольную функцию +upper = RunnableLambda(lambda x: x.upper()) +word_count = RunnableLambda(lambda text: {"text": text, "words": len(text.split())}) + +# RunnablePassthrough -- пропускает вход дальше (для branching) +passthrough = RunnablePassthrough() + +# Пример: текст -> uppercase -> посчитать слова -> смёрджить с оригиналом +chain = ( + word_count + | RunnablePassthrough.assign(upper=upper) +) + +result = chain.invoke("hello world from langchain") +print(result) +# {"text": "hello world from langchain", +# "words": 4, "upper": "HELLO WORLD FROM LANGCHAIN"} diff --git a/examples/slide-017/script.py b/examples/slide-017/script.py new file mode 100644 index 0000000..e1c41cb --- /dev/null +++ b/examples/slide-017/script.py @@ -0,0 +1,55 @@ +""" +Slide 17: Parallel + Branch -- fan-out и роутинг +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-17.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-017/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 17 === +from langchain_core.runnables import RunnableParallel, RunnableBranch + +# Parallel -- один вход, несколько параллельных Runnable-ов, dict на выходе +joke_chain = ChatPromptTemplate.from_template("Tell a joke about {topic}") | model +poem_chain = ChatPromptTemplate.from_template("Write a poem about {topic}") | model + +parallel = RunnableParallel(joke=joke_chain, poem=poem_chain) +result = parallel.invoke({"topic": "cats"}) +print(result.keys()) # dict_keys(["joke", "poem"]) + +# Branch -- if/else роутинг по условию +branch = RunnableBranch( + (lambda x: "code" in x["topic"].lower(), code_chain), + (lambda x: "math" in x["topic"].lower(), math_chain), + general_chain, # default +) +print(branch.invoke({"topic": "code review"})) diff --git a/examples/slide-018/script.py b/examples/slide-018/script.py new file mode 100644 index 0000000..a35583d --- /dev/null +++ b/examples/slide-018/script.py @@ -0,0 +1,63 @@ +""" +Slide 18: Vector store -> retriever -> RAG-цепочка +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-18.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-018/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 18 === +from langchain_openai import OpenAIEmbeddings +from langchain_community.vectorstores import FAISS +from langchain_core.runnables import RunnablePassthrough + +# 1. Эмбеддинги и индекс +embeddings = OpenAIEmbeddings(model="text-embedding-3-small") +docs = ["Cats are mammals.", "Python is a programming language.", + "LangChain helps build LLM apps."] +vectorstore = FAISS.from_texts(docs, embedding=embeddings) + +# 2. .as_retriever() превращает store в Runnable +retriever = vectorstore.as_retriever(search_kwargs={"k": 2}) + +# 3. RAG-цепочка через LCEL: context + question -> answer +from langchain_core.prompts import ChatPromptTemplate +from langchain.chat_models import init_chat_model + +prompt = ChatPromptTemplate.from_template( + "Answer based on context.\nContext: {context}\nQ: {question}" +) +model = init_chat_model("openai:gpt-4.1-mini") +rag = ( + {"context": retriever, "question": RunnablePassthrough()} + | prompt | model | StrOutputParser() +) diff --git a/examples/slide-019/script.py b/examples/slide-019/script.py new file mode 100644 index 0000000..972b75a --- /dev/null +++ b/examples/slide-019/script.py @@ -0,0 +1,58 @@ +""" +Slide 19: @tool -- любая функция становится инструментом +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-19.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-019/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 19 === +from langchain.tools import tool +from langchain.chat_models import init_chat_model + +@tool +def get_weather(city: str) -> str: + """Get the current weather for a given city.""" + return f"Sunny, 22C in {city}" + +@tool +def search_docs(query: str, top_k: int = 3) -> list[str]: + """Search internal documentation. Returns top_k snippets.""" + return [f"doc about {query} #{i}" for i in range(top_k)] + +# bind_tools -- модель знает, какие функции можно вызвать +model = init_chat_model("openai:gpt-4.1-mini") +bound = model.bind_tools([get_weather, search_docs]) + +result = bound.invoke("What is the weather in Paris?") +print(result.tool_calls) +# [{"name": "get_weather", "args": {"city": "Paris"}, "id": "..."}] diff --git a/examples/slide-020/script.py b/examples/slide-020/script.py new file mode 100644 index 0000000..795291a --- /dev/null +++ b/examples/slide-020/script.py @@ -0,0 +1,57 @@ +""" +Slide 20: create_agent -- один вход для всех агентов +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-20.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-020/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 20 === +from langchain.agents import create_agent +from langchain.tools import tool + +@tool +def get_weather(city: str) -> str: + """Get weather for a city.""" + return f"Sunny, 22C in {city}" + +# Один вызов вместо create_react_agent / create_openai_functions_agent / ... +agent = create_agent( + model="openai:gpt-4.1", + tools=[get_weather], + system_prompt="You are a weather assistant.", +) + +# invoke -> dict {"messages": [...]}; последнее сообщение = ответ +result = agent.invoke({"messages": [{"role": "user", + "content": "weather in Paris?"}]}) +print(result["messages"][-1].content) diff --git a/examples/slide-021/script.py b/examples/slide-021/script.py new file mode 100644 index 0000000..e79419b --- /dev/null +++ b/examples/slide-021/script.py @@ -0,0 +1,59 @@ +""" +Slide 21: Краткосрочная память: thread + checkpointer +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-21.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-021/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 21 === +from langchain.agents import create_agent +from langgraph.checkpoint.memory import InMemorySaver + +checkpointer = InMemorySaver() + +agent = create_agent( + model="openai:gpt-4.1-mini", + tools=[...], + checkpointer=checkpointer, # <-- persistent state +) + +config = {"configurable": {"thread_id": "user-42"}} + +# Первый turn +agent.invoke({"messages": [{"role": "user", + "content": "My name is Alice."}]}, config=config) + +# Второй turn -- модель помнит имя +result = agent.invoke({"messages": [{"role": "user", + "content": "What is my name?"}]}, config=config) +print(result["messages"][-1].content) # "Alice" diff --git a/examples/slide-022/script.py b/examples/slide-022/script.py new file mode 100644 index 0000000..2028b96 --- /dev/null +++ b/examples/slide-022/script.py @@ -0,0 +1,59 @@ +""" +Slide 22: Долгосрочная память: store + namespace +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-22.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-022/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 22 === +from langgraph.store.memory import InMemoryStore +from langchain.agents import create_agent +from langchain.embeddings import init_embeddings + +# Store может быть in-memory (dev) или Postgres (prod) +store = InMemoryStore( + index={"embed": init_embeddings("openai:text-embedding-3-small"), + "dims": 1536}, +) + +agent = create_agent( + model="openai:gpt-4.1-mini", + tools=[...], + store=store, +) + +# Namespace = ("user-id", "facts") -- разделение по пользователям +ns = ("user-42", "facts") +store.put(ns, "pref-1", {"text": "User prefers concise answers."}) + +# В новой сессии store.search(ns, query="preferences") вернёт факты diff --git a/examples/slide-023/script.py b/examples/slide-023/script.py new file mode 100644 index 0000000..7d14498 --- /dev/null +++ b/examples/slide-023/script.py @@ -0,0 +1,60 @@ +""" +Slide 23: Cross-cutting concerns одной строкой +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-23.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-023/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 23 === +from langchain.agents import create_agent +from langchain.agents.middleware import ( + HumanInTheLoopMiddleware, + PIIRedactionMiddleware, + SummarizationMiddleware, +) +from langchain.tools import tool + +@tool +def send_email(to: str, body: str) -> str: + """Send an email to recipient.""" + return f"sent to {to}" + +agent = create_agent( + model="openai:gpt-4.1", + tools=[send_email], + middleware=[ + HumanInTheLoopMiddleware(interrupt_on={"send_email": True}), + PIIRedactionMiddleware(redact_emails=True, redact_phones=True), + SummarizationMiddleware(trigger=("tokens", 4000)), + ], +) diff --git a/examples/slide-024/script.py b/examples/slide-024/script.py new file mode 100644 index 0000000..6c1428e --- /dev/null +++ b/examples/slide-024/script.py @@ -0,0 +1,39 @@ +""" +Slide 24: Что нового в 1.0 vs 0.3 +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-24.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-024/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 24: Что нового в 1.0 vs 0.3') diff --git a/examples/slide-025/script.py b/examples/slide-025/script.py new file mode 100644 index 0000000..8bd9784 --- /dev/null +++ b/examples/slide-025/script.py @@ -0,0 +1,54 @@ +""" +Slide 25: JS-аналог: что есть, чего нет +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-25.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-025/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 25 === +import { initChatModel } from "langchain/chat_models/universal"; +import { createAgent } from "langchain/agents"; +import { tool } from "@langchain/core/tools"; +import { z } from "zod"; + +const getWeather = tool( + async ({ city }) => `Sunny, 22C in ${city}`, + { name: "get_weather", description: "Get weather", + schema: z.object({ city: z.string() }) }, +); + +const model = await initChatModel("openai:gpt-4.1"); +const agent = createAgent({ model, tools: [getWeather] }); +const result = await agent.invoke({ + messages: [{ role: "user", content: "weather in Paris?" }], +}); diff --git a/examples/slide-026/script.py b/examples/slide-026/script.py new file mode 100644 index 0000000..2b029a7 --- /dev/null +++ b/examples/slide-026/script.py @@ -0,0 +1,39 @@ +""" +Slide 26: Когда LangChain 1.0 -- правильный выбор +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-26.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-026/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 26: Когда LangChain 1.0 -- правильный выбор') diff --git a/examples/slide-027/script.py b/examples/slide-027/script.py new file mode 100644 index 0000000..7f0d156 --- /dev/null +++ b/examples/slide-027/script.py @@ -0,0 +1,39 @@ +""" +Slide 27: Дальше: LangGraph -- явный control flow +Section 1: LangChain 1.0 +Source: slides/section1-chains/slide-27.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-027/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 27: Дальше: LangGraph -- явный control flow') diff --git a/examples/slide-028/script.py b/examples/slide-028/script.py new file mode 100644 index 0000000..de671a3 --- /dev/null +++ b/examples/slide-028/script.py @@ -0,0 +1,39 @@ +""" +Slide 28: LangGraph 1.0: stateful runtime +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-028/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 28: LangGraph 1.0: stateful runtime') diff --git a/examples/slide-029/script.py b/examples/slide-029/script.py new file mode 100644 index 0000000..723e8d7 --- /dev/null +++ b/examples/slide-029/script.py @@ -0,0 +1,39 @@ +""" +Slide 29: Slide 2 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-029/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 29: Slide 2') diff --git a/examples/slide-030/script.py b/examples/slide-030/script.py new file mode 100644 index 0000000..f64b45d --- /dev/null +++ b/examples/slide-030/script.py @@ -0,0 +1,46 @@ +""" +Slide 30: Slide 3 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-030/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 30 === + pip install -U langgraph + + # extras: postgres / sqlite checkpointers -- отдельные пакеты + pip install -U langgraph langgraph-checkpoint-postgres + pip install -U langgraph langgraph-checkpoint-sqlite + + # JS / TypeScript + npm install @langchain/langgraph @langchain/langgraph-checkpoint diff --git a/examples/slide-031/script.py b/examples/slide-031/script.py new file mode 100644 index 0000000..941daad --- /dev/null +++ b/examples/slide-031/script.py @@ -0,0 +1,59 @@ +""" +Slide 31: Slide 4 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-031/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 31 === + from typing_extensions import TypedDict + from langgraph.graph import StateGraph, START, END + +> class State(TypedDict): +> question: str +> answer: str + steps: int + + def answer(state: State) -> dict: + return {"answer": f"echo: {state['question']}", "steps": 1} + + builder = StateGraph(State) + builder.add_node("answer", answer) + builder.add_edge(START, "answer") + builder.add_edge("answer", END) + graph = builder.compile() +> +> print(graph.invoke({"question": "hi", "steps": 0})) +> # -> {'question': 'hi', 'answer': 'echo: hi', 'steps': 1} + +// note: snippet has 19 lines, card fits ~17 diff --git a/examples/slide-032/script.py b/examples/slide-032/script.py new file mode 100644 index 0000000..2631b3d --- /dev/null +++ b/examples/slide-032/script.py @@ -0,0 +1,60 @@ +""" +Slide 32: Slide 5 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-032/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 32 === + from typing import Annotated + from typing_extensions import TypedDict + from langgraph.graph import StateGraph, START, END + from langgraph.graph.message import add_messages + + class State(TypedDict): + # reducer add_messages склеивает списки сообщений по правилам +> messages: Annotated[list, add_messages] + + def echo(state: State): + last = state["messages"][-1] +> return {"messages": [{"role": "assistant", "content": f"echo: {last.content}"}]} +> + g = StateGraph(State) + g.add_node("echo", echo) + g.add_edge(START, "echo") + g.add_edge("echo", END) + app = g.compile() + + print(app.invoke({"messages": [{"role": "user", "content": "hi"}]})) + +// note: snippet has 20 lines, card fits ~18 diff --git a/examples/slide-033/script.py b/examples/slide-033/script.py new file mode 100644 index 0000000..aff94e7 --- /dev/null +++ b/examples/slide-033/script.py @@ -0,0 +1,65 @@ +""" +Slide 33: Slide 6 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-033/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 33 === + import operator + from typing import Annotated + from typing_extensions import TypedDict + from langgraph.graph import StateGraph, START, END + + class State(TypedDict): + # каждый узел возвращает кусок списка, operator.add склеивает + log: Annotated[list[str], operator.add] +> tokens: Annotated[int, operator.add] +> + def step(state: State): + return {"log": ["step-1"], "tokens": 12} + + def another(state: State): + return {"log": ["step-2"], "tokens": 7} + + g = StateGraph(State) + g.add_node("a", step) + g.add_node("b", another) + g.add_edge(START, "a") + g.add_edge("a", "b") + g.add_edge("b", END) + app = g.compile() + print(app.invoke({"log": [], "tokens": 0})) + # -> {'log': ['step-1', 'step-2'], 'tokens': 19} + +// note: snippet has 25 lines, card fits ~18 diff --git a/examples/slide-034/script.py b/examples/slide-034/script.py new file mode 100644 index 0000000..811f276 --- /dev/null +++ b/examples/slide-034/script.py @@ -0,0 +1,61 @@ +""" +Slide 34: Slide 7 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-034/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 34 === + from dataclasses import dataclass, field + from typing import Annotated + from langgraph.graph import StateGraph, START, END + from langgraph.graph.message import add_messages + + @dataclass + class State: + question: str = "" +> # dataclass + Annotated -- каналы работают точно так же +> messages: Annotated[list, add_messages] = field(default_factory=list) + approved: bool = False + + def greet(state: State): + return {"messages": [{"role": "assistant", "content": f"hi, {state.question}"}]} + + g = StateGraph(State) + g.add_node("greet", greet) + g.add_edge(START, "greet") + g.add_edge("greet", END) + app = g.compile() + print(app.invoke(State(question="alex"))) + +// note: snippet has 21 lines, card fits ~18 diff --git a/examples/slide-035/script.py b/examples/slide-035/script.py new file mode 100644 index 0000000..913c9d5 --- /dev/null +++ b/examples/slide-035/script.py @@ -0,0 +1,55 @@ +""" +Slide 35: Slide 8 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-035/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 35 === + from typing_extensions import TypedDict + from langgraph.graph import StateGraph, START, END + + class State(TypedDict): + n: int + + def inc(state: State): + return {"n": state["n"] + 1} + + builder = StateGraph(State) + builder.add_node("inc", inc) # регистрируем узел +> builder.add_edge(START, "inc") # поток входа +> builder.add_edge("inc", END) # поток выхода +> + graph = builder.compile() # компиляция -- граф готов к invoke + +> print(graph.invoke({"n": 0})) # -> {'n': 1} diff --git a/examples/slide-036/script.py b/examples/slide-036/script.py new file mode 100644 index 0000000..2ed8433 --- /dev/null +++ b/examples/slide-036/script.py @@ -0,0 +1,61 @@ +""" +Slide 36: Slide 9 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-036/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 36 === + # START и END -- это специальные sentinel-узлы, не callable + from langgraph.graph import StateGraph, START, END +> + class State(TypedDict): + text: str + + def upper(state: State): + return {"text": state["text"].upper()} + + def exclaim(state: State): + return {"text": state["text"] + "!"} + + g = StateGraph(State) +> g.add_node("upper", upper) + g.add_node("exclaim", exclaim) +> g.add_edge(START, "upper") # вход в граф +> g.add_edge("upper", "exclaim") # внутреннее ребро + g.add_edge("exclaim", END) # выход из графа + + app = g.compile() + print(app.invoke({"text": "hi"})) # -> {'text': 'HI!'} + +// note: snippet has 21 lines, card fits ~18 diff --git a/examples/slide-037/script.py b/examples/slide-037/script.py new file mode 100644 index 0000000..79e20f5 --- /dev/null +++ b/examples/slide-037/script.py @@ -0,0 +1,64 @@ +""" +Slide 37: Slide 10 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-037/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 37 === + import asyncio + from typing_extensions import TypedDict + from langgraph.graph import StateGraph, START, END + + class State(TypedDict): + out: str + + def sync_node(state: State): # обычная функция + return {"out": "sync-ok"} + +> async def async_node(state: State): # async тоже работает + await asyncio.sleep(0) +> return {"out": "async-ok"} + + g = StateGraph(State) + g.add_node("s", sync_node) + g.add_node("a", async_node) + g.add_edge(START, "s") + g.add_edge("s", "a") + g.add_edge("a", END) + app = g.compile() + + print(app.invoke({"out": ""})) + print(asyncio.run(app.ainvoke({"out": ""}))) + +// note: snippet has 24 lines, card fits ~18 diff --git a/examples/slide-038/script.py b/examples/slide-038/script.py new file mode 100644 index 0000000..786ce63 --- /dev/null +++ b/examples/slide-038/script.py @@ -0,0 +1,60 @@ +""" +Slide 38: Slide 11 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-038/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 38 === + from langgraph.graph import StateGraph, START, END + from langgraph.types import Command + from typing_extensions import TypedDict + from typing import Literal + class State(TypedDict): + n: int +> def decide(state: State) -> Command[Literal["inc", "halt"]]: +> # Command(update, goto) -- обновляет state и сам выбирает узел + if state["n"] < 3: +> return Command(update={"n": state["n"] + 1}, goto="inc") + return Command(update={}, goto="halt") + g = StateGraph(State) + g.add_node("decide", decide) + g.add_node("inc", inc) + g.add_node("halt", lambda s: s) + g.add_edge(START, "decide") + g.add_edge("inc", "decide") + g.add_edge("halt", END) + app = g.compile() + print(app.invoke({"n": 0})) + +// note: snippet has 20 lines, card fits ~18 diff --git a/examples/slide-039/script.py b/examples/slide-039/script.py new file mode 100644 index 0000000..2077b4f --- /dev/null +++ b/examples/slide-039/script.py @@ -0,0 +1,68 @@ +""" +Slide 39: Slide 12 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-039/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 39 === + from typing_extensions import TypedDict + from langgraph.graph import StateGraph, START, END + + class State(TypedDict): + needs_tool: bool + answer: str + + def agent(state: State) -> dict: + return {"answer": "model-output"} + + def tool_node(state: State) -> dict: + return {"answer": "tool-output"} + + def route(state: State) -> str: + # возвращаем ключ, который есть в path_map ниже + return "tool_node" if state["needs_tool"] else END + + g = StateGraph(State) +> g.add_node("agent", agent) +> g.add_node("tool_node", tool_node) +> g.add_edge(START, "agent") +> g.add_conditional_edges("agent", route, { +> "tool_node": "tool_node", + END: END, + }) + g.add_edge("tool_node", END) + app = g.compile() + print(app.invoke({"needs_tool": True, "answer": ""})) + +// note: snippet has 28 lines, card fits ~18 diff --git a/examples/slide-040/script.py b/examples/slide-040/script.py new file mode 100644 index 0000000..937c94e --- /dev/null +++ b/examples/slide-040/script.py @@ -0,0 +1,61 @@ +""" +Slide 40: Slide 13 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-040/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 40 === + # агентный цикл: agent <-> tools, выход когда done=true + from typing_extensions import TypedDict + from langgraph.graph import StateGraph, START, END + + class State(TypedDict): + done: bool + iter: int + + def agent(state: State) -> dict: + return {"iter": state["iter"] + 1, "done": state["iter"] >= 3} + + def maybe_continue(state: State) -> str: + return "agent" if not state["done"] else END + +> g = StateGraph(State) +> g.add_node("agent", agent) + g.add_edge(START, "agent") + g.add_conditional_edges("agent", maybe_continue, {"agent": "agent", END: END}) + app = g.compile() + print(app.invoke({"done": False, "iter": 0})) + # agent крутится 3 раза, потом уходит в END + +// note: snippet has 21 lines, card fits ~18 diff --git a/examples/slide-041/script.py b/examples/slide-041/script.py new file mode 100644 index 0000000..36053d1 --- /dev/null +++ b/examples/slide-041/script.py @@ -0,0 +1,66 @@ +""" +Slide 41: Slide 14 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-041/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 41 === + from typing import Annotated + from typing_extensions import TypedDict + from langgraph.graph import StateGraph, START, END + from langgraph.graph.message import add_messages + from langgraph.checkpoint.memory import InMemorySaver + + class State(TypedDict): + messages: Annotated[list, add_messages] + + def echo(state: State): + last = state["messages"][-1] + return {"messages": [{"role": "assistant", "content": f"echo: {last.content}"}]} + + g = StateGraph(State) + g.add_node("echo", echo) + g.add_edge(START, "echo") + g.add_edge("echo", END) + + # checkpointer -- обязателен для thread persistence + checkpointer = InMemorySaver() + app = g.compile(checkpointer=checkpointer) +> +> cfg = {"configurable": {"thread_id": "user-1"}} + app.invoke({"messages": [{"role": "user", "content": "hi"}]}, cfg) +> app.invoke({"messages": [{"role": "user", "content": "again"}]}, cfg) +> # второй вызов видит все сообщения первого: thread persistence работает + +// note: snippet has 26 lines, card fits ~18 diff --git a/examples/slide-042/script.py b/examples/slide-042/script.py new file mode 100644 index 0000000..77f0b0b --- /dev/null +++ b/examples/slide-042/script.py @@ -0,0 +1,59 @@ +""" +Slide 42: Slide 15 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-042/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 42 === +> # SQLite -- файл на диске, идеален для dev / small-prod + from langgraph.checkpoint.sqlite import SqliteSaver +> +> with SqliteSaver.from_conn_string("./checkpoints.db") as cp: +> app = g.compile(checkpointer=cp) + cfg = {"configurable": {"thread_id": "u1"}} + app.invoke({"messages": []}, cfg) + + # данные переживают рестарт процесса. + # Для asyncio-варианта -- aiosqlite. + +> # Postgres -- production-grade, multi-instance + from langgraph.checkpoint.postgres import PostgresSaver +> + DB = "postgresql://user:pass@host:5432/lg" + with PostgresSaver.from_conn_string(DB) as cp: +> # первый запуск создаст schema + cp.setup() + app = g.compile(checkpointer=cp) + cfg = {"configurable": {"thread_id": "u1"}} + app.invoke({"messages": []}, cfg) diff --git a/examples/slide-043/script.py b/examples/slide-043/script.py new file mode 100644 index 0000000..97fd946 --- /dev/null +++ b/examples/slide-043/script.py @@ -0,0 +1,50 @@ +""" +Slide 43: Slide 16 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-043/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 43 === + # После invoke можно достать текущий snapshot: +> snapshot = app.get_state(cfg) + + # snapshot -- это StateSnapshot с полями: +> # .config -- thread_id + checkpoint_id +> # .metadata -- step, source, writes +> # .values -- текущие значения всех каналов state +> # .next -- tuple узлов, которые будут выполняться следующими +> # .tasks -- PregelTask с pending/result/error + + print(snapshot.next) # () -- граф завершён + print(snapshot.values["messages"][-1].content) diff --git a/examples/slide-044/script.py b/examples/slide-044/script.py new file mode 100644 index 0000000..e075514 --- /dev/null +++ b/examples/slide-044/script.py @@ -0,0 +1,50 @@ +""" +Slide 44: Slide 17 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-044/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 44 === + # Каждый super-step -- отдельный checkpoint в thread +> history = list(app.get_state_history(cfg)) +> +> # history[0] -- последний шаг (самый свежий) + # history[-1] -- самый первый (начало сессии) + +> for i, snap in enumerate(history): + print(i, snap.metadata.get("step"), snap.values.get("iter")) + +> # replays = форк от любого прошлого snapshot: +> old = history[2].config + app.invoke(None, old) # переигрывает только следующие шаги diff --git a/examples/slide-045/script.py b/examples/slide-045/script.py new file mode 100644 index 0000000..2668c52 --- /dev/null +++ b/examples/slide-045/script.py @@ -0,0 +1,50 @@ +""" +Slide 45: Slide 18 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-045/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 45 === + # patch значения канала прямо в snapshot -- создаётся новый checkpoint +> app.update_state( +> cfg, +> values={"messages": [{"role": "user", "content": "rewind"}]}, +> as_node="user_input", # от имени какого узла пишем + ) + + # Дальше invoke(None, cfg) переигрывает граф с нового состояния + app.invoke(None, cfg) +> +> # Типичный приём: "что если пользователь сказал не X, а Y?" -- + # ответвляемся, смотрим альтернативный прогон без потери истории. diff --git a/examples/slide-046/script.py b/examples/slide-046/script.py new file mode 100644 index 0000000..a2435a5 --- /dev/null +++ b/examples/slide-046/script.py @@ -0,0 +1,60 @@ +""" +Slide 46: Slide 19 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-046/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 46 === + from langgraph.types import interrupt, Command + from langgraph.graph import StateGraph, START, END + from langgraph.checkpoint.memory import InMemorySaver + from typing_extensions import TypedDict + class State(TypedDict): + question: str + approved: bool + def ask(state: State): + # interrupt() -- пауза. Возвращает значение из Command(resume=...) +> answer = interrupt({"question": "Approve sending this message?"}) + return {"approved": answer == "yes"} + g = StateGraph(State) + g.add_node("ask", ask) + g.add_edge(START, "ask") + g.add_edge("ask", END) + app = g.compile(checkpointer=InMemorySaver()) +> cfg = {"configurable": {"thread_id": "approval-1"}} +> app.invoke({"question": "send email", "approved": False}, cfg) # пауза +> result = app.invoke(Command(resume="yes"), cfg) # resume + print(result["approved"]) # -> True + +// note: snippet has 20 lines, card fits ~18 diff --git a/examples/slide-047/script.py b/examples/slide-047/script.py new file mode 100644 index 0000000..1a6beb0 --- /dev/null +++ b/examples/slide-047/script.py @@ -0,0 +1,39 @@ +""" +Slide 47: Slide 20 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-047/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 47: Slide 20') diff --git a/examples/slide-048/script.py b/examples/slide-048/script.py new file mode 100644 index 0000000..abba55b --- /dev/null +++ b/examples/slide-048/script.py @@ -0,0 +1,56 @@ +""" +Slide 48: Slide 21 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-048/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 48 === + # Узел review -- маршрутизация по ответу человека + from langgraph.types import interrupt, Command + from typing_extensions import TypedDict + class State(TypedDict): + plan: str + executed: bool + def plan(state: State): + return {"plan": "step-A; step-B; step-C"} +> def review(state: State) -> Command: +> # interrupt() возвращает ответ из Command(resume=...) +> decision = interrupt({"plan": state["plan"]}) +> if decision == "approve": +> return Command(goto="execute") +> if decision == "abort": +> return Command(goto=END) + return Command(goto="plan") # перепланировать + def execute(state: State): + return {"executed": True} diff --git a/examples/slide-049/script.py b/examples/slide-049/script.py new file mode 100644 index 0000000..96cb5ad --- /dev/null +++ b/examples/slide-049/script.py @@ -0,0 +1,55 @@ +""" +Slide 49: Slide 22 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-049/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 49 === + from langgraph.graph import StateGraph, START, END + from langgraph.checkpoint.memory import InMemorySaver + # plan, review, execute -- из предыдущего слайда + g = StateGraph(State) +> g.add_node("plan", plan) +> g.add_node("review", review) + g.add_node("execute", execute) +> g.add_edge(START, "plan") + g.add_edge("plan", "review") + g.add_edge("execute", END) + app = g.compile(checkpointer=InMemorySaver()) + + # Цикл: каждый review -- это пауза; Command(goto=...) -- ответвление +> # plan -> review -> (approve: execute | abort: END | else: plan) +> cfg = {"configurable": {"thread_id": "u1"}} + app.invoke({"plan": "", "executed": False}, cfg) # пауза 1 + app.invoke(Command(resume="approve"), cfg) # resume -> execute diff --git a/examples/slide-050/script.py b/examples/slide-050/script.py new file mode 100644 index 0000000..9f2fcb9 --- /dev/null +++ b/examples/slide-050/script.py @@ -0,0 +1,66 @@ +""" +Slide 50: Slide 23 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-050/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 50 === + from typing_extensions import TypedDict + from langgraph.graph import StateGraph, START, END + + class SubState(TypedDict): + internal: str + + def inner(state: SubState): + return {"internal": "sub-output"} + + # Собираем subgraph -- у него свой state +> sub = StateGraph(SubState) +> sub.add_node("inner", inner) +> sub.add_edge(START, "inner") +> sub.add_edge("inner", END) + sub_compiled = sub.compile() + + # Вставляем как обычный узел в родительский граф + class ParentState(TypedDict): + out: str + +> parent = StateGraph(ParentState) +> parent.add_node("sub_block", sub_compiled) # <- subgraph целиком + parent.add_edge(START, "sub_block") + parent.add_edge("sub_block", END) + app = parent.compile() + print(app.invoke({"out": ""})) + +// note: snippet has 26 lines, card fits ~18 diff --git a/examples/slide-051/script.py b/examples/slide-051/script.py new file mode 100644 index 0000000..6c29706 --- /dev/null +++ b/examples/slide-051/script.py @@ -0,0 +1,54 @@ +""" +Slide 51: Slide 24 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-051/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 51 === + cfg = {"configurable": {"thread_id": "u1"}} + # values -- весь state после каждого super-step +> for snap in app.stream({"messages": []}, cfg, stream_mode="values"): + print(snap["messages"][-1].content) + # updates -- delta: только что вернул каждый узел +> for upd in app.stream({"messages": []}, cfg, stream_mode="updates"): + print(upd) + # events -- низкоуровневые события (start, end, error, interrupt) +> for ev in app.stream({"messages": []}, cfg, stream_mode="events"): + print(ev["event"], ev["name"]) + # messages -- токены LLM по мере генерации +> for tok, meta in app.stream({"messages": []}, cfg, stream_mode="messages"): + print(tok.content, end="|") + # custom -- только то, что узлы пишут через get_stream_writer() +> for chunk in app.stream({"messages": []}, cfg, stream_mode="custom"): + print(chunk) diff --git a/examples/slide-052/script.py b/examples/slide-052/script.py new file mode 100644 index 0000000..a669aac --- /dev/null +++ b/examples/slide-052/script.py @@ -0,0 +1,62 @@ +""" +Slide 52: Slide 25 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-052/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 52 === + from langgraph.graph import StateGraph, START, END + from langgraph.config import get_stream_writer + from typing_extensions import TypedDict + + class State(TypedDict): + total: int + + def progress(state: State): + writer = get_stream_writer() # доступен только во время выполнения узла +> for i in range(3): +> writer({"progress": i, "phase": "thinking"}) +> return {"total": 3} + + g = StateGraph(State) + g.add_node("progress", progress) + g.add_edge(START, "progress") + g.add_edge("progress", END) + app = g.compile() +> +> # в UI -- только custom-чанки, без промежуточного state + for chunk in app.stream({"total": 0}, stream_mode="custom"): + print(chunk) + +// note: snippet has 22 lines, card fits ~18 diff --git a/examples/slide-053/script.py b/examples/slide-053/script.py new file mode 100644 index 0000000..b3ba71c --- /dev/null +++ b/examples/slide-053/script.py @@ -0,0 +1,56 @@ +""" +Slide 53: Slide 26 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-053/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 53 === + from langchain_openai import ChatOpenAI + from langchain.tools import tool + from langgraph.graph.message import add_messages + from typing import Annotated + from typing_extensions import TypedDict + @tool + def add(a: int, b: int) -> int: + "Add two numbers." + return a + b + tools = [add] +> llm = ChatOpenAI(model="gpt-4o-mini").bind_tools(tools) + class State(TypedDict): + messages: Annotated[list, add_messages] + def agent(state: State): + return {"messages": [llm.invoke(state["messages"])]} + def route(state: State) -> str: +> last = state["messages"][-1] + return "tools" if getattr(last, "tool_calls", None) else END diff --git a/examples/slide-054/script.py b/examples/slide-054/script.py new file mode 100644 index 0000000..c92f34b --- /dev/null +++ b/examples/slide-054/script.py @@ -0,0 +1,54 @@ +""" +Slide 54: Slide 27 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-054/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 54 === + from langgraph.graph import StateGraph, START, END + from langgraph.prebuilt import ToolNode + # tools, llm, agent, route -- из предыдущего слайда + g = StateGraph(State) + g.add_node("agent", agent) +> g.add_node("tools", ToolNode(tools)) + g.add_edge(START, "agent") + g.add_conditional_edges("agent", route, {"tools": "tools", END: END}) + g.add_edge("tools", "agent") +> app = g.compile() + + # Цикл: agent решает -> tools исполняет -> agent снова читает результат +> result = app.invoke({ +> "messages": [{"role": "user", "content": "What is 2 + 3?"}] + }) + print(result["messages"][-1].content) diff --git a/examples/slide-055/script.py b/examples/slide-055/script.py new file mode 100644 index 0000000..2f71831 --- /dev/null +++ b/examples/slide-055/script.py @@ -0,0 +1,39 @@ +""" +Slide 55: Slide 28 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-055/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 55 === + # запуск: langgraph dev -- поднимает Studio на http://localhost:8123 diff --git a/examples/slide-056/script.py b/examples/slide-056/script.py new file mode 100644 index 0000000..5c2cb34 --- /dev/null +++ b/examples/slide-056/script.py @@ -0,0 +1,49 @@ +""" +Slide 56: Slide 29 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-056/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 56 === + # langgraph.json -- declarative config +> { + "graphs": {"agent": "./agent.py:graph"}, + "env": "./.env", + "python_version": "3.11" + } + + # CLI: локальный dev-сервер и deploy + langgraph dev # локальный API + Studio +> langgraph up # Docker-compose stack (Redis + API + Studio) + langgraph deploy # пуш в LangGraph Platform (managed) diff --git a/examples/slide-057/script.py b/examples/slide-057/script.py new file mode 100644 index 0000000..23d2422 --- /dev/null +++ b/examples/slide-057/script.py @@ -0,0 +1,39 @@ +""" +Slide 57: Slide 30 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-057/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 57: Slide 30') diff --git a/examples/slide-058/script.py b/examples/slide-058/script.py new file mode 100644 index 0000000..1513dcd --- /dev/null +++ b/examples/slide-058/script.py @@ -0,0 +1,60 @@ +""" +Slide 58: Slide 31 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-058/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 58 === +> import { StateGraph, START, END, Annotation } from "@langchain/langgraph"; +> import { MemorySaver } from "@langchain/langgraph-checkpoint"; + + const State = Annotation.Root({ + messages: Annotation({ + reducer: (a, b) => a.concat(b), + default: () => [], + }), + }); + + const g = new StateGraph(State) + .addNode("echo", (s) => ({ + messages: [{ role: "assistant", content: "echo: " + s.messages.at(-1).content }], + })) + .addEdge(START, "echo") + .addEdge("echo", END); + + const app = g.compile({ checkpointer: new MemorySaver() }); +> const cfg = { configurable: { thread_id: "t1" } }; + const result = await app.invoke({ messages: [{ role: "user", content: "hi" }] }, cfg); + +// note: snippet has 20 lines, card fits ~18 diff --git a/examples/slide-059/script.py b/examples/slide-059/script.py new file mode 100644 index 0000000..4ce87d5 --- /dev/null +++ b/examples/slide-059/script.py @@ -0,0 +1,39 @@ +""" +Slide 59: Slide 32 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-059/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 59: Slide 32') diff --git a/examples/slide-060/script.py b/examples/slide-060/script.py new file mode 100644 index 0000000..05f4119 --- /dev/null +++ b/examples/slide-060/script.py @@ -0,0 +1,39 @@ +""" +Slide 60: Slide 33 +Section 2: LangGraph 1.0 +Source: slides/section2-langgraph/section2.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-060/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 60: Slide 33') diff --git a/examples/slide-061/script.py b/examples/slide-061/script.py new file mode 100644 index 0000000..6373ace --- /dev/null +++ b/examples/slide-061/script.py @@ -0,0 +1,39 @@ +""" +Slide 61: Deep Agents 1.0 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-061/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 61: Deep Agents 1.0') diff --git a/examples/slide-062/script.py b/examples/slide-062/script.py new file mode 100644 index 0000000..05593ac --- /dev/null +++ b/examples/slide-062/script.py @@ -0,0 +1,39 @@ +""" +Slide 62: Slide 2 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-062/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 62: Slide 2') diff --git a/examples/slide-063/script.py b/examples/slide-063/script.py new file mode 100644 index 0000000..3ae8c93 --- /dev/null +++ b/examples/slide-063/script.py @@ -0,0 +1,56 @@ +""" +Slide 63: Slide 3 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-063/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 63 === +// examples/hello.py:1-14 + + # One import, one call -- you get: + # - write_todos planning tool + # - ls / read_file / write_file / edit_file + # - glob, grep, execute (bash) + # - task tool for subagents + # - summarization middleware + + from deepagents import create_deep_agent +> +> agent = create_deep_agent( +> model="openai:gpt-4.1", +> tools=[my_tool], +> system_prompt="...", +> ) + +// note: snippet has 14 lines, card fits ~10 diff --git a/examples/slide-064/script.py b/examples/slide-064/script.py new file mode 100644 index 0000000..4e69c4f --- /dev/null +++ b/examples/slide-064/script.py @@ -0,0 +1,47 @@ +""" +Slide 64: Slide 4 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-064/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 64 === +// setup.sh:1-5 + + pip install deepagents + # or, with uv: + uv add deepagents + # JS analogue: + npm install deepagents + +// note: snippet has 5 lines, card fits ~3 diff --git a/examples/slide-065/script.py b/examples/slide-065/script.py new file mode 100644 index 0000000..559bae7 --- /dev/null +++ b/examples/slide-065/script.py @@ -0,0 +1,52 @@ +""" +Slide 65: Slide 5 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-065/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 65 === +// examples/hello.py:1-12 + + from deepagents import create_deep_agent + +> agent = create_deep_agent( +> model="openai:gpt-4.1", +> tools=[], +> system_prompt="You are a helpful assistant.", + ) + + result = agent.invoke({ + "messages": "Write a haiku about Python" + }) + print(result["messages"][-1].content) diff --git a/examples/slide-066/script.py b/examples/slide-066/script.py new file mode 100644 index 0000000..7f2dd04 --- /dev/null +++ b/examples/slide-066/script.py @@ -0,0 +1,55 @@ +""" +Slide 66: Slide 6 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-066/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 66 === +// examples/full_api.py:1-15 + + from deepagents import create_deep_agent + from deepagents.backends import FilesystemBackend + +> agent = create_deep_agent( +> model="anthropic:claude-sonnet-4-5", +> tools=[my_tool], +> system_prompt="...", +> subagents=[researcher, writer], +> skills=["./skills/review.md"], +> backend=FilesystemBackend("./ws"), +> middleware=[my_hitl, my_logger], +> checkpointer=InMemorySaver(), +> store=InMemoryStore(), +> interrupt_on={"bash": True}, + ) diff --git a/examples/slide-067/script.py b/examples/slide-067/script.py new file mode 100644 index 0000000..20fb98d --- /dev/null +++ b/examples/slide-067/script.py @@ -0,0 +1,56 @@ +""" +Slide 67: Slide 7 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-067/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 67 === +// examples/system_prompt.py:1-16 + + SYSTEM_PROMPT = """ + You are a senior backend engineer. + +> WORKFLOW: +> 1. Plan with write_todos before non-trivial work. +> 2. Explore the codebase via ls / glob / grep first. +> 3. Use edit_file for surgical changes, write_file for new files. +> 4. Delegate research tasks to the "researcher" subagent. + 5. Run tests via execute; never claim success without output. + + CONSTRAINTS: +> - Do not modify files outside ./src. +> - Stop and ask the user if requirements are ambiguous. + """ + + agent = create_deep_agent(model=..., system_prompt=SYSTEM_PROMPT) diff --git a/examples/slide-068/script.py b/examples/slide-068/script.py new file mode 100644 index 0000000..7453f91 --- /dev/null +++ b/examples/slide-068/script.py @@ -0,0 +1,55 @@ +""" +Slide 68: Slide 8 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-068/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 68 === +write_todos + +ls + +read_file + +write_file + +edit_file + +glob + +grep + +execute + +task diff --git a/examples/slide-069/script.py b/examples/slide-069/script.py new file mode 100644 index 0000000..1e15d4e --- /dev/null +++ b/examples/slide-069/script.py @@ -0,0 +1,51 @@ +""" +Slide 69: Slide 9 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-069/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 69 === +// examples/fs_tools.py:1-11 + + # The agent calls these tools by name; + # you describe the goal in the prompt. + +> PROMPT = """ +> 1. Write src/hello.py with a greet(name) function. +> 2. Use edit_file to add a docstring to greet(). +> 3. Use write_file to add tests/test_hello.py. + """ + + agent = create_deep_agent(model="openai:gpt-4.1") + agent.invoke({"messages": PROMPT}) diff --git a/examples/slide-070/script.py b/examples/slide-070/script.py new file mode 100644 index 0000000..eb37b6b --- /dev/null +++ b/examples/slide-070/script.py @@ -0,0 +1,54 @@ +""" +Slide 70: Slide 10 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-070/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 70 === +// examples/write_todos.py:1-12 + + write_todos( +> todos=[ +> {"content": "Read repo structure", "status": "in_progress", +> "activeForm": "Reading repo structure"}, +> {"content": "Implement greet()", "status": "pending", +> "activeForm": "Implementing greet()"}, +> {"content": "Add pytest cases", "status": "pending", +> "activeForm": "Adding pytest cases"}, +> ] + ) + # Status: pending | in_progress | completed + # activeForm: present-continuous shown in UI + +// note: snippet has 12 lines, card fits ~10 diff --git a/examples/slide-071/script.py b/examples/slide-071/script.py new file mode 100644 index 0000000..1faf014 --- /dev/null +++ b/examples/slide-071/script.py @@ -0,0 +1,39 @@ +""" +Slide 71: Slide 11 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-071/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 71: Slide 11') diff --git a/examples/slide-072/script.py b/examples/slide-072/script.py new file mode 100644 index 0000000..331b27e --- /dev/null +++ b/examples/slide-072/script.py @@ -0,0 +1,50 @@ +""" +Slide 72: Slide 12 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-072/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 72 === +// examples/task_call.py:1-10 + + # When the main agent emits a tool call like: +> task( +> subagent_type="researcher", +> description="Find papers on RAG evaluation", +> prompt="Search arXiv for 2025-2026 RAG evaluation surveys. +> Return a 150-word summary with 3 citations.", + ) + + # Deep Agents spins up a fresh deep agent with the researcher profile, + # runs it to completion, and returns only the final message. diff --git a/examples/slide-073/script.py b/examples/slide-073/script.py new file mode 100644 index 0000000..49d2e4a --- /dev/null +++ b/examples/slide-073/script.py @@ -0,0 +1,65 @@ +""" +Slide 73: Slide 13 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-073/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 73 === +// examples/subagents.py:1-23 + + from deepagents import create_deep_agent + + researcher = { +> "name": "researcher", +> "description": "Does deep web research, returns citations", +> "system_prompt": "You are a research specialist. Always cite sources.", +> "tools": [web_search], # optional, can be [] + } + + writer = { +> "name": "writer", +> "description": "Polishes prose into a final report", +> "system_prompt": "You are a writing specialist.", +> "tools": [], + } + + agent = create_deep_agent( + model="openai:gpt-4.1", +> tools=[], +> subagents=[researcher, writer], +> ) +> + agent.invoke({"messages": "Research quantum computing and write a 200-word summary."}) + +// note: snippet has 23 lines, card fits ~16 diff --git a/examples/slide-074/script.py b/examples/slide-074/script.py new file mode 100644 index 0000000..7b3ca51 --- /dev/null +++ b/examples/slide-074/script.py @@ -0,0 +1,58 @@ +""" +Slide 74: Slide 14 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-074/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 74 === +context = [ + user task, + summary from researcher, + summary from writer +] + +task("researcher") + +context = [ + full web_search results, + all 14 sources, + notes, drafts, citations +] +return: 150-word summary + +context = [ + researcher summary, + original user task +] +return: polished 200-word report diff --git a/examples/slide-075/script.py b/examples/slide-075/script.py new file mode 100644 index 0000000..d2feacb --- /dev/null +++ b/examples/slide-075/script.py @@ -0,0 +1,49 @@ +""" +Slide 75: Slide 15 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-075/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 75 === +// examples/virtual_fs.py:1-9 + + # Inspect the virtual filesystem after a run: + result = agent.invoke({"messages": "Summarize repo"}) +> +> files = result.get("files", {}) +> for path, doc in files.items(): +> print(f"{path}: {len(doc.get('content', []))} bytes") + + # /repo/src/main.py: 421 bytes + # /repo/README.md: 1804 bytes diff --git a/examples/slide-076/script.py b/examples/slide-076/script.py new file mode 100644 index 0000000..d618b0c --- /dev/null +++ b/examples/slide-076/script.py @@ -0,0 +1,45 @@ +""" +Slide 76: Slide 16 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-076/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 76 === +before_model + +after_model + +before_tool + +after_tool diff --git a/examples/slide-077/script.py b/examples/slide-077/script.py new file mode 100644 index 0000000..671d541 --- /dev/null +++ b/examples/slide-077/script.py @@ -0,0 +1,60 @@ +""" +Slide 77: Slide 17 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-077/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 77 === +// examples/middleware.py:1-18 + + from langchain.agents.middleware import AgentMiddleware + from deepagents import create_deep_agent + + class LoggerMiddleware(AgentMiddleware): +> def before_model(self, state, runtime): +> print(f"[model] {len(state['messages'])} msgs in") +> return state +> +> def after_tool(self, state, runtime, tool_result): +> print(f"[tool] {tool_result.tool_call_id} -> {len(str(tool_result.content))} chars") + return state + + agent = create_deep_agent( + model="openai:gpt-4.1", + middleware=[LoggerMiddleware(), HumanInTheLoopMiddleware( +> interrupt_on={"execute": True}, # ask before running bash +> )], +> ) + +// note: snippet has 18 lines, card fits ~16 diff --git a/examples/slide-078/script.py b/examples/slide-078/script.py new file mode 100644 index 0000000..d4ae75b --- /dev/null +++ b/examples/slide-078/script.py @@ -0,0 +1,39 @@ +""" +Slide 78: Slide 18 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-078/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 78: Slide 18') diff --git a/examples/slide-079/script.py b/examples/slide-079/script.py new file mode 100644 index 0000000..91e805c --- /dev/null +++ b/examples/slide-079/script.py @@ -0,0 +1,62 @@ +""" +Slide 79: Slide 19 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-079/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 79 === +// examples/composite_backend.py:1-20 + + from deepagents import create_deep_agent + from deepagents.backends import ( + CompositeBackend, FilesystemBackend, StoreBackend + ) + from langgraph.store.memory import InMemoryStore + + store = InMemoryStore() # or PostgresStore in prod + + backend = CompositeBackend( + default=FilesystemBackend(root_dir="./workspace"), + routes={ +> "/memory/": StoreBackend(store=store, namespace=("agent", "kb")), +> "/scratch/": FilesystemBackend(root_dir="/tmp/scratch"), +> }, +> ) + + agent = create_deep_agent(model=..., backend=backend) + # /workspace/notes.md -> local disk + # /memory/lessons.md -> Postgres, shared across sessions +> # /scratch/tmp.py -> ephemeral tmpfs + +// note: snippet has 20 lines, card fits ~16 diff --git a/examples/slide-080/script.py b/examples/slide-080/script.py new file mode 100644 index 0000000..5d4dbe4 --- /dev/null +++ b/examples/slide-080/script.py @@ -0,0 +1,65 @@ +""" +Slide 80: Slide 20 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-080/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 80 === +// examples/hitl.py:1-23 + + from langchain.agents.middleware import HumanInTheLoopMiddleware + from deepagents import create_deep_agent + from langgraph.checkpoint.memory import InMemorySaver + from langgraph.types import Command + + agent = create_deep_agent( + model="openai:gpt-4.1", + checkpointer=InMemorySaver(), +> middleware=[HumanInTheLoopMiddleware( +> interrupt_on={ +> "execute": True, # bash -- always ask +> "write_file": True, # disk writes -- always ask +> "task": False, # subagents -- run unattended +> }, + )], + ) + + cfg = {"configurable": {"thread_id": "user-42"}} + try: + agent.invoke({"messages": "deploy to staging"}, cfg) + except InterruptedError: + decision = ask_user("Approve execute()?") # your UI + agent.invoke(Command(resume=decision), cfg) + +// note: snippet has 23 lines, card fits ~15 diff --git a/examples/slide-081/script.py b/examples/slide-081/script.py new file mode 100644 index 0000000..05d34ab --- /dev/null +++ b/examples/slide-081/script.py @@ -0,0 +1,60 @@ +""" +Slide 81: Slide 21 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-081/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 81 === +// examples/streaming.py:1-18 + + cfg = {"configurable": {"thread_id": "user-42"}} + + # 1. Stream model tokens as they arrive +> for token, meta in agent.stream( +> {"messages": "..."}, cfg, +> stream_mode="messages", + ): + print(token.content, end="", flush=True) + + # 2. Stream state updates per node + for chunk in agent.stream( +> {"messages": "..."}, cfg, +> stream_mode="updates", +> ): + print(chunk) # {"model": {...}, "tools": {...}} + + # 3. Subagent streams are surfaced as + # {"subagent": {"name": "researcher", "chunk": ...}} + +// note: snippet has 18 lines, card fits ~16 diff --git a/examples/slide-082/script.py b/examples/slide-082/script.py new file mode 100644 index 0000000..38a8e3d --- /dev/null +++ b/examples/slide-082/script.py @@ -0,0 +1,48 @@ +""" +Slide 82: Slide 22 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-082/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 82 === +// examples/langsmith.sh:1-8 + + export LANGSMITH_TRACING=true + export LANGSMITH_API_KEY=lsv2_... + export LANGSMITH_PROJECT=deepagents-evals + + python my_deep_agent.py + # -> all runs visible in smith.langchain.com + # -> subagent runs nested under the parent + # -> token usage, latency, tool errors captured diff --git a/examples/slide-083/script.py b/examples/slide-083/script.py new file mode 100644 index 0000000..2bac656 --- /dev/null +++ b/examples/slide-083/script.py @@ -0,0 +1,66 @@ +""" +Slide 83: Slide 23 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-083/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 83 === +// examples/research_agent.py:1-24 + + from langchain.tools import tool + from deepagents import create_deep_agent + + @tool + def arxiv_search(query: str, max_results: int = 5) -> str: + """Search arXiv for papers matching the query.""" + import arxiv + client = arxiv.Client() + results = list(client.results(arxiv.Search(query=query, max_results=max_results))) + return "\n\n".join( + f"{r.title}\n{r.summary[:300]}..." for r in results + ) + + agent = create_deep_agent( + model="openai:gpt-4.1", + tools=[arxiv_search], + system_prompt=("You are a research assistant. Always cite paper titles and arXiv IDs."), +> subagents=[{ +> "name": "summarizer", +> "description": "Compresses paper abstracts into a paragraph", +> "system_prompt": "You are a precise summarizer.", +> "tools": [], +> }], +> ) + +// note: snippet has 24 lines, card fits ~16 diff --git a/examples/slide-084/script.py b/examples/slide-084/script.py new file mode 100644 index 0000000..ac7821b --- /dev/null +++ b/examples/slide-084/script.py @@ -0,0 +1,59 @@ +""" +Slide 84: Slide 24 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-084/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 84 === +// examples/coding_agent.py:1-17 + + from deepagents import create_deep_agent + from deepagents.backends import SandboxBackend + +> agent = create_deep_agent( +> model="anthropic:claude-sonnet-4-5", +> backend=SandboxBackend( +> provider="daytona", # or modal / runloop +> api_key=os.environ["DAYTONA_API_KEY"], +> image="python:3.12-slim", +> ), +> system_prompt=("You are a coding agent. Always run tests after edits. Stop and ask if requirements are ambiguous."), + ) + + agent.invoke({"messages": "Add a /healthz endpoint to the FastAPI app, with tests."}) + + # Daytona/Modal/Runloop execute code in an isolated container; + # the local process never sees a stray rm -rf. + +// note: snippet has 17 lines, card fits ~16 diff --git a/examples/slide-085/script.py b/examples/slide-085/script.py new file mode 100644 index 0000000..c4246de --- /dev/null +++ b/examples/slide-085/script.py @@ -0,0 +1,53 @@ +""" +Slide 85: Slide 25 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-085/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 85 === +// examples/deepagentsjs.ts:1-13 + + // TypeScript analogue (deepagentsjs) + import { createDeepAgent } from "deepagents"; + import { tool, z } from "@langchain/core/tools"; + + const search = tool( + async ({ q }) => fetch("/api/search?q=" + q).then(r => r.text()), + { name: "search", schema: z.object({ q: z.string() }) }, + ); + +> const agent = await createDeepAgent({ +> model: "openai:gpt-4.1", +> tools: [search], +> }); diff --git a/examples/slide-086/script.py b/examples/slide-086/script.py new file mode 100644 index 0000000..0cb0dea --- /dev/null +++ b/examples/slide-086/script.py @@ -0,0 +1,39 @@ +""" +Slide 86: Slide 26 +Section 3: Deep Agents +Source: slides/section3-deepagents/section3.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-086/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 86: Slide 26') diff --git a/examples/slide-087/script.py b/examples/slide-087/script.py new file mode 100644 index 0000000..7f746c8 --- /dev/null +++ b/examples/slide-087/script.py @@ -0,0 +1,39 @@ +""" +Slide 87: Open SWE: async coding agent +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-087/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 87: Open SWE: async coding agent') diff --git a/examples/slide-088/script.py b/examples/slide-088/script.py new file mode 100644 index 0000000..68a0a51 --- /dev/null +++ b/examples/slide-088/script.py @@ -0,0 +1,52 @@ +""" +Slide 88: Slide 2 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-088/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 88 === +// meta: open-swe repo:1-12 + + # github.com/langchain-ai/open-swe + + stars: ~10k + commits: 971+ + license: MIT + language: Python + TypeScript + announce: 08.2025 + rewrite: 03.2026 + + # blog.langchain.com/open-swe + # INSTALLATION.md + # CUSTOMIZATION.md diff --git a/examples/slide-089/script.py b/examples/slide-089/script.py new file mode 100644 index 0000000..63b237b --- /dev/null +++ b/examples/slide-089/script.py @@ -0,0 +1,65 @@ +""" +Slide 89: Slide 3 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-089/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 89 === +Slack / Linear / GitHub / Web UI + +prompt + middleware (HITL, approval) + +subagents + middleware + +AGENTS.md из репозитория + +execute, fetch_url, linear_comment, slack_thread_reply + +Modal / Daytona / Runloop / LangSmith + +create_deep_agent (Deep Agents) + +// open_swe/__init__.py:1-11 + + from open_swe.agent import create_agent + from open_swe.middleware import ( + check_message_queue_before_model, + notify_step_limit_reached, + open_pr_if_needed, + ToolErrorMiddleware, + ) + from open_swe.sandbox import ( + SandboxBackend, + ModalBackend, DaytonaBackend, + ) diff --git a/examples/slide-090/script.py b/examples/slide-090/script.py new file mode 100644 index 0000000..1c035ce --- /dev/null +++ b/examples/slide-090/script.py @@ -0,0 +1,51 @@ +""" +Slide 90: Slide 4 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-090/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 90 === +// examples/minimal_agent.py:1-11 + + from deepagents import create_deep_agent + from open_swe.sandbox import DaytonaBackend + +> agent = create_deep_agent( + model="anthropic:claude-opus-4-6", + tools=[execute, fetch_url, + linear_comment, +> slack_thread_reply], +> backend=DaytonaBackend(api_key="..."), + middleware=[open_pr_if_needed], + ) diff --git a/examples/slide-091/script.py b/examples/slide-091/script.py new file mode 100644 index 0000000..f9d8c19 --- /dev/null +++ b/examples/slide-091/script.py @@ -0,0 +1,59 @@ +""" +Slide 91: Slide 5 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-091/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 91 === +// open_swe/prompts.py:1-9 + + from pathlib import Path + + def construct_system_prompt( + repo_dir, base_prompt + ): + agents_md = Path(repo_dir) / "AGENTS.md" + extra = agents_md.read_text() if + agents_md.exists() else "" + return base_prompt + extra + +// note: snippet has 9 lines, card fits ~6 + +// AGENTS.md:1-5 + + # AGENTS.md (repo root) + + - use uv, not pip + - run pytest before commit + - never push to main directly diff --git a/examples/slide-092/script.py b/examples/slide-092/script.py new file mode 100644 index 0000000..770088d --- /dev/null +++ b/examples/slide-092/script.py @@ -0,0 +1,53 @@ +""" +Slide 92: Slide 6 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-092/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 92 === +// examples/audit_middleware.py:1-13 + + from langchain.agents.middleware import ( + AgentMiddleware, + ) + +> class AuditMiddleware(AgentMiddleware): +> def after_model(self, state, runtime): +> runtime.logger.info( +> f"step={state.get("step")}" + ) + return state + + def before_model(self, state, runtime): + return state # inject reminder diff --git a/examples/slide-093/script.py b/examples/slide-093/script.py new file mode 100644 index 0000000..e046b2b --- /dev/null +++ b/examples/slide-093/script.py @@ -0,0 +1,77 @@ +""" +Slide 93: Slide 7 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-093/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 93 === +ModalBackend + +token_id + token_secret + +per-second + GB-s + +yes (volume) + +Python-first, GPU-доступный + +DaytonaBackend + +api_key + +per-session + +yes (volume) + +default в README + +RunloopBackend + +api_key + +per-second + +yes (volume) + +dev-цикл, snapshot/restart + +LangSmithBackend + +LANGSMITH_API_KEY + +через LangSmith + +через LS + +уже платите за LangSmith diff --git a/examples/slide-094/script.py b/examples/slide-094/script.py new file mode 100644 index 0000000..7e4c689 --- /dev/null +++ b/examples/slide-094/script.py @@ -0,0 +1,87 @@ +""" +Slide 94: Slide 8 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-094/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 94 === +// open_swe/sandbox/__init__.py:1-12 + + from open_swe.sandbox import ( + ModalBackend, + DaytonaBackend, + RunloopBackend, + LangSmithBackend, + ) + + # Modal (Python-first, GPU) + backend = ModalBackend( + token_id="..." + token_secret="..." + ) + +// examples/my_internal_backend.py:1-11 + + # свой backend: devbox-пул + from open_swe.sandbox import ( + SandboxBackend, + ) +> + class MyInternalBackend(SandboxBackend): + def execute(self, cmd): +> return self._run(cmd) +> + def read_file(self, p): +> return self._fetch(p) + +// examples/my_internal_backend.py:1-16 + + # свой backend: devbox-пул + from open_swe.sandbox import ( + SandboxBackend, + ) + + class MyInternalBackend( + SandboxBackend + ): + def __init__(self, conn): + self.conn = conn +> +> def execute(self, cmd): +> return self._run(cmd) +> +> def read_file(self, p): +> return self._fetch(p) + +// note: snippet has 16 lines, card fits ~15 diff --git a/examples/slide-095/script.py b/examples/slide-095/script.py new file mode 100644 index 0000000..9925e11 --- /dev/null +++ b/examples/slide-095/script.py @@ -0,0 +1,53 @@ +""" +Slide 95: Slide 9 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-095/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 95 === +// INSTALLATION.md: step 1:1-5 + + # 1. prerequisites + python --version # >= 3.11 + node --version # >= 20 + uv --version # или pip + docker --version # для dev + +// INSTALLATION.md: step 2:1-5 + + # 2. clone + install + git clone https://github.com/ + langchain-ai/open-swe.git + cd open-swe + uv sync # или pip install -e . diff --git a/examples/slide-096/script.py b/examples/slide-096/script.py new file mode 100644 index 0000000..939075f --- /dev/null +++ b/examples/slide-096/script.py @@ -0,0 +1,53 @@ +""" +Slide 96: Slide 10 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-096/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 96 === +// INSTALLATION.md: steps 3-5:1-13 + + # 3. backend (FastAPI) + uv run apps/open-swe/main.py + # -> :8000 + + # 4. UI (TanStack Start + Vite) + cd apps/open-swe-ui + pnpm install && pnpm dev + # -> :3000 + + # 5. smoke-test + curl -X POST :8000/webhooks/slack \ + -d '{"text":"@open-swe hello"}' + # -> {"thread_id": "..."} diff --git a/examples/slide-097/script.py b/examples/slide-097/script.py new file mode 100644 index 0000000..f9111e2 --- /dev/null +++ b/examples/slide-097/script.py @@ -0,0 +1,53 @@ +""" +Slide 97: Slide 11 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-097/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 97 === +// config/github-app-manifest.yaml:1-13 + + # github-app-manifest.yaml + name: open-swe-internal +> url: https://open-swe.example.com +> hook_attributes: +> url: https://open-swe.example.com/ +> webhooks/github +> events: +> - issue_comment +> - pull_request + - pull_request_review + default_permissions: + contents: write + pull_requests: write diff --git a/examples/slide-098/script.py b/examples/slide-098/script.py new file mode 100644 index 0000000..36fd8fa --- /dev/null +++ b/examples/slide-098/script.py @@ -0,0 +1,50 @@ +""" +Slide 98: Slide 12 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-098/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 98 === +// .env:1-10 + + # .env (НЕ коммитить) + LANGSMITH_TRACING=true + LANGSMITH_ENDPOINT=https:// + api.smith.langchain.com + LANGSMITH_API_KEY=lsv2_... + LANGSMITH_PROJECT=open-swe-prod + LANGSMITH_SNAPSHOT=true + + # для sandbox-прокси: + LANGSMITH_SANDBOX_BACKEND=true diff --git a/examples/slide-099/script.py b/examples/slide-099/script.py new file mode 100644 index 0000000..8cdea1d --- /dev/null +++ b/examples/slide-099/script.py @@ -0,0 +1,46 @@ +""" +Slide 99: Slide 13 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-099/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 99 === +@open-swe fix the auth bug +repo:acme/api + +@openswe implement the +acceptance criteria + +@openswe address +the review comments diff --git a/examples/slide-100/script.py b/examples/slide-100/script.py new file mode 100644 index 0000000..41ac5fa --- /dev/null +++ b/examples/slide-100/script.py @@ -0,0 +1,51 @@ +""" +Slide 100: Slide 14 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-100/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 100 === +// open_swe/triggers/router.py:1-11 + + # open_swe/triggers/router.py +> def thread_id_for(source, ref, comment_id): +> # Deterministic thread_id: +> # все follow-up попадают в один run. +> if source == "slack": +> return f"slack:{ref}" +> if source == "linear": +> return f"linear:{ref}" +> if source == "github": + return f"github:{ref}:{comment_id}" + raise ValueError(source) diff --git a/examples/slide-101/script.py b/examples/slide-101/script.py new file mode 100644 index 0000000..46635db --- /dev/null +++ b/examples/slide-101/script.py @@ -0,0 +1,55 @@ +""" +Slide 101: Slide 15 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-101/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 101 === +// apps/open-swe/webhooks.py:1-15 + + # apps/open-swe/webhooks.py + from fastapi import APIRouter, Request + from open_swe.triggers.router import ( + thread_id_for, + ) + + router = APIRouter() + +> @router.post("/webhooks/slack") +> async def slack_webhook(req: Request): +> payload = await req.json() +> if "@open-swe" not in payload["text"]: +> return {"ok": True} +> await enqueue_run(thread_id_for( +> "slack", payload["channel"])) diff --git a/examples/slide-102/script.py b/examples/slide-102/script.py new file mode 100644 index 0000000..278c2d5 --- /dev/null +++ b/examples/slide-102/script.py @@ -0,0 +1,53 @@ +""" +Slide 102: Slide 16 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-102/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 102 === +// apps/open-swe-ui/tree.sh:1-13 + + # apps/open-swe-ui/ + # TanStack Start + Vite + React 19 + + src/routes/ + __root.tsx + index.tsx # thread list + thread.$id.tsx # chat + settings.tsx + src/components/ + ChatMessage.tsx + DiffViewer.tsx + src/lib/ + client.ts # OpenSWEClient diff --git a/examples/slide-103/script.py b/examples/slide-103/script.py new file mode 100644 index 0000000..3283249 --- /dev/null +++ b/examples/slide-103/script.py @@ -0,0 +1,55 @@ +""" +Slide 103: Slide 17 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-103/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 103 === +// open_swe/settings.py:1-15 + + # settings schema (per-user) + USER_DEFAULTS = { +> "model": "anthropic:claude-opus-4-6", +> "profile": "balanced", +> "max_steps": 80, +> "auto_open_pr": False, +> "require_approval": True, + "extra_repos": ["acme/internal-tools"], + } + +> # team defaults (allowlist) +> TEAM_DEFAULTS = { + "sandbox_backend": "ModalBackend", + "allowed_repos": ["acme/*"], + } diff --git a/examples/slide-104/script.py b/examples/slide-104/script.py new file mode 100644 index 0000000..227b44d --- /dev/null +++ b/examples/slide-104/script.py @@ -0,0 +1,49 @@ +""" +Slide 104: Slide 18 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-104/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 104 === +backend=ModalBackend(...) + +init_chat_model("anthropic:...") + +tools=[execute, fetch_url, ...] + +router.add_route("/my", my_handler) + +construct_system_prompt(repo_dir, ...) + +middleware=[AuditLogger(), ...] diff --git a/examples/slide-105/script.py b/examples/slide-105/script.py new file mode 100644 index 0000000..0c239bd --- /dev/null +++ b/examples/slide-105/script.py @@ -0,0 +1,49 @@ +""" +Slide 105: Slide 19 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-105/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 105 === +agent graph + +START -> plan -> execute -> review -> open_pr -> END + +reviewer graph + +START -> run_ci -> parse -> report -> END + +analyzer graph + +START -> load_trace -> aggregate -> summarize -> END diff --git a/examples/slide-106/script.py b/examples/slide-106/script.py new file mode 100644 index 0000000..25c5445 --- /dev/null +++ b/examples/slide-106/script.py @@ -0,0 +1,55 @@ +""" +Slide 106: Slide 20 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-106/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 106 === +// open_swe/observability.py:1-15 + + # observability.py + from datadog import statsd +> from langchain_core.tracers import ( +> LangChainTracer, +> ) + + tracer = LangChainTracer( +> project_name="open-swe-prod", +> ) +> + def record_step(thread_id, duration_s): + statsd.histogram( + "open_swe.step.duration_s", + duration_s, tags=[f"t:{thread_id}"], + ) diff --git a/examples/slide-107/script.py b/examples/slide-107/script.py new file mode 100644 index 0000000..4403d95 --- /dev/null +++ b/examples/slide-107/script.py @@ -0,0 +1,39 @@ +""" +Slide 107: Slide 21 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-107/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 107: Slide 21') diff --git a/examples/slide-108/script.py b/examples/slide-108/script.py new file mode 100644 index 0000000..07f5efe --- /dev/null +++ b/examples/slide-108/script.py @@ -0,0 +1,53 @@ +""" +Slide 108: Slide 22 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-108/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 108 === +// apps/open-swe-ui/src/lib/client.ts:1-13 + + // apps/open-swe-ui/src/lib/client.ts + import { OpenSWEClient } from + "@openswe/client"; + + const client = new OpenSWEClient({ + langsmithApiKey: process.env. + LANGSMITH_API_KEY!, + }); + + await client.invoke({ + threadId: "issue-123", + prompt: "Fix the bug", + }); diff --git a/examples/slide-109/script.py b/examples/slide-109/script.py new file mode 100644 index 0000000..1ee0930 --- /dev/null +++ b/examples/slide-109/script.py @@ -0,0 +1,73 @@ +""" +Slide 109: Slide 23 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-109/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 109 === +IDE + +SaaS + +org + +proprietary + +proprietary + +MIT + +manual + +Web UI + +Slack/Lin/GH + +local+cloud + +remote + +pluggable + +config + +no + +full src + +IDE/term + +cloud + +your infra diff --git a/examples/slide-110/script.py b/examples/slide-110/script.py new file mode 100644 index 0000000..ff6e6ea --- /dev/null +++ b/examples/slide-110/script.py @@ -0,0 +1,53 @@ +""" +Slide 110: Slide 24 +Section 4: Open SWE +Source: slides/section4-openswe/section4.pptx + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-110/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 110 === +> deep-agent harness заморожен +> API стабилизирован +> semver commitment + +> per-org quota + billing +> team-level audit log +> rbac на sandbox providers + +> Jira / Azure DevOps +> Sentry для авто-fix багов +> PagerDuty для incident triage + +> public SDK (Python + TS) +> plugin marketplace +> shared subagent library diff --git a/examples/slide-111/script.py b/examples/slide-111/script.py new file mode 100644 index 0000000..6ac24e9 --- /dev/null +++ b/examples/slide-111/script.py @@ -0,0 +1,39 @@ +""" +Slide 111: Untitled +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-01.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-111/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 111: Untitled') diff --git a/examples/slide-112/script.py b/examples/slide-112/script.py new file mode 100644 index 0000000..bd8dcce --- /dev/null +++ b/examples/slide-112/script.py @@ -0,0 +1,39 @@ +""" +Slide 112: Три столпа поверх LangChain/LangGraph +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-02.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-112/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 112: Три столпа поверх LangChain/LangGraph') diff --git a/examples/slide-113/script.py b/examples/slide-113/script.py new file mode 100644 index 0000000..a2f9276 --- /dev/null +++ b/examples/slide-113/script.py @@ -0,0 +1,45 @@ +""" +Slide 113: pip install и 3 переменные окружения +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-03.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-113/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 113 === +# Standalone SDK (если не используете langchain) +pip install langsmith + +# С langchain/LangGraph auto-tracing работает +# автоматически при env-переменных -- отдельно +# ставить SDK необязательно. +pip install langchain langgraph diff --git a/examples/slide-114/script.py b/examples/slide-114/script.py new file mode 100644 index 0000000..7f66247 --- /dev/null +++ b/examples/slide-114/script.py @@ -0,0 +1,54 @@ +""" +Slide 114: @traceable -- декоратор для любой функции +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-04.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-114/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 114 === +from langsmith import traceable + +# Декоратор превращает функцию в traced Run. +# Все аргументы и return попадают в trace автоматически. +@traceable(name="retrieve_docs", run_type="retriever") +def retrieve(query: str, k: int = 4) -> list[str]: + return vector_store.similarity_search(query, k=k) + +@traceable(name="generate_answer", run_type="chain") +def generate_answer(query: str) -> str: + docs = retrieve(query) # nested Run + context = "\n".join(docs) + return llm.invoke(f"Q: {query}\nCtx: {context}") + +# Дерево: generate_answer -> retrieve_docs -> ChatModel +print(generate_answer("What is LCEL?")) diff --git a/examples/slide-115/script.py b/examples/slide-115/script.py new file mode 100644 index 0000000..4487c76 --- /dev/null +++ b/examples/slide-115/script.py @@ -0,0 +1,57 @@ +""" +Slide 115: RunTree -- ручной контроль над деревом +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-05.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-115/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 115 === +from langsmith.run_trees import RunTree + +# RunTree -- explicit handle: создается руками, передается +# в код, потом post() отправляет в LangSmith. Нужно когда +# @traceable не подходит (динамич. дерево, не-Python, metadata). +parent = RunTree( + name="agent_turn", + run_type="chain", + inputs={"q": "Who invented Python?"}, + project_name="my-agent-dev", +) +try: + answer = my_agent(question, run_tree=parent) + parent.end(outputs={"answer": answer}) +except Exception as e: + parent.end(error=str(e)) # ошибка логируется + raise +finally: + parent.post() # flush в LangSmith diff --git a/examples/slide-116/script.py b/examples/slide-116/script.py new file mode 100644 index 0000000..eb85b9c --- /dev/null +++ b/examples/slide-116/script.py @@ -0,0 +1,58 @@ +""" +Slide 116: Auto-tracing LangChain + метаданные +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-06.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-116/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 116 === +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate + +prompt = ChatPromptTemplate.from_template("Tell a joke about {topic}") +model = ChatOpenAI(model="gpt-4o-mini") +chain = prompt | model + +# auto-tracing: invoke/batch/stream автоматически создают Run +# с input, output, token usage, latency. +result = chain.invoke( + {"topic": "databases"}, + config={ + "run_name": "joke_chain", # имя в UI + "tags": ["prod", "experiment-v3"], # фильтр в UI + "metadata": { # любые поля + "user_id": "u_123", + "request_id": "req_abc", + }, + }, +) diff --git a/examples/slide-117/script.py b/examples/slide-117/script.py new file mode 100644 index 0000000..f4bda8b --- /dev/null +++ b/examples/slide-117/script.py @@ -0,0 +1,58 @@ +""" +Slide 117: create_dataset -- набор примеров под eval +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-07.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-117/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 117 === +from langsmith import Client + +client = Client() + +# 1. Создаем датасет (или достаем существующий по имени) +dataset = client.create_dataset( + dataset_name="rag-qa-eval-v1", + description="RAG golden set", +) + +# 2. Добавляем примеры пачкой: inputs + reference outputs +client.create_examples( + dataset_id=dataset.id, + inputs=[{"q": "Что такое LCEL?"}, {"q": "Что такое HITL?"}], + outputs=[{"a": "LangChain Expression Language"}, + {"a": "Human-in-the-loop через interrupt"}], +) + +# 3. Версионирование: новые данные -> новый датасет +client.clone_dataset(dataset.id, dataset_name="rag-qa-eval-v2") diff --git a/examples/slide-118/script.py b/examples/slide-118/script.py new file mode 100644 index 0000000..26b73f6 --- /dev/null +++ b/examples/slide-118/script.py @@ -0,0 +1,58 @@ +""" +Slide 118: run_on_dataset + evaluator-ы +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-08.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-118/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 118 === +from langsmith import Client +from langsmith.schemas import Example, Run + +client = Client() + +# 1. Target -- что прогоняем (chain, graph, agent, callable) +def target(inputs: dict) -> dict: + return {"answer": my_chain.invoke(inputs["question"])} + +# 2. Evaluator -- scoring function +def answer_match(run: Run, example: Example) -> dict: + score = 1.0 if example.outputs["answer"] in run.outputs["answer"] else 0.0 + return {"key": "answer_match", "score": score} + +# 3. Прогон: target на датасете + scoring +client.run_on_dataset( + dataset_name="rag-qa-eval-v1", + llm_or_chain_factory=target, + evaluators=[answer_match], +) diff --git a/examples/slide-119/script.py b/examples/slide-119/script.py new file mode 100644 index 0000000..c68b877 --- /dev/null +++ b/examples/slide-119/script.py @@ -0,0 +1,39 @@ +""" +Slide 119: Визуальный дебаг графа +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-09.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-119/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 119: Визуальный дебаг графа') diff --git a/examples/slide-120/script.py b/examples/slide-120/script.py new file mode 100644 index 0000000..870b83e --- /dev/null +++ b/examples/slide-120/script.py @@ -0,0 +1,48 @@ +""" +Slide 120: LangGraph Platform: deploy + invoke +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-10.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-120/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 120 === +# 1. Конфиг: langgraph.json в корне репо +cat > langgraph.json <<'JSON' +{ + "graphs": {"agent": "./agent.py:graph"}, + "env": "./.env" +} +JSON + +# 2. Deploy one-shot +langgraph deploy --name my-agent-prod diff --git a/examples/slide-121/script.py b/examples/slide-121/script.py new file mode 100644 index 0000000..5c7f232 --- /dev/null +++ b/examples/slide-121/script.py @@ -0,0 +1,39 @@ +""" +Slide 121: Self-hosted vs Cloud: что выбрать +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-11.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-121/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# (No extractable code on this slide — visual-only or section divider) +print('Slide 121: Self-hosted vs Cloud: что выбрать') diff --git a/examples/slide-122/script.py b/examples/slide-122/script.py new file mode 100644 index 0000000..77e97f7 --- /dev/null +++ b/examples/slide-122/script.py @@ -0,0 +1,55 @@ +""" +Slide 122: TypeScript SDK и плюсы/минусы +Section 5: Экосистема +Source: slides/section5-ecosystem/slide-12.js + +Сгенерировано автоматически из slide-NN.js / sectionN.pptx. +Паттерн провайдера скопирован из +bro-js/agents/teacher/assistant/src/angry_teacher/llm.py. + +Запуск: + cd langchain-evolution-deck + cp .env.example .env # заполни OPENAI_API_KEY + python examples/slide-122/script.py +""" +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI + +load_dotenv() + +llm = ChatOpenAI( + # model="openrouter/free", + # model="qwen/qwen3.5-35b-a3b", + model="openai/gpt-oss-20b", + # model="nvidia/nemotron-3-nano", + # model="qwen/qwen3.5-9b", + base_url="https://llm.brojs.ru/v1", + # base_url="https://api.minimax.io/v1", + # base_url="http://0.0.0.0:8090/v1", + # base_url="https://openrouter.ai/api/v1", + # api_key=os.getenv("MINIMAX_API_KEY"), + api_key=(os.getenv("OPENAI_API_KEY") or "").strip() or None, + temperature=0.7, + stream_usage=True, +) + + +# === Code from slide 122 === +import { Client, traceable } from "langsmith"; + +const client = new Client(); + +// @traceable decorator -- точно как в Python +const retrieve = traceable( + async function retrieve(query: string) { + return vectorStore.similaritySearch(query, 4); + }, + { name: "retrieve_docs", runType: "retriever" } +); + +// run_on_dataset для eval -- тоже есть +await client.runOnDataset( + "rag-qa-eval-v1", target, + { evaluators: [answerMatch] } +); diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0930fb6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,23 @@ +# Python deps для examples/*.py +# Запуск: pip install -r requirements.txt +# (или uv pip install -r requirements.txt) + +# LLM provider (паттерн скопирован из bro-js/agents/teacher/assistant) +python-dotenv>=1.0.0 +langchain-openai>=0.2.0 +langchain>=1.0.0 +langchain-core>=1.0.0 + +# LangGraph 1.0 (секция 2) +langgraph>=1.0.0 + +# Deep Agents (секция 3) +deepagents>=0.6.0 + +# Open SWE (секция 4) — приложение, не pip-пакет; см. github.com/langchain-ai/open-swe + +# LangSmith (секция 5) +langsmith>=0.8.0 + +# Tooling +pydantic>=2.0