восстановил часть
This commit is contained in:
parent
351a1fc134
commit
76351e365e
BIN
primakov-article-images/bro-gamma-plan.gif
Normal file
BIN
primakov-article-images/bro-gamma-plan.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 MiB |
BIN
primakov-article-images/bro-gamma-slides.gif
Normal file
BIN
primakov-article-images/bro-gamma-slides.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 MiB |
BIN
primakov-article-images/bro-gamma-slides.mp4
Normal file
BIN
primakov-article-images/bro-gamma-slides.mp4
Normal file
Binary file not shown.
95
primakov.md
95
primakov.md
@ -115,16 +115,13 @@ const result = await chain.invoke({
|
||||
- Создание контента с учётом контекста
|
||||
- Условная логика и роутинг
|
||||
|
||||

|
||||
*[0:09:55 → 0:10:57]*
|
||||

|
||||

|
||||
|
||||
Здесь на помощь приходит **LangGraph**.
|
||||
|
||||
## Архитектура LangGraph: от нод к агентам
|
||||
|
||||

|
||||
*[0:10:57 → 0:26:20]*
|
||||
|
||||
### Основные концепции
|
||||
|
||||
LangGraph работает с **нодами** (nodes) — функциями, которые выполняют конкретные задачи:
|
||||
@ -135,6 +132,8 @@ LangGraph работает с **нодами** (nodes) — функциями,
|
||||
|
||||
### Архитектура моего клона
|
||||
|
||||

|
||||
|
||||
Я создал компактный и эффективный граф из нескольких нод:
|
||||
|
||||
1. **Prepare** — подготовка данных и присвоение ID слайдам
|
||||
@ -152,7 +151,7 @@ async function prepareNode(state: GraphState): Promise<Partial<GraphState>> {
|
||||
|
||||
// Присваиваем ID каждому слайду
|
||||
presentation.slides.forEach((slide, index) => {
|
||||
slide.id = `slide-${index}`;
|
||||
slide.id = uuidv4();
|
||||
});
|
||||
|
||||
return { presentation };
|
||||
@ -185,19 +184,17 @@ function routeToWebSearch(state: GraphState): string[] {
|
||||
|
||||
```typescript
|
||||
async function webSearchNode(data: { slide: Slide }): Promise<Partial<GraphState>> {
|
||||
const tavily = new TavilySearchResults({
|
||||
maxResults: 5,
|
||||
searchDepth: "advanced"
|
||||
});
|
||||
|
||||
const results = await tavily.search(data.slide.webSearchQuery);
|
||||
|
||||
// Экранируем фигурные скобки в коде
|
||||
const cleanResults = results.replace(/\{/g, '{{').replace(/\}/g, '}}');
|
||||
|
||||
const client = await tavily({ apiKey: process.env['TAVILY_API_KEY'] })
|
||||
|
||||
const researchResult = await client.search(slide.webResearchQuery, {
|
||||
maxResults: 3,
|
||||
})
|
||||
|
||||
return {
|
||||
webSearchResults: {
|
||||
[data.slide.id]: cleanResults
|
||||
webResearchResult: {
|
||||
[slide.id as string]: researchResult.results
|
||||
.map(r => r.content?.replace(/\{/g, '{{').replace(/\}/g, '}}'))
|
||||
.join('\n\n')
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -209,20 +206,27 @@ async function webSearchNode(data: { slide: Slide }): Promise<Partial<GraphState
|
||||
|
||||
Для генерации изображений использую российскую нейросеть Kandinsky через API:
|
||||
|
||||
Для работы с api я написал простенькую обёртку для упрощения работы [@brojs/kandinsky](https://gitverse.ru/primakov.a.a/kandinsky-js)
|
||||
|
||||
```typescript
|
||||
async function generateImageNode(data: {
|
||||
slide: Slide,
|
||||
imageStyle: string
|
||||
}): Promise<Partial<GraphState>> {
|
||||
const kandinsky = new KandinskyAPI();
|
||||
|
||||
const imagePrompt = `${data.slide.imagePrompt}, ${data.imageStyle}`;
|
||||
const image = await kandinsky.generateImage({
|
||||
prompt: imagePrompt,
|
||||
negativePrompt: data.slide.negativeImagePrompt,
|
||||
const { imagePrompt, imageNegativePrompt } = slide
|
||||
|
||||
const imageSize = {
|
||||
width: data.slide.type === 'title' ? 512 : 768,
|
||||
height: data.slide.type === 'title' ? 512 : 432
|
||||
});
|
||||
}
|
||||
|
||||
const images = await generateKandinskyImage({
|
||||
imagePrompt,
|
||||
imagesStyle,
|
||||
...imageSize,
|
||||
imageNegativePrompt,
|
||||
})
|
||||
|
||||
return {
|
||||
generatedImages: {
|
||||
@ -236,6 +240,8 @@ async function generateImageNode(data: {
|
||||
|
||||
Самая интересная часть — создание контента с учётом предыдущих слайдов:
|
||||
|
||||
|
||||
|
||||
```typescript
|
||||
async function generateContentNode(state: GraphState): Promise<Partial<GraphState>> {
|
||||
const messages = [
|
||||
@ -278,20 +284,23 @@ async function generateContentNode(state: GraphState): Promise<Partial<GraphStat
|
||||
### Сборка графа воедино
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(GraphState)
|
||||
.addNode("prepare", prepareNode)
|
||||
.addNode("webresearch", webSearchNode)
|
||||
.addNode("generateImages", generateImageNode)
|
||||
.addNode("generateContent", generateContentNode)
|
||||
.addNode("final", finalNode)
|
||||
.setEntryPoint("prepare")
|
||||
.addConditionalEdges("prepare", routeToWebSearch)
|
||||
.addEdge("webresearch", "generateImages")
|
||||
.addEdge("generateImages", "generateContent")
|
||||
.addEdge("generateContent", "final")
|
||||
.setFinishPoint("final");
|
||||
const builder = new StateGraph(agentState)
|
||||
.addNode("prepare", prepareData)
|
||||
.addNode('webResearch', webResearch)
|
||||
.addNode('toGenerate', () => ({}))
|
||||
.addNode('final', state => state)
|
||||
.addNode('imageGenerator', imageGenerator)
|
||||
.addNode('generateSlideContent', generateSlideContent)
|
||||
|
||||
const app = graph.compile();
|
||||
builder
|
||||
.addEdge(START, "prepare")
|
||||
.addConditionalEdges("prepare", afterPrepareRouter)
|
||||
.addConditionalEdges("toGenerate", generateImagesAndFirstContent)
|
||||
.addConditionalEdges("generateSlideContent", toGenerateNextSlideContent)
|
||||
.addEdge('webResearch', 'toGenerate')
|
||||
.addEdge('final', END);
|
||||
|
||||
const app = builder.compile();
|
||||
|
||||
// Запуск генерации
|
||||
const result = await app.invoke({
|
||||
@ -316,13 +325,7 @@ LangGraph позволяет запускать ноды параллельно.
|
||||
|
||||
### Стоимость генерации
|
||||
|
||||
Примерная стоимость создания презентации из 8 слайдов:
|
||||
- Планирование: ~$0.02
|
||||
- Веб-поиск: ~$0.01
|
||||
- Генерация изображений: ~$0.06
|
||||
- Создание контента: ~$0.02
|
||||
|
||||
**Итого: ~$0.11** себестоимость при продажной цене $2-5.
|
||||
Примерная стоимость создания презентации из 8 слайдов около 20-30 Р
|
||||
|
||||
## Практические советы
|
||||
|
||||
@ -338,9 +341,7 @@ LangGraph позволяет запускать ноды параллельно.
|
||||
|
||||
Главное — **пробовать и экспериментировать**. Это направление развивается очень быстро, и скоро каждый разработчик будет работать с AI-агентами.
|
||||
|
||||
[Ссылка на прототип](https://platform.bro-js.ru/bro-gamma)
|
||||
|
||||
*QR-код для доступа к демо-версии клона Gamma.app*
|
||||
[Ссыылка на рабочий прототип](https://platform.bro-js.ru/bro-gamma)
|
||||
|
||||
---
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user