Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update external client check + google #193

Merged
merged 4 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eleven-needles-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@instructor-ai/instructor": minor
---

update peer deps + remove baseUrl check on generic client type guard
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
nodejs 20.9.0
bun 1.0.15
bun 1.1.43
python 3.12.1
Binary file modified bun.lockb
Binary file not shown.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"zod": ">=3.23.8"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.29.2",
"@anthropic-ai/sdk": "0.33.1",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.1",
"@ianvs/prettier-plugin-sort-imports": "4.1.0",
Expand All @@ -75,8 +75,8 @@
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-prettier": "^5.1.2",
"husky": "^8.0.3",
"llm-polyglot": "2.2.0",
"openai": "4.68.1",
"llm-polyglot": "2.5.0",
"openai": "4.78.1",
"prettier": "latest",
"ts-inference-check": "^0.3.0",
"tsup": "^8.0.1",
Expand Down
1 change: 0 additions & 1 deletion src/instructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,6 @@ function isGenericClient(client: any): client is GenericClient {
return (
typeof client === "object" &&
client !== null &&
"baseURL" in client &&
"chat" in client &&
typeof client.chat === "object" &&
"completions" in client.chat &&
Expand Down
295 changes: 295 additions & 0 deletions tests/google.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
import Instructor from "@/index"
import { omit } from "@/lib"
import { describe, expect, test } from "bun:test"
import { createLLMClient } from "llm-polyglot"
import z from "zod"

const googleClient = createLLMClient({
provider: "google",
apiKey: process.env.GEMINI_API_KEY
})

describe("LLMClient google Provider - mode: TOOLS", () => {
const instructor = Instructor({
client: googleClient,
mode: "TOOLS"
})

test("basic completion", async () => {
const completion = await instructor.chat.completions.create({
model: "gemini-1.5-flash-latest",
max_tokens: 1000,
messages: [
{
role: "user",
content: "My name is Dimitri Kennedy."
}
],
response_model: {
name: "extract_name",
schema: z.object({
name: z.string()
})
}
})

expect(omit(["_meta"], completion)).toEqual({ name: "Dimitri Kennedy" })
})

test("complex schema", async () => {
const completion = await instructor.chat.completions.create({
model: "gemini-1.5-flash-latest",
max_tokens: 1000,
messages: [
{
role: "user",
content: `User Data Submission:

First Name: John
Last Name: Doe
Contact Details:
Email: [email protected]
Phone Number: 555-1234
Job History:

Company Name: Acme Corp
Role: Software Engineer
Years: 5
Company Name: Globex Inc.
Role: Lead Developer
Years: 3
Skills:

Programming
Leadership
Communication
`
}
],
response_model: {
name: "process_user_data",
schema: z.object({
story: z
.string()
.describe("A long and mostly made up story about the user - minimum 500 words"),
userDetails: z.object({
firstName: z.string(),
lastName: z.string(),
contactDetails: z.object({
email: z.string(),
phoneNumber: z.string().optional()
})
}),
jobHistory: z.array(
z.object({
companyName: z.string(),
role: z.string(),
years: z.number().optional()
})
),
skills: z.array(z.string())
})
}
})

expect(omit(["_meta", "story"], completion)).toEqual({
userDetails: {
firstName: "John",
lastName: "Doe",
contactDetails: {
email: "[email protected]",
phoneNumber: "555-1234"
}
},
jobHistory: [
{
companyName: "Acme Corp",
role: "Software Engineer",
years: 5
},
{
companyName: "Globex Inc.",
role: "Lead Developer",
years: 3
}
],
skills: ["Programming", "Leadership", "Communication"]
})
})
})

describe("LLMClient google Provider - mode: TOOLS - stream", () => {
const instructor = Instructor({
client: googleClient,
mode: "TOOLS"
})

test("basic completion", async () => {
const completion = await instructor.chat.completions.create({
model: "gemini-1.5-flash-latest",
stream: true,
max_tokens: 1000,
messages: [
{
role: "user",
content: "My name is Dimitri Kennedy."
}
],
response_model: {
name: "extract_name",
schema: z.object({
name: z.string()
})
}
})

let final = {}

for await (const result of completion) {
final = result
}

//@ts-expect-error ignore for testing
expect(omit(["_meta"], final)).toEqual({ name: "Dimitri Kennedy" })
})

test("complex schema", async () => {
const completion = await instructor.chat.completions.create({
model: "gemini-1.5-flash-latest",
max_tokens: 1000,
stream: true,
messages: [
{
role: "user",
content: `User Data Submission:

First Name: John
Last Name: Doe
Contact Details:
Email: [email protected]
Phone Number: 555-1234
Job History:

Company Name: Acme Corp
Role: Software Engineer
Years: 5
Company Name: Globex Inc.
Role: Lead Developer
Years: 3
Skills:

Programming
Leadership
Communication
`
}
],
response_model: {
name: "process_user_data",
schema: z.object({
story: z
.string()
.describe("A long and mostly made up story about the user - minimum 500 words"),
userDetails: z.object({
firstName: z.string(),
lastName: z.string(),
contactDetails: z.object({
email: z.string(),
phoneNumber: z.string().optional()
})
}),
jobHistory: z.array(
z.object({
companyName: z.string(),
role: z.string(),
years: z.number().optional()
})
),
skills: z.array(z.string())
})
}
})

let final = {}

for await (const result of completion) {
final = result
}

//@ts-expect-error ignore for testing
expect(omit(["_meta", "story"], final)).toEqual({
userDetails: {
firstName: "John",
lastName: "Doe",
contactDetails: {
email: "[email protected]",
phoneNumber: "555-1234"
}
},
jobHistory: [
{
companyName: "Acme Corp",
role: "Software Engineer",
years: 5
},
{
companyName: "Globex Inc.",
role: "Lead Developer",
years: 3
}
],
skills: ["Programming", "Leadership", "Communication"]
})
})
})

describe("LLMClient google Provider - Grounding", () => {
test("grounding with structured output", async () => {
const completion = await googleClient.chat.completions.create({
model: "gemini-1.5-flash-latest",
messages: [
{
role: "user",
content:
"You are a food critic. Review the most popular Italian restaurants in Boston's North End, focusing on their traditional dishes."
}
],
groundingThreshold: 0.7,
max_tokens: 1000,
stream: false
})

expect(completion.choices?.[0]?.message?.content).toBeTruthy()
})

test("grounding with different thresholds", async () => {
const highThreshold = await googleClient.chat.completions.create({
model: "gemini-1.5-flash-latest",
messages: [
{
role: "user",
content: "What are the current top-rated Italian restaurants in Boston's North End?"
}
],
groundingThreshold: 0.9,
max_tokens: 1000
})

// Lower threshold for more general responses
const lowThreshold = await googleClient.chat.completions.create({
model: "gemini-1.5-flash-latest",
messages: [
{
role: "user",
content: "What are the current top-rated Italian restaurants in Boston's North End?"
}
],
groundingThreshold: 0.5,
max_tokens: 1000
})

expect(highThreshold.choices?.[0]?.message?.content).not.toBe(
lowThreshold.choices?.[0]?.message?.content
)
})
})
Loading