Skip to content

Commit 88a6660

Browse files
committed
refactor(llm): remove content enhancement rules and update related modules
- Deleted the content enhancement rules module and its associated logic from the LLM text generation process. - Updated the getLLMText function to remove enhancement rule application. - Refactored normalizeLLMBody and related tests to support asynchronous processing. - Adjusted route handlers to ensure proper content type headers for markdown responses.
1 parent 83fb01c commit 88a6660

File tree

16 files changed

+319
-186
lines changed

16 files changed

+319
-186
lines changed

docs/app/_llms/content-enhancement-rules.ts

Lines changed: 0 additions & 87 deletions
This file was deleted.

docs/app/_llms/get-llm-text.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,22 @@
11
import type { LLMPage, Section } from "./types";
22
import { getGitHubSourceUrl } from "./config";
33
import { normalizeLLMBody } from "./normalize-llm-body";
4-
import { CONTENT_ENHANCEMENT_RULES } from "./content-enhancement-rules";
54

65
export async function getLLMText(page: LLMPage, section: Section): Promise<string> {
7-
const processed = normalizeLLMBody(await page.data.getText("processed"));
6+
const processed = await normalizeLLMBody(await page.data.getText("processed"));
87
const sourceUrl = getGitHubSourceUrl(section, page.path);
98

10-
let content = `# ${page.data.title}
11-
9+
return `# ${page.data.title}
1210
URL: ${page.url}
1311
Source: ${sourceUrl}
1412
1513
${page.data.description ?? ""}
1614
1715
${processed}`;
18-
19-
// Apply content enhancement rules
20-
for (const rule of CONTENT_ENHANCEMENT_RULES) {
21-
if (rule.shouldApply(page, section)) {
22-
console.log(`[getLLMText] Applying enhancement rule: ${rule.name}`);
23-
content = await rule.enhance(content, page);
24-
}
25-
}
26-
27-
// Normalize the final content after enhancements
28-
return normalizeLLMBody(content);
2916
}
3017

3118
export async function getLLMTextForFullCompilation(page: LLMPage): Promise<string> {
32-
const processed = normalizeLLMBody(await page.data.getText("processed"));
19+
const processed = await normalizeLLMBody(await page.data.getText("processed"));
3320

3421
return `file: ${page.path}
3522

docs/app/_llms/normalize-llm-body.pipeline.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import { normalizeLLMBody } from "./normalize-llm-body";
33
import { normalizeForAssert, readFixture } from "./test-utils";
44

55
describe("normalizeLLMBody pipeline", () => {
6-
it("applies ComponentExample and CodeBlockTabs rules together", () => {
6+
it("applies ComponentExample and CodeBlockTabs rules together", async () => {
77
const input = readFixture("pipeline", "combined.input.mdx");
88
const expected = readFixture("pipeline", "combined.output.mdx");
99

10-
const actual = normalizeLLMBody(input);
10+
const actual = await normalizeLLMBody(input);
1111

1212
expect(normalizeForAssert(actual)).toBe(normalizeForAssert(expected));
1313
});

docs/app/_llms/normalize-llm-body.ts

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,64 @@ function hasChildren(node: RootContent): node is RootContent & { children: RootC
5454
return "children" in node && Array.isArray(node.children);
5555
}
5656

57-
function transformNodes(nodes: RootContent[], rules: Rule[], context: RuleContext): RootContent[] {
57+
/*
58+
비동기 rule의 결과를 트리 전체에서 미리 수집합니다.
59+
재귀 async 호출을 하나의 await로 줄여 Next.js AsyncLocalStorage Map 폭발을 방지합니다. (Map maximum size exceeded 에러)
60+
*/
61+
async function collectAsyncResults(
62+
nodes: RootContent[],
63+
rules: Rule[],
64+
context: RuleContext,
65+
cache: Map<MdxJsxFlowElement, RootContent[]>,
66+
): Promise<void> {
67+
const promises: Promise<void>[] = [];
68+
69+
function walk(nodes: RootContent[]): void {
70+
for (const node of nodes) {
71+
if (isMdxJsxFlowElement(node)) {
72+
const matchedRule = rules.find((rule) => rule.match(node));
73+
if (matchedRule) {
74+
const result = matchedRule.transform(node, context);
75+
if (result instanceof Promise) {
76+
promises.push(
77+
result
78+
.then((r) => cache.set(node, r))
79+
.catch(() => cache.set(node, [node]))
80+
.then(() => {}),
81+
);
82+
} else {
83+
cache.set(node, result);
84+
}
85+
continue;
86+
}
87+
}
88+
if (hasChildren(node)) walk(node.children);
89+
}
90+
}
91+
92+
walk(nodes);
93+
await Promise.all(promises);
94+
}
95+
96+
/*
97+
async 결과가 캐시된 상태에서 동기적으로 트리를 변환합니다.
98+
*/
99+
function transformNodesSync(
100+
nodes: RootContent[],
101+
rules: Rule[],
102+
context: RuleContext,
103+
cache: Map<MdxJsxFlowElement, RootContent[]>,
104+
): RootContent[] {
58105
const transformed: RootContent[] = [];
59106

60107
for (const node of nodes) {
61108
if (isMdxJsxFlowElement(node)) {
62109
const matchedRule = rules.find((rule) => rule.match(node));
63110
if (matchedRule) {
64111
try {
65-
const nextNodes = matchedRule.transform(node, context);
66-
transformed.push(...transformNodes(nextNodes, rules, context));
112+
const cachedOrResult = cache.get(node) ?? matchedRule.transform(node, context);
113+
const nextNodes = cachedOrResult instanceof Promise ? [node] : cachedOrResult;
114+
transformed.push(...transformNodesSync(nextNodes, rules, context, cache));
67115
} catch {
68116
transformed.push(node);
69117
}
@@ -74,7 +122,7 @@ function transformNodes(nodes: RootContent[], rules: Rule[], context: RuleContex
74122
if (hasChildren(node)) {
75123
transformed.push({
76124
...node,
77-
children: transformNodes(node.children, rules, context),
125+
children: transformNodesSync(node.children, rules, context, cache),
78126
} as RootContent);
79127
continue;
80128
}
@@ -85,7 +133,10 @@ function transformNodes(nodes: RootContent[], rules: Rule[], context: RuleContex
85133
return transformed;
86134
}
87135

88-
export function normalizeLLMBodyWithRules(content: string | undefined, rules: Rule[]): string {
136+
export async function normalizeLLMBodyWithRules(
137+
content: string | undefined,
138+
rules: Rule[],
139+
): Promise<string> {
89140
if (!content) return "";
90141

91142
const context: RuleContext = {
@@ -94,14 +145,20 @@ export function normalizeLLMBodyWithRules(content: string | undefined, rules: Ru
94145
};
95146

96147
const tree = processor.parse(content) as Root;
97-
tree.children = transformNodes(tree.children as RootContent[], rules, context);
148+
149+
// 1단계: async rule 결과를 한 번의 await로 모두 수집
150+
const cache = new Map<MdxJsxFlowElement, RootContent[]>();
151+
await collectAsyncResults(tree.children as RootContent[], rules, context, cache);
152+
153+
// 2단계: 캐시된 결과로 동기 변환
154+
tree.children = transformNodesSync(tree.children as RootContent[], rules, context, cache);
98155

99156
return processor
100157
.stringify(tree)
101158
.replace(/\n{3,}/g, "\n\n")
102159
.trim();
103160
}
104161

105-
export function normalizeLLMBody(content?: string): string {
162+
export async function normalizeLLMBody(content?: string): Promise<string> {
106163
return normalizeLLMBodyWithRules(content, activeRules);
107164
}

docs/app/_llms/rules/codeblock-tabs-rule.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,29 @@ import { normalizeForAssert, readFixture } from "../test-utils";
44
import { codeBlockTabsRule } from "./codeblock-tabs-rule";
55

66
describe("codeBlockTabsRule", () => {
7-
it("converts known package managers to sorted markdown list", () => {
7+
it("converts known package managers to sorted markdown list", async () => {
88
const input = readFixture("codeblock-tabs", "basic.input.mdx");
99
const expected = readFixture("codeblock-tabs", "basic.output.mdx");
1010

11-
const actual = normalizeLLMBodyWithRules(input, [codeBlockTabsRule]);
11+
const actual = await normalizeLLMBodyWithRules(input, [codeBlockTabsRule]);
1212

1313
expect(normalizeForAssert(actual)).toBe(normalizeForAssert(expected));
1414
});
1515

16-
it("sorts known managers first and appends unknown managers", () => {
16+
it("sorts known managers first and appends unknown managers", async () => {
1717
const input = readFixture("codeblock-tabs", "partial.input.mdx");
1818
const expected = readFixture("codeblock-tabs", "partial.output.mdx");
1919

20-
const actual = normalizeLLMBodyWithRules(input, [codeBlockTabsRule]);
20+
const actual = await normalizeLLMBodyWithRules(input, [codeBlockTabsRule]);
2121

2222
expect(normalizeForAssert(actual)).toBe(normalizeForAssert(expected));
2323
});
2424

25-
it("keeps the original node when tabs are malformed", () => {
25+
it("keeps the original node when tabs are malformed", async () => {
2626
const input = readFixture("codeblock-tabs", "malformed.input.mdx");
2727
const expected = readFixture("codeblock-tabs", "malformed.output.mdx");
2828

29-
const actual = normalizeLLMBodyWithRules(input, [codeBlockTabsRule]);
29+
const actual = await normalizeLLMBodyWithRules(input, [codeBlockTabsRule]);
3030

3131
expect(normalizeForAssert(actual)).toBe(normalizeForAssert(expected));
3232
});

docs/app/_llms/rules/component-example-rule.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ import { normalizeForAssert, readFixture } from "../test-utils";
44
import { componentExampleRule } from "./component-example-rule";
55

66
describe("componentExampleRule", () => {
7-
it("adds preview heading when name ends with /preview", () => {
7+
it("adds preview heading when name ends with /preview", async () => {
88
const input = readFixture("component-example", "component-example.input.mdx");
99
const expected = readFixture("component-example", "component-example.output.mdx");
1010

11-
const actual = normalizeLLMBodyWithRules(input, [componentExampleRule]);
11+
const actual = await normalizeLLMBodyWithRules(input, [componentExampleRule]);
1212

1313
expect(normalizeForAssert(actual)).toBe(normalizeForAssert(expected));
1414
});
1515

16-
it("unwraps non-preview examples without adding a heading", () => {
16+
it("unwraps non-preview examples without adding a heading", async () => {
1717
const input = readFixture("component-example", "non-preview.input.mdx");
1818
const expected = readFixture("component-example", "non-preview.output.mdx");
1919

20-
const actual = normalizeLLMBodyWithRules(input, [componentExampleRule]);
20+
const actual = await normalizeLLMBodyWithRules(input, [componentExampleRule]);
2121

2222
expect(normalizeForAssert(actual)).toBe(normalizeForAssert(expected));
2323
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export interface EstreeNode {
2+
type: string;
3+
}
4+
5+
export interface ProgramNode extends EstreeNode {
6+
type: "Program";
7+
body: EstreeNode[];
8+
}
9+
10+
export interface ExpressionStatementNode extends EstreeNode {
11+
type: "ExpressionStatement";
12+
expression: EstreeNode;
13+
}
14+
15+
export interface ArrayExpressionNode extends EstreeNode {
16+
type: "ArrayExpression";
17+
elements: (EstreeNode | null)[];
18+
}
19+
20+
export interface LiteralNode extends EstreeNode {
21+
type: "Literal";
22+
value: unknown;
23+
}
24+
25+
export function isProgramNode(node: unknown): node is ProgramNode {
26+
return Boolean(node) && typeof node === "object" && (node as EstreeNode).type === "Program";
27+
}
28+
29+
export function isExpressionStatementNode(node: unknown): node is ExpressionStatementNode {
30+
return (
31+
Boolean(node) &&
32+
typeof node === "object" &&
33+
(node as EstreeNode).type === "ExpressionStatement" &&
34+
"expression" in (node as ExpressionStatementNode)
35+
);
36+
}
37+
38+
export function isLiteralNode(node: unknown): node is LiteralNode {
39+
return Boolean(node) && typeof node === "object" && (node as EstreeNode).type === "Literal";
40+
}
41+
42+
export function isStringLiteral(node: unknown): node is LiteralNode & { value: string } {
43+
return isLiteralNode(node) && typeof (node as LiteralNode).value === "string";
44+
}

0 commit comments

Comments
 (0)