diff --git a/packages/core/package.json b/packages/core/package.json index be2b5e3f3..17da97fce 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -85,7 +85,8 @@ "@latitude-data/mailers": "workspace:^", "@sindresorhus/slugify": "^2.2.1", "@t3-oss/env-core": "^0.11.1", - "ai": "^3.2.42", + "ai": "^3.3.3", + "json-schema": "^0.4.0", "argon2": "^0.41.0", "csv-parse": "^5.5.6", "drizzle-orm": "^0.33.0", diff --git a/packages/core/src/services/ai/index.ts b/packages/core/src/services/ai/index.ts index ed2627ddb..68bfd3546 100644 --- a/packages/core/src/services/ai/index.ts +++ b/packages/core/src/services/ai/index.ts @@ -9,6 +9,8 @@ import { CompletionTokenUsage, CoreMessage, FinishReason, + jsonSchema, + streamObject, streamText, } from 'ai' import { v4 } from 'uuid' @@ -17,6 +19,7 @@ import { z } from 'zod' import { LogSources, ProviderApiKey, Providers } from '../../browser' import { publisher } from '../../events/publisher' import { CreateProviderLogProps } from '../providerLogs/create' +import { JSONSchema7 } from 'json-schema' export type FinishCallbackEvent = { finishReason: FinishReason @@ -38,14 +41,17 @@ export type FinishCallbackEvent = { } export type FinishCallback = (event: FinishCallbackEvent) => void -export type Config = { +export type GenerationConfig = { [key: string]: any - provider: string model: string + schema?: JSONSchema7 azure?: { resourceName: string } } -export type PartialConfig = Omit + +export type Config = GenerationConfig & { + provider: string +} const GROQ_API_URL = 'https://api.groq.com/openai/v1' @@ -56,7 +62,7 @@ function createProvider({ }: { provider: Providers apiKey: string - config?: PartialConfig + config?: GenerationConfig }) { switch (provider) { case Providers.OpenAI: @@ -100,12 +106,12 @@ export async function ai( provider: apiProvider, prompt, messages, - config, + config: _config, documentLogUuid, source, }: { provider: ProviderApiKey - config: PartialConfig + config: GenerationConfig messages: Message[] documentLogUuid?: string prompt?: string @@ -126,12 +132,22 @@ export async function ai( } = apiProvider const model = config.model const m = createProvider({ provider, apiKey, config })(model) + const { provider, token: apiKey, id: providerId } = apiProvider + + const config = { + ..._config, + schema: _config.schema ? jsonSchema(_config.schema) : undefined, + structuredOutputs: _config.schema !== undefined, + } as GenerationConfig + + const model = createProvider({ provider, apiKey, config })(config.model) - const result = await streamText({ - model: m, + const props = { + ...config, + model, prompt, messages: messages as CoreMessage[], - onFinish: (event) => { + onFinish: (event: FinishCallbackEvent) => { publisher.publish({ type: 'aiProviderCallCompleted', data: { @@ -141,7 +157,7 @@ export async function ai( documentLogUuid, providerId, providerType, - model, + model: config.model, config, messages, responseText: event.text, @@ -159,11 +175,13 @@ export async function ai( }, }) + const result = config.structuredOutputs ? await streamObject(props) : await streamText(props) + return { fullStream: result.fullStream, - text: result.text, + text: config.structuredOutputs ? result.object : result.text, usage: result.usage, - toolCalls: result.toolCalls, + toolCalls: config.structuredOutputs ? [] : result.toolCalls, } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4fe8c6e88..b55a2058e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -517,6 +517,9 @@ importers: '@latitude-data/mailers': specifier: workspace:^ version: link:../mailers + json-schema: + specifier: ^0.4.0 + version: 0.4.0 devDependencies: '@ai-sdk/anthropic': specifier: ^0.0.48 @@ -2059,8 +2062,8 @@ packages: '@floating-ui/react-dom@2.1.1': resolution: {integrity: sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==} peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' + react: '>=16.8.0 || >=18.x' + react-dom: '>=16.8.0 || >=18.x' '@floating-ui/utils@0.2.7': resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==} @@ -2283,9 +2286,6 @@ packages: peerDependencies: drizzle-orm: '>= 0.29 <1' lucia: 3.x - peerDependenciesMeta: - drizzle-orm: - optional: true '@lukeed/ms@2.0.2': resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} @@ -2306,8 +2306,8 @@ packages: resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==} peerDependencies: monaco-editor: '>= 0.25.0 < 1' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=18.x + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=18.x '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} @@ -3036,8 +3036,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3049,8 +3049,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3070,7 +3070,7 @@ packages: resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3127,7 +3127,7 @@ packages: resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3137,8 +3137,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3154,7 +3154,7 @@ packages: resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3203,8 +3203,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3216,8 +3216,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3229,8 +3229,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3324,7 +3324,7 @@ packages: resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3379,8 +3379,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x peerDependenciesMeta: '@types/react': optional: true @@ -3423,7 +3423,7 @@ packages: resolution: {integrity: sha512-RcBoffx2IZG6quLBXo5sj3fF47rKmmkiMhG1ZBua4nFjHYlmW8j1uUMyO5HNglxIF9E52NYq4sF7XeZRp9jYjg==} engines: {node: '>=18.0.0'} peerDependencies: - react: ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.0 || ^19.0 || ^19.0.0-rc || >=18.x '@react-email/container@0.0.14': resolution: {integrity: sha512-NgoaJJd9tTtsrveL86Ocr/AYLkGyN3prdXKd/zm5fQpfDhy/NXezyT3iF6VlwAOEUIu64ErHpAJd+P6ygR+vjg==} @@ -10470,9 +10470,8 @@ snapshots: '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.8)(@types/react@18.3.0)(pg@8.12.0)(react@19.0.0-rc-f994737d14-20240522))(lucia@3.2.0)': dependencies: - lucia: 3.2.0 - optionalDependencies: drizzle-orm: 0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.8)(@types/react@18.3.0)(pg@8.12.0)(react@19.0.0-rc-f994737d14-20240522) + lucia: 3.2.0 '@lukeed/ms@2.0.2': {}