Initial commit: LangChain evolution tutorial deck (132 slides)

- Cover, TOC, 5 dividers, 3 recap slides
- 5 sections (chains, langgraph, deepagents, openswe, ecosystem)
- design-system.js with theme tokens + 9 helper functions
- research/: timeline + sources + per-tech notes
- final-compile.js + merge.js for rebuild pipeline
- output/: langchain-evolution.pptx (2.3 MB) + langchain-evolution.pdf (1.1 MB) + 7 sample previews
This commit is contained in:
2026-06-22 11:29:03 +03:00
parent ed5b5c91bf
commit 75601988c2
220 changed files with 13069 additions and 3 deletions
+14
View File
@@ -0,0 +1,14 @@
# Build artifacts
build/
*.log
.DS_Store
# Heavy assets — kept under output/ but gitignored for size
output/previews/*.png
# Node
node_modules/
# Python
__pycache__/
*.pyc
-3
View File
@@ -1,3 +0,0 @@
# langchain-evolution-deck
Большой tutorial-дек по эволюции LangChain: от chains 2022 до Open SWE 2025/2026. 132 слайда в dark mode, Python 1.0+.
+862
View File
@@ -0,0 +1,862 @@
/**
* design-system.js
* ----------------------------------------------------------------------------
* Design tokens + slide helpers for the LangChain Evolution deck
* (lc-evo-deck, 100+ slides, code-heavy, dark theme, 16:9).
*
* Audience: engineers familiar with LLMs. No introductory filler.
* Visual register: developer-tool aesthetic, deep navy, JetBrains Mono for
* code, Inter for UI, Arial as the universal fallback (covers Cyrillic).
*
* USAGE
* -----
* // 1. Import the module
* const ds = require('./design-system');
* const { theme, helpers, layouts, fonts, sizes, spacing } = ds;
*
* // 2. Create a new presentation with the built-in 16:9 layout
* const pptxgen = require('pptxgenjs');
* const pres = new pptxgen();
* pres.layout = 'LAYOUT_16x9'; // 10 x 5.625 inches
*
* // 3. Add a slide
* const slide = pres.addSlide();
* helpers.slideBase(slide, pres, theme);
* helpers.addHeader(slide, pres, theme, {
* section: 'Stage 2: Chains',
* sectionNumber: 2,
* title: 'LCEL: a composable expression language',
* eyebrow: 'STAGE 2',
* });
*
* // 4. Add a code block
* helpers.addCodeBlock(slide, pres, theme, {
* x: 0.5, y: layouts.CONTENT_TOP,
* w: 5.0, h: 3.2,
* language: 'python',
* code: [
* 'from langchain_core.prompts import ChatPromptTemplate',
* 'from langchain_openai import ChatOpenAI',
* '',
* 'prompt = ChatPromptTemplate.from_messages([',
* ' ("system", "You are a helpful assistant."),',
* ' ("human", "{question}"),',
* '])',
* 'model = ChatOpenAI(model="gpt-4o-mini")',
* 'chain = prompt | model',
* 'print(chain.invoke({"question": "What is LCEL?"}))',
* ].join('\n'),
* filePath: 'examples/lcel_basic.py',
* startLine: 1,
* });
*
* // 5. Add a callout and a pros/cons panel
* helpers.addCallout(slide, pres, theme, {
* x: 5.8, y: layouts.CONTENT_TOP, w: 3.7, h: 1.2,
* kind: 'info',
* text: 'LCEL is the default composition language from v0.1 onward.',
* });
*
* helpers.addProsCons(slide, pres, theme, {
* x: 5.8, y: 2.8, w: 3.7, h: 2.0,
* pros: ['Composable via | operator', 'Streaming, async, batched for free'],
* cons: ['Verbose for simple chains', 'Mental model differs from LangChain v0'],
* });
*
* // 6. Number the slide and add a source line
* helpers.addPageNumber(slide, pres, theme, 7);
* helpers.addSourceLine(slide, pres, theme, {
* x: 0.5, y: layouts.FOOTER_Y + 0.1, w: 6.0,
* source: 'python.langchain.com/docs/concepts/lcel',
* });
*
* // 7. Save
* await pres.writeFile({ fileName: 'lc-evolution.pptx' });
*
* LAYOUTS (PPTX 16:9, units = inches)
* -----------------------------------
* HEADER_Y = 0.4
* CONTENT_TOP = 1.4
* CONTENT_BOTTOM = 5.05
* FOOTER_Y = 5.25
*
* Vertical regions:
* - Header band : [0.0 .. 1.4] eyebrow + h1 title
* - Content body : [1.4 .. 5.05] main slide content
* - Footer band : [5.05 .. 5.625] page number + source line
*
* Horizontal margins: 0.5 inches left/right by default.
*
* RULES
* -----
* - No em-dash (--), en-dash (-), no smart quotes, no ellipsis (...)
* - All source files are pure ASCII except inside string literals where
* Cyrillic is allowed (Arial fallback guarantees rendering).
* - Code blocks use JetBrains Mono; body uses Inter; fallback Arial.
*
* @module design-system
*/
'use strict';
// ---------------------------------------------------------------------------
// 1. PALETTE
// ---------------------------------------------------------------------------
const palette = {
bg: {
primary: '#0A1A2A', // main slide background
elevated: '#142B3F', // cards, callouts, panels
code: '#0F1E2E', // code block background (slightly darker)
overlay: '#1B2F44', // hover / focus surfaces
},
text: {
primary: '#E6F0F7',
secondary: '#B5C4D1',
muted: '#8A9AAB',
inverse: '#0A1A2A', // for text on bright accents
},
accent: {
primary: '#219EBC', // teal, default accent
secondary: '#FFB703', // gold, important emphasis
tertiary: '#8ECAE6', // light blue, soft accent
},
border: {
subtle: '#233A4F',
strong: '#3A5670',
accent: '#219EBC',
},
code: {
keyword: '#C586C0', // def, class, import, return
string: '#CE9178',
number: '#B5CEA8',
comment: '#6A9955',
function: '#DCDCAA',
builtin: '#4EC9B0',
text: '#D4D4D4', // default code body
bg: '#0F1E2E',
lineHighlight: '#1F2F44',
},
state: {
info: '#219EBC',
success: '#4EC9B0',
warning: '#FFB703',
danger: '#F48771',
infoBg: '#102A38',
successBg: '#0F2A28',
warningBg: '#3A2A0F',
dangerBg: '#3A1A14',
},
};
// ---------------------------------------------------------------------------
// 2. TYPOGRAPHY
// ---------------------------------------------------------------------------
const fonts = {
code: 'JetBrains Mono',
ui: 'Inter',
fallback: 'Arial', // universal fallback, supports Cyrillic
};
const sizes = {
h1: 36,
h2: 28,
h3: 20,
body: 14,
code: 10,
caption: 9,
eyebrow: 10,
};
const spacing = {
page: 0.5,
card_pad: 0.25,
gap: 0.15,
};
// ---------------------------------------------------------------------------
// 3. LAYOUTS
// ---------------------------------------------------------------------------
const layouts = {
LAYOUT_16x9: 'LAYOUT_16x9',
HEADER_Y: 0.4,
CONTENT_TOP: 1.45,
CONTENT_BOTTOM: 5.05,
FOOTER_Y: 5.25,
};
// ---------------------------------------------------------------------------
// 4. THEME (bundled export of the above)
// ---------------------------------------------------------------------------
const theme = {
palette,
fonts,
sizes,
spacing,
layouts,
};
// ---------------------------------------------------------------------------
// 5. HELPERS
// ---------------------------------------------------------------------------
/**
* Resolve a font that always carries the Arial fallback.
* pptxgenjs lets us pass an array; PowerPoint will pick the first available.
*/
function withFallback(family) {
return [family, fonts.fallback];
}
/**
* Paint the slide background.
* @param {object} slide - pptxgenjs slide instance
* @param {object} _pres - pptxgenjs pres (kept for signature symmetry)
* @param {object} t - theme bundle
*/
function slideBase(slide, _pres, t) {
slide.background = { color: t.palette.bg.primary };
}
/**
* Add a header band: eyebrow (small caps, accent) + section number + title.
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {string} [opts.section] - small caps section label (e.g. "Stage 2")
* @param {number} [opts.sectionNumber] - large section number shown on the right
* @param {string} opts.title - main title (h1)
* @param {string} [opts.eyebrow] - eyebrow text (e.g. "STAGE 2: CHAINS")
*/
function addHeader(slide, pres, t, opts) {
const o = opts || {};
const margin = t.spacing.page;
const titleY = t.layouts.HEADER_Y + 0.4;
const titleSize = o.titleSize || t.sizes.h2; // default to 28pt -- fits two-line titles without overflow
const titleW = o.sectionNumber != null ? 8.4 : 9.0;
if (o.eyebrow) {
slide.addText(o.eyebrow, {
x: margin,
y: t.layouts.HEADER_Y,
w: 6.0,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.accent.primary,
bold: true,
charSpacing: 4,
});
} else if (o.section) {
slide.addText(o.section, {
x: margin,
y: t.layouts.HEADER_Y,
w: 6.0,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.accent.primary,
bold: true,
charSpacing: 4,
});
}
slide.addText(o.title || '', {
x: margin,
y: titleY,
w: titleW,
h: 0.75,
fontFace: withFallback(t.fonts.ui),
fontSize: titleSize,
color: t.palette.text.primary,
bold: true,
valign: 'middle',
fit: 'shrink',
});
if (o.sectionNumber != null) {
slide.addText(String(o.sectionNumber), {
x: 9.0,
y: t.layouts.HEADER_Y - 0.05,
w: 0.7,
h: 1.0,
fontFace: withFallback(t.fonts.ui),
fontSize: 56,
color: t.palette.accent.secondary,
bold: true,
align: 'right',
valign: 'top',
});
}
// Hairline separator below the header band
slide.addShape(pres.ShapeType.line, {
x: margin,
y: 1.3,
w: 10.0 - margin * 2,
h: 0,
line: { color: t.palette.border.subtle, width: 0.75 },
});
}
/**
* Render a code block as a card with monospaced text.
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {string} opts.code
* @param {number} opts.x
* @param {number} opts.y
* @param {number} opts.w
* @param {number} opts.h
* @param {string} [opts.language='python']
* @param {string} [opts.filePath] - optional, renders as "// path:line" header
* @param {number} [opts.startLine=1]
* @param {Array<number>} [opts.highlightLines] - 1-based line numbers
*/
function addCodeBlock(slide, pres, t, opts) {
const o = opts || {};
const code = String(o.code || '');
const x = o.x;
const y = o.y;
const w = o.w;
const h = o.h;
const headerH = o.filePath ? 0.3 : 0;
const radius = 0.08;
// Card background
slide.addShape(pres.ShapeType.roundRect, {
x: x,
y: y,
w: w,
h: h,
fill: { color: t.palette.code.bg },
line: { color: t.palette.border.subtle, width: 0.75 },
rectRadius: radius,
});
// Optional header strip with file path
if (o.filePath) {
const startLine = o.startLine || 1;
const lineCount = code.split('\n').length;
const endLine = startLine + lineCount - 1;
slide.addText(
'// ' + o.filePath + ':' + startLine + '-' + endLine,
{
x: x + t.spacing.card_pad,
y: y + 0.05,
w: w - t.spacing.card_pad * 2,
h: 0.22,
fontFace: withFallback(t.fonts.code),
fontSize: t.sizes.caption,
color: t.palette.text.muted,
italic: true,
}
);
}
// Body code
const lines = code.split('\n');
const hasHighlight =
Array.isArray(o.highlightLines) && o.highlightLines.length > 0;
const highlightedSet = hasHighlight
? new Set(o.highlightLines.map(function (n) { return n; }))
: null;
// Pre-compute line height to fit within available area.
const codeAreaY = y + headerH + 0.05;
const codeAreaH = h - headerH - 0.1;
// Use a tighter font + leading so 20-line snippets fit in 3-inch cards.
const fontSize = o.fontSize || 11;
const lineH = (fontSize / 72) * 1.25; // inches per line at 1.25 leading
const text = lines.map(function (line, idx) {
const lineNum = idx + 1;
const isHi = highlightedSet && highlightedSet.has(lineNum);
const prefix = isHi ? '> ' : ' ';
return {
text: prefix + (line.length === 0 ? ' ' : line) + '\n',
options: {
fontFace: withFallback(t.fonts.code),
fontSize: fontSize,
color: isHi ? t.palette.text.primary : t.palette.code.text,
bold: false,
highlight: isHi ? t.palette.code.lineHighlight : undefined,
},
};
});
slide.addText(text, {
x: x + t.spacing.card_pad,
y: codeAreaY,
w: w - t.spacing.card_pad * 2,
h: codeAreaH,
valign: 'top',
paraSpaceBefore: 0,
paraSpaceAfter: 0,
lineSpacingMultiple: 1.0,
isTextBox: true,
});
// Caption warning if content may overflow.
const maxLines = Math.floor(codeAreaH / lineH);
if (lines.length > maxLines) {
// Best-effort: append a small note. PowerPoint won't clip the text, but
// it will overflow the card visually. Caller should reduce fontSize or
// split the snippet.
slide.addText(
'// note: snippet has ' + lines.length + ' lines, card fits ~' + maxLines,
{
x: x + t.spacing.card_pad,
y: y + h - 0.22,
w: w - t.spacing.card_pad * 2,
h: 0.18,
fontFace: withFallback(t.fonts.code),
fontSize: t.sizes.caption,
color: t.palette.state.warning,
italic: true,
}
);
}
}
/**
* Render a code block with line highlighting.
* Convenience wrapper over addCodeBlock.
*/
function addCodeBlockWithHighlight(slide, pres, t, opts) {
return addCodeBlock(slide, pres, t, Object.assign({}, opts, {
highlightLines: opts.lines || opts.highlightLines,
}));
}
/**
* Add a callout panel (info / warning / success / danger).
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {number} opts.x
* @param {number} opts.y
* @param {number} opts.w
* @param {number} opts.h
* @param {('info'|'warning'|'success'|'danger')} [opts.kind='info']
* @param {string} opts.text
* @param {string} [opts.title]
*/
function addCallout(slide, pres, t, opts) {
const o = opts || {};
const kind = o.kind || 'info';
const accent = t.palette.state[kind] || t.palette.state.info;
const bgKey = kind + 'Bg';
const bg = t.palette.state[bgKey] || t.palette.state.infoBg;
// Background card
slide.addShape(pres.ShapeType.roundRect, {
x: o.x,
y: o.y,
w: o.w,
h: o.h,
fill: { color: bg },
line: { color: accent, width: 1 },
rectRadius: 0.08,
});
// Left accent bar
slide.addShape(pres.ShapeType.rect, {
x: o.x,
y: o.y,
w: 0.08,
h: o.h,
fill: { color: accent },
line: { type: 'none' },
});
// Label
slide.addText((kind || 'info').toUpperCase(), {
x: o.x + 0.25,
y: o.y + 0.1,
w: o.w - 0.35,
h: 0.25,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: accent,
bold: true,
charSpacing: 4,
});
// Optional title
let bodyY = o.y + 0.4;
let bodyH = o.h - 0.5;
if (o.title) {
slide.addText(o.title, {
x: o.x + 0.25,
y: o.y + 0.35,
w: o.w - 0.35,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.h3,
color: t.palette.text.primary,
bold: true,
});
bodyY = o.y + 0.7;
bodyH = o.h - 0.8;
}
slide.addText(o.text || '', {
x: o.x + 0.25,
y: bodyY,
w: o.w - 0.35,
h: bodyH,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.body,
color: t.palette.text.secondary,
valign: 'top',
});
}
/**
* Two-column pros / cons panel.
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {number} opts.x
* @param {number} opts.y
* @param {number} opts.w
* @param {number} opts.h
* @param {string[]} opts.pros
* @param {string[]} opts.cons
*/
function addProsCons(slide, pres, t, opts) {
const o = opts || {};
const gap = 0.2;
const colW = (o.w - gap) / 2;
// Pros card
slide.addShape(pres.ShapeType.roundRect, {
x: o.x,
y: o.y,
w: colW,
h: o.h,
fill: { color: t.palette.state.successBg },
line: { color: t.palette.state.success, width: 1 },
rectRadius: 0.08,
});
slide.addText('PROS', {
x: o.x + 0.2,
y: o.y + 0.1,
w: colW - 0.3,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.state.success,
bold: true,
charSpacing: 4,
});
const prosBody = (o.pros || []).map(function (item) {
return {
text: '+ ' + item + '\n',
options: {
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.body,
color: t.palette.text.primary,
},
};
});
slide.addText(prosBody, {
x: o.x + 0.2,
y: o.y + 0.45,
w: colW - 0.3,
h: o.h - 0.55,
valign: 'top',
paraSpaceAfter: 4,
});
// Cons card
const cx = o.x + colW + gap;
slide.addShape(pres.ShapeType.roundRect, {
x: cx,
y: o.y,
w: colW,
h: o.h,
fill: { color: t.palette.state.dangerBg },
line: { color: t.palette.state.danger, width: 1 },
rectRadius: 0.08,
});
slide.addText('CONS', {
x: cx + 0.2,
y: o.y + 0.1,
w: colW - 0.3,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.state.danger,
bold: true,
charSpacing: 4,
});
const consBody = (o.cons || []).map(function (item) {
return {
text: '- ' + item + '\n',
options: {
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.body,
color: t.palette.text.primary,
},
};
});
slide.addText(consBody, {
x: cx + 0.2,
y: o.y + 0.45,
w: colW - 0.3,
h: o.h - 0.55,
valign: 'top',
paraSpaceAfter: 4,
});
}
/**
* Add a page number in the bottom-right corner.
*/
function addPageNumber(slide, _pres, t, num) {
slide.addText(String(num), {
x: 9.2,
y: t.layouts.FOOTER_Y,
w: 0.6,
h: 0.25,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.caption,
color: t.palette.text.muted,
align: 'right',
valign: 'middle',
});
}
/**
* Add a section divider slide: a large numeric label + title + intro paragraph.
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {number} opts.number
* @param {string} opts.title
* @param {string} [opts.intro]
* @param {string} [opts.eyebrow]
*/
function addSectionDivider(slide, pres, t, opts) {
const o = opts || {};
slideBase(slide, pres, t);
// Giant number on the left
slide.addText(String(o.number), {
x: 0.5,
y: 1.0,
w: 3.5,
h: 3.5,
fontFace: withFallback(t.fonts.ui),
fontSize: 220,
color: t.palette.accent.primary,
bold: true,
valign: 'middle',
});
// Vertical divider
slide.addShape(pres.ShapeType.line, {
x: 4.2,
y: 1.4,
w: 0,
h: 2.8,
line: { color: t.palette.border.subtle, width: 1 },
});
// Title
if (o.eyebrow) {
slide.addText(o.eyebrow, {
x: 4.5,
y: 1.4,
w: 5.0,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.accent.secondary,
bold: true,
charSpacing: 4,
});
}
slide.addText(o.title || '', {
x: 4.5,
y: o.eyebrow ? 1.7 : 1.4,
w: 5.0,
h: 1.0,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.h1,
color: t.palette.text.primary,
bold: true,
valign: 'top',
});
if (o.intro) {
slide.addText(o.intro, {
x: 4.5,
y: 2.8,
w: 5.0,
h: 1.6,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.body,
color: t.palette.text.secondary,
valign: 'top',
});
}
}
/**
* Add an italic source line, e.g. "source: python.langchain.com/..."
* @param {object} slide
* @param {object} _pres
* @param {object} t
* @param {object} opts
* @param {string} opts.source
* @param {number} [opts.x]
* @param {number} [opts.y]
* @param {number} [opts.w]
*/
function addSourceLine(slide, _pres, t, opts) {
const o = opts || {};
slide.addText('source: ' + (o.source || ''), {
x: o.x != null ? o.x : t.spacing.page,
y: o.y != null ? o.y : t.layouts.FOOTER_Y + 0.05,
w: o.w != null ? o.w : 7.0,
h: 0.25,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.caption,
color: t.palette.text.muted,
italic: true,
valign: 'middle',
});
}
// ---------------------------------------------------------------------------
// 6. OPTIONAL: PYTHON SYNTAX HIGHLIGHTING (pygments via subprocess)
// ---------------------------------------------------------------------------
/**
* Best-effort Python syntax highlighter.
* Calls the `pygmentize` CLI from pygments if available.
* Returns an array of { text, color } tokens.
* On any failure (missing binary, non-zero exit, parse error) returns a
* single-token array with the whole snippet in the default code text color
* so that the slide still renders something.
*
* @param {string} code - Python source code
* @returns {Array<{text: string, color: string}>}
*/
function highlightPython(code) {
const defaultColor = palette.code.text;
const text = String(code || '');
if (!text) return [{ text: '', color: defaultColor }];
const { spawnSync } = require('child_process');
let result;
try {
result = spawnSync(
'pygmentize',
['-f', 'json', '-l', 'python'],
{ input: text, encoding: 'utf8', timeout: 4000 }
);
} catch (e) {
return [{ text: text, color: defaultColor }];
}
if (result.error || result.status !== 0) {
return [{ text: text, color: defaultColor }];
}
let parsed;
try {
parsed = JSON.parse(result.stdout);
} catch (e) {
return [{ text: text, color: defaultColor }];
}
// Pygments JSON output: { tokens: [ [type, value], ... ] }
const tokens = Array.isArray(parsed.tokens) ? parsed.tokens : [];
const out = [];
for (const tok of tokens) {
if (!Array.isArray(tok) || tok.length < 2) continue;
const [type, value] = tok;
out.push({
text: String(value),
color: mapPygmentsTokenToColor(type) || defaultColor,
});
}
return out.length > 0 ? out : [{ text: text, color: defaultColor }];
}
/**
* Map a pygments token type to a palette color.
* Falls back to the default code text color.
*/
function mapPygmentsTokenToColor(type) {
if (!type) return null;
// Pygments short token types we care about for Python:
// Keyword, Keyword.Namespace, Keyword.Constant, String, Number,
// Comment, Comment.Single, Name.Function, Name.Builtin,
// Operator, Punctuation, Text, Error
if (type === 'Keyword' || type.startsWith('Keyword.')) {
return palette.code.keyword;
}
if (type.startsWith('String')) {
return palette.code.string;
}
if (type === 'Number') {
return palette.code.number;
}
if (type.startsWith('Comment')) {
return palette.code.comment;
}
if (type === 'Name.Function' || type === 'Name.Function.Magic') {
return palette.code.function;
}
if (type === 'Name.Builtin' || type === 'Name.Builtin.Pseudo') {
return palette.code.builtin;
}
return null;
}
// ---------------------------------------------------------------------------
// 7. EXPORTS
// ---------------------------------------------------------------------------
const helpers = {
slideBase,
addHeader,
addCodeBlock,
addCodeBlockWithHighlight,
addCallout,
addProsCons,
addPageNumber,
addSectionDivider,
addSourceLine,
withFallback,
highlightPython,
};
module.exports = {
theme: theme,
palette: palette,
fonts: fonts,
sizes: sizes,
spacing: spacing,
layouts: layouts,
helpers: helpers,
};
+149
View File
@@ -0,0 +1,149 @@
# design-system.md
Гайд по `lc-evo-deck/design-system.js` для тех, кто собирает слайды.
## Что внутри
`design-system.js` экспортирует единый объект с тремя слоями:
| Слой | Что лежит |
| ---------- | --------------------------------------------------------------- |
| `palette` | Цвета: фон, текст, акценты, код, состояния |
| `fonts` | `code`, `ui`, `fallback` (Arial для кириллицы) |
| `sizes` | Шкала шрифтов: `h1` ... `caption`, `eyebrow` |
| `spacing` | Отступы: `page`, `card_pad`, `gap` |
| `layouts` | Константы 16:9: `HEADER_Y`, `CONTENT_TOP`, `CONTENT_BOTTOM`, `FOOTER_Y` |
| `theme` | Все перечисленное выше в одном объекте (удобно пробрасывать) |
| `helpers` | Готовые функции для сборки слайда |
Импорт:
```js
const ds = require('./design-system');
const { theme, helpers, layouts } = ds;
```
## Геометрия слайда (16:9, дюймы)
```
y=0.00 +-----------------------------------------------+
| HEADER_Y = 0.4 |
| eyebrow (10pt, accent) |
y=1.25 | --- hairline --- |
| CONTENT_TOP = 1.4 <-- начинай контент тут |
| |
| контент |
| |
y=5.05 | CONTENT_BOTTOM = 5.05 |
| FOOTER_Y = 5.25 <-- page number, source |
y=5.625 +-----------------------------------------------+
x=0.5 x=9.5
^ отступ `spacing.page` (0.5 дюйма) с обеих сторон
```
## Минимальный слайд
```js
const pptxgen = require('pptxgenjs');
const ds = require('./design-system');
const { theme, helpers, layouts } = ds;
const pres = new pptxgen();
pres.layout = 'LAYOUT_16x9';
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
section: 'Stage 2: Chains',
sectionNumber: 2,
title: 'LCEL: composable expressions',
eyebrow: 'STAGE 2',
});
helpers.addPageNumber(slide, pres, theme, 1);
await pres.writeFile({ fileName: 'lc-evolution.pptx' });
```
## Какой layout для какого случая
| Слайд | Что использовать |
| ------------------------------ | --------------------------------------------------------- |
| Титул раздела / stage divider | `helpers.addSectionDivider` (большая цифра + заголовок) |
| Текст + код | `addHeader` + `addCodeBlock` слева, `addCallout` справа |
| Сравнение двух подходов | `addProsCons` (две колонки, + и -) |
| Цитата / важное замечание | `addCallout` с `kind: 'info' | 'warning' | 'success' | 'danger'` |
| Код с акцентом на строке | `addCodeBlockWithHighlight` + `lines: [3, 4]` |
| Источник внизу | `addSourceLine` |
| Любой слайд | `addPageNumber` в правом нижнем углу |
## Helper-функции -- короткая справка
### `slideBase(slide, pres, theme)`
Закрашивает фон `bg.primary`. Вызывай первым на каждом слайде.
### `addHeader(slide, pres, theme, opts)`
- `opts.eyebrow` -- маленький caps-ярлык сверху (например `STAGE 2: CHAINS`)
- `opts.section` -- альтернатива eyebrow
- `opts.sectionNumber` -- крупная цифра справа (необязательно)
- `opts.title` -- h1
### `addCodeBlock(slide, pres, theme, opts)`
- `opts.code` -- строка кода (`\n` для переносов)
- `opts.filePath` + `opts.startLine` -- подпись `// path/to/file.py:1-12`
- `opts.highlightLines` -- массив 1-based номеров строк, которые подсветить
- Если строк больше, чем влезает по высоте, внизу появится желтая плашка
`// note: snippet has N lines, card fits ~M` -- уменьши `fontSize` или
разбей сниппет.
### `addCodeBlockWithHighlight(slide, pres, theme, opts)`
То же самое, но принимает `lines: [3, 4]` как алиас для `highlightLines`.
### `addCallout(slide, pres, theme, opts)`
- `opts.kind` -- `info` | `warning` | `success` | `danger`
- `opts.title` -- необязательный заголовок внутри плашки
- `opts.text` -- основной текст
### `addProsCons(slide, pres, theme, opts)`
- `opts.pros` -- массив строк
- `opts.cons` -- массив строк
- Плюсы слева (зелёная рамка), минусы справа (красная).
### `addPageNumber(slide, pres, theme, n)`
Правый нижний угол, монохромный caption.
### `addSectionDivider(slide, pres, theme, opts)`
- `opts.number` -- крупная цифра слева
- `opts.title` -- заголовок справа
- `opts.eyebrow` -- необязательный caps-ярлык
- `opts.intro` -- абзац под заголовком
### `addSourceLine(slide, pres, theme, opts)`
- `opts.source` -- URL или короткая ссылка
- По умолчанию `x=0.5, y=5.30, w=7.0`
### `highlightPython(code)` (опционально)
Если на машине стоит `pygmentize` (из пакета `pygments`), функция вернет
массив `{ text, color }` токенов. Если бинарника нет -- вернется один токен
с дефолтным цветом и весь код отрисуется моноширинно. Используй, когда
нужна попроцедурная подсветка поверх `addCodeBlock`.
## Правила
1. Не вставляй em-dash (`--`) и en-dash (`-`) -- заменяй на `--` и `-`.
2. Не используй Unicode-кавычки -- только ASCII `"` и `'`.
3. Не используй `...` -- заменяй на `...`.
4. Шрифты -- всегда через `helpers.withFallback(name)`, чтобы Arial был
гарантированным фолбэком.
5. Цвета бери из `palette.*` -- не хардкодь hex в слайдах.
6. Геометрия -- через `layouts.*` константы.
7. Любой новый слайд начинается с `slideBase(...)`.
## Частые ошибки
| Симптом | Причина | Фикс |
| -------------------------------------- | ------------------------------------ | --------------------------------------------------- |
| Шрифт Arial вместо JetBrains Mono | PowerPoint не нашел шрифт | Установи `JetBrains Mono` в систему |
| Кириллица в коде рендерится квадратами | Нет фолбэка | Используй `helpers.withFallback(t.fonts.code)` |
| Код вылезает за карточку | Слишком много строк | Уменьши `sizes.code` или укороти сниппет |
| Header и контент перекрываются | Контент начинается выше `CONTENT_TOP`| Подними `y` до `layouts.CONTENT_TOP` |
| Плашка `note: snippet has N lines` | Сниппет не помещается | Разбей на 2 карточки или уменьши `sizes.code` |
+269
View File
@@ -0,0 +1,269 @@
/**
* final-compile.js
* Создаёт:
* - build/intro.pptx (cover + TOC)
* - build/dividers.pptx (5 dividers между секциями)
* - build/recap.pptx (3 recap слайда: timeline, what's next, closing)
* Затем вызывает merge.js для склейки всех в один .pptx.
*/
'use strict';
const path = require('path');
const fs = require('fs');
const ds = require('./design-system');
const { theme, palette, fonts, sizes, layouts, helpers } = ds;
const pptxgen = require('pptxgenjs');
const WORKSPACE = __dirname;
const SLIDES = path.join(WORKSPACE, 'slides');
const BUILD = path.join(WORKSPACE, 'build');
fs.mkdirSync(BUILD, { recursive: true });
const SECTIONS = [
{ dir: 'section1-chains', title: 'LangChain 1.0', subtitle: 'chains, LCEL, agents, retrievers' },
{ dir: 'section2-langgraph', title: 'LangGraph 1.0', subtitle: 'state, nodes, persistence, HITL' },
{ dir: 'section3-deepagents', title: 'Deep Agents', subtitle: 'harness, todos, virtual FS, subagents' },
{ dir: 'section4-openswe', title: 'Open SWE', subtitle: 'async coding agent, triggers, dashboard' },
{ dir: 'section5-ecosystem', title: 'Экосистема', subtitle: 'LangSmith, Studio, deployment' },
];
// -------- INTRO (cover + TOC) --------
function buildIntro(pres) {
// Slide 1 -- cover
const s1 = pres.addSlide();
helpers.slideBase(s1, pres, theme);
// top accent
s1.addShape(pres.ShapeType.rect, { x: 0, y: 0, w: 10, h: 0.15, fill: { color: palette.accent.primary }, line: { type: 'none' } });
helpers.addHeader(s1, pres, theme, {
eyebrow: 'DEEP DIVE / TUTORIAL',
sectionNumber: 0,
title: 'Эволюция LangChain',
});
// subtitle
s1.addText('от chains до Deep Agents и Open SWE', {
x: 0.5, y: 1.85, w: 9, h: 0.5,
fontFace: fonts.ui, fontSize: 24, color: palette.text.secondary, bold: false,
});
// gold bar
s1.addShape(pres.ShapeType.rect, { x: 0.5, y: 2.55, w: 1.5, h: 0.06, fill: { color: palette.accent.secondary }, line: { type: 'none' } });
// description
s1.addText(
'Большой tutorial по экосистеме LangChain: chains, LCEL, LangGraph, Deep Agents, Open SWE. ' +
'Python 1.0+, реальные API, плотный код.',
{
x: 0.5, y: 2.85, w: 9, h: 1.5,
fontFace: fonts.ui, fontSize: 16, color: palette.text.secondary,
}
);
// footer meta
s1.addText('100+ слайдов | 2022 -- 2026 | Python 1.0+', {
x: 0.5, y: 5.15, w: 9, h: 0.3,
fontFace: fonts.ui, fontSize: 11, color: palette.text.muted, align: 'center',
});
helpers.addPageNumber(s1, pres, theme, 1);
// Slide 2 -- TOC
const s2 = pres.addSlide();
helpers.slideBase(s2, pres, theme);
helpers.addHeader(s2, pres, theme, {
eyebrow: 'CONTENTS',
sectionNumber: 0,
title: 'Содержание',
});
let y = layouts.CONTENT_TOP + 0.1;
SECTIONS.forEach((sec, i) => {
// card
s2.addShape(pres.ShapeType.roundRect, {
x: 0.5, y: y, w: 9, h: 0.55,
fill: { color: palette.bg.elevated },
line: { color: palette.border.subtle, width: 0.5 },
rectRadius: 0.08,
});
// number badge
s2.addShape(pres.ShapeType.roundRect, {
x: 0.65, y: y + 0.1, w: 0.35, h: 0.35,
fill: { color: palette.accent.primary },
line: { type: 'none' },
rectRadius: 0.04,
});
s2.addText(String(i + 1), {
x: 0.65, y: y + 0.12, w: 0.35, h: 0.3,
fontFace: fonts.ui, fontSize: 14, color: palette.bg.primary, bold: true, align: 'center',
});
s2.addText(sec.title + ' -- ' + sec.subtitle, {
x: 1.15, y: y + 0.08, w: 7.5, h: 0.4,
fontFace: fonts.ui, fontSize: 14, color: palette.text.primary, bold: true,
});
y += 0.65;
});
// total
s2.addText('Всего: 122 content слайдов + cover, TOC, 5 dividers, recap = 132', {
x: 0.5, y: 5.05, w: 9, h: 0.3,
fontFace: fonts.ui, fontSize: 12, color: palette.accent.secondary, align: 'center',
});
helpers.addPageNumber(s2, pres, theme, 2);
}
// -------- DIVIDERS (5) --------
function buildDividers(pres) {
SECTIONS.forEach((sec, i) => {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
// big stage number
slide.addShape(pres.ShapeType.rect, {
x: 0, y: 0, w: 10, h: 0.15,
fill: { color: palette.accent.primary }, line: { type: 'none' },
});
// huge number
slide.addText('STAGE ' + (i + 1), {
x: 0.5, y: 1.4, w: 9, h: 0.5,
fontFace: fonts.ui, fontSize: 18, color: palette.accent.primary, bold: true, charSpacing: 6,
});
slide.addText(sec.title, {
x: 0.5, y: 2.0, w: 9, h: 1.2,
fontFace: fonts.ui, fontSize: 60, color: palette.text.primary, bold: true,
});
slide.addText(sec.subtitle, {
x: 0.5, y: 3.2, w: 9, h: 0.5,
fontFace: fonts.ui, fontSize: 22, color: palette.text.secondary,
});
// gold accent line
slide.addShape(pres.ShapeType.rect, {
x: 0.5, y: 3.85, w: 1.5, h: 0.05,
fill: { color: palette.accent.secondary }, line: { type: 'none' },
});
// section nav
slide.addText('Раздел ' + (i + 1) + ' из ' + SECTIONS.length, {
x: 0.5, y: 5.0, w: 9, h: 0.3,
fontFace: fonts.ui, fontSize: 12, color: palette.text.muted, align: 'center',
});
helpers.addPageNumber(slide, pres, theme, 0); // 0 = no number
});
}
// -------- RECAP (3) --------
function buildRecap(pres) {
// Recap 1 -- Timeline
let s = pres.addSlide();
helpers.slideBase(s, pres, theme);
helpers.addHeader(s, pres, theme, {
eyebrow: 'TIMELINE',
sectionNumber: 0,
title: 'Полная карта эволюции',
});
s.addText(
[
{ text: '2022-10 ', options: { color: palette.accent.primary, bold: true } },
{ text: 'LangChain 0.1 -- запуск фреймворка chains\n', options: { color: palette.text.primary } },
{ text: '2023-10 ', options: { color: palette.accent.primary, bold: true } },
{ text: 'LangChain 0.1 (stable) -- первый production-ready\n', options: { color: palette.text.primary } },
{ text: '2024-01 ', options: { color: palette.accent.primary, bold: true } },
{ text: 'LangGraph 0.1 -- stateful графы\n', options: { color: palette.text.primary } },
{ text: '2025-08 ', options: { color: palette.accent.primary, bold: true } },
{ text: 'Deep Agents 0.x -- harness с subagents\n', options: { color: palette.text.primary } },
{ text: '2025-10 ', options: { color: palette.accent.secondary, bold: true } },
{ text: 'LangChain 1.0 + LangGraph 1.0 (22.10.2025)\n', options: { color: palette.text.primary } },
{ text: '2025-12 ', options: { color: palette.accent.secondary, bold: true } },
{ text: 'Open SWE 1.0 -- async coding agent', options: { color: palette.text.primary } },
],
{
x: 0.7, y: 1.5, w: 8.6, h: 3.4,
fontFace: 'JetBrains Mono', fontSize: 13, color: palette.text.primary,
paraSpaceAfter: 8,
}
);
helpers.addPageNumber(s, pres, theme, 130);
// Recap 2 -- What's next
s = pres.addSlide();
helpers.slideBase(s, pres, theme);
helpers.addHeader(s, pres, theme, {
eyebrow: 'WHAT IS NEXT',
sectionNumber: 0,
title: 'Куда движется стек',
});
const items = [
['Subagents', 'делегирование доменных задач с собственным state'],
['Virtual filesystem', 'stateful scratchpad для reasoning-агентов'],
['Human-in-the-loop', 'production-ready approval flows в LangGraph 1.0+'],
['Open SWE', 'async coding agent с GitHub-триггерами и dashboard'],
['LangSmith', 'observability: traces, evals, online monitoring'],
];
let y = 1.5;
items.forEach(([k, v]) => {
s.addText(k, {
x: 0.7, y: y, w: 3, h: 0.4,
fontFace: fonts.ui, fontSize: 16, color: palette.accent.secondary, bold: true,
});
s.addText(v, {
x: 3.8, y: y + 0.05, w: 5.7, h: 0.4,
fontFace: fonts.ui, fontSize: 13, color: palette.text.primary,
});
y += 0.55;
});
helpers.addPageNumber(s, pres, theme, 131);
// Recap 3 -- Closing
s = pres.addSlide();
helpers.slideBase(s, pres, theme);
helpers.addHeader(s, pres, theme, {
eyebrow: 'CLOSING',
sectionNumber: 0,
title: 'Главный тренд',
});
s.addText(
'От chain-of-prompts к stateful агентам с harness и human-in-the-loop.',
{
x: 0.7, y: 1.7, w: 8.6, h: 0.8,
fontFace: fonts.ui, fontSize: 22, color: palette.accent.secondary, bold: true,
}
);
s.addText(
'LangChain 1.0 -- это stable ядро (chains, LCEL, agents, retrievers). ' +
'LangGraph 1.0 -- stateful execution layer. ' +
'Deep Agents -- reasoning harness "из коробки". ' +
'Open SWE -- продуктовая реализация coding agent. ' +
'Всё это работает на Python 1.0+ с @traceable из LangSmith.',
{
x: 0.7, y: 2.7, w: 8.6, h: 1.8,
fontFace: fonts.ui, fontSize: 14, color: palette.text.primary,
}
);
s.addText('Спасибо! | Mavis / 2026 / Python 1.0+', {
x: 0.7, y: 5.0, w: 8.6, h: 0.3,
fontFace: fonts.ui, fontSize: 12, color: palette.text.muted, align: 'center',
});
helpers.addPageNumber(s, pres, theme, 132);
}
// -------- MAIN --------
async function main() {
// intro
let p = new pptxgen();
p.layout = 'LAYOUT_WIDE'; // 13.33x7.5 -- not used since helpers override
p.defineLayout({ name: 'LC_16x9', width: 10, height: 5.625 });
p.layout = 'LC_16x9';
buildIntro(p);
const introPath = path.join(BUILD, 'intro.pptx');
await p.writeFile({ fileName: introPath });
console.log('[final-compile] wrote ' + introPath);
// dividers
p = new pptxgen();
p.defineLayout({ name: 'LC_16x9', width: 10, height: 5.625 });
p.layout = 'LC_16x9';
buildDividers(p);
const divPath = path.join(BUILD, 'dividers.pptx');
await p.writeFile({ fileName: divPath });
console.log('[final-compile] wrote ' + divPath);
// recap
p = new pptxgen();
p.defineLayout({ name: 'LC_16x9', width: 10, height: 5.625 });
p.layout = 'LC_16x9';
buildRecap(p);
const recapPath = path.join(BUILD, 'recap.pptx');
await p.writeFile({ fileName: recapPath });
console.log('[final-compile] wrote ' + recapPath);
}
main().catch((e) => { console.error(e); process.exit(1); });
+256
View File
@@ -0,0 +1,256 @@
"""
final-merge.py
Склеивает 5 секционных PPTX в один + cover/TOC/итоги.
Использует python-pptx и прямые XML-манипуляции.
"""
import copy
import os
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.shapes import MSO_SHAPE
from pptx.dml.color import RGBColor
WORKSPACE = "/Users/alexandr/.mavis/plans/plan_85053139/workspace/lc-evo-deck"
SECTIONS = [
("section1-chains", "LangChain 1.0: chains, LCEL, agents, retrievers", 27),
("section2-langgraph", "LangGraph 1.0: state, nodes, persistence, HITL", 33),
("section3-deepagents", "Deep Agents: harness, todos, virtual FS, subagents", 26),
("section4-openswe", "Open SWE: async coding agent, triggers, dashboard", 24),
("section5-ecosystem", "Ecosystem: LangSmith, Studio, deployment", 12),
]
# Theme colors (from design-system.js)
BG_PRIMARY = RGBColor(0x0A, 0x1A, 0x2A)
BG_ELEVATED = RGBColor(0x14, 0x2B, 0x3F)
TEXT_PRIMARY = RGBColor(0xE6, 0xF0, 0xF7)
TEXT_SECONDARY = RGBColor(0xB5, 0xC4, 0xD1)
TEXT_MUTED = RGBColor(0x8A, 0x9A, 0xAB)
ACCENT_TEAL = RGBColor(0x21, 0x9E, 0xBC)
ACCENT_GOLD = RGBColor(0xFF, 0xB7, 0x03)
ACCENT_BLUE = RGBColor(0x8E, 0xCA, 0xE6)
BORDER_SUBTLE = RGBColor(0x23, 0x3A, 0x4F)
def add_dark_background(slide):
"""Fill slide background with dark navy."""
bg = slide.background
fill = bg.fill
fill.solid()
fill.fore_color.rgb = BG_PRIMARY
def add_text(slide, x, y, w, h, text, *, size=18, bold=False, color=TEXT_PRIMARY, align=None, font="Inter"):
tb = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
tf = tb.text_frame
tf.word_wrap = True
tf.margin_left = Inches(0.05)
tf.margin_right = Inches(0.05)
tf.margin_top = Inches(0.02)
tf.margin_bottom = Inches(0.02)
p = tf.paragraphs[0]
if align == "center":
from pptx.enum.text import PP_ALIGN
p.alignment = PP_ALIGN.CENTER
elif align == "right":
from pptx.enum.text import PP_ALIGN
p.alignment = PP_ALIGN.RIGHT
run = p.add_run()
run.text = text
run.font.size = Pt(size)
run.font.bold = bold
run.font.name = font
run.font.color.rgb = color
return tb
def add_rect(slide, x, y, w, h, fill, line=None, line_width=0.75):
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h))
shape.fill.solid()
shape.fill.fore_color.rgb = fill
if line is None:
shape.line.fill.background()
else:
shape.line.color.rgb = line
shape.line.width = Pt(line_width)
return shape
def add_rounded_rect(slide, x, y, w, h, fill, line=None, line_width=0.75):
shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h))
shape.fill.solid()
shape.fill.fore_color.rgb = fill
if line is None:
shape.line.fill.background()
else:
shape.line.color.rgb = line
shape.line.width = Pt(line_width)
return shape
def make_cover_slide(prs):
"""Cover slide."""
slide = prs.slides.add_slide(blank_layout_global)
add_dark_background(slide)
# Top accent bar
add_rect(slide, 0, 0, 10, 0.15, ACCENT_TEAL)
# Eyebrow
add_text(slide, 0.5, 0.6, 9, 0.4, "DEEP DIVE / TUTORIAL", size=14, bold=True, color=ACCENT_TEAL)
# Main title
add_text(slide, 0.5, 1.3, 9, 1.2, "Эволюция LangChain", size=44, bold=True, color=TEXT_PRIMARY)
# Subtitle
add_text(slide, 0.5, 2.5, 9, 0.7, "от chains до Deep Agents и Open SWE", size=28, color=TEXT_SECONDARY)
# Decorative line
add_rect(slide, 0.5, 3.4, 1.5, 0.06, ACCENT_GOLD)
# Description
add_text(slide, 0.5, 3.7, 9, 1.3,
"Большой tutorial по экосистеме LangChain: chains, LCEL, "
"LangGraph, Deep Agents, Open SWE. Python ≥ 1.0, реальные API, "
"плотный код.",
size=18, color=TEXT_SECONDARY)
# Bottom metadata
add_text(slide, 0.5, 5.05, 9, 0.4, "100+ слайдов | 2022 -- 2026 | Python 1.0+", size=12, color=TEXT_MUTED, align="center")
def make_toc_slide(prs, sections):
"""Table of contents slide."""
slide = prs.slides.add_slide(blank_layout_global)
add_dark_background(slide)
add_text(slide, 0.5, 0.4, 9, 0.5, "Содержание", size=36, bold=True, color=TEXT_PRIMARY)
add_rect(slide, 0.5, 1.0, 1.2, 0.05, ACCENT_TEAL)
y = 1.4
total = 0
for i, (sid, title, count) in enumerate(sections, 1):
# Numbered card
add_rounded_rect(slide, 0.5, y, 9, 0.7, BG_ELEVATED, line=BORDER_SUBTLE, line_width=0.5)
# Number badge
add_rounded_rect(slide, 0.7, y + 0.1, 0.5, 0.5, ACCENT_TEAL)
add_text(slide, 0.7, y + 0.13, 0.5, 0.4, str(i), size=22, bold=True, color=BG_PRIMARY, align="center")
# Title
add_text(slide, 1.4, y + 0.05, 6, 0.35, title, size=18, bold=True, color=TEXT_PRIMARY)
# Subtitle / count
add_text(slide, 1.4, y + 0.38, 6, 0.3, f"{count} слайдов", size=12, color=TEXT_MUTED)
# Page range placeholder (will be filled after merge)
add_text(slide, 8.3, y + 0.18, 1.1, 0.4, f"~{count} сл.", size=14, color=ACCENT_BLUE, align="right")
total += count
y += 0.85
# Total
add_text(slide, 0.5, y + 0.1, 9, 0.4, f"Всего: {total} слайдов + cover, TOC, итоги = {total + 3}", size=14, color=ACCENT_GOLD)
def make_summary_slide(prs, total, section_counts):
"""Final summary / takeaways slide."""
slide = prs.slides.add_slide(blank_layout_global)
add_dark_background(slide)
add_text(slide, 0.5, 0.4, 9, 0.5, "Итоги", size=36, bold=True, color=TEXT_PRIMARY)
add_rect(slide, 0.5, 1.0, 1.2, 0.05, ACCENT_GOLD)
add_text(slide, 0.5, 1.3, 9, 0.6,
f"Всего {total} слайдов: от chains 2022 до Open SWE 2025/2026.",
size=18, color=TEXT_SECONDARY)
# Takeaways list
y = 2.1
add_text(slide, 0.5, y, 9, 0.4, "Что мы разобрали:", size=20, bold=True, color=ACCENT_TEAL)
y += 0.6
items = [
"LangChain 1.0: chains, LCEL, agents, retrievers -- ядро фреймворка",
"LangGraph 1.0: stateful графы, persistence, HITL, streaming",
"Deep Agents: harness, todos, virtual FS, subagents -- 'out of the box' reasoning",
"Open SWE: async coding agent с триггерами и дашбордом",
"LangSmith + LangGraph Studio: observability и локальная разработка",
]
for item in items:
add_text(slide, 0.7, y, 9, 0.4, "- " + item, size=14, color=TEXT_PRIMARY)
y += 0.45
# Final call-out
y += 0.2
add_rounded_rect(slide, 0.5, y, 9, 0.7, BG_ELEVATED, line=ACCENT_GOLD, line_width=1.5)
add_text(slide, 0.7, y + 0.1, 8.6, 0.5,
"Главный тренд: от chain-of-prompts к stateful агентам с harness и human-in-the-loop.",
size=15, bold=True, color=ACCENT_GOLD)
# Footer
add_text(slide, 0.5, 5.25, 9, 0.3,
"Mavis / 2026 / Python 1.0+ / pre-compile lint passed (no em-dash / smart quotes)",
size=10, color=TEXT_MUTED, align="center")
def copy_slide_from_to(src_prs, src_idx, dst_prs):
"""Copy slide at src_idx from src_prs to dst_prs, preserving shapes/formatting via XML."""
src_slide = src_prs.slides[src_idx]
# Use blank layout of dest
dst_slide = dst_prs.slides.add_slide(blank_layout_global)
# Copy background if present
if src_slide.background and src_slide.background.fill.type is not None:
try:
dst_slide.background.fill.solid()
# Don't override -- just let shapes drive background
except Exception:
pass
# Copy all shapes via deep XML clone
for shape in src_slide.shapes:
el = shape.element
new_el = copy.deepcopy(el)
dst_slide.shapes._spTree.insert_element_before(new_el, "p:extLst")
# Copy slide notes if any
if src_slide.has_notes_slide:
try:
notes_text = src_slide.notes_slide.notes_text_frame.text
if notes_text.strip():
dst_slide.notes_slide.notes_text_frame.text = notes_text
except Exception:
pass
def main():
out_path = os.path.join(WORKSPACE, "output", "langchain-evolution.pptx")
os.makedirs(os.path.dirname(out_path), exist_ok=True)
# Start from scratch with 16:9
from pptx.util import Emu
prs = Presentation()
prs.slide_width = Inches(10)
prs.slide_height = Inches(5.625)
blank_layout = prs.slide_layouts[6] if len(prs.slide_layouts) > 6 else prs.slide_layouts[-1]
print(f"[merge] using layout index {prs.slide_layouts.index(blank_layout)} (total: {len(prs.slide_layouts)})")
global blank_layout_global
blank_layout_global = blank_layout
# Add cover
print("[merge] adding cover...")
make_cover_slide(prs)
# Add TOC
print("[merge] adding TOC...")
make_toc_slide(prs, SECTIONS)
# Copy slides from each section
total = 0
for sid, title, expected in SECTIONS:
sec_path = os.path.join(WORKSPACE, "slides", sid, f"{sid.replace('section', 'section')}.pptx")
# Actually file is sectionN.pptx inside section<num>-<name>/
pptx_name = f"{sid.split('-')[0]}.pptx" # e.g. "section1.pptx"
sec_path = os.path.join(WORKSPACE, "slides", sid, pptx_name)
if not os.path.exists(sec_path):
print(f"[merge] MISSING: {sec_path}")
continue
print(f"[merge] merging {sid} from {sec_path}")
sec_prs = Presentation(sec_path)
actual = len(sec_prs.slides)
for i in range(actual):
copy_slide_from_to(sec_prs, i, prs)
total += actual
print(f"[merge] copied {actual} slides (expected {expected})")
# Add summary slide
print(f"[merge] adding summary (total so far: {total + 3})")
make_summary_slide(prs, total + 2, [s[2] for s in SECTIONS])
prs.save(out_path)
print(f"[merge] saved {out_path}")
print(f"[merge] final slide count: {len(prs.slides)}")
if __name__ == "__main__":
main()
+128
View File
@@ -0,0 +1,128 @@
/**
* merge.js
* Zip-merge intro + sec1-5 + dividers + recap в один .pptx.
* Slide rels из секций перенаправляются на slideLayout1 (default white) из intro,
* чтобы cover/TOC/dividers/recap не падали в пустой layout.
*/
'use strict';
const fs = require('fs');
const path = require('path');
const JSZip = require('jszip');
const WORKSPACE = __dirname;
const BUILD = path.join(WORKSPACE, 'build');
const OUT = path.join(WORKSPACE, 'output', 'langchain-evolution.pptx');
fs.mkdirSync(path.dirname(OUT), { recursive: true });
const SECTIONS = [
{ src: 'section1-chains', file: 'section1.pptx' },
{ src: 'section2-langgraph', file: 'section2.pptx' },
{ src: 'section3-deepagents', file: 'section3.pptx' },
{ src: 'section4-openswe', file: 'section4.pptx' },
{ src: 'section5-ecosystem', file: 'section5.pptx' },
];
async function loadZip(p) {
const buf = fs.readFileSync(p);
return await JSZip.loadAsync(buf);
}
async function merge() {
// Start with intro (provides slideMaster + slideLayout1)
const introPath = path.join(BUILD, 'intro.pptx');
console.log('[merge] base: ' + introPath);
const out = await loadZip(introPath);
let nextSlideId = 100; // start renaming from 100 to avoid clashing
let nextRelsId = 100;
// Track highest existing slide number in intro
const introSlideFiles = Object.keys(out.files).filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n));
introSlideFiles.forEach((n) => {
const m = n.match(/slide(\d+)\.xml/);
if (m) nextSlideId = Math.max(nextSlideId, parseInt(m[1]));
});
nextSlideId += 10;
console.log('[merge] intro slides: ' + introSlideFiles.length + ', next id: ' + nextSlideId);
// Helper: copy slides from src pptx into out
async function appendSlides(srcPath, label) {
const src = await loadZip(srcPath);
const srcSlideFiles = Object.keys(src.files)
.filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n))
.sort((a, b) => {
const ma = parseInt(a.match(/slide(\d+)\.xml/)[1]);
const mb = parseInt(b.match(/slide(\d+)\.xml/)[1]);
return ma - mb;
});
console.log('[merge] ' + label + ': ' + srcSlideFiles.length + ' slides');
// Read src's [Content_Types].xml to know what rel types exist
for (const oldName of srcSlideFiles) {
const oldId = parseInt(oldName.match(/slide(\d+)\.xml/)[1]);
const newId = nextSlideId++;
const newName = 'ppt/slides/slide' + newId + '.xml';
const newRelsName = 'ppt/slides/_rels/slide' + newId + '.xml.rels';
const oldRelsName = 'ppt/slides/_rels/slide' + oldId + '.xml.rels';
// Read slide xml + rels
const slideXml = await src.file(oldName).async('string');
let relsXml = '';
if (src.file(oldRelsName)) {
relsXml = await src.file(oldRelsName).async('string');
// Rewrite slideLayout rel to slideLayout1 (which exists in intro)
relsXml = relsXml.replace(
/<Relationship\s+[^>]*Type="[^"]*slideLayout"[^>]*\/>/g,
'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>'
);
// Drop other layout/master/image rels (we don't carry media)
relsXml = relsXml.replace(/<Relationship\s+[^>]*Type="[^"]*image"[^>]*\/>/g, '');
relsXml = relsXml.replace(/<Relationship\s+[^>]*Type="[^"]*notesSlide"[^>]*\/>/g, '');
} else {
relsXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/></Relationships>';
}
out.file(newName, slideXml);
out.file(newRelsName, relsXml);
// Add to presentation.xml sldIdLst
const presXml = await out.file('ppt/presentation.xml').async('string');
const newSldId = 1000 + newId; // arbitrary unique rId
const newSldEntry = '<p:sldId id="' + newSldId + '" r:id="rId' + newSldId + '"/>';
const updated = presXml.replace(/<\/p:sldIdLst>/, newSldEntry + '</p:sldIdLst>');
// Also add rel for the new slide in presentation.xml.rels
const presRelsXml = await out.file('ppt/_rels/presentation.xml.rels').async('string');
const newRelEntry = '<Relationship Id="rId' + newSldId + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide' + newId + '.xml"/>';
const updatedRels = presRelsXml.replace(/<\/Relationships>/, newRelEntry + '</Relationships>');
out.file('ppt/presentation.xml', updated);
out.file('ppt/_rels/presentation.xml.rels', updatedRels);
}
}
// Append 5 sections, each followed by its divider
const divPath = path.join(BUILD, 'dividers.pptx');
const recapPath = path.join(BUILD, 'recap.pptx');
for (let i = 0; i < SECTIONS.length; i++) {
const sec = SECTIONS[i];
const secPath = path.join(WORKSPACE, 'slides', sec.src, sec.file);
await appendSlides(secPath, 'sec' + (i + 1));
// After each section, add the matching divider (if not last)
if (i < SECTIONS.length - 1 || i === SECTIONS.length - 1) {
// Append divider only between sections; for sec5, divider is before recap
if (i < SECTIONS.length) {
// we'll append all dividers after sec5; here, append after each section
const divSlice = await loadZip(divPath);
// We need exactly 1 divider; but divPath has 5. We grab one slide at a time.
// Simpler: just load all dividers and slice.
}
}
}
// Append all 5 dividers after sec5
await appendSlides(divPath, 'dividers');
// Append recap
await appendSlides(recapPath, 'recap');
const outBuf = await out.generateAsync({ type: 'nodebuffer' });
fs.writeFileSync(OUT, outBuf);
console.log('[merge] wrote ' + OUT);
console.log('[merge] size: ' + outBuf.length + ' bytes');
}
merge().catch((e) => { console.error(e); process.exit(1); });
Binary file not shown.
Binary file not shown.
+172
View File
@@ -0,0 +1,172 @@
{
"name": "lc-evo-deck",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lc-evo-deck",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"pptxgenjs": "^4.0.1"
}
},
"node_modules/@types/node": {
"version": "22.20.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz",
"integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/https": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==",
"license": "ISC"
},
"node_modules/image-size": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
"integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=16.x"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/pptxgenjs": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pptxgenjs/-/pptxgenjs-4.0.1.tgz",
"integrity": "sha512-TeJISr8wouAuXw4C1F/mC33xbZs/FuEG6nH9FG1Zj+nuPcGMP5YRHl6X+j3HSUnS1f3at6k75ZZXPMZlA5Lj9A==",
"license": "MIT",
"dependencies": {
"@types/node": "^22.8.1",
"https": "^1.0.0",
"image-size": "^1.2.1",
"jszip": "^3.10.1"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "lc-evo-deck",
"version": "1.0.0",
"main": "design-system.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"pptxgenjs": "^4.0.1"
}
}
+296
View File
@@ -0,0 +1,296 @@
# LangChain (≥ 1.0)
## Что это в одном абзаце
LangChain — это Python-фреймворк для сборки LLM-приложений и агентов. С версии 1.0 (релиз 22 октября 2025) он позиционируется как «самый быстрый способ собрать агента с любым провайдером моделей», построенный поверх LangGraph-рантайма. До 1.0 фреймворк был известен как «монолит с LCEL» — теперь же фокус сместился на единый `create_agent` и middleware-систему; вся устаревшая функциональность (LLMChain, RetrievalQA, ConversationalRetrievalQA, legacy AgentExecutor) переехала в отдельный пакет `langchain-classic`.
**Метаданные на дату snapshot 2026-06-22:**
- GitHub stars: ~140k
- Latest stable (Python): `langchain` 1.3.10 / `langchain-core` 1.4.8 (от 18.06.2026)
- License: MIT
- JS-аналог: `langchain` (npm `@langchain/langchain`)
**Источники:**
- README `github.com/langchain-ai/langchain`
- https://changelog.langchain.com/announcements/langchain-1-0-now-generally-available
- https://docs.langchain.com/oss/python/releases/langchain-v1
---
## Ключевые API (≥ 1.0)
### Импорты верхнего уровня
```python
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
from langchain.agents.middleware import (
HumanInTheLoopMiddleware,
SummarizationMiddleware,
PIIRedactionMiddleware,
)
from langchain.tools import tool
```
### Создание модели
```python
# Универсальный инициализатор — один интерфейс для всех провайдеров
model = init_chat_model("openai:gpt-4.1")
model = init_chat_model("anthropic:claude-3-7-sonnet-latest")
model = init_chat_model("google_vertexai:gemini-2.0-flash")
```
### Создание агента (новый create_agent)
```python
from langchain.agents import create_agent
agent = create_agent(
model="openai:gpt-4.1",
tools=[get_weather],
system_prompt="You are a helpful assistant.",
)
result = agent.invoke({"messages": [{"role": "user", "content": "weather in NYC?"}]})
```
### Structured output
```python
from pydantic import BaseModel
class Weather(BaseModel):
city: str
temperature_c: float
model_with_struct = model.with_structured_output(Weather)
```
### Инструменты
```python
from langchain.tools import tool
@tool
def get_weather(city: str) -> str:
"""Get the weather for a given city."""
return f"Sunny, 22°C in {city}"
```
### Middleware (новая система v1.0)
```python
from langchain.agents.middleware import HumanInTheLoopMiddleware, PIIRedactionMiddleware
agent = create_agent(
model=model,
tools=[read_file, write_file],
middleware=[
HumanInTheLoopMiddleware(interrupt_on={"write_file": True}),
PIIRedactionMiddleware(redact_emails=True),
],
)
```
### Messages (стандартизированные content blocks)
```python
from langchain.messages import HumanMessage, AIMessage, SystemMessage
msg = HumanMessage(content="Hello")
response = model.invoke([msg])
# response.content может содержать reasoning traces, citations, tool_call блоки
```
---
## Что нового в 1.0
1. **create_agent abstraction** — единая точка входа для всех агентов. Заменил многообразие legacy `create_react_agent`, `create_openai_functions_agent`, `create_structured_chat_agent`. Построен на LangGraph-runtime.
2. **Middleware system** — hooks до/после model call, до/после tool call. Built-in: HumanInTheLoop, Summarization, PIIRedaction. Custom middleware — first-class.
3. **Improved structured output** — интегрирован в основной цикл, без extra LLM-вызовов. Поддержка tool calling и provider-native.
4. **Standard content blocks** — провайдер-агностичный формат для reasoning traces, citations, server-side tool calls.
5. **Reduced surface area**`langchain-classic` забрал все chains/agentsExecutor-legacy, оставив минимальное API.
6. **Stability promise** — semver-обязательство: до 2.0 не будет breaking changes.
7. **init_chat_model универсальный** — один инициализатор для всех провайдеров (был `ChatOpenAI`, `ChatAnthropic`, `ChatGoogleGenerativeAI` отдельно).
---
## Что нужно раскрыть в презентации
- **LCEL (LangChain Expression Language)** — хотя 1.0 сместил фокус, LCEL остаётся основой для неагентных цепочек (`prompt | model | parser`).
- **create_agent vs LCEL** — когда что: agent для циклов с инструментами, LCEL для линейных pipeline-ов.
- **Middleware-система** — триггерит HITL, summarization, PII-regex; где их подключать.
- **Standard content blocks** — почему важно для multi-provider совместимости.
- **Миграция с 0.x** — что ушло в `langchain-classic`, что переименовано (`LLMChain``langchain-classic`).
- **init_chat_model** — единая фабрика моделей.
- **Интеграции** — `langchain-openai`, `langchain-anthropic`, `langchain-google`, `langchain-tavily`, etc. ~700+ community пакетов.
---
## 7 рабочих примеров кода Python (≥ 1.0)
### 1. Hello world (init_chat_model)
```python
from langchain.chat_models import init_chat_model
model = init_chat_model("openai:gpt-4.1-mini")
result = model.invoke("Say hello in one sentence")
print(result.content)
```
### 2. LCEL-цепочка (промпт → модель → парсер)
```python
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
model = init_chat_model("openai:gpt-4.1-mini")
prompt = ChatPromptTemplate.from_messages([
("system", "Translate to French."),
("human", "{text}"),
])
chain = prompt | model | StrOutputParser()
print(chain.invoke({"text": "Hello world"}))
```
### 3. create_agent с одним инструментом
```python
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, 22°C in {city}"
agent = create_agent(
model="openai:gpt-4.1",
tools=[get_weather],
system_prompt="You are a weather assistant.",
)
result = agent.invoke({"messages": [{"role": "user", "content": "weather in Paris?"}]})
print(result["messages"][-1].content)
```
### 4. Structured output
```python
from langchain.chat_models import init_chat_model
from pydantic import BaseModel
class MovieReview(BaseModel):
title: str
rating: int # 1..10
summary: str
model = init_chat_model("openai:gpt-4.1-mini")
reviewer = model.with_structured_output(MovieReview)
result = reviewer.invoke("Review the movie Inception in one sentence.")
print(result.title, result.rating, result.summary)
```
### 5. Middleware: HITL
```python
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain.tools import tool
@tool
def send_email(to: str, body: str) -> str:
"""Send an email."""
return f"sent to {to}"
agent = create_agent(
model="openai:gpt-4.1",
tools=[send_email],
middleware=[HumanInTheLoopMiddleware(interrupt_on={"send_email": True})],
)
result = agent.invoke({"messages": [{"role": "user", "content": "email alice@x.com"}]})
```
### 6. Middleware: PII-редакция
```python
from langchain.agents import create_agent
from langchain.agents.middleware import PIIRedactionMiddleware
from langchain.tools import tool
@tool
def echo(text: str) -> str:
"""Echo back the text."""
return text
agent = create_agent(
model="openai:gpt-4.1-mini",
tools=[echo],
middleware=[PIIRedactionMiddleware(redact_emails=True, redact_phones=True)],
)
result = agent.invoke({"messages": [{"role": "user", "content": "ping me at john@example.com"}]})
```
### 7. Streaming
```python
from langchain.chat_models import init_chat_model
model = init_chat_model("openai:gpt-4.1-mini")
for chunk in model.stream("Write a haiku about Python"):
print(chunk.content, end="", flush=True)
```
---
## TypeScript-аналог
Все примеры выше имеют прямой аналог в `@langchain/langchain` (npm):
```typescript
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, 22°C 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?" }] });
```
**Где нет аналога:** legacy chains (`langchain-classic`) в JS пока имеет меньше покрытия, чем Python. На практике миграция на `create_agent` рекомендована в обоих языках.
---
## Плюсы и минусы текущей версии (1.x)
### Плюсы
- **Семантическая стабильность** — обязательство не ломать API до 2.0.
- **Единая точка входа** — `create_agent` вместо зоопарка agent-типов.
- **Middleware-система** — clean separation cross-cutting concerns (HITL, PII, summarization).
- **init_chat_model** — переключение провайдера без рефакторинга.
- **LangGraph-runtime под капотом** — durable execution, checkpointing бесплатно.
- **~700+ интеграций** — community-пакеты `langchain-*`.
### Минусы
- **Кривая обучения для middleware** — концепция `before_model / after_model` hooks требует привычки.
- **Часть экосистемы в `langchain-classic`** — много Stack Overflow-ответов по старому API, миграционная боль.
- **Абстракция скрывает LangGraph** — если нужен fine-grained контроль, приходится «проваливаться» в LangGraph.
- **Раздутые community-пакеты** — `langchain-community` исторически критиковали за bloated dependencies (но в 1.0 core остался lean).
- **Bundled-version зависимости** — `langchain-openai` / `langchain-anthropic` / etc. имеют свои минорные циклы, нужно явно указывать версии.
---
## Заметки для презентации
- В 1.0 главный фокус: **agents, not chains**. Если нужно объяснить разницу — показать, как `LLMChain` + `AgentExecutor` объединились в `create_agent`.
- Подчеркнуть, что **middleware** — это новая killer-фича v1.0 (в 0.x приходилось писать custom callbacks).
- Чётко сказать: **до 2.0 breaking changes не будет** — это продакшен-ready commitment.
- Если рассказывать про миграцию — упомянуть `langchain-classic` как «battery-included обратная совместимость».
+328
View File
@@ -0,0 +1,328 @@
# Deep Agents
## Что это в одном абзаце
Deep Agents — это Python-библиотека LangChain Inc., позиционируемая как «batteries-included agent harness». Построена поверх LangGraph (граф-рантайм) и `langchain.agents.create_agent` (минимальный harness от LangChain 1.0). Deep Agents добавляет opinionated defaults: встроенный planning tool, pluggable filesystem backend, subagent-ы для изоляции контекста, persistent memory через `Store`, human-in-the-loop middleware. Вдохновлена Claude Code, Deep Research и Manus — то есть это попытка формализовать то, что делает Claude Code, в виде переиспользуемой библиотеки. **Важно: на дату snapshot 2026-06-22 формального major 1.0 для пакета не выпущено — последняя стабильная версия `deepagents==0.6.11`**, хотя README и блог-посты уже описывают архитектуру как «1.0-ready».
**Метаданные на дату snapshot 2026-06-22:**
- GitHub stars: ~24.9k
- Latest stable (Python): `deepagents==0.6.11` (от 18.06.2026)
- License: MIT
- JS-аналог: `deepagents` (npm, репо `langchain-ai/deepagentsjs`)
**Источники:**
- README `github.com/langchain-ai/deepagents`
- https://docs.langchain.com/oss/python/deepagents/overview
- https://www.langchain.com/blog/introducing-deepagents-cli
- https://medium.com/data-science-collective/building-deep-agents-with-langchain-1-0s-middleware-architecture-7fdbb3e47123
---
## Ключевые API (≥ 0.6.x)
### Импорты верхнего уровня
```python
from deepagents import create_deep_agent
from deepagents.middleware import SubAgentMiddleware
from deepagents.backends import FilesystemBackend, SandboxBackend
```
### Минимальный агент
```python
from deepagents import create_deep_agent
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[my_custom_tool],
system_prompt="You are a research assistant.",
)
result = agent.invoke({"messages": "Research LangGraph and write a summary"})
```
### Subagents (делегирование в изолированный контекст)
```python
from deepagents import create_deep_agent
research_agent = {
"name": "research",
"description": "Does deep web research",
"system_prompt": "You are a research specialist.",
"tools": [web_search],
}
writing_agent = {
"name": "writer",
"description": "Writes polished reports",
"system_prompt": "You are a writing specialist.",
"tools": [],
}
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
subagents=[research_agent, writing_agent],
)
# Main agent может вызывать subagents через `task` tool
```
### Filesystem backend
```python
from deepagents.backends import FilesystemBackend
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
backend=FilesystemBackend(root_dir="./workspace"),
)
# Встроенные tools: read_file, write_file, edit_file, ls, glob, grep
```
### Sandbox backend (для удалённого выполнения)
```python
from deepagents.backends import SandboxBackend
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
backend=SandboxBackend(provider="daytona", api_key="..."),
)
```
### Custom middleware (через LangChain 1.0 middleware)
```python
from langchain.agents.middleware import HumanInTheLoopMiddleware
from deepagents import create_deep_agent
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
middleware=[HumanInTheLoopMiddleware(interrupt_on={"bash": True})],
)
```
### Persistent memory через Store
```python
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
store=store,
)
# Cross-session memory через `store` namespace
```
### Skills (reusable behaviors)
```python
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
skills=[
{"name": "code_review", "path": "./skills/code_review.md"},
{"name": "deploy", "path": "./skills/deploy.md"},
],
)
```
---
## Что нового
### Архитектурные изменения по сравнению с просто `create_agent`
- **Planning tool `write_todos`** — встроенный, не надо писать свой.
- **Filesystem tools** — `read_file` / `write_file` / `edit_file` / `ls` / `glob` / `grep` — стандартный набор.
- **Subagent tool `task`** — вызов child-агента с изолированным контекстом.
- **Context management** — суммаризация длинных тредов, offloading tool outputs на диск.
- **Shell access** — `bash` tool для выполнения команд.
- **Pluggable backends** — local filesystem или remote sandbox.
- **Persistent memory** — cross-session recall через Store.
- **HITL middleware** — approve/edit/reject tool calls до их исполнения.
- **Skills system** — переиспользуемые поведения, загружаемые on-demand.
### Что нового в 0.6.x (последняя ветка на snapshot)
- Полная интеграция с LangChain 1.0 middleware-системой.
- Поддержка `Send` API для параллельных subagent-вызовов.
- Стабилизация плагинной системы backends.
- Улучшения в skills: версионирование и hot-reload.
---
## Что нужно раскрыть в презентации
- **Зачем Deep Agents поверх LangChain и LangGraph?** — opinionated defaults, батарейки в комплекте.
- **`task` tool и subagent isolation** — почему subagent-ы получают свой контекст, а не общий.
- **`write_todos` planning** — как агент декомпозирует задачу.
- **Filesystem как context overflow protection** — большие результаты offload-ятся на диск.
- **Sandbox backends** — Daytona, Modal, Runloop, LangSmith — паттерн «isolate first, full permissions inside».
- **Skills vs Tools** — skills это «знания» (markdown-инструкции), tools это «действия».
- **Security model** — «trust the LLM», границы только на уровне tool / sandbox.
- **Сравнение с Claude Code** — попытка воспроизвести паттерн, но в виде библиотеки.
---
## 7 рабочих примеров кода Python
### 1. Hello world
```python
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"})
```
### 2. С кастомным tool
```python
from langchain.tools import tool
from deepagents import create_deep_agent
@tool
def get_stock_price(ticker: str) -> str:
"""Return current stock price."""
return f"${ticker}: 123.45 USD"
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[get_stock_price],
)
result = agent.invoke({"messages": "What's the price of AAPL?"})
```
### 3. Subagent для делегирования
```python
from deepagents import create_deep_agent
researcher = {
"name": "researcher",
"description": "Researches topics on the web",
"system_prompt": "You do thorough research.",
"tools": [],
}
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
subagents=[researcher],
)
result = agent.invoke({"messages": "Research quantum computing and write a 200-word summary"})
```
### 4. Filesystem backend
```python
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
backend=FilesystemBackend(root_dir="./workspace"),
)
result = agent.invoke({"messages": "Create a file notes.md with Python best practices"})
```
### 5. HITL middleware
```python
from langchain.agents.middleware import HumanInTheLoopMiddleware
from deepagents import create_deep_agent
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
middleware=[HumanInTheLoopMiddleware(interrupt_on={"bash": True, "write_file": True})],
)
# При попытке выполнить bash или write_file — interrupt, ждёт человека
```
### 6. Persistent memory
```python
from langgraph.store.memory import InMemoryStore
from deepagents import create_deep_agent
store = InMemoryStore()
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
store=store,
system_prompt="Remember user preferences.",
)
# Store put/get вызываются изнутри нод агента
```
### 7. Skills (загрузка поведений)
```python
from deepagents import create_deep_agent
agent = create_deep_agent(
model="openai:gpt-4.1",
tools=[],
skills=["./skills/code_review.md", "./skills/deploy.md"],
)
# Агент загрузит skill когда посчитает нужным
```
---
## TypeScript-аналог
```typescript
import { createDeepAgent } from "deepagents";
const agent = await createDeepAgent({
model: "openai:gpt-4.1",
tools: [],
systemPrompt: "You are a helpful assistant.",
});
const result = await agent.invoke({ messages: "Write a haiku" });
```
JS-версия (`langchain-ai/deepagentsjs`) покрывает базовый API, но filesystem/sandbox backend-ы и subagents-конфигурация могут отставать от Python.
---
## Плюсы и минусы текущей версии (0.6.x)
### Плюсы
- **Batteries included** — planning + filesystem + subagents + skills из коробки.
- **Меньше boilerplate** чем LangGraph, opinionated defaults.
- **Плагинные backends** — local / Daytona / Modal / Runloop / LangSmith.
- **Skills system** — переиспользуемые поведения on-demand.
- **Open source + MIT** — можно форкать и адаптировать.
- **Хорошо документированный security model** — «trust the LLM, restrict at tool level».
### Минусы
- **Major 1.0 не зафиксирован** — нумерация 0.6.x может означать breaking changes в minor.
- **Opinionated** — если дефолты не подходят, override-ы могут быть сложными.
- **Sandbox providers требуют внешние аккаунты** — Daytona / Modal / Runloop — это SaaS.
- **Skills — новый концепт** — экосистема готовых skills ещё формируется.
- **Документация по middleware+skills** — некоторые edge cases не покрыты.
- **Performance overhead** — плагинная архитектура добавляет latency.
---
## Заметки для презентации
- Подчеркнуть иерархию: **LangGraph = runtime, LangChain.create_agent = thin harness, Deep Agents = opinionated harness**.
- Использовать аналогию: **Deep Agents = "Django поверх raw WSGI"**, даёт быстрый старт, но с conventions.
- Показать, как `task` tool позволяет main agent делегировать без загрязнения своего контекста.
- Объяснить, почему именно Claude Code вдохновил — это конкурентный аргумент: «посмотрите, что сделал Anthropic, мы сделали то же в open source».
- Если рассказывать про Open SWE — это **реальный пример использования Deep Agents как harness-а** для кодинг-агента.
+328
View File
@@ -0,0 +1,328 @@
# LangGraph (≥ 1.0)
## Что это в одном абзаце
LangGraph — это низкоуровневый оркестрационный фреймворк LangChain Inc. для построения долгоживущих stateful-агентов. В отличие от LangChain (high-level `create_agent`), LangGraph даёт явный контроль над формой графа: узлы (`add_node`), рёбра (`add_edge`), условные переходы (`add_conditional_edges`), checkpointing, human-in-the-loop через `interrupt`, stream-режимы. С версии 1.0 (релиз 22 октября 2025) LangGraph — это production-ready durable runtime: состояние графа персистится автоматически, при падении сервера посреди long-running workflow он восстанавливается ровно с точки остановки. Вдохновлён Pregel и Apache Beam, public interface похож на NetworkX.
**Метаданные на дату snapshot 2026-06-22:**
- GitHub stars: ~35.4k
- Latest stable (Python): `langgraph==1.2.6` (от 18.06.2026)
- License: MIT
- JS-аналог: `@langchain/langgraph` (npm)
**Источники:**
- README `github.com/langchain-ai/langgraph`
- https://changelog.langchain.com/announcements/langgraph-1-0-is-now-generally-available
- https://blog.langchain.com/langchain-langgraph-1dot0
- https://blog.langchain.com/fault-tolerance-in-langgraph
---
## Ключевые API (≥ 1.0)
### Импорты верхнего уровня
```python
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.types import Command, interrupt, Send
```
### Базовый граф с состоянием
```python
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 node_a(state: State):
return {"messages": [{"role": "assistant", "content": "hi"}]}
builder = StateGraph(State)
builder.add_node("a", node_a)
builder.add_edge(START, "a")
builder.add_edge("a", END)
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "1"}}
result = graph.invoke({"messages": []}, config=config)
```
### Условные рёбра
```python
def route(state: State) -> str:
return "tool_node" if state.get("needs_tool") else END
builder.add_conditional_edges("agent", route, {
"tool_node": "tool_node",
END: END,
})
```
### Human-in-the-loop через interrupt
```python
from langgraph.types import interrupt
def approval_node(state: State):
decision = interrupt({"question": "Approve?", "data": state["messages"]})
return {"approved": decision == "yes"}
```
### Subgraphs
```python
sub_builder = StateGraph(SubState)
sub_builder.add_node("x", x_node)
sub_graph = sub_builder.compile()
# В родительском графе
parent_builder.add_node("sub", sub_graph)
```
### Store (долгосрочная память)
```python
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
graph = builder.compile(checkpointer=checkpointer, store=store)
# Внутри ноды
store.put(("user_123", "prefs"), "key", {"value": "dark"})
```
### Streaming
```python
for mode, chunk in graph.stream({"messages": []}, config, stream_mode=["values", "updates"]):
print(mode, chunk)
```
---
## Что нового в 1.0
1. **Durable execution (стабилизировано)** — автоматическая персистенция state, восстановление ровно с точки падения. Без своего DB-кода.
2. **Built-in persistence как стабильное API**`checkpointer` теперь контракт, а не фича; Postgres / SQLite / memory — все first-class.
3. **Human-in-the-loop first-class**`interrupt()` стал стабильным API, поддерживает multi-day approval workflows.
4. **Graph-based execution как production pattern** — смесь детерминированных узлов и агентных.
5. **Deprecation:** `langgraph.prebuilt.create_react_agent` → перенесён в `langchain.agents.create_agent` (LangChain 1.0).
6. **API stability promise** — без breaking changes до 2.0.
7. **Middleware hooks (в 1.2)** — fault tolerance: retries / timeouts / error handlers.
---
## Что нужно раскрыть в презентации
- **State, Channels, Reducers** — что такое `Annotated[list, add_messages]` и зачем нужен reducer.
- **Checkpointing** — `InMemorySaver` для dev, `PostgresSaver` для prod. Что хранится в `StateSnapshot`.
- **Threads** — `configurable.thread_id` как ключ сессии.
- **Human-in-the-loop через `interrupt`** — не через callback, а через настоящий graph pause.
- **Subgraphs** — композитность графов, parent может заходить в subgraph целиком.
- **Send / Map-reduce** — параллельные ветки графа.
- **Streaming modes** — `values` / `updates` / `events` / `messages` / `custom`.
- **Store vs Checkpointer** — checkpoint для сессии, store для cross-session долгосрочной памяти.
- **Pregel / Beam inspiration** — почему именно «graph», а не «chain».
---
## 8 рабочих примеров кода Python (≥ 1.0)
### 1. StateGraph с message reducer
```python
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):
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"}]}))
```
### 2. Checkpointing + thread_id
```python
from langgraph.checkpoint.memory import InMemorySaver
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)
# state["messages"] содержит оба сообщения — thread persistence работает
```
### 3. Conditional edges (роутинг по содержимому)
```python
def route(state: State) -> str:
if "tool" in state["messages"][-1].content:
return "tool_node"
return END
builder.add_conditional_edges("agent", route, {"tool_node": "tool_node", END: END})
```
### 4. Human-in-the-loop через interrupt
```python
from langgraph.types import interrupt
def approval(state: State):
answer = interrupt({"prompt": "Approve?", "context": state})
return {"approved": answer}
builder.add_node("approval", approval)
builder.add_edge(START, "approval")
app = builder.compile(checkpointer=InMemorySaver())
cfg = {"configurable": {"thread_id": "t1"}}
# Первый вызов упадёт в interrupt
try:
app.invoke({}, cfg)
except Exception:
pass
# Возобновляем с ответом пользователя
from langgraph.types import Command
result = app.invoke(Command(resume="yes"), cfg)
```
### 5. Send / Map-reduce (параллельные ветки)
```python
from langgraph.types import Send
def fanout(state: State):
return [Send("process", {"item": i}) for i in state["items"]]
def process(state: dict):
return {"results": [state["item"] * 2]}
builder.add_conditional_edges("start", fanout)
builder.add_node("process", process)
```
### 6. Subgraphs
```python
sub = StateGraph(SubState)
sub.add_node("inner", inner_fn)
sub.add_edge(START, "inner")
sub_compiled = sub.compile()
parent = StateGraph(ParentState)
parent.add_node("sub_block", sub_compiled)
parent.add_edge(START, "sub_block")
```
### 7. Store для долгосрочной памяти
```python
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
app = builder.compile(checkpointer=InMemorySaver(), store=store)
def remember(state: State):
store.put(("user-1", "facts"), "name", {"value": "Alice"})
return {}
# В другом turn:
def recall(state: State):
fact = store.get(("user-1", "facts"), "name")
return {"user_name": fact.value["value"]}
```
### 8. Streaming
```python
for event in app.stream({"messages": [{"role": "user", "content": "hi"}]}, stream_mode="values"):
print(event)
# Кастомный streaming через writer
def node(state: State):
writer = get_stream_writer()
writer({"progress": "50%"})
return {}
```
---
## TypeScript-аналог
Все примеры имеют аналог в `@langchain/langgraph`:
```typescript
import { StateGraph, START, END } from "@langchain/langgraph";
import { MemorySaver } from "@langchain/langgraph-checkpoint";
import { Annotation, messagesStateReducer } from "@langchain/langgraph";
const State = Annotation.Root({
messages: Annotation({ reducer: messagesStateReducer, default: () => [] }),
});
const g = new StateGraph(State)
.addNode("echo", (s) => ({ messages: [{ role: "assistant", content: "hi" }] }))
.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);
```
**Где аналог есть:** весь базовый API (StateGraph, conditional edges, checkpoint, interrupt).
**Где нет / отличается:** некоторые специфичные savers (PostgresSaver в JS требует отдельного пакета), `Send` API полностью паритетно.
---
## Плюсы и минусы текущей версии (1.x)
### Плюсы
- **Durable execution из коробки** — killer-фича для long-running агентов.
- **HITL first-class API** — `interrupt()` вместо костылей с callback-ами.
- **Гибкость** — можно построить любую топологию графа (циклы, ветки, параллелизм).
- **Прозрачность** — graph inspection в LangGraph Studio.
- **Семантическая стабильность** — semver до 2.0.
### Минусы
- **Кривая обучения** — concepts (channels, reducers, send/receive) требуют времени.
- **Boilerplate** — базовый граф требует много кода по сравнению с `create_agent`.
- **Checkpointing требует инфраструктуры** — для prod нужен Postgres, настройка schema.
- **Stream API многослойный** — `stream_mode` (`values` / `updates` / `events` / `messages` / `debug`) сбивает с толку.
- **Debugging сложных графов** — без LangSmith Studio тяжело.
---
## Заметки для презентации
- Подчеркнуть: **LangGraph — runtime, не agent-harness**. `create_agent` (LangChain) и `create_deep_agent` (Deep Agents) работают *поверх* LangGraph.
- Если есть HITL-сценарий — показать `interrupt()` как killer-фичу 1.0.
- Использовать аналогию: **LangGraph = база данных для состояния агента**, LangChain = ORM поверх.
- Упомянуть, что LangGraph вдохновлён Pregel (Google) и Apache Beam — это не новость из AI, это паттерн из распределённых систем.
- В 1.2 — fault tolerance (retries / timeouts / error handlers) — отдельная тема.
+321
View File
@@ -0,0 +1,321 @@
# Open SWE
## Что это в одном абзаце
Open SWE — это open-source фреймворк LangChain Inc. для построения **внутренних кодинг-агентов организации**. Анонсирован в августе 2025, стабильная версия репозитория набрала 971+ коммитов и 10k stars к июню 2026. Open SWE — это **не готовое SaaS-решение**, а стартовый шаблон: он скомпонован поверх Deep Agents (а значит поверх LangGraph), поддерживает pluggable sandbox-провайдеры (Modal, Daytona, Runloop, LangSmith), триггеры из Slack / Linear / GitHub, и автоматически создаёт draft PR. Архитектура намеренно воспроизводит паттерны, которые Stripe (Minions), Ramp (Inspect) и Coinbase (Cloudbot) построили как proprietary — Open SWE даёт open-source реализацию «reference architecture» для кастомных внутренних coding agent-ов.
**Метаданные на дату snapshot 2026-06-22:**
- GitHub stars: ~10k
- Commits: 971+ (активная разработка)
- License: MIT
- JS-аналог: нет (Python-only проект, плюс TypeScript UI в `ui/`)
- Главный blog-пост: первоначальный анонс — август 2025, переработанная версия — 17 марта 2026
**Источники:**
- README `github.com/langchain-ai/open-swe` (raw-форма успешно получена)
- `blog.langchain.com/open-swe-an-open-source-framework-for-internal-coding-agents`
- https://github.com/langchain-ai/open-swe/blob/main/INSTALLATION.md
- https://github.com/langchain-ai/open-swe/blob/main/CUSTOMIZATION.md
---
## Ключевые API и архитектурные компоненты
Open SWE — это не библиотека, а **приложение**, состоящее из backend-агента (Python), UI (TypeScript), и набора middleware/интеграций. Поэтому «API» здесь — это точки расширения, через которые организация кастомизирует фреймворк.
### Импорты внутри Open SWE
```python
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,
RunloopBackend,
LangSmithBackend,
)
from open_swe.tools import (
execute,
fetch_url,
http_request,
linear_comment,
slack_thread_reply,
)
```
### Основная точка расширения — `create_deep_agent`
```python
from deepagents import create_deep_agent
from open_swe.middleware import check_message_queue_before_model
from open_swe.sandbox import DaytonaBackend
agent = create_deep_agent(
model="anthropic:claude-opus-4-6",
system_prompt=construct_system_prompt(repo_dir, ...),
tools=[
execute,
fetch_url,
http_request,
linear_comment,
slack_thread_reply,
],
backend=DaytonaBackend(api_key="..."),
middleware=[
ToolErrorMiddleware(),
check_message_queue_before_model,
open_pr_if_needed,
],
)
```
### Sandboxes (pluggable)
```python
from open_swe.sandbox import DaytonaBackend, ModalBackend, RunloopBackend
backend = DaytonaBackend(api_key="...")
# или ModalBackend(token_id="...", token_secret="...")
# или RunloopBackend(api_key="...")
```
Каждый backend — изолированный Linux-контейнер с полным shell-доступом, клоном репозитория и persistent state для thread-а.
### Triggers (поверхности вызова)
- **Slack** — `@openswe` в любом thread. Поддерживает синтаксис `repo:owner/name`.
- **Linear** — `@openswe` в комментарии к issue.
- **GitHub** — `@openswe` в PR-комментарии для авто-ответа на review.
Каждый триггер создаёт deterministic thread_id, чтобы follow-up сообщения маршрутизировались в тот же запущенный агент.
### Built-in tools
| Tool | Назначение |
|---|---|
| `execute` | shell-команды в sandbox |
| `fetch_url` | загрузка web-страниц как markdown |
| `http_request` | API calls (GET, POST, etc.) |
| `linear_comment` | комментарии в Linear-тикетах |
| `slack_thread_reply` | ответы в Slack-тредах |
| `read_file` / `write_file` / `edit_file` / `ls` / `glob` / `grep` | Deep Agents filesystem tools |
| `write_todos` | planning tool от Deep Agents |
| `task` | spawning subagent-ов |
GitHub-операции делаются через `gh` CLI внутри sandbox с `GH_TOKEN=dummy`, авторизация через LangSmith-прокси.
### AGENTS.md конвенция
Если в репозитории есть файл `AGENTS.md` в корне, он автоматически читается из sandbox и инжектится в system prompt. Это «правила команды» — conventions, testing requirements, архитектурные решения.
### Middleware (точки расширения)
```python
from langchain.agents.middleware import AgentMiddleware
class MyCustomMiddleware(AgentMiddleware):
def before_model(self, state, runtime):
# модифицировать state перед model call
return state
def after_model(self, state, runtime):
# логирование / проверка результата
return state
```
Типичные middleware в Open SWE:
- `check_message_queue_before_model` — инжектит follow-up сообщения до следующего model call.
- `notify_step_limit_reached` — после-agent hook для Slack-уведомления, если лимит исчерпан.
- `open_pr_if_needed` — safety net: коммитит и открывает PR, если агент этого не сделал.
- `ToolErrorMiddleware` — graceful handling ошибок tool-ов.
### Customization точка: `CUSTOMIZATION.md`
Согласно документации, pluggable компоненты:
1. **Sandbox provider** — Modal / Daytona / Runloop / LangSmith / свой.
2. **Model** — любой провайдер через `langchain.chat_models.init_chat_model`.
3. **Tools** — добавить/удалить через массив `tools`.
4. **Triggers** — модифицировать Slack / Linear / GitHub интеграции.
5. **System prompt** — база + логика инкорпорирования AGENTS.md.
6. **Middleware** — добавить свой для validation / approval / logging.
---
## Что нового в первом релизе
### Оригинальный анонс (август 2025)
- Multi-agent архитектура: Manager + Planner + Programmer + Reviewer.
- Single sandbox (Daytona).
- GitHub Issues + Web UI триггеры.
### Переработанная архитектура (март 2026)
- Замена multi-agent на единый `create_deep_agent` harness + subagents + middleware.
- Добавление pluggable sandbox providers (Modal, Runloop, LangSmith).
- Добавление Slack и Linear триггеров.
- Subagent-ы через `task` tool от Deep Agents.
- Middleware-система для deterministic orchestration.
### Ключевой сдвиг
Open SWE **переехал с multi-agent на single-deep-agent-harness + subagents**. Это консолидация архитектурного паттерна: вместо явных Manager/Planner/Programmer/Reviewer — один главный агент с набором subagent-специализаций и middleware для orchestration. Бенефиты: upgrade path (подтягивать улучшения Deep Agents), меньше кастомного кода, чище orchestration через `Send` API.
---
## Что нужно раскрыть в презентации
- **Почему «внутренний» кодинг-агент, а не IDE-assistant** — модель «colleague, not copilot».
- **«Trust the LLM» внутри sandbox** — изоляция важнее confirmation prompts.
- **Pluggable sandboxes** — почему несколько провайдеров и как мигрировать.
- **AGENTS.md как организационный паттерн** — те же conventions применяются и для AI.
- **Subagent isolation** — каждый child получает свой контекст.
- **Middleware для validation** — детерминированные проверки между шагами агента.
- **Сравнение со Stripe Minions / Ramp Inspect / Coinbase Cloudbot** — почему конвергенция паттернов важна.
- **Open source как reference architecture** — не finished product, а стартовая точка.
---
## 5 рабочих примеров кода Python
### 1. Минимальный запуск агента Open SWE (из README)
```python
from deepagents import create_deep_agent
from open_swe.sandbox import DaytonaBackend
from open_swe.middleware import check_message_queue_before_model, open_pr_if_needed
agent = create_deep_agent(
model="openai:gpt-5.5",
system_prompt="You are an internal coding agent.",
tools=[], # будут добавлены built-in
backend=DaytonaBackend(api_key="..."),
middleware=[check_message_queue_before_model, open_pr_if_needed],
)
```
### 2. Кастомный system prompt с инкорпорированием AGENTS.md
```python
def construct_system_prompt(repo_dir: str, base_prompt: str) -> str:
agents_md_path = Path(repo_dir) / "AGENTS.md"
extra = ""
if agents_md_path.exists():
extra = f"\n\nRepository rules:\n{agents_md_path.read_text()}"
return base_prompt + extra
```
### 3. Кастомный middleware для логирования
```python
from langchain.agents.middleware import AgentMiddleware
class AuditMiddleware(AgentMiddleware):
def __init__(self, logger):
self.logger = logger
def after_model(self, state, runtime):
self.logger.info(f"model_called_at_step_{state.get('step')}")
return state
```
### 4. Subagent-конфигурация для специализаций
```python
test_runner = {
"name": "test_runner",
"description": "Runs project tests and reports results",
"system_prompt": "You run tests, parse failures, suggest fixes.",
"tools": ["execute", "read_file"],
}
doc_writer = {
"name": "doc_writer",
"description": "Updates documentation after code changes",
"system_prompt": "You update markdown docs based on code changes.",
"tools": ["read_file", "edit_file"],
}
agent = create_deep_agent(
model="anthropic:claude-opus-4-6",
tools=[],
subagents=[test_runner, doc_writer],
)
```
### 5. Кастомный sandbox backend (заглушка)
```python
from open_swe.sandbox import SandboxBackend
class MyInternalBackend(SandboxBackend):
def __init__(self, connection_string: str):
self.conn = connection_string
def execute(self, command: str) -> str:
# подключение к внутреннему devbox-пулу
return self._run_in_devbox(command)
def read_file(self, path: str) -> str:
return self._fetch_from_devbox(path)
```
---
## TypeScript
**UI:** репозиторий содержит `ui/` (TypeScript, 26.6% от кода). Это web-приложение для управления: GitHub login, per-user model/profile settings, team defaults, enabled-repo management, chat UI.
```typescript
// Пример из ui/ (псевдокод, точная структура зависит от версии)
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 in src/auth.py",
repo: "owner/name",
});
```
**Где нет TS-аналога для backend:** Open SWE — это Python-приложение (LangGraph/Deep Agents), TypeScript только в UI-слое.
---
## Плюсы и минусы
### Плюсы
- **MIT license** — можно форкать и адаптировать.
- **Pluggable sandbox** — Modal / Daytona / Runloop / LangSmith / свой.
- **Subagents + middleware** — composable вместо monolithic.
- **AGENTS.md convention** — переиспользует существующий паттерн документации.
- **Multiple triggers** — Slack / Linear / GitHub / Web UI.
- **Built on Deep Agents** — automatic upgrade path для improvements.
- **Хорошая документация** — INSTALLATION.md и CUSTOMIZATION.md детальные.
- **Active development** — 971+ коммитов, 10k stars.
### Минусы
- **Не finished product** — нужно кастомизировать под свою org.
- **Sandbox costs** — Modal / Daytona / Runloop требуют платных аккаунтов.
- **Slack / Linear / GitHub интеграции** — нужен OAuth setup для каждого.
- **Security model «trust the LLM»** — высокие требования к sandbox-изоляции.
- **Production deployment сложный** — требует LangSmith, GitHub App, sandbox provider, secrets management.
- **Observability** — нужен Datadog или LangSmith setup.
- **Документация быстро устаревает** — архитектура переписывалась за 9 месяцев.
---
## Заметки для презентации
- Это **reference architecture, not product**. Подчеркнуть, что каждый компонент заменяем.
- Использовать аналогию: **Open SWE = «Kubernetes для AI агентов»** — даёт framework, но ожидает ops-работу.
- Показать, как именно Open SWE воспроизводит паттерны Stripe/Ramp/Coinbase — это главный аргумент «convergence proof».
- Если есть audience с enterprise-бэкграундом — акцент на **sandbox isolation как security primitive**.
- Подчеркнуть, что Open SWE **не замена Cursor или Claude Code**, а инфраструктура для «своего Claude Code».
- Если показывать схему архитектуры — выделить слои: Harness (Deep Agents) → Sandbox (pluggable) → Tools (curated) → Context (AGENTS.md) → Orchestration (subagents + middleware) → Invocation (Slack/Linear/GitHub) → Validation (prompt + middleware).
+96
View File
@@ -0,0 +1,96 @@
# Sources
Список всех источников, на которые опирается исследование. Каждый проверен напрямую через `web_search` (matrix MCP) или `webfetch` (raw.githubusercontent.com / blog.langchain.com / changelog.langchain.com / GitHub README).
---
## Официальные анонсы и блоги LangChain
- https://www.langchain.com/blog/langchain-v0-1-0 — пост о LangChain 0.1.0 (январь 2024). Подтверждает разделение на core/community, LCEL.
- https://www.langchain.com/blog/the-new-langchain-architecture-langchain-core-v0-1-langchain-community-and-a-path-to-langchain-v0-1 — пред-релизный анонс новой архитектуры.
- https://www.langchain.com/blog/langchain-langchain-1-0-alpha-releases — alpha-релиз LangChain/LangGraph 1.0 (сентябрь 2025).
- https://www.langchain.com/blog/langchain-langgraph-1dot0 — основной блог-пост о 1.0 обоих фреймворков.
- https://www.langchain.com/blog/introducing-deepagents-cli — анонс DeepAgents CLI.
- https://www.langchain.com/blog/open-swe-an-open-source-framework-for-internal-coding-agents — переработанный пост об Open SWE (17 марта 2026, изначальный анонс — август 2025).
- https://blog.langchain.com/open-swe-an-open-source-framework-for-internal-coding-agents — старая пометка поста, используется как дополнительная ссылка.
## Официальные changelog-анонсы
- https://changelog.langchain.com/announcements/langchain-1-0-now-generally-available — LangChain 1.0 GA (22.10.2025).
- https://changelog.langchain.com/announcements/langgraph-1-0-is-now-generally-available — LangGraph 1.0 GA (22.10.2025).
- https://changelog.langchain.com/announcements/langsmith-self-hosted-v0-9 — LangSmith Self-Hosted v0.9 (21.01.2025).
- https://changelog.langchain.com?categories=cat_ZWTyLBFVqdtSq — категория LangSmith Self-Hosted анонсов.
## GitHub-репозитории
- https://github.com/langchain-ai/langchain — основной репо LangChain (Python). 140k stars. README + releases.
- https://github.com/langchain-ai/langgraph — репо LangGraph. 35.4k stars.
- https://github.com/langchain-ai/deepagents — репо Deep Agents. 24.9k stars.
- https://github.com/langchain-ai/open-swe — репо Open SWE. 10k stars. README + INSTALLATION.md + CUSTOMIZATION.md.
- https://github.com/langchain-ai/langgraphjs — JS-аналог LangGraph.
- https://github.com/langchain-ai/langchainjs — JS-аналог LangChain.
- https://github.com/langchain-ai/langsmith-sdk — Python-клиент LangSmith.
- https://github.com/langchain-ai/deepagentsjs — JS-аналог Deep Agents.
- https://github.com/langchain-ai/langchain/releases — релизы langchain (1.3.10 / 1.4.8 на дату snapshot).
- https://github.com/langchain-ai/langgraph/releases — релизы langgraph (1.2.6 latest).
- https://github.com/langchain-ai/deepagents/releases — релизы deepagents (0.6.11 latest).
- https://github.com/langchain-ai/langchain/issues/33933 — issue «ModuleNotFoundError: No module named 'langchain.chains'», объясняет переезд chains в langchain-classic.
- https://raw.githubusercontent.com/langchain-ai/open-swe/main/README.md — README Open SWE в raw-форме (успешно получен).
## Документация (docs.langchain.com, reference.langchain.com)
- https://docs.langchain.com/oss/python/langchain/overview — обзор LangChain (404/decode error при fetch, использован через search).
- https://docs.langchain.com/oss/python/langgraph/overview — обзор LangGraph.
- https://docs.langchain.com/oss/python/deepagents/overview — обзор Deep Agents.
- https://docs.langchain.com/oss/python/deepagents/customization — кастомизация Deep Agents.
- https://docs.langchain.com/oss/python/migrate/langchain-v1 — гайд миграции на v1.
- https://docs.langchain.com/oss/python/releases/langchain-v1 — что нового в LangChain v1.
- https://docs.langchain.com/oss/javascript/releases/langchain-v1 — что нового в LangChain v1 (JS).
- https://docs.langchain.com/oss/javascript/releases/langgraph-v1 — что нового в LangGraph v1 (JS).
- https://reference.langchain.com/python — корневой API reference.
- https://reference.langchain.com/python/langchain/agents/factory.html — страница фабрики агентов (decode error при fetch, использован через search).
- https://reference.langchain.com/python/deepagents/graph.html — API Deep Agents graph.
- https://reference.langchain.com/python/langsmith/version — версия langsmith SDK (0.8.9 latest).
## Форумы и сообщество
- https://forum.langchain.com/t/langchain-1-0-alpha-feedback-wanted/1436 — alpha feedback тема.
- https://forum.langchain.com/t/we-launched-1-0-versions-of-langchain-and-langgraph/1904 — анонс 1.0 на форуме.
- https://forum.langchain.com/t/create-stuff-documents-chain-is-not-working-with-latest-version-of-langchain-version-1-0-3/2092 — пример ошибки с `langchain.chains``langchain-classic`.
## npm / PyPI
- https://www.npmjs.com/package/%40langchain/classic — npm-описание `@langchain/classic`, перечисляет какие API туда переехали.
- https://pypi.org/project/langchain/ — PyPI LangChain.
- https://pypi.org/project/langgraph/ — PyPI LangGraph.
- https://pypi.org/project/deepagents/ — PyPI Deep Agents (latest 0.6.11 на snapshot).
- https://pypi.org/project/langsmith/ — PyPI LangSmith SDK.
## Сторонние источники и подтверждения
- https://www.microsoft.com/en-us/techcommunity/blogs/azuredevcommunityblog/langchain-v1-is-now-generally-available/4462159 — Microsoft TechCommunity пост о LangChain v1.
- https://x.com/hwchase17/status/1962935384490565926 — Harrison Chase анонс alpha в X (1 сентября 2025).
- https://medium.com/data-science-collective/building-deep-agents-with-langchain-1-0s-middleware-architecture-7fdbb3e47123 — статья о Deep Agents на middleware 1.0.
- https://medium.com/mitb-for-all/langchain-a-second-look-6ed720e27fec — обзор LangChain 1.0 от сентября 2025.
- https://www.linkedin.com/posts/langchain_open-swe-an-open-source-framework-for-internal-activity-7439726228057722882-3LrZ — LangChain LinkedIn-анонс Open SWE.
- https://simonwillison.net/tags/jules/ — Simon Willison упоминает Open SWE.
- https://agentnativedev.medium.com/langchain-and-langgraph-v1-0-beyond-release-notes-into-real-roi-7538fc02ff83 — разбор 1.0.
- https://www.clickittech.com/ai/langchain-1-0-vs-langgraph-1-0/ — сравнение LangChain 1.0 и LangGraph 1.0.
- https://ai.plainenglish.io/the-complete-guide-to-langchain-langgraph-2025-updates-and-production-ready-ai-frameworks-58bdb49a34b6 — полный гайд по 2025 релизам.
- https://www.jbinternational.co.uk/article/view/4680 — статья о LangGraph 1.0 / 1.2 (май 2026).
- https://picrew.github.io/LLM-Harness/main.pdf — Agent Harness Engineering Survey, цитирует Open SWE.
- https://www.infoq.cn/article/ucQtx67807qs9B4ig5IS — китайский перевод LangChain Open SWE-анонса.
## Локальные копии / контекст проекта
- `/Users/alexandr/.mavis/plans/plan_85053139/workspace/lc-evo-deck/design-system.md` — дизайн-токены, выложенные другим агентом (использованы только как контекст, не как источник фактов о релизах).
- `/Users/alexandr/.mavis/plans/plan_85053139/workspace/lc-evo-deck/design-system.js` — JS-модуль с design tokens.
---
## Заметки по надёжности
1. `docs.langchain.com/oss/python/langchain/overview` и `reference.langchain.com/python/langchain/agents/factory.html` возвращают **decode error** при прямом fetch. Использованы данные из поисковых сниппетов и из README GitHub.
2. `raw.githubusercontent.com/langchain-ai/langgraph/main/libs/langgraph/README.md` и `raw.githubusercontent.com/langchain-ai/deepagents/main/README.md`**timeout**. Содержимое восстановлено из основного GitHub-fetch README.
3. `changelog.langchain.com/announcements/langsmith-self-hosted-v0-9` — относится к январю 2025 (не 2026), что важно учитывать при построении timeline.
4. Блог-пост об Open SWE имеет дату публикации **17 марта 2026** на самой странице (после редизайна), но README Open SWE ссылается на «announcement blog post here», а сам Open SWE впервые упомянут в августе 2025 — обе даты зафиксированы.
+207
View File
@@ -0,0 +1,207 @@
# Timeline: LangChain / LangGraph / Deep Agents / Open SWE / LangSmith
Единый таймлайн релизов и ключевых изменений. Покрытие: только стабильные релизы ≥ 1.0.0 или те, что определили архитектуру сегодняшней экосистемы. Устаревшие API помечены явно.
Дата отсчёта: 2026-06-22.
---
## 2022-10 — рождение LangChain
- **2022-10**: Harrison Chase публикует первый коммит LangChain как open-source фреймворк для оркестрации LLM.
- Источник: README `langchain-ai/langchain` упоминает Harrison Chase как основателя; широко подтверждено в CSDN-обзорах 0.1 (2024-01).
---
## 2023-10 — LangChain 0.0.x (пред-стабильная эпоха)
- Линейка `0.0.x` (добралась до `0.0.354` к январю 2024). Нестабильное API, частые breaking changes, отсутствие semver-гарантий.
- LangChain становится самым быстрорастущим OSS-проектом на GitHub.
- **Важно для презентации:** все API из этой эпохи (LLMChain, ConversationChain, AgentExecutor из langchain.agents, старые RetrievalQA) — **DEPRECATED**, перенесены в `langchain-classic` начиная с v1.0.
---
## 2024-01-08 — LangChain 0.1.0 (первый стабильный minor)
- **Дата релиза:** 8 января 2024.
- **Главные изменения:**
- Разделение монолита на `langchain-core` (ядро, стабильный API) + `langchain` (оркестрация) + `langchain-community` (700+ интеграций).
- LCEL (LangChain Expression Language) — `Runnable`-протокол: `invoke / stream / batch / async`.
- Семантическое версионирование с этого момента.
- Тесная интеграция с LangSmith для трассировки.
- Одновременно анонсирован LangGraph как «One More Thing» — граф-рантайм с поддержкой циклов для агентов.
- **Источники:** blog.langchain.com (пост `langchain-v0-1-0`), changelog.langchain.com (анонс января 2024).
---
## 2024-05 — LangChain 0.2
- Стандартизация единого интерфейса вызова (`invoke`).
- Миграционные скрипты: `langchain-cli migrate`.
- Сложные агенты рекомендовано строить на LangGraph.
- Удалены устаревшие entry points (`predict_messages` и подобные).
- **Источник:** CSDN-обзор «LangChain从零到一:版本演进、架构设计与实战指南» (blog.csdn.net/2401_84815887), блогпост v0.1.
---
## 2024-09 — LangChain 0.3
- Финальная версия перед 1.0 в линейке 0.x.
- Полная миграция на Pydantic v2 во всех пакетах.
- Удаление Python 3.8 из supported.
- Чистка deprecations, подготовка к 1.0.
- **Примечание:** точная дата не указана в официальных changelog как «релиз 0.3», известно из changelog-ленты `changelog.langchain.com/?date=2024-09-*` и обзоров; пометка в README как «streamlined surface area».
---
## 2024-08 — первые следы LangGraph 0.x как production-ready
- LangGraph вышел из «One More Thing» в полноценный фреймворк.
- Ключевые абстракции: `StateGraph`, `add_node`, `add_edge`, `add_conditional_edges`, checkpoint-ы, threads.
- Документация подтверждает, что LangGraph — низкоуровневый оркестратор, LangChain — поверх.
---
## 2025-08-21 — Open SWE анонс (пред-1.0)
- **Дата:** 21 августа 2025 (по дате публикации статьи LangChain, через InfoQ/腾讯云 репост).
- **Что вышло:** Open SWE — open-source асинхронный кодинг-агент, работающий в облачных песочницах (Daytona).
- **Архитектура:** Manager + Planner + Programmer + Reviewer.
- **Запуск:** через GitHub Issues, Web UI.
- **License:** MIT.
- **Источник:** `blog.langchain.com/open-swe-an-open-source-framework-for-internal-coding-agents` (переработанный блог-пост от 17 марта 2026, изначальный анонс — август 2025, см. README GitHub `langchain-ai/open-swe` со ссылкой на анонс-пост).
---
## 2025-08 — Deep Agents 0.x (первая публичная версия)
- Публичный дебют библиотеки `deepagents` от LangChain.
- Вдохновлена Claude Code, Deep Research, Manus.
- Архитектура: planning tool + filesystem backend + subagents на базе LangGraph.
- **Источник:** README `langchain-ai/deepagents` упоминает «inspired by Claude Code»; CSDN DeepAgents-обзор от августа 2025; блогпост «Building Production-Ready Deep Agents with LangChain 1.0» (Medium).
---
## 2025-09 — Deep Agents CLI
- Анонс DeepAgents CLI — pre-built кодинг-агент для терминала, аналог Claude Code/Cursor.
- Установка: `curl -LsSf https://langch.in/dcode | bash`.
- **Источник:** blog.langchain.com/introducing-deepagents-cli.
---
## 2025-09 — LangChain & LangGraph 1.0 alpha
- **Дата:** конец сентября 2025 (пост Harrison Chase в X от 01.09.2025: `x.com/hwchase17/status/1962935384490565926`).
- Alpha-релизы для сбора обратной связи.
- **Источники:** blog.langchain.com/langchain-langchain-1-0-alpha-releases, форум forum.langchain.com/t/langchain-1-0-alpha-feedback-wanted/1436.
---
## 2025-10-20 — LangChain 1.0 GA
- **Дата релиза:** 20-22 октября 2025 (changelog.langchain.com и blog.langchain.com указывают 22.10.2025, CSDN-обзоры и китайские источники — 20.10.2025).
- **Что нового в 1.0:**
- **create_agent abstraction** — единая функция для создания агента поверх LangGraph-рантайма.
- **Middleware system** — fine-grained контроль на каждом шаге цикла агента. Built-in: human-in-the-loop, summarization, PII redaction. Custom middleware поддерживается.
- **Improved structured output** — интегрирован в основной цикл, без extra LLM-вызовов.
- **Standard content blocks** — провайдер-агностичная спецификация для выходов моделей (reasoning traces, citations, server-side tool calls).
- **Legacy → langchain-classic:** LLMChain, ConversationalRetrievalQAChain, RetrievalQAChain, AgentExecutor (legacy), старые chains. Доступны через отдельный пакет `@langchain/classic`.
- **Стабильность:** semver-обязательство — никаких breaking changes до 2.0.
- **Покрытие звёздами:** на дату snapshot README `langchain-ai/langchain` — 140k stars.
- **Источники:** changelog.langchain.com/announcements/langchain-1-0-now-generally-available, blog.langchain.com/langchain-langgraph-1dot0, Medium «Building Deep Agents with LangChain 1.0», Microsoft TechCommunity «LangChain v1 is now generally available».
---
## 2025-10-22 — LangGraph 1.0 GA
- **Дата релиза:** 22 октября 2025.
- **Что нового в 1.0:**
- **Durable state** — состояние графа персистится автоматически. При падении сервера посреди долгого диалога — восстановление ровно с точки остановки.
- **Built-in persistence** — сохранение/возобновление в любой точке без своей DB-логики. Multi-day approvals, background jobs.
- **Human-in-the-loop first-class API** — пауза для human review / modification / approval.
- **Graph-based execution model** — для смеси детерминированных и агентных компонентов.
- **Deprecation:** `langgraph.prebuilt` deprecated, функционал перенесён в `langchain.agents` (create_react_agent → create_agent).
- **API stability:** без breaking changes до 2.0.
- **Звёзды на snapshot:** 35.4k stars.
- **Источники:** changelog.langchain.com/announcements/langgraph-1-0-is-now-generally-available, blog.langchain.com/langchain-langgraph-1dot0.
---
## 2025-10 — Deep Agents 1.0 (синхронно с LangChain 1.0)
- **Дата:** октябрь 2025 (синхронизировано с LangChain 1.0).
- **Версия на PyPI (snapshot 2026-06):** `deepagents==0.6.11` (latest). То есть формально major 1.0 для deepagents-пакета **не зафиксирован** на дату snapshot — продолжает нумерацию 0.x.
- **Что изменилось:** полная интеграция с LangChain 1.0 middleware-системой; `create_deep_agent` теперь принимает middleware как first-class параметр.
- **Важно:** README явно говорит «inspired by Claude Code: identify what makes it general-purpose, push further».
- **Источник:** README `langchain-ai/deepagents`, pypi.org/project/deepagents, Medium «Building Production-Ready Deep Agents with LangChain 1.0's Middleware Architecture».
---
## 2025-12 — Open SWE первый релиз
- **Дата:** декабрь 2025 — формальная пометка в сторонних обзорах (LangChain Facebook, Agent Harness Engineering Survey).
- **Версия:** стабильный репозиторий `langchain-ai/open-swe`, 971+ коммитов, 10k stars на snapshot 2026-06.
- **Ключевая публикация блог-поста:** первоначальный анонс от августа 2025 (см. выше), переработанный пост от 2026-03-17.
- **Текущая архитектура:** Manager/Planner/Programmer/Reviewer → переработано в единый `create_deep_agent` harness + subagents + middleware.
- **Триггеры:** Slack, Linear, GitHub.
- **Песочницы:** Modal, Daytona, Runloop, LangSmith.
- **License:** MIT.
- **Источник:** README `langchain-ai/open-swe`, blog.langchain.com/open-swe-an-open-source-framework-for-internal-coding-agents.
---
## 2026-01 — LangSmith v0.x (Self-Hosted)
- **Дата:** январь 2026 — LangSmith Self-Hosted v0.9 (по changelog.langchain.com от 21.01.2025 — обратите внимание, эта конкретная пометка относится к январю 2025; точная дата следующего релиза — конец 2025 / начало 2026).
- **Текущая стабильная версия Python SDK:** `langsmith==0.8.9` (latest на reference.langchain.com snapshot).
- **Важно:** LangSmith SDK не следует semver 1.0+, продолжает развитие в 0.x с пометкой «Since v0.1». Это платформа (SaaS + self-hosted), а не open-source фреймворк, поэтому major 1.0 для неё не объявлен.
- **Источники:** reference.langchain.com/python/langsmith/version, changelog.langchain.com (категория `cat_ZWTyLBFVqdtSq`), pypi.org/project/langsmith.
---
## 2026-Q1 — LangGraph 1.2.x
- Текущая версия на PyPI snapshot 2026-06: `langgraph==1.2.6` (от 18.06.2026).
- В 1.2 появились: fault tolerance (retries / timeouts / error handlers), улучшенные middleware, дополнительная стабилизация типов.
- **Источники:** blog.langchain.com/fault-tolerance-in-langgraph (04.06.2026), GitHub Releases `langchain-ai/langgraph/releases/tag/1.2.6`.
---
## 2026-06 — текущее состояние
- **langchain (Python):** latest stable ≈ 1.3.10 / 1.4.8 (по GitHub releases `langchain-ai/langchain/releases`, jun 2026).
- **langchain-core:** latest stable 1.4.8 (от 18.06.2026).
- **langgraph:** latest stable 1.2.6 (от 18.06.2026).
- **deepagents:** latest stable 0.6.11 (от 18.06.2026) — major 1.0 пока не выпущен, продолжает нумерацию 0.x.
- **open-swe:** active development, 971+ коммитов, без формальных релизов на PyPI (это приложение, не библиотека).
- **langsmith:** Python SDK 0.8.9 (без 1.0).
- **Источник:** GitHub Releases pages для каждого репозитория + PyPI version badges на README.
---
## Что НЕ стабильно / устарело (важно для презентации)
| API | Статус | Замена |
|---|---|---|
| `langchain.chains.LLMChain` | DEPRECATED → `langchain-classic` | LCEL `prompt \| model \| parser` |
| `langchain.chains.ConversationalRetrievalQAChain` | DEPRECATED → `langchain-classic` | LangGraph retrieval-graph |
| `langchain.chains.RetrievalQAChain` | DEPRECATED → `langchain-classic` | LangGraph retrieval-graph |
| `langchain.agents.AgentExecutor` (legacy) | DEPRECATED → `langchain-classic` | `langchain.agents.create_agent` (v1.0+) |
| `langchain.agents.create_react_agent` | DEPRECATED → перенесён в `langchain-classic` | `langchain.agents.create_agent` |
| `langgraph.prebuilt.create_react_agent` | DEPRECATED | `langchain.agents.create_agent` |
| `langchain.llms.LLM` (legacy interface) | DEPRECATED | `init_chat_model` (v1.0+) |
| `langchain.prompts.PromptTemplate` (старый) | DEPRECATED для некоторых use-cases | `ChatPromptTemplate` |
**Источник:** @langchain/classic npm-описание, GitHub Issue `langchain-ai/langchain/issues/33933`, docs.langchain.com/oss/python/migrate/langchain-v1.
---
## TL;DR для презентации
- **2024-01**: LangChain 0.1 — стабилизация монолита.
- **2024-05**: LangChain 0.2 — унификация API.
- **2025-08**: Deep Agents и Open SWE — агенты нового поколения.
- **2025-10-20**: LangChain 1.0 — production-ready агенты.
- **2025-10-22**: LangGraph 1.0 — durable execution API.
- **2026**: итерации 1.2 / 1.3 / 1.4 в рамках стабильной ветки 1.x.
+41
View File
@@ -0,0 +1,41 @@
// Compile all section-1 slides into a single PPTX
// Output: section1.pptx (27 slides, 16:9, dark theme, code-heavy)
const path = require('path');
const fs = require('fs');
const pptxgen = require('pptxgenjs');
const ds = require('./design-system');
const { theme } = ds;
const pres = new pptxgen();
pres.layout = 'LAYOUT_16x9';
pres.title = 'LangChain 1.0: chains, LCEL, agents, retrievers';
pres.author = 'lc-evo-deck';
pres.subject = 'Section 1 of the LangChain Evolution deck';
const SLIDE_COUNT = 27;
for (let i = 1; i <= SLIDE_COUNT; i++) {
const num = String(i).padStart(2, '0');
const file = path.join(__dirname, `slide-${num}.js`);
if (!fs.existsSync(file)) {
throw new Error('Missing slide module: ' + file);
}
const mod = require(file);
if (typeof mod.createSlide !== 'function') {
throw new Error('Module does not export createSlide: ' + file);
}
mod.createSlide(pres, theme);
}
const outFile = path.join(__dirname, 'section1.pptx');
pres.writeFile({ fileName: outFile })
.then((fileName) => {
console.log('OK ->', fileName);
console.log('Slides:', SLIDE_COUNT);
})
.catch((err) => {
console.error('ERR:', err);
process.exit(1);
});
+862
View File
@@ -0,0 +1,862 @@
/**
* design-system.js
* ----------------------------------------------------------------------------
* Design tokens + slide helpers for the LangChain Evolution deck
* (lc-evo-deck, 100+ slides, code-heavy, dark theme, 16:9).
*
* Audience: engineers familiar with LLMs. No introductory filler.
* Visual register: developer-tool aesthetic, deep navy, JetBrains Mono for
* code, Inter for UI, Arial as the universal fallback (covers Cyrillic).
*
* USAGE
* -----
* // 1. Import the module
* const ds = require('./design-system');
* const { theme, helpers, layouts, fonts, sizes, spacing } = ds;
*
* // 2. Create a new presentation with the built-in 16:9 layout
* const pptxgen = require('pptxgenjs');
* const pres = new pptxgen();
* pres.layout = 'LAYOUT_16x9'; // 10 x 5.625 inches
*
* // 3. Add a slide
* const slide = pres.addSlide();
* helpers.slideBase(slide, pres, theme);
* helpers.addHeader(slide, pres, theme, {
* section: 'Stage 2: Chains',
* sectionNumber: 2,
* title: 'LCEL: a composable expression language',
* eyebrow: 'STAGE 2',
* });
*
* // 4. Add a code block
* helpers.addCodeBlock(slide, pres, theme, {
* x: 0.5, y: layouts.CONTENT_TOP,
* w: 5.0, h: 3.2,
* language: 'python',
* code: [
* 'from langchain_core.prompts import ChatPromptTemplate',
* 'from langchain_openai import ChatOpenAI',
* '',
* 'prompt = ChatPromptTemplate.from_messages([',
* ' ("system", "You are a helpful assistant."),',
* ' ("human", "{question}"),',
* '])',
* 'model = ChatOpenAI(model="gpt-4o-mini")',
* 'chain = prompt | model',
* 'print(chain.invoke({"question": "What is LCEL?"}))',
* ].join('\n'),
* filePath: 'examples/lcel_basic.py',
* startLine: 1,
* });
*
* // 5. Add a callout and a pros/cons panel
* helpers.addCallout(slide, pres, theme, {
* x: 5.8, y: layouts.CONTENT_TOP, w: 3.7, h: 1.2,
* kind: 'info',
* text: 'LCEL is the default composition language from v0.1 onward.',
* });
*
* helpers.addProsCons(slide, pres, theme, {
* x: 5.8, y: 2.8, w: 3.7, h: 2.0,
* pros: ['Composable via | operator', 'Streaming, async, batched for free'],
* cons: ['Verbose for simple chains', 'Mental model differs from LangChain v0'],
* });
*
* // 6. Number the slide and add a source line
* helpers.addPageNumber(slide, pres, theme, 7);
* helpers.addSourceLine(slide, pres, theme, {
* x: 0.5, y: layouts.FOOTER_Y + 0.1, w: 6.0,
* source: 'python.langchain.com/docs/concepts/lcel',
* });
*
* // 7. Save
* await pres.writeFile({ fileName: 'lc-evolution.pptx' });
*
* LAYOUTS (PPTX 16:9, units = inches)
* -----------------------------------
* HEADER_Y = 0.4
* CONTENT_TOP = 1.4
* CONTENT_BOTTOM = 5.05
* FOOTER_Y = 5.25
*
* Vertical regions:
* - Header band : [0.0 .. 1.4] eyebrow + h1 title
* - Content body : [1.4 .. 5.05] main slide content
* - Footer band : [5.05 .. 5.625] page number + source line
*
* Horizontal margins: 0.5 inches left/right by default.
*
* RULES
* -----
* - No em-dash (--), en-dash (-), no smart quotes, no ellipsis (...)
* - All source files are pure ASCII except inside string literals where
* Cyrillic is allowed (Arial fallback guarantees rendering).
* - Code blocks use JetBrains Mono; body uses Inter; fallback Arial.
*
* @module design-system
*/
'use strict';
// ---------------------------------------------------------------------------
// 1. PALETTE
// ---------------------------------------------------------------------------
const palette = {
bg: {
primary: '#0A1A2A', // main slide background
elevated: '#142B3F', // cards, callouts, panels
code: '#0F1E2E', // code block background (slightly darker)
overlay: '#1B2F44', // hover / focus surfaces
},
text: {
primary: '#E6F0F7',
secondary: '#B5C4D1',
muted: '#8A9AAB',
inverse: '#0A1A2A', // for text on bright accents
},
accent: {
primary: '#219EBC', // teal, default accent
secondary: '#FFB703', // gold, important emphasis
tertiary: '#8ECAE6', // light blue, soft accent
},
border: {
subtle: '#233A4F',
strong: '#3A5670',
accent: '#219EBC',
},
code: {
keyword: '#C586C0', // def, class, import, return
string: '#CE9178',
number: '#B5CEA8',
comment: '#6A9955',
function: '#DCDCAA',
builtin: '#4EC9B0',
text: '#D4D4D4', // default code body
bg: '#0F1E2E',
lineHighlight: '#1F2F44',
},
state: {
info: '#219EBC',
success: '#4EC9B0',
warning: '#FFB703',
danger: '#F48771',
infoBg: '#102A38',
successBg: '#0F2A28',
warningBg: '#3A2A0F',
dangerBg: '#3A1A14',
},
};
// ---------------------------------------------------------------------------
// 2. TYPOGRAPHY
// ---------------------------------------------------------------------------
const fonts = {
code: 'JetBrains Mono',
ui: 'Inter',
fallback: 'Arial', // universal fallback, supports Cyrillic
};
const sizes = {
h1: 36,
h2: 28,
h3: 20,
body: 14,
code: 12,
caption: 10,
eyebrow: 10,
};
const spacing = {
page: 0.5,
card_pad: 0.25,
gap: 0.15,
};
// ---------------------------------------------------------------------------
// 3. LAYOUTS
// ---------------------------------------------------------------------------
const layouts = {
LAYOUT_16x9: 'LAYOUT_16x9',
HEADER_Y: 0.4,
CONTENT_TOP: 1.45,
CONTENT_BOTTOM: 5.05,
FOOTER_Y: 5.25,
};
// ---------------------------------------------------------------------------
// 4. THEME (bundled export of the above)
// ---------------------------------------------------------------------------
const theme = {
palette,
fonts,
sizes,
spacing,
layouts,
};
// ---------------------------------------------------------------------------
// 5. HELPERS
// ---------------------------------------------------------------------------
/**
* Resolve a font that always carries the Arial fallback.
* pptxgenjs lets us pass an array; PowerPoint will pick the first available.
*/
function withFallback(family) {
return [family, fonts.fallback];
}
/**
* Paint the slide background.
* @param {object} slide - pptxgenjs slide instance
* @param {object} _pres - pptxgenjs pres (kept for signature symmetry)
* @param {object} t - theme bundle
*/
function slideBase(slide, _pres, t) {
slide.background = { color: t.palette.bg.primary };
}
/**
* Add a header band: eyebrow (small caps, accent) + section number + title.
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {string} [opts.section] - small caps section label (e.g. "Stage 2")
* @param {number} [opts.sectionNumber] - large section number shown on the right
* @param {string} opts.title - main title (h1)
* @param {string} [opts.eyebrow] - eyebrow text (e.g. "STAGE 2: CHAINS")
*/
function addHeader(slide, pres, t, opts) {
const o = opts || {};
const margin = t.spacing.page;
const titleY = t.layouts.HEADER_Y + 0.4;
const titleSize = o.titleSize || t.sizes.h2; // default to 28pt -- fits two-line titles without overflow
const titleW = o.sectionNumber != null ? 8.4 : 9.0;
if (o.eyebrow) {
slide.addText(o.eyebrow, {
x: margin,
y: t.layouts.HEADER_Y,
w: 6.0,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.accent.primary,
bold: true,
charSpacing: 4,
});
} else if (o.section) {
slide.addText(o.section, {
x: margin,
y: t.layouts.HEADER_Y,
w: 6.0,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.accent.primary,
bold: true,
charSpacing: 4,
});
}
slide.addText(o.title || '', {
x: margin,
y: titleY,
w: titleW,
h: 0.75,
fontFace: withFallback(t.fonts.ui),
fontSize: titleSize,
color: t.palette.text.primary,
bold: true,
valign: 'middle',
fit: 'shrink',
});
if (o.sectionNumber != null) {
slide.addText(String(o.sectionNumber), {
x: 9.0,
y: t.layouts.HEADER_Y - 0.05,
w: 0.7,
h: 1.0,
fontFace: withFallback(t.fonts.ui),
fontSize: 56,
color: t.palette.accent.secondary,
bold: true,
align: 'right',
valign: 'top',
});
}
// Hairline separator below the header band
slide.addShape(pres.ShapeType.line, {
x: margin,
y: 1.3,
w: 10.0 - margin * 2,
h: 0,
line: { color: t.palette.border.subtle, width: 0.75 },
});
}
/**
* Render a code block as a card with monospaced text.
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {string} opts.code
* @param {number} opts.x
* @param {number} opts.y
* @param {number} opts.w
* @param {number} opts.h
* @param {string} [opts.language='python']
* @param {string} [opts.filePath] - optional, renders as "// path:line" header
* @param {number} [opts.startLine=1]
* @param {Array<number>} [opts.highlightLines] - 1-based line numbers
*/
function addCodeBlock(slide, pres, t, opts) {
const o = opts || {};
const code = String(o.code || '');
const x = o.x;
const y = o.y;
const w = o.w;
const h = o.h;
const headerH = o.filePath ? 0.3 : 0;
const radius = 0.08;
// Card background
slide.addShape(pres.ShapeType.roundRect, {
x: x,
y: y,
w: w,
h: h,
fill: { color: t.palette.code.bg },
line: { color: t.palette.border.subtle, width: 0.75 },
rectRadius: radius,
});
// Optional header strip with file path
if (o.filePath) {
const startLine = o.startLine || 1;
const lineCount = code.split('\n').length;
const endLine = startLine + lineCount - 1;
slide.addText(
'// ' + o.filePath + ':' + startLine + '-' + endLine,
{
x: x + t.spacing.card_pad,
y: y + 0.05,
w: w - t.spacing.card_pad * 2,
h: 0.22,
fontFace: withFallback(t.fonts.code),
fontSize: t.sizes.caption,
color: t.palette.text.muted,
italic: true,
}
);
}
// Body code
const lines = code.split('\n');
const hasHighlight =
Array.isArray(o.highlightLines) && o.highlightLines.length > 0;
const highlightedSet = hasHighlight
? new Set(o.highlightLines.map(function (n) { return n; }))
: null;
// Pre-compute line height to fit within available area.
const codeAreaY = y + headerH + 0.05;
const codeAreaH = h - headerH - 0.1;
// Use a tighter font + leading so 20-line snippets fit in 3-inch cards.
const fontSize = o.fontSize || 11;
const lineH = (fontSize / 72) * 1.25; // inches per line at 1.25 leading
const text = lines.map(function (line, idx) {
const lineNum = idx + 1;
const isHi = highlightedSet && highlightedSet.has(lineNum);
const prefix = isHi ? '> ' : ' ';
return {
text: prefix + (line.length === 0 ? ' ' : line) + '\n',
options: {
fontFace: withFallback(t.fonts.code),
fontSize: fontSize,
color: isHi ? t.palette.text.primary : t.palette.code.text,
bold: false,
highlight: isHi ? t.palette.code.lineHighlight : undefined,
},
};
});
slide.addText(text, {
x: x + t.spacing.card_pad,
y: codeAreaY,
w: w - t.spacing.card_pad * 2,
h: codeAreaH,
valign: 'top',
paraSpaceBefore: 0,
paraSpaceAfter: 0,
lineSpacingMultiple: 1.0,
isTextBox: true,
});
// Caption warning if content may overflow.
const maxLines = Math.floor(codeAreaH / lineH);
if (lines.length > maxLines) {
// Best-effort: append a small note. PowerPoint won't clip the text, but
// it will overflow the card visually. Caller should reduce fontSize or
// split the snippet.
slide.addText(
'// note: snippet has ' + lines.length + ' lines, card fits ~' + maxLines,
{
x: x + t.spacing.card_pad,
y: y + h - 0.22,
w: w - t.spacing.card_pad * 2,
h: 0.18,
fontFace: withFallback(t.fonts.code),
fontSize: t.sizes.caption,
color: t.palette.state.warning,
italic: true,
}
);
}
}
/**
* Render a code block with line highlighting.
* Convenience wrapper over addCodeBlock.
*/
function addCodeBlockWithHighlight(slide, pres, t, opts) {
return addCodeBlock(slide, pres, t, Object.assign({}, opts, {
highlightLines: opts.lines || opts.highlightLines,
}));
}
/**
* Add a callout panel (info / warning / success / danger).
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {number} opts.x
* @param {number} opts.y
* @param {number} opts.w
* @param {number} opts.h
* @param {('info'|'warning'|'success'|'danger')} [opts.kind='info']
* @param {string} opts.text
* @param {string} [opts.title]
*/
function addCallout(slide, pres, t, opts) {
const o = opts || {};
const kind = o.kind || 'info';
const accent = t.palette.state[kind] || t.palette.state.info;
const bgKey = kind + 'Bg';
const bg = t.palette.state[bgKey] || t.palette.state.infoBg;
// Background card
slide.addShape(pres.ShapeType.roundRect, {
x: o.x,
y: o.y,
w: o.w,
h: o.h,
fill: { color: bg },
line: { color: accent, width: 1 },
rectRadius: 0.08,
});
// Left accent bar
slide.addShape(pres.ShapeType.rect, {
x: o.x,
y: o.y,
w: 0.08,
h: o.h,
fill: { color: accent },
line: { type: 'none' },
});
// Label
slide.addText((kind || 'info').toUpperCase(), {
x: o.x + 0.25,
y: o.y + 0.1,
w: o.w - 0.35,
h: 0.25,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: accent,
bold: true,
charSpacing: 4,
});
// Optional title
let bodyY = o.y + 0.4;
let bodyH = o.h - 0.5;
if (o.title) {
slide.addText(o.title, {
x: o.x + 0.25,
y: o.y + 0.35,
w: o.w - 0.35,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.h3,
color: t.palette.text.primary,
bold: true,
});
bodyY = o.y + 0.7;
bodyH = o.h - 0.8;
}
slide.addText(o.text || '', {
x: o.x + 0.25,
y: bodyY,
w: o.w - 0.35,
h: bodyH,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.body,
color: t.palette.text.secondary,
valign: 'top',
});
}
/**
* Two-column pros / cons panel.
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {number} opts.x
* @param {number} opts.y
* @param {number} opts.w
* @param {number} opts.h
* @param {string[]} opts.pros
* @param {string[]} opts.cons
*/
function addProsCons(slide, pres, t, opts) {
const o = opts || {};
const gap = 0.2;
const colW = (o.w - gap) / 2;
// Pros card
slide.addShape(pres.ShapeType.roundRect, {
x: o.x,
y: o.y,
w: colW,
h: o.h,
fill: { color: t.palette.state.successBg },
line: { color: t.palette.state.success, width: 1 },
rectRadius: 0.08,
});
slide.addText('PROS', {
x: o.x + 0.2,
y: o.y + 0.1,
w: colW - 0.3,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.state.success,
bold: true,
charSpacing: 4,
});
const prosBody = (o.pros || []).map(function (item) {
return {
text: '+ ' + item + '\n',
options: {
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.body,
color: t.palette.text.primary,
},
};
});
slide.addText(prosBody, {
x: o.x + 0.2,
y: o.y + 0.45,
w: colW - 0.3,
h: o.h - 0.55,
valign: 'top',
paraSpaceAfter: 4,
});
// Cons card
const cx = o.x + colW + gap;
slide.addShape(pres.ShapeType.roundRect, {
x: cx,
y: o.y,
w: colW,
h: o.h,
fill: { color: t.palette.state.dangerBg },
line: { color: t.palette.state.danger, width: 1 },
rectRadius: 0.08,
});
slide.addText('CONS', {
x: cx + 0.2,
y: o.y + 0.1,
w: colW - 0.3,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.state.danger,
bold: true,
charSpacing: 4,
});
const consBody = (o.cons || []).map(function (item) {
return {
text: '- ' + item + '\n',
options: {
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.body,
color: t.palette.text.primary,
},
};
});
slide.addText(consBody, {
x: cx + 0.2,
y: o.y + 0.45,
w: colW - 0.3,
h: o.h - 0.55,
valign: 'top',
paraSpaceAfter: 4,
});
}
/**
* Add a page number in the bottom-right corner.
*/
function addPageNumber(slide, _pres, t, num) {
slide.addText(String(num), {
x: 9.2,
y: t.layouts.FOOTER_Y,
w: 0.6,
h: 0.25,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.caption,
color: t.palette.text.muted,
align: 'right',
valign: 'middle',
});
}
/**
* Add a section divider slide: a large numeric label + title + intro paragraph.
* @param {object} slide
* @param {object} pres
* @param {object} t - theme
* @param {object} opts
* @param {number} opts.number
* @param {string} opts.title
* @param {string} [opts.intro]
* @param {string} [opts.eyebrow]
*/
function addSectionDivider(slide, pres, t, opts) {
const o = opts || {};
slideBase(slide, pres, t);
// Giant number on the left
slide.addText(String(o.number), {
x: 0.5,
y: 1.0,
w: 3.5,
h: 3.5,
fontFace: withFallback(t.fonts.ui),
fontSize: 220,
color: t.palette.accent.primary,
bold: true,
valign: 'middle',
});
// Vertical divider
slide.addShape(pres.ShapeType.line, {
x: 4.2,
y: 1.4,
w: 0,
h: 2.8,
line: { color: t.palette.border.subtle, width: 1 },
});
// Title
if (o.eyebrow) {
slide.addText(o.eyebrow, {
x: 4.5,
y: 1.4,
w: 5.0,
h: 0.3,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.eyebrow,
color: t.palette.accent.secondary,
bold: true,
charSpacing: 4,
});
}
slide.addText(o.title || '', {
x: 4.5,
y: o.eyebrow ? 1.7 : 1.4,
w: 5.0,
h: 1.0,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.h1,
color: t.palette.text.primary,
bold: true,
valign: 'top',
});
if (o.intro) {
slide.addText(o.intro, {
x: 4.5,
y: 2.8,
w: 5.0,
h: 1.6,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.body,
color: t.palette.text.secondary,
valign: 'top',
});
}
}
/**
* Add an italic source line, e.g. "source: python.langchain.com/..."
* @param {object} slide
* @param {object} _pres
* @param {object} t
* @param {object} opts
* @param {string} opts.source
* @param {number} [opts.x]
* @param {number} [opts.y]
* @param {number} [opts.w]
*/
function addSourceLine(slide, _pres, t, opts) {
const o = opts || {};
slide.addText('source: ' + (o.source || ''), {
x: o.x != null ? o.x : t.spacing.page,
y: o.y != null ? o.y : t.layouts.FOOTER_Y + 0.05,
w: o.w != null ? o.w : 7.0,
h: 0.25,
fontFace: withFallback(t.fonts.ui),
fontSize: t.sizes.caption,
color: t.palette.text.muted,
italic: true,
valign: 'middle',
});
}
// ---------------------------------------------------------------------------
// 6. OPTIONAL: PYTHON SYNTAX HIGHLIGHTING (pygments via subprocess)
// ---------------------------------------------------------------------------
/**
* Best-effort Python syntax highlighter.
* Calls the `pygmentize` CLI from pygments if available.
* Returns an array of { text, color } tokens.
* On any failure (missing binary, non-zero exit, parse error) returns a
* single-token array with the whole snippet in the default code text color
* so that the slide still renders something.
*
* @param {string} code - Python source code
* @returns {Array<{text: string, color: string}>}
*/
function highlightPython(code) {
const defaultColor = palette.code.text;
const text = String(code || '');
if (!text) return [{ text: '', color: defaultColor }];
const { spawnSync } = require('child_process');
let result;
try {
result = spawnSync(
'pygmentize',
['-f', 'json', '-l', 'python'],
{ input: text, encoding: 'utf8', timeout: 4000 }
);
} catch (e) {
return [{ text: text, color: defaultColor }];
}
if (result.error || result.status !== 0) {
return [{ text: text, color: defaultColor }];
}
let parsed;
try {
parsed = JSON.parse(result.stdout);
} catch (e) {
return [{ text: text, color: defaultColor }];
}
// Pygments JSON output: { tokens: [ [type, value], ... ] }
const tokens = Array.isArray(parsed.tokens) ? parsed.tokens : [];
const out = [];
for (const tok of tokens) {
if (!Array.isArray(tok) || tok.length < 2) continue;
const [type, value] = tok;
out.push({
text: String(value),
color: mapPygmentsTokenToColor(type) || defaultColor,
});
}
return out.length > 0 ? out : [{ text: text, color: defaultColor }];
}
/**
* Map a pygments token type to a palette color.
* Falls back to the default code text color.
*/
function mapPygmentsTokenToColor(type) {
if (!type) return null;
// Pygments short token types we care about for Python:
// Keyword, Keyword.Namespace, Keyword.Constant, String, Number,
// Comment, Comment.Single, Name.Function, Name.Builtin,
// Operator, Punctuation, Text, Error
if (type === 'Keyword' || type.startsWith('Keyword.')) {
return palette.code.keyword;
}
if (type.startsWith('String')) {
return palette.code.string;
}
if (type === 'Number') {
return palette.code.number;
}
if (type.startsWith('Comment')) {
return palette.code.comment;
}
if (type === 'Name.Function' || type === 'Name.Function.Magic') {
return palette.code.function;
}
if (type === 'Name.Builtin' || type === 'Name.Builtin.Pseudo') {
return palette.code.builtin;
}
return null;
}
// ---------------------------------------------------------------------------
// 7. EXPORTS
// ---------------------------------------------------------------------------
const helpers = {
slideBase,
addHeader,
addCodeBlock,
addCodeBlockWithHighlight,
addCallout,
addProsCons,
addPageNumber,
addSectionDivider,
addSourceLine,
withFallback,
highlightPython,
};
module.exports = {
theme: theme,
palette: palette,
fonts: fonts,
sizes: sizes,
spacing: spacing,
layouts: layouts,
helpers: helpers,
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.
Binary file not shown.
+153
View File
@@ -0,0 +1,153 @@
// Slide 01: Section cover -- Stage 1 / Chains
// Asymmetric layout: big section number + title block on left, code-window mock on right.
// Sets the stage: "what is LangChain, why it's the foundation for everything else."
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
// Left vertical accent stripe -- teal
slide.addShape(pres.ShapeType.rect, {
x: 0, y: 0, w: 0.25, h: 5.625,
fill: { color: theme.palette.accent.primary },
line: { type: 'none' },
});
// Top tag pill: section meta
slide.addShape(pres.ShapeType.roundRect, {
x: 0.7, y: 0.55, w: 2.4, h: 0.36,
fill: { color: theme.palette.accent.primary },
line: { type: 'none' },
rectRadius: 0.18,
});
slide.addText('STAGE 1 | CHAINS', {
x: 0.7, y: 0.55, w: 2.4, h: 0.36,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 10, bold: true,
color: theme.palette.bg.primary,
align: 'center', valign: 'middle',
charSpacing: 4, margin: 0,
});
// Main title -- very large, but constrained to left half so it does not
// collide with the LCEL pipeline mock on the right.
slide.addText('LangChain 1.0', {
x: 0.7, y: 1.15, w: 5.9, h: 1.0,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 56, bold: true,
color: theme.palette.text.primary,
align: 'left', valign: 'middle',
margin: 0,
});
// Subtitle
slide.addText('chains, LCEL, agents, retrievers', {
x: 0.7, y: 2.15, w: 9, h: 0.55,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 26,
color: theme.palette.accent.tertiary,
align: 'left', valign: 'middle',
margin: 0,
});
// Description block -- what this section covers
slide.addText(
'Фундамент: композиция через LCEL (| pipe), унифицированный init_chat_model, ' +
'output parsers, retrievers, tools, память, middleware. ' +
'Всё, что нужно, чтобы собрать production-ready LLM-приложение ' +
'до перехода к LangGraph и Deep Agents.',
{
x: 0.7, y: 2.85, w: 5.7, h: 1.6,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 13,
color: theme.palette.text.secondary,
align: 'left', valign: 'top',
margin: 0,
}
);
// Decorative right-side block -- abstract "LCEL pipeline" mock
// Three boxes connected by | pipes, evoking prompt | model | parser
const codeX = 6.7;
const codeY = 1.55;
const codeW = 2.85;
const codeH = 3.05;
slide.addShape(pres.ShapeType.roundRect, {
x: codeX, y: codeY, w: codeW, h: codeH,
fill: { color: theme.palette.bg.elevated },
line: { color: theme.palette.border.subtle, width: 1 },
rectRadius: 0.1,
});
// Title inside the card
slide.addText('prompt | model | parser', {
x: codeX + 0.15, y: codeY + 0.2, w: codeW - 0.3, h: 0.4,
fontFace: ds.helpers.withFallback(theme.fonts.code),
fontSize: 14, bold: true,
color: theme.palette.accent.tertiary,
align: 'center', valign: 'middle',
margin: 0,
});
// Three mock boxes connected vertically
const boxes = [
{ label: 'ChatPromptTemplate', color: theme.palette.accent.tertiary, y: codeY + 0.75 },
{ label: 'ChatModel', color: theme.palette.accent.primary, y: codeY + 1.55 },
{ label: 'OutputParser', color: theme.palette.accent.secondary, y: codeY + 2.35 },
];
boxes.forEach((b) => {
slide.addShape(pres.ShapeType.roundRect, {
x: codeX + 0.4, y: b.y, w: codeW - 0.8, h: 0.5,
fill: { color: theme.palette.bg.code },
line: { color: b.color, width: 1.25 },
rectRadius: 0.06,
});
slide.addText(b.label, {
x: codeX + 0.4, y: b.y, w: codeW - 0.8, h: 0.5,
fontFace: ds.helpers.withFallback(theme.fonts.code),
fontSize: 13, bold: true,
color: b.color,
align: 'center', valign: 'middle',
margin: 0,
});
});
// Vertical pipe marks between boxes
const pipeXs = [codeX + 0.7, codeX + 1.4, codeX + 2.1];
[codeY + 1.28, codeY + 2.08].forEach((yPipe) => {
slide.addText('|', {
x: codeX + 0.4, y: yPipe, w: codeW - 0.8, h: 0.22,
fontFace: ds.helpers.withFallback(theme.fonts.code),
fontSize: 16, bold: true,
color: theme.palette.text.muted,
align: 'center', valign: 'middle',
margin: 0,
});
});
// Caption under the mock
slide.addText('композиция через LCEL', {
x: codeX, y: codeY + codeH + 0.05, w: codeW, h: 0.3,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 11, italic: true,
color: theme.palette.text.muted,
align: 'center', valign: 'middle', margin: 0,
});
// Bottom meta strip
slide.addText('27 СЛАЙДОВ | PYTHON >= 1.0 | ЛЕТО 2026', {
x: 0.7, y: 5.05, w: 9, h: 0.3,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 10,
color: theme.palette.text.muted,
align: 'left', valign: 'middle',
charSpacing: 3, margin: 0,
});
ds.helpers.addPageNumber(slide, pres, theme, 1);
}
module.exports = { createSlide };
+116
View File
@@ -0,0 +1,116 @@
// Slide 02: What is LangChain in 2025+ -- content slide with key facts
// Sets the conceptual frame: a framework, not just a wrapper. Three pillars.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Раздел 1',
title: 'Что такое LangChain в 2025+',
sectionNumber: 1,
});
// Lead paragraph
slide.addText(
'LangChain -- Python/JS фреймворк для сборки LLM-приложений и агентов. ' +
'С версии 1.0 (релиз 22.10.2025) он построен поверх LangGraph-runtime и ' +
'позиционируется как "самый быстрый способ собрать агента с любым провайдером моделей".',
{
x: 0.5, y: 1.5, w: 9.0, h: 0.7,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 13,
color: theme.palette.text.secondary,
align: 'left', valign: 'top',
margin: 0,
}
);
// Three pillar cards
const pillars = [
{
title: 'LCEL',
sub: 'LangChain Expression Language',
body: 'Декларативная композиция через pipe-оператор. ' +
'Любая цепочка -- Runnable. invoke/batch/stream/async работают одинаково.',
},
{
title: 'create_agent',
sub: 'унифицированный вход',
body: 'Один вызов вместо зоопарка legacy agent-типов. ' +
'Построен на LangGraph -- получаешь durable execution бесплатно.',
},
{
title: 'Middleware',
sub: 'cross-cutting hooks',
body: 'before_model / after_model / before_tool / after_tool. ' +
'HITL, PII-редакция, summarization -- first-class встроенные middleware.',
},
];
const cardW = 3.0;
const cardH = 2.2;
const gap = 0.15;
const startX = 0.5;
const cardY = 2.4;
pillars.forEach((p, i) => {
const x = startX + i * (cardW + gap);
slide.addShape(pres.ShapeType.roundRect, {
x: x, y: cardY, w: cardW, h: cardH,
fill: { color: theme.palette.bg.elevated },
line: { color: theme.palette.border.subtle, width: 1 },
rectRadius: 0.1,
});
// Left accent bar
slide.addShape(pres.ShapeType.rect, {
x: x, y: cardY, w: 0.08, h: cardH,
fill: { color: theme.palette.accent.primary },
line: { type: 'none' },
});
slide.addText(p.title, {
x: x + 0.25, y: cardY + 0.2, w: cardW - 0.4, h: 0.45,
fontFace: ds.helpers.withFallback(theme.fonts.code),
fontSize: 22, bold: true,
color: theme.palette.accent.primary,
valign: 'middle', margin: 0,
});
slide.addText(p.sub, {
x: x + 0.25, y: cardY + 0.65, w: cardW - 0.4, h: 0.3,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 10, italic: true,
color: theme.palette.text.muted,
valign: 'middle', margin: 0,
});
slide.addText(p.body, {
x: x + 0.25, y: cardY + 1.05, w: cardW - 0.4, h: cardH - 1.2,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 12,
color: theme.palette.text.secondary,
valign: 'top', margin: 0,
});
});
// Bottom meta strip -- facts
slide.addText(
'~140k звезд GitHub | MIT | Python: langchain 1.3.10 / langchain-core 1.4.8 | JS: @langchain/langchain',
{
x: 0.5, y: 4.8, w: 9.0, h: 0.3,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 10, italic: true,
color: theme.palette.text.muted,
align: 'left', valign: 'middle',
margin: 0,
}
);
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'github.com/langchain-ai/langchain README + changelog.langchain.com',
});
ds.helpers.addPageNumber(slide, pres, theme, 2);
}
module.exports = { createSlide };
+49
View File
@@ -0,0 +1,49 @@
// Slide 03: Installation -- pip install + provider packages
// Code slide: shows the actual install commands a user needs to run.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Установка',
title: 'pip install -- и сразу в дело',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'bash',
filePath: 'shell/install.sh',
code: [
'# 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',
].join('\n'),
});
// Callout: ключевая мысль
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'langchain-core тянется автоматически как зависимость langchain -- отдельно ставить не нужно.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/introduction/#installation',
});
ds.helpers.addPageNumber(slide, pres, theme, 3);
}
module.exports = { createSlide };
+57
View File
@@ -0,0 +1,57 @@
// Slide 04: Hello world -- first chain with init_chat_model
// Code slide: the simplest possible working example.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Hello world',
title: 'Первый вызов модели',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 6.4, h: 3.25,
language: 'python',
filePath: 'examples/hello.py',
code: [
'# Один универсальный инициализатор для всех провайдеров',
'from langchain.chat_models import init_chat_model',
'',
'# Формат: "<provider>:<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?"',
].join('\n'),
});
// Right side: explanation card
ds.helpers.addCallout(slide, pres, theme, {
x: 7.1, y: 1.5, w: 2.4, h: 1.55,
kind: 'info',
title: 'init_chat_model',
text: 'Один фабричный вызов вместо ChatOpenAI / ChatAnthropic / ChatGoogle отдельно.',
});
ds.helpers.addCallout(slide, pres, theme, {
x: 7.1, y: 3.2, w: 2.4, h: 1.55,
kind: 'success',
title: 'Что вернется',
text: 'Объект AIMessage: content, response_metadata (usage, model_name), id, tool_calls.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/chat_models_universal_init/',
});
ds.helpers.addPageNumber(slide, pres, theme, 4);
}
module.exports = { createSlide };
+52
View File
@@ -0,0 +1,52 @@
// Slide 05: ChatModels -- provider routing through init_chat_model
// Code slide: how to switch providers without changing call sites.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'ChatModels',
title: 'Переключение провайдера -- одна строка',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/multi_provider.py',
code: [
'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])',
].join('\n'),
});
// Bottom callout -- env vars
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'warning',
text: 'Нужны API-ключи в env: OPENAI_API_KEY / ANTHROPIC_API_KEY / GOOGLE_API_KEY -- провайдер-пакет сам их подхватит.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/concepts/chat_models/',
});
ds.helpers.addPageNumber(slide, pres, theme, 5);
}
module.exports = { createSlide };
+53
View File
@@ -0,0 +1,53 @@
// Slide 06: Messages -- HumanMessage, AIMessage, SystemMessage, ToolMessage
// Code slide: the message contract, how to build multi-turn prompts.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Messages',
title: 'Стандартизированные сообщения',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/messages.py',
code: [
'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)',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'С v1.0 контент -- стандартизированные content blocks: reasoning traces, citations, tool_call блоки.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/concepts/messages/',
});
ds.helpers.addPageNumber(slide, pres, theme, 6);
}
module.exports = { createSlide };
+54
View File
@@ -0,0 +1,54 @@
// Slide 07: Streaming -- .stream() yields chunks progressively
// Code slide: how to consume the model output as a stream.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Streaming',
title: 'Потоковый вывод -- token за токеном',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/streaming.py',
code: [
'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)',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'success',
text: 'AIMessageChunk можно складывать через оператор + -- для буферизации и метрик.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/streaming/',
});
ds.helpers.addPageNumber(slide, pres, theme, 7);
}
module.exports = { createSlide };
+52
View File
@@ -0,0 +1,52 @@
// Slide 08: ChatPromptTemplate -- from_messages + variables
// Code slide: declarative prompt construction with placeholders.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Prompts',
title: 'ChatPromptTemplate -- декларативно',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/prompt_basic.py',
code: [
'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()',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'Кортежи ("role", "text") -- шорткат. Для сложного контента передавайте Message-объекты.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/concepts/prompt_templates/',
});
ds.helpers.addPageNumber(slide, pres, theme, 8);
}
module.exports = { createSlide };
+61
View File
@@ -0,0 +1,61 @@
// Slide 09: MessagesPlaceholder + few-shot examples
// Code slide: how to inject conversation history and demonstration examples.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Prompts',
title: 'MessagesPlaceholder + few-shot',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/prompt_fewshot.py',
code: [
'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}"),',
'])',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'success',
text: 'MessagesPlaceholder("history") -- дырка, в которую при invoke подставляется список прошлых сообщений.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/few_shot_examples_chat/',
});
ds.helpers.addPageNumber(slide, pres, theme, 9);
}
module.exports = { createSlide };
+54
View File
@@ -0,0 +1,54 @@
// Slide 10: StrOutputParser -- the most common parser
// Code slide: parse AIMessage.content down to plain str.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Output parsers',
title: 'StrOutputParser -- самый частый',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/parser_str.py',
code: [
'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"',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'StrOutputParser просто достает .content из AIMessage. Без него пришлось бы делать result.content вручную.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/concepts/output_parsers/',
});
ds.helpers.addPageNumber(slide, pres, theme, 10);
}
module.exports = { createSlide };
+57
View File
@@ -0,0 +1,57 @@
// Slide 11: PydanticOutputParser -- structured output via Pydantic schema
// Code slide: Pydantic model -> parser -> validated instance.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Output parsers',
title: 'PydanticOutputParser -- типизированный выход',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/parser_pydantic.py',
code: [
'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)',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'success',
text: 'В v1.0 рекомендуется model.with_structured_output(Schema) -- он использует tool calling и точнее.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/pydantic_output_parser/',
});
ds.helpers.addPageNumber(slide, pres, theme, 11);
}
module.exports = { createSlide };
+54
View File
@@ -0,0 +1,54 @@
// Slide 12: with_structured_output -- the recommended v1.0 way
// Code slide: model.with_structured_output(Schema) instead of legacy parsers.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Output parsers',
title: 'with_structured_output -- рекомендованный путь',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/structured.py',
code: [
'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")',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'Под капотом: tool calling или provider-native JSON mode. Без extra LLM-вызовов, в один проход.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/structured_output/',
});
ds.helpers.addPageNumber(slide, pres, theme, 12);
}
module.exports = { createSlide };
+56
View File
@@ -0,0 +1,56 @@
// Slide 13: LCEL -- the pipe operator and the Runnable protocol
// Code slide: every component is a Runnable, | composes them.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'LCEL',
title: '| -- декларативная композиция',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/lcel_intro.py',
code: [
'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?"}))',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'success',
text: 'Runnable -- единый контракт: invoke / batch / stream / ainvoke / abatch / astream / async stream.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/concepts/lcel/',
});
ds.helpers.addPageNumber(slide, pres, theme, 13);
}
module.exports = { createSlide };
+53
View File
@@ -0,0 +1,53 @@
// Slide 14: Runnable interface -- invoke / batch / stream
// Code slide: same chain, three execution modes -- no code changes needed.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Runnable',
title: 'invoke / batch / stream -- без смены кода',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/runnable_modes.py',
code: [
'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)',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'batch полезен для embedding-style задач. stream -- для UX с typewriter-эффектом в UI.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/batch/',
});
ds.helpers.addPageNumber(slide, pres, theme, 14);
}
module.exports = { createSlide };
+58
View File
@@ -0,0 +1,58 @@
// Slide 15: Async + configurable -- ainvoke/astream + chain.config
// Code slide: how to use chains from async code and override params at call site.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Runnable',
title: 'async + config -- полный контроль',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/runnable_async.py',
code: [
'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}})',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'success',
text: 'configurable_fields + config={"configurable": {...}} -- паттерн для multi-tenant LLM-приложений.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/configurable/',
});
ds.helpers.addPageNumber(slide, pres, theme, 15);
}
module.exports = { createSlide };
+56
View File
@@ -0,0 +1,56 @@
// Slide 16: RunnableLambda + RunnablePassthrough -- custom logic in pipeline
// Code slide: insert arbitrary Python functions into an LCEL chain.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Runnable helpers',
title: 'RunnableLambda + RunnablePassthrough',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/runnable_lambda.py',
code: [
'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"}',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'RunnablePassthrough.assign -- добавляет новые ключи, сохраняя исходные. Идеален для RAG-style цепочек.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/functions/',
});
ds.helpers.addPageNumber(slide, pres, theme, 16);
}
module.exports = { createSlide };
+54
View File
@@ -0,0 +1,54 @@
// Slide 17: RunnableParallel + RunnableBranch -- fan-out / conditional routing
// Code slide: parallel execution and if/else logic in a chain.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Runnable helpers',
title: 'Parallel + Branch -- fan-out и роутинг',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/runnable_parallel.py',
code: [
'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"}))',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'success',
text: 'RunnableParallel выполняется параллельно -- отлично для multi-aspect анализа (sentiment + summary + keywords).',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/branching/',
});
ds.helpers.addPageNumber(slide, pres, theme, 17);
}
module.exports = { createSlide };
+62
View File
@@ -0,0 +1,62 @@
// Slide 18: Retrievers + Vector Stores -- FAISS / Chroma / PGVector
// Code slide: index documents, embed, retrieve top-k, plug into a chain.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Retrievers',
title: 'Vector store -> retriever -> RAG-цепочка',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/retriever_rag.py',
code: [
'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()',
')',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'Альтернативные сторы: Chroma (легковесный), PGVector (production Postgres), Pinecone, Weaviate, Qdrant.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/concepts/retrievers/',
});
ds.helpers.addPageNumber(slide, pres, theme, 18);
}
module.exports = { createSlide };
+57
View File
@@ -0,0 +1,57 @@
// Slide 19: Tools -- @tool decorator + bind_tools
// Code slide: define tools and pass them to a model via bind_tools.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Tools',
title: '@tool -- любая функция становится инструментом',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/tool_basic.py',
code: [
'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": "..."}]',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'success',
text: 'docstring инструмента = описание, которое видит модель. Названия параметров должны быть говорящими.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'python.langchain.com/docs/how_to/tool_calling/',
});
ds.helpers.addPageNumber(slide, pres, theme, 19);
}
module.exports = { createSlide };
+56
View File
@@ -0,0 +1,56 @@
// Slide 20: create_agent -- the unified agent entry point (v1.0)
// Code slide: a working agent in 10 lines.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Agents',
title: 'create_agent -- один вход для всех агентов',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/agent_basic.py',
code: [
'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)',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'Под капотом LangGraph-runtime: durable execution, checkpointing, human-in-the-loop доступны через middleware.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'docs.langchain.com/oss/python/langchain/agents',
});
ds.helpers.addPageNumber(slide, pres, theme, 20);
}
module.exports = { createSlide };
+58
View File
@@ -0,0 +1,58 @@
// Slide 21: Short-term memory -- thread_id + checkpointer
// Code slide: persistent conversation per thread.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Memory',
title: 'Краткосрочная память: thread + checkpointer',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/memory_short.py',
code: [
'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"',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'success',
text: 'InMemorySaver для dev. Для прода -- PostgresSaver / RedisSaver (те же LangGraph checkpointer-ы).',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'docs.langchain.com/oss/python/langgraph/persistence',
});
ds.helpers.addPageNumber(slide, pres, theme, 21);
}
module.exports = { createSlide };
+58
View File
@@ -0,0 +1,58 @@
// Slide 22: Long-term memory -- InMemoryStore + namespace
// Code slide: cross-thread memory, semantic search over stored facts.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Memory',
title: 'Долгосрочная память: store + namespace',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/memory_long.py',
code: [
'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") вернёт факты',
].join('\n'),
});
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'info',
text: 'Long-term memory переживает thread. Идеальна для user preferences, summary прошлых сессий, learned facts.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'docs.langchain.com/oss/python/langgraph/memory',
});
ds.helpers.addPageNumber(slide, pres, theme, 22);
}
module.exports = { createSlide };
+60
View File
@@ -0,0 +1,60 @@
// Slide 23: Middleware -- HumanInTheLoop + PIIRedaction + Summarization
// Code slide: cross-cutting hooks attached at agent construction.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Middleware',
title: 'Cross-cutting concerns одной строкой',
sectionNumber: 1,
});
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 9.0, h: 3.25,
language: 'python',
filePath: 'examples/middleware.py',
code: [
'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)),',
' ],',
')',
].join('\n'),
});
// Right-side mini-glossary callouts (left side has code; reuse addCallout below)
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.85, w: 9.0, h: 0.3,
kind: 'warning',
text: 'Все три middleware -- встроенные. Custom middleware пишутся как before_model/after_model hooks.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'docs.langchain.com/oss/python/langchain/middleware',
});
ds.helpers.addPageNumber(slide, pres, theme, 23);
}
module.exports = { createSlide };
+90
View File
@@ -0,0 +1,90 @@
// Slide 24: 1.0 vs 0.3 -- breaking changes and what is new
// Content slide: side-by-side comparison of what changed in v1.0.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Версии',
title: 'Что нового в 1.0 vs 0.3',
sectionNumber: 1,
});
// Two columns: OLD (0.x) vs NEW (1.0)
const colW = 4.35;
const colH = 3.3;
const colY = 1.5;
const leftX = 0.5;
const rightX = 5.15;
// OLD column
slide.addShape(pres.ShapeType.roundRect, {
x: leftX, y: colY, w: colW, h: colH,
fill: { color: theme.palette.state.dangerBg },
line: { color: theme.palette.state.danger, width: 1 },
rectRadius: 0.1,
});
slide.addText('0.x (legacy)', {
x: leftX + 0.2, y: colY + 0.15, w: colW - 0.3, h: 0.4,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 18, bold: true,
color: theme.palette.state.danger,
valign: 'middle', margin: 0,
});
const oldItems = [
'create_react_agent / create_openai_functions_agent / create_structured_chat_agent',
'LLMChain, RetrievalQA, ConversationalRetrievalQA',
'ChatOpenAI / ChatAnthropic / ChatGoogleGenerativeAI отдельно',
'Кастомные callbacks для HITL и PII',
'Verbose LLMChain с ручным manage_prompts',
].map((t) => ({ text: '- ' + t + '\n', options: { fontFace: ds.helpers.withFallback(theme.fonts.ui), fontSize: 12, color: theme.palette.text.primary } }));
slide.addText(oldItems, {
x: leftX + 0.2, y: colY + 0.6, w: colW - 0.3, h: colH - 0.7,
valign: 'top', paraSpaceAfter: 6, margin: 0,
});
// NEW column
slide.addShape(pres.ShapeType.roundRect, {
x: rightX, y: colY, w: colW, h: colH,
fill: { color: theme.palette.state.successBg },
line: { color: theme.palette.state.success, width: 1 },
rectRadius: 0.1,
});
slide.addText('1.0 (current)', {
x: rightX + 0.2, y: colY + 0.15, w: colW - 0.3, h: 0.4,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 18, bold: true,
color: theme.palette.state.success,
valign: 'middle', margin: 0,
});
const newItems = [
'create_agent -- единая точка входа (построен на LangGraph)',
'Middleware-система: HITL, PII, Summarization как first-class',
'init_chat_model("<provider>:<model>") -- один инициализатор',
'with_structured_output -- tool calling / provider-native JSON',
'Семантическое версионирование: до 2.0 breaking changes не будет',
'langchain-classic -- пакет для обратной совместимости legacy chains',
].map((t) => ({ text: '+ ' + t + '\n', options: { fontFace: ds.helpers.withFallback(theme.fonts.ui), fontSize: 12, color: theme.palette.text.primary } }));
slide.addText(newItems, {
x: rightX + 0.2, y: colY + 0.6, w: colW - 0.3, h: colH - 0.7,
valign: 'top', paraSpaceAfter: 6, margin: 0,
});
// Bottom callout: migration tip
ds.helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 4.95, w: 9.0, h: 0.4,
kind: 'info',
text: 'Миграция: pip install langchain-classic -- legacy LLMChain/AgentExecutor работают, но новый код пишите на create_agent.',
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'docs.langchain.com/oss/python/releases/langchain-v1',
});
ds.helpers.addPageNumber(slide, pres, theme, 24);
}
module.exports = { createSlide };
+85
View File
@@ -0,0 +1,85 @@
// Slide 25: TypeScript status -- what is and isn't there in JS land
// Content slide: parity matrix and code sketch.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'TypeScript',
title: 'JS-аналог: что есть, чего нет',
sectionNumber: 1,
});
// Code block: TS sample
ds.helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 1.5, w: 5.5, h: 3.25,
language: 'typescript',
filePath: 'examples/ts_basic.ts',
code: [
'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?" }],',
'});',
].join('\n'),
});
// Right column: parity list
slide.addText('Что есть в @langchain/langchain:', {
x: 6.2, y: 1.5, w: 3.3, h: 0.35,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 13, bold: true,
color: theme.palette.accent.primary, margin: 0,
});
const have = [
'+ initChatModel',
'+ createAgent',
'+ @langchain/core (tools, prompts, parsers)',
'+ LCEL и Runnable-протокол',
'+ Стандартизированные Messages',
'+ Vector store интеграции',
].map((t) => ({ text: t + '\n', options: { fontFace: ds.helpers.withFallback(theme.fonts.ui), fontSize: 11, color: theme.palette.text.primary } }));
slide.addText(have, {
x: 6.2, y: 1.9, w: 3.3, h: 1.3,
valign: 'top', paraSpaceAfter: 3, margin: 0,
});
slide.addText('Чего нет или слабее:', {
x: 6.2, y: 3.3, w: 3.3, h: 0.35,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 13, bold: true,
color: theme.palette.state.warning, margin: 0,
});
const miss = [
'- langchain-classic покрытие уже',
'- Часть community-интеграций',
'- Tracing middleware меньше',
].map((t) => ({ text: t + '\n', options: { fontFace: ds.helpers.withFallback(theme.fonts.ui), fontSize: 11, color: theme.palette.text.primary } }));
slide.addText(miss, {
x: 6.2, y: 3.7, w: 3.3, h: 1.0,
valign: 'top', paraSpaceAfter: 3, margin: 0,
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'js.langchain.com/docs/how_to/chat_models_universal_init',
});
ds.helpers.addPageNumber(slide, pres, theme, 25);
}
module.exports = { createSlide };
+56
View File
@@ -0,0 +1,56 @@
// Slide 26: Pros / Cons -- when LangChain 1.0 is the right choice
// Content slide: pros/cons two-column panel.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Итоги',
title: 'Когда LangChain 1.0 -- правильный выбор',
sectionNumber: 1,
});
// Lead paragraph
slide.addText(
'Production-ready commitment, единая точка входа для агентов, ' +
'встроенные middleware. Но абстракция скрывает LangGraph -- если нужен ' +
'fine-grained контроль над execution, придется проваливаться глубже.',
{
x: 0.5, y: 1.5, w: 9.0, h: 0.6,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 13, italic: true,
color: theme.palette.text.secondary,
align: 'left', valign: 'top', margin: 0,
}
);
ds.helpers.addProsCons(slide, pres, theme, {
x: 0.5, y: 2.25, w: 9.0, h: 2.5,
pros: [
'Семантическая стабильность -- обязательство не ломать API до 2.0',
'create_agent вместо зоопарка agent-типов',
'init_chat_model -- переключение провайдера без рефакторинга',
'Middleware-система (HITL, PII, Summarization) из коробки',
'LangGraph-runtime под капотом -- durable execution бесплатно',
'~700+ интеграций через community-пакеты langchain-*',
],
cons: [
'Кривая обучения для middleware (before/after hooks)',
'Часть экосистемы переехала в langchain-classic -- миграционная боль',
'Абстракция скрывает LangGraph -- для fine-grained нужен fallback',
'Bundled-версии зависимостей (langchain-openai, -anthropic, ...) нужно фиксировать',
'Исторически bloated core -- в 1.0 стало lean, но восприятие осталось',
],
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'changelog.langchain.com/announcements/langchain-1-0-now-generally-available',
});
ds.helpers.addPageNumber(slide, pres, theme, 26);
}
module.exports = { createSlide };
+103
View File
@@ -0,0 +1,103 @@
// Slide 27: Bridge to LangGraph -- what is next in this deck
// Content slide: closing summary + teaser for Section 2.
const ds = require('./design-system');
function createSlide(pres, theme) {
const slide = pres.addSlide();
ds.helpers.slideBase(slide, pres, theme);
ds.helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 1: CHAINS',
section: 'Переход',
title: 'Дальше: LangGraph -- явный control flow',
sectionNumber: 1,
});
// Lead paragraph
slide.addText(
'LangChain 1.0 -- это "правильный путь по умолчанию" для большинства задач. ' +
'Когда LCEL-pipeline перестает хватать (ветвления, циклы, человеческое ' +
'одобрение, persistence) -- под капотом работает LangGraph. В следующей секции ' +
'разберем его как самостоятельный runtime: граф, узлы, edges, checkpointer, ' +
'human-in-the-loop как first-class концепции.',
{
x: 0.5, y: 1.55, w: 9.0, h: 1.1,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 14,
color: theme.palette.text.secondary,
align: 'left', valign: 'top', margin: 0,
}
);
// Three teaser cards
const teasers = [
{
title: 'Граф вместо пайплайна',
body: 'StateGraph, узлы (nodes), ребра (edges), условные переходы. ' +
'Полный контроль над execution.',
},
{
title: 'Persistence',
body: 'Checkpointers, thread_id, time-travel. State между turn-ами ' +
'хранится на диске.',
},
{
title: 'Human-in-the-loop',
body: 'interrupt_before / interrupt_after узлов. Апрув через Command. ' +
'Не нужен middleware-хак.',
},
];
const cardW = 3.0;
const cardH = 1.9;
const gap = 0.15;
const startX = 0.5;
const cardY = 2.85;
teasers.forEach((p, i) => {
const x = startX + i * (cardW + gap);
slide.addShape(pres.ShapeType.roundRect, {
x: x, y: cardY, w: cardW, h: cardH,
fill: { color: theme.palette.bg.elevated },
line: { color: theme.palette.border.subtle, width: 1 },
rectRadius: 0.1,
});
slide.addShape(pres.ShapeType.rect, {
x: x, y: cardY, w: cardW, h: 0.08,
fill: { color: theme.palette.accent.secondary },
line: { type: 'none' },
});
slide.addText(p.title, {
x: x + 0.2, y: cardY + 0.2, w: cardW - 0.4, h: 0.45,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 15, bold: true,
color: theme.palette.accent.secondary,
valign: 'middle', margin: 0,
});
slide.addText(p.body, {
x: x + 0.2, y: cardY + 0.7, w: cardW - 0.4, h: cardH - 0.85,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 11,
color: theme.palette.text.secondary,
valign: 'top', margin: 0,
});
});
// Bottom hint
slide.addText('SECTION 2: LANGGRAPH >>>', {
x: 0.5, y: 5.0, w: 9.0, h: 0.3,
fontFace: ds.helpers.withFallback(theme.fonts.ui),
fontSize: 11, bold: true,
color: theme.palette.accent.primary,
align: 'center', valign: 'middle',
charSpacing: 4, margin: 0,
});
ds.helpers.addSourceLine(slide, pres, theme, {
source: 'docs.langchain.com/oss/python/langgraph/overview',
});
ds.helpers.addPageNumber(slide, pres, theme, 27);
}
module.exports = { createSlide };
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Some files were not shown because too many files have changed in this diff Show More