Skip to content

Commit

Permalink
fix: bugs (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
glucrypto authored Nov 13, 2024
1 parent 0620f14 commit 1b9d035
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 46 deletions.
5 changes: 5 additions & 0 deletions .changeset/tough-dolls-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fatduckai/ai": patch
---

fixed empty content bug
58 changes: 30 additions & 28 deletions packages/ai/src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class PromptBuilder {

constructor(template: string, options: PromptBuilderOptions = {}) {
this.template = template;
this.linter = new Linter(this.context);
this.linter = new Linter();

this.options = {
validateOnBuild: true,
Expand All @@ -27,16 +27,23 @@ export class PromptBuilder {
}

withContext(context: Record<string, any>): this {
Object.entries(context).forEach(([key, value]) => {
if (value === undefined || value === null) {
throw new Error(
`Context value for "${key}" cannot be undefined or null`
);
}
});
// Don't validate values if allowEmptyContent is true
if (!this.options.allowEmptyContent) {
Object.entries(context).forEach(([key, value]) => {
if (
value === undefined ||
value === null ||
value.toString().trim() === ""
) {
throw new Error(
`Empty content not allowed for "${key}". Set allowEmptyContent to true to allow empty, null, or undefined values.`
);
}
});
}

this.context = { ...this.context, ...context };
this.linter = new Linter(this.context); // Update linter with new context
this.linter = new Linter(this.context, this.options.allowEmptyContent);
return this;
}

Expand Down Expand Up @@ -69,11 +76,13 @@ export class PromptBuilder {

build(): ChatMessage[] {
try {
// Still validate but don't throw
if (this.options.validateOnBuild) {
const validation = this.validate();

// Only throw if throwOnWarnings is true and we have warnings
if (!validation.isValid) {
throw new Error(
`Template validation failed:\n${validation.errors.join("\n")}`
);
}
if (this.options.throwOnWarnings && validation.warnings.length > 0) {
throw new Error(
`Template has warnings:\n${validation.warnings.join("\n")}`
Expand All @@ -93,10 +102,12 @@ export class PromptBuilder {
.map((block) => {
let content = block.content;

// Replace variables that exist in context, leave others as is
// Replace variables, converting undefined/null to empty string if allowEmptyContent is true
Object.entries(this.context).forEach(([key, value]) => {
const regex = new RegExp(`<${key}>`, "g");
content = content.replace(regex, String(value));
const replacement =
value === undefined || value === null ? "" : String(value);
content = content.replace(regex, replacement);
});

if (!content.trim() && !this.options.allowEmptyContent) {
Expand All @@ -117,20 +128,11 @@ export class PromptBuilder {
return message;
});
} catch (error) {
// Instead of throwing, return the blocks with unreplaced variables
const parsed = Parser.parse(this.template);
return parsed.blocks
.filter(
(block): block is Block =>
block.type === "system" ||
block.type === "user" ||
block.type === "assistant"
)
.map((block) => ({
role: block.type,
content: block.content.trim(),
...(block.name ? { name: block.name } : {}),
}));
throw new Error(
`Failed to build prompt: ${
error instanceof Error ? error.message : String(error)
}`
);
}
}
}
4 changes: 2 additions & 2 deletions packages/ai/src/examples/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ What are the key trends?</user>`;
.join("\n");
console.log("before");
const builder = new PromptBuilder(template, {
allowEmptyContent: true,
}).withContext({ data: formattedData });
allowEmptyContent: false,
}).withContext({ data: null });
const validation = await builder.validate();
console.log("validation", validation);
const messages = await builder.build();
Expand Down
45 changes: 29 additions & 16 deletions packages/ai/src/linter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { LintResult, ParsedTemplate } from "./types";
export class Linter {
constructor(private context: Record<string, any> = {}) {}
constructor(
private context: Record<string, any> = {},
private allowEmptyContent: boolean = false
) {}

lint(template: ParsedTemplate): LintResult[] {
const results: LintResult[] = [];
Expand All @@ -18,21 +21,31 @@ export class Linter {
});
}

// Check for undefined variables by comparing against context
const missingVariables = template.variables.filter(
(variable) => !(variable in this.context)
);

if (missingVariables.length > 0) {
results.push({
message: `Required variables not provided: ${missingVariables.join(
", "
)}`,
severity: "error",
line: 1,
column: 1,
});
}
// Check variables
template.variables.forEach((variable) => {
if (!(variable in this.context)) {
results.push({
message: `Required variable not provided: ${variable}`,
severity: "error",
line: this.findVariableLocation(variable, template.raw),
column: 1,
});
} else if (!this.allowEmptyContent) {
const value = this.context[variable];
if (
value === undefined ||
value === null ||
value.toString().trim() === ""
) {
results.push({
message: `Empty content not allowed for variable: ${variable}`,
severity: "error",
line: this.findVariableLocation(variable, template.raw),
column: 1,
});
}
}
});

return results;
}
Expand Down

0 comments on commit 1b9d035

Please sign in to comment.