Skip to content

Instantly share code, notes, and snippets.

@CallumVass
Last active May 17, 2024 14:46
Show Gist options
  • Save CallumVass/6753af6b8f8e25f614cd648a15fc5ae3 to your computer and use it in GitHub Desktop.
Save CallumVass/6753af6b8f8e25f614cd648a15fc5ae3 to your computer and use it in GitHub Desktop.
Haiku structured data - My own implementation of instructor for Haiku
import Anthropic from "@anthropic-ai/sdk";
import type { MessageParam } from "@anthropic-ai/sdk/resources";
import dotenv from "dotenv";
import { Instructor } from "./instructor";
import type { z } from "zod";
dotenv.config();
function sendToClaude<T>(message: MessageParam, schema: z.Schema<T>) {
const anthropicClient = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const instructor = new Instructor(anthropicClient);
return instructor.sendMessage(message, schema);
}
export { sendToClaude };
import type Anthropic from "@anthropic-ai/sdk";
import type { MessageParam } from "@anthropic-ai/sdk/resources";
import { assistantMessage, getSystemPrompt } from "./prompt-utils";
import type { ValidationError } from "zod-validation-error";
import { parseResponse } from "./response-utils";
import type { z } from "zod";
import type { ExtendedResponse } from "./types";
class Instructor {
private client: Anthropic;
constructor(client: Anthropic) {
this.client = client;
}
async sendMessage<T>(message: MessageParam, schema: z.Schema<T>) {
return this.sendToClaude(message, schema);
}
private async sendToClaude<T>(
message: MessageParam,
schema: z.Schema<T>,
attempt = 1,
error?: ValidationError,
previousResponse?: string,
): Promise<ExtendedResponse<T>> {
const retries = 5;
if (attempt > retries) {
throw new Error("Reached maximum number of retries");
}
let prompt = getSystemPrompt(schema);
if (error) {
const formattedError = error.toString();
prompt = `${prompt}\n\nThere was an error in the previous try:\n${previousResponse}\n\nCan you try to rectify based on this error:\n${formattedError}`;
}
const response = await this.client.messages.create({
messages: [message].concat({
role: "assistant",
content: assistantMessage,
}),
temperature: 0.0,
model: "claude-3-haiku-20240307",
max_tokens: 4096,
system: prompt,
});
const responseText = response.content[0].text;
const parsedResponse = parseResponse<T>(responseText, schema);
if (!parsedResponse.success) {
attempt += 1;
await new Promise((resolve) => setTimeout(resolve, 300));
return this.sendToClaude(
message,
schema,
attempt,
parsedResponse.error,
responseText,
);
}
return {
data: parsedResponse.data!,
_raw: response,
};
}
}
export { Instructor };
import type { z } from "zod";
import zodToJsonSchema from "zod-to-json-schema";
const assistantMessage =
"I am only going to return the JSON for your request, here is the perfectly correctly formatted and valid JSON with no additional formatting or text around it:";
const getSystemPrompt = <T>(schema: z.Schema<T>) => {
const jsonSchema = zodToJsonSchema(schema, {
errorMessages: true,
});
return `As a genius expert, your task is to understand the content and provide the parsed objects in json that match the following json_schema:\n\`\`\`${JSON.stringify(jsonSchema, null, 2)}\`\`\``;
};
export { assistantMessage, getSystemPrompt };
import type { z } from "zod";
import { ZodError } from "zod";
import { fromZodError, ValidationError } from "zod-validation-error";
function parseResponse<T>(
response: string,
schema: z.ZodSchema<T>,
): { success: boolean; error?: ValidationError; data?: T } {
try {
const openingBracketIndex = response.indexOf("{");
const closingBracketIndex = response.lastIndexOf("}");
const jsonResponse = response.slice(
openingBracketIndex,
closingBracketIndex + 1,
);
const parsed = JSON.parse(jsonResponse);
return { success: true, data: schema.parse(parsed) };
} catch (error) {
if (error instanceof ZodError) {
return {
success: false,
error: fromZodError(error),
};
}
return {
success: false,
error: new ValidationError(
"Unknown error occurred - most likely invalid JSON. Please try again.",
),
};
}
}
export { parseResponse };
import { z } from "zod";
const ReceiptSchema = z
.object({
merchant: z.object({
name: z.string().describe("The name of the merchant."),
address: z
.string()
.optional()
.describe(
"The physical address of the merchant. Usually at the top or bottom of a receipt.",
),
phone: z
.string()
.optional()
.describe(
"The phone number of the merchant. Usually at the top or bottom of a receipt",
),
}),
date: z.coerce.date().optional().describe("The date of the transaction."),
items: z
.array(
z
.object({
name: z.string().describe("The name of the item."),
price: z.number().describe("The price of the item."),
quantity: z.number().describe("The quantity of the item."),
sku: z
.string()
.optional()
.describe("The stock keeping unit of the item."),
})
.describe("An item purchased in the transaction."),
)
.describe("The items purchased in the transaction."),
total: z.number().describe("The total amount of the transaction."),
currency: z
.string()
.optional()
.describe("The ISO currency code of the transaction."),
tax: z.number().optional().describe("The tax amount of the transaction."),
discount: z
.number()
.optional()
.describe("The discount amount of the transaction."),
grandTotal: z
.number()
.optional()
.describe("The grand total amount of the transaction."),
payment: z
.object({
method: z
.string()
.describe("The payment method used in the transaction."),
card: z
.object({
type: z.string().optional().describe("The type of card used."),
})
.optional()
.describe("The card used in the transaction."),
})
.optional()
.describe("The payment information for the transaction."),
})
.describe("A receipt for a transaction.");
export { ReceiptSchema };
const prompt = `
Please generate the JSON representation of the receipt data in the image.
The image is a receipt with fields for the date, time, and items purchased. The form also includes a total amount field.
`;
const message: MessageParam = {
role: "user",
content: [
{
type: "image",
source: {
type: "base64",
media_type: "image/png",
data: image.base64,
},
},
{
type: "text",
text: prompt,
},
],
};
const response = await sendToClaude(message, ReceiptSchema);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment