Skip to content

Commit

Permalink
Revert "Stream LLM response when editing apps (#430)" (#431)
Browse files Browse the repository at this point in the history
This reverts commit a6bfde7.
  • Loading branch information
nichochar authored Oct 30, 2024
1 parent a6bfde7 commit 8b2f55d
Show file tree
Hide file tree
Showing 12 changed files with 65 additions and 894 deletions.
42 changes: 16 additions & 26 deletions packages/api/ai/generate.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { streamText, generateText, type GenerateTextResult } from 'ai';
import { generateText, type GenerateTextResult } from 'ai';
import { getModel } from './config.mjs';
import {
type CodeLanguageType,
Expand All @@ -13,7 +13,7 @@ import Path from 'node:path';
import { PROMPTS_DIR } from '../constants.mjs';
import { encode, decodeCells } from '../srcmd.mjs';
import { buildProjectXml, type FileContent } from '../ai/app-parser.mjs';
import { logAppGeneration } from './logger.mjs';
import { type AppGenerationLog, logAppGeneration } from './logger.mjs';

const makeGenerateSrcbookSystemPrompt = () => {
return readFileSync(Path.join(PROMPTS_DIR, 'srcbook-generator.txt'), 'utf-8');
Expand Down Expand Up @@ -259,40 +259,30 @@ export async function generateApp(
return result.text;
}

export async function streamEditApp(
export async function editApp(
projectId: string,
files: FileContent[],
query: string,
appId: string,
planId: string,
) {
): Promise<string> {
const model = await getModel();

const systemPrompt = makeAppEditorSystemPrompt();
const userPrompt = makeAppEditorUserPrompt(projectId, files, query);

let response = '';

const result = await streamText({
const result = await generateText({
model,
system: systemPrompt,
prompt: userPrompt,
onChunk: (chunk) => {
if (chunk.chunk.type === 'text-delta') {
response += chunk.chunk.textDelta;
}
},
onFinish: () => {
if (process.env.SRCBOOK_DISABLE_ANALYTICS !== 'true') {
logAppGeneration({
appId,
planId,
llm_request: { model, system: systemPrompt, prompt: userPrompt },
llm_response: response,
});
}
},
});

return result.textStream;
const log: AppGenerationLog = {
appId,
planId,
llm_request: { model, system: systemPrompt, prompt: userPrompt },
llm_response: result,
};

if (process.env.SRCBOOK_DISABLE_ANALYTICS !== 'true') {
logAppGeneration(log);
}
return result.text;
}
110 changes: 0 additions & 110 deletions packages/api/ai/plan-parser.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { XMLParser } from 'fast-xml-parser';
import Path from 'node:path';
import { type App as DBAppType } from '../db/schema.mjs';
import { loadFile } from '../apps/disk.mjs';
import { StreamingXMLParser, TagType } from './stream-xml-parser.mjs';
import { ActionChunkType, DescriptionChunkType } from '@srcbook/shared';

// The ai proposes a plan that we expect to contain both files and commands
// Here is an example of a plan:
Expand Down Expand Up @@ -169,111 +167,3 @@ export function getPackagesToInstall(plan: Plan): string[] {
)
.flatMap((action) => action.packages);
}

export async function streamParsePlan(
stream: AsyncIterable<string>,
app: DBAppType,
_query: string,
planId: string,
) {
let parser: StreamingXMLParser;

return new ReadableStream({
async pull(controller) {
if (parser === undefined) {
parser = new StreamingXMLParser({
async onTag(tag) {
if (tag.name === 'planDescription' || tag.name === 'action') {
const chunk = await toStreamingChunk(app, tag, planId);
if (chunk) {
controller.enqueue(JSON.stringify(chunk) + '\n');
}
}
},
});
}

try {
for await (const chunk of stream) {
parser.parse(chunk);
}
controller.close();
} catch (error) {
console.error(error);
controller.enqueue(
JSON.stringify({
type: 'error',
data: { content: 'Error while parsing streaming response' },
}) + '\n',
);
controller.error(error);
}
},
});
}

async function toStreamingChunk(
app: DBAppType,
tag: TagType,
planId: string,
): Promise<DescriptionChunkType | ActionChunkType | null> {
switch (tag.name) {
case 'planDescription':
return {
type: 'description',
planId: planId,
data: { content: tag.content },
} as DescriptionChunkType;
case 'action': {
const descriptionTag = tag.children.find((t) => t.name === 'description');
const description = descriptionTag?.content ?? '';
const type = tag.attributes.type;

if (type === 'file') {
const fileTag = tag.children.find((t) => t.name === 'file')!;

const filePath = fileTag.attributes.filename as string;
let originalContent = null;

try {
const fileContent = await loadFile(app, filePath);
originalContent = fileContent.source;
} catch (error) {
// If the file doesn't exist, it's likely that it's a new file.
}

return {
type: 'action',
planId: planId,
data: {
type: 'file',
description,
path: filePath,
dirname: Path.dirname(filePath),
basename: Path.basename(filePath),
modified: fileTag.content,
original: originalContent,
},
} as ActionChunkType;
} else if (type === 'command') {
const commandTag = tag.children.find((t) => t.name === 'commandType')!;
const packageTags = tag.children.filter((t) => t.name === 'package');

return {
type: 'action',
planId: planId,
data: {
type: 'command',
description,
command: commandTag.content,
packages: packageTags.map((t) => t.content),
},
} as ActionChunkType;
} else {
return null;
}
}
default:
return null;
}
}
138 changes: 0 additions & 138 deletions packages/api/ai/stream-xml-parser.mts

This file was deleted.

12 changes: 5 additions & 7 deletions packages/api/server/http.mts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
listSessions,
exportSrcmdText,
} from '../session.mjs';
import { generateCells, generateSrcbook, healthcheck, streamEditApp } from '../ai/generate.mjs';
import { streamParsePlan } from '../ai/plan-parser.mjs';
import { generateCells, generateSrcbook, healthcheck, editApp } from '../ai/generate.mjs';
import { parsePlan } from '../ai/plan-parser.mjs';
import {
getConfig,
updateConfig,
Expand Down Expand Up @@ -63,7 +63,6 @@ import { CreateAppSchema } from '../apps/schemas.mjs';
import { AppGenerationFeedbackType } from '@srcbook/shared';
import { createZipFromApp } from '../apps/disk.mjs';
import { checkoutCommit, commitAllFiles, getCurrentCommitSha } from '../apps/git.mjs';
import { streamJsonResponse } from './utils.mjs';

const app: Application = express();

Expand Down Expand Up @@ -556,10 +555,9 @@ router.post('/apps/:id/edit', cors(), async (req, res) => {
}
const validName = toValidPackageName(app.name);
const files = await getFlatFilesForApp(String(app.externalId));
const result = await streamEditApp(validName, files, query, app.externalId, planId);
const planStream = await streamParsePlan(result, app, query, planId);

return streamJsonResponse(planStream, res, { status: 200 });
const result = await editApp(validName, files, query, id, planId);
const parsedResult = await parsePlan(result, app, query, planId);
return res.json({ data: parsedResult });
} catch (e) {
return error500(res, e as Error);
}
Expand Down
28 changes: 0 additions & 28 deletions packages/api/server/utils.mts

This file was deleted.

Loading

0 comments on commit 8b2f55d

Please sign in to comment.