восстановил часть
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**.
|
||||||
|
|
||||||
## Архитектура LangGraph: от нод к агентам
|
## Архитектура LangGraph: от нод к агентам
|
||||||
|
|
||||||

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

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