-
-
Save ichim-david/1d5e58ade7d6881d4fd3d51e6efe5428 to your computer and use it in GitHub Desktop.
Haiku structured data - My own implementation of instructor for Haiku
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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