Skip to content

Commit 3c70709

Browse files
committed
refactor: api/chat と lib/chatGenerator.ts のプロンプト定義および関連処理を共通化
1 parent 813c02c commit 3c70709

2 files changed

Lines changed: 271 additions & 337 deletions

File tree

app/api/chat/route.ts

Lines changed: 23 additions & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { generateContentStream } from "@/lib/ai";
33
import {
44
addChat,
55
addMessagesAndDiffs,
6-
CreateChatDiff,
76
deleteChat,
87
initContext,
98
revalidateChatOnDemand,
@@ -17,6 +16,11 @@ import {
1716
PageSlug,
1817
SectionId,
1918
} from "@/lib/docs";
19+
import {
20+
buildChatPrompt,
21+
buildRoutePrompt,
22+
parseDiffsAndCleanMessage,
23+
} from "@/lib/chatGenerator";
2024
import {
2125
ReplCommandSchema,
2226
ReplOutputSchema,
@@ -68,36 +72,12 @@ export async function POST(request: NextRequest) {
6872
let targetSectionContent = sectionContent;
6973

7074
if (questionScope === "language" && langEntry) {
71-
const routePrompt: string[] = [];
72-
routePrompt.push(
73-
`あなたは${langName}言語チュートリアルの質問ルーターです。ユーザーの質問に最も関連するページslugを選んでください。`
74-
);
75-
routePrompt.push(``);
76-
routePrompt.push("# 指示");
77-
routePrompt.push(
78-
"- 1行目にページslugのみを出力してください(引用符や補足説明は不要です)。"
79-
);
80-
routePrompt.push(
81-
"- どのページにも関連しない場合は null と出力してください。"
82-
);
83-
routePrompt.push(``);
84-
routePrompt.push("# 目次");
85-
routePrompt.push(``);
86-
for (const page of langEntry.pages) {
87-
const sections = await getMarkdownSections(path.lang, page.slug);
88-
const sectionTitles = sections
89-
.map((s) => s.title.trim())
90-
.filter((title) => title.length > 0)
91-
.join(" / ");
92-
routePrompt.push(
93-
`- slug: ${page.slug} | 章題: ${page.title} | セクション: ${sectionTitles}`
94-
);
95-
}
96-
routePrompt.push(``);
97-
routePrompt.push(`# ユーザーの質問`);
98-
routePrompt.push(userQuestion);
99-
routePrompt.push(``);
100-
routePrompt.push(`# 回答`);
75+
const routePrompt = await buildRoutePrompt({
76+
lang: path.lang,
77+
langName,
78+
pages: langEntry.pages,
79+
userQuestion,
80+
});
10181

10282
let routeResult = "";
10383
for await (const chunk of generateContentStream(
@@ -135,176 +115,14 @@ export async function POST(request: NextRequest) {
135115

136116
const isSandbox = path.page === ("sandbox" as PageSlug);
137117

138-
const prompt: string[] = [];
139-
140-
if (isSandbox) {
141-
prompt.push(
142-
`あなたは${langName}プログラミングの学習者をサポートする講師AIアシスタントです。`
143-
);
144-
prompt.push(
145-
`ユーザーからの質問に対して、初心者にも分かりやすく、丁寧な解説を提供してください。`
146-
);
147-
prompt.push(``);
148-
} else {
149-
prompt.push(`あなたは${langName}言語のチュートリアルの講師をしています。`);
150-
prompt.push(
151-
`以下の${langName}チュートリアルのドキュメントの内容を正確に理解し、ユーザーからの質問に対して、初心者にも分かりやすく、丁寧な解説を提供してください。`
152-
);
153-
prompt.push(``);
154-
const sectionTitlesInView = targetSectionContent
155-
.filter((s) => s.inView)
156-
.map((s) => s.title);
157-
if (sectionTitlesInView.length > 0) {
158-
prompt.push(
159-
`ユーザーはドキュメント内の ${sectionTitlesInView.join(", ")} の付近のセクションを閲覧している際にこの質問を行っていると推測されます。`
160-
);
161-
prompt.push(
162-
`質問に答える際には、ユーザーが閲覧しているセクションの内容を特に考慮してください。`
163-
);
164-
}
165-
prompt.push(``);
166-
prompt.push(
167-
`質問への回答はユーザー向けのメッセージに加えて、ドキュメント自体を改訂するという形でも可能です。`
168-
);
169-
prompt.push(
170-
`質問内容とドキュメントの内容の関連性が深く、比較的長めの解説をしたい場合、またはドキュメントへの補足がしたい場合は、そちらの形式での回答を検討してください。`
171-
);
172-
prompt.push(``);
173-
prompt.push(`# ドキュメント`);
174-
prompt.push(``);
175-
for (const section of targetSectionContent) {
176-
prompt.push(`[セクションid: ${section.id}]`);
177-
prompt.push(section.replacedContent.trim());
178-
prompt.push(``);
179-
}
180-
prompt.push(``);
181-
}
182-
183-
if (Object.keys(replOutputs).length > 0) {
184-
prompt.push(
185-
`# ターミナルのログ(ユーザーが入力したコマンドとその実行結果)`
186-
);
187-
prompt.push(``);
188-
if (isSandbox) {
189-
prompt.push("以下はユーザーがREPLで実行したコマンドです。");
190-
} else {
191-
prompt.push(
192-
"以下はドキュメント内で実行例を示した各コードブロックの内容に加えてユーザーが追加で実行したコマンドです。"
193-
);
194-
prompt.push(
195-
"例えば ```python-repl:foo のコードブロックに対してユーザーが実行したログが ターミナル #foo です。"
196-
);
197-
}
198-
prompt.push(``);
199-
for (const [replId, replCommands] of Object.entries(replOutputs)) {
200-
prompt.push(`## ターミナル #${replId}`);
201-
for (const replCmd of replCommands) {
202-
prompt.push(`\n- コマンド: ${replCmd.command}`);
203-
prompt.push("```");
204-
for (const output of replCmd.output) {
205-
prompt.push(output.message);
206-
}
207-
prompt.push("```");
208-
}
209-
prompt.push(``);
210-
}
211-
}
212-
213-
if (Object.keys(files).length > 0) {
214-
prompt.push("# ファイルエディターの内容");
215-
prompt.push(``);
216-
if (isSandbox) {
217-
prompt.push("以下はユーザーが編集したファイルの内容です。");
218-
} else {
219-
prompt.push(
220-
"以下はドキュメント内でファイルの内容を示した各コードブロックの内容に加えてユーザーが編集を加えたものです。"
221-
);
222-
prompt.push(
223-
"例えば ```python:foo.py のコードブロックに対してユーザーが編集した後の内容が ファイル: foo.py です。"
224-
);
225-
}
226-
prompt.push(``);
227-
for (const [filename, content] of Object.entries(files)) {
228-
prompt.push(`## ファイル: ${filename}`);
229-
prompt.push("```");
230-
prompt.push(content);
231-
prompt.push("```");
232-
prompt.push(``);
233-
}
234-
}
235-
236-
if (Object.keys(execResults).length > 0) {
237-
prompt.push("# ファイルの実行結果");
238-
prompt.push(``);
239-
for (const [filename, outputs] of Object.entries(execResults)) {
240-
prompt.push(`## ファイル: ${filename}`);
241-
prompt.push("```");
242-
for (const output of outputs) {
243-
prompt.push(output.message);
244-
}
245-
prompt.push("```");
246-
prompt.push(``);
247-
}
248-
}
249-
250-
prompt.push("# 指示");
251-
prompt.push("");
252-
if (isSandbox) {
253-
prompt.push(`- 1行目に sandbox とのみ出力してください。`);
254-
} else {
255-
prompt.push(
256-
`- 1行目に、ユーザーの質問ともっとも関連性の高いドキュメント内のセクションのidを回答してください。`
257-
);
258-
prompt.push(
259-
" - idのみを出力してください。 セクションid: や括弧や引用符などは不要です。"
260-
);
261-
prompt.push(
262-
" - ユーザーの質問がドキュメントのどのセクションとも直接的に関連しない場合は null と出力してください。"
263-
);
264-
}
265-
prompt.push(
266-
"- 2行目に、この質問と回答を後から参照するためのわかりやすいタイトルをつけて記述してください。"
267-
);
268-
prompt.push(
269-
" - 太字やコードブロックなどのMarkdownの記法は使わずテキストのみで出力してください。"
270-
);
271-
prompt.push(
272-
"- 3行目以降に、ユーザーに伝える回答をMarkdown形式で記述してください。"
273-
);
274-
prompt.push(
275-
" - ユーザーが入力したターミナルのコマンドやファイルの内容、実行結果を参考にして回答してください。"
276-
);
277-
prompt.push(" - 必要であれば、具体的なコード例を提示してください。");
278-
prompt.push(
279-
" - 回答内でコードブロックを使用する際は ```言語名 としてください。" +
280-
"ドキュメント内では ```言語名-repl や ```言語名:ファイル名 、 ```言語名-exec:ファイル名 などの特殊なコードブロックが登場しますが、ユーザーへの回答ではこれらの記法は使用しないでください。"
281-
);
282-
283-
if (!isSandbox) {
284-
prompt.push("- ドキュメントの一部を改訂したい場合はその差分を");
285-
prompt.push("<<<<<<< SEARCH");
286-
prompt.push("修正したい元の文章の塊(一字一句違わずに)");
287-
prompt.push("=======");
288-
prompt.push("修正後の新しい文章の塊");
289-
prompt.push(">>>>>>> REPLACE");
290-
prompt.push("の形式で出力してください。");
291-
prompt.push(
292-
" - 複数箇所改訂したい場合は上の形式の出力を複数回繰り返してください。"
293-
);
294-
prompt.push(
295-
" - ドキュメントにテキストを追加したい場合は追加したい箇所の前後のテキストを含めて出力してください。"
296-
);
297-
prompt.push(
298-
" - セクションid、セクション見出しを編集、追加、削除することはできません。"
299-
);
300-
prompt.push(
301-
" - ドキュメント内の特殊なコードブロック(```言語名-repl , ```言語名:ファイル名 , ```言語名-exec:ファイル名 )は編集、追加、削除することはできません。それ以外の文章のみを編集してください。" +
302-
"ただし通常のコードブロック(```言語名 )の追加は可能です。"
303-
);
304-
prompt.push(
305-
" - 改訂後のドキュメントと同じ内容はユーザーに伝える回答としては省略できます。(「修正後のドキュメントを参照してください。」など)"
306-
);
307-
}
118+
const prompt = await buildChatPrompt({
119+
path: targetPath,
120+
targetSectionContent,
121+
replOutputs,
122+
files,
123+
execResults,
124+
langName,
125+
});
308126

309127
console.log(prompt);
310128

@@ -421,23 +239,10 @@ export async function POST(request: NextRequest) {
421239
}
422240

423241
// Parse diffs from the full body content
424-
const diffRegex =
425-
/<{3,}\s*SEARCH\n*([\s\S]*?)\n*={3,}\n*([\s\S]*?)\n*>{3,}\s*REPLACE/g;
426-
const diffRaw: CreateChatDiff[] = [];
427-
for (const m of contentAfterHeader.matchAll(diffRegex)) {
428-
const search = m[1];
429-
const replace = m[2];
430-
const targetSection = targetSectionContent.find((s) =>
431-
s.replacedContent.includes(search)
432-
);
433-
diffRaw.push({
434-
search,
435-
replace,
436-
sectionId: targetSection?.id ?? ("" as SectionId),
437-
targetMD5: targetSection?.md5 ?? "",
438-
});
439-
}
440-
const cleanMessage = contentAfterHeader.replace(diffRegex, "").trim();
242+
const { diffRaw, cleanMessage } = parseDiffsAndCleanMessage(
243+
contentAfterHeader,
244+
targetSectionContent
245+
);
441246

442247
// Save messages and diffs to DB
443248
await addMessagesAndDiffs(

0 commit comments

Comments
 (0)