From 14d11eb3cfbff0946f7e2c879f6f31ff112cece9 Mon Sep 17 00:00:00 2001 From: Dominic Chapman Date: Fri, 12 Dec 2025 17:41:49 +0000 Subject: [PATCH 1/2] feat: introduce review assistant --- .gitignore | 2 + examples/review-assistant/.env.example | 4 + examples/review-assistant/axiom.config.ts | 16 + examples/review-assistant/package.json | 24 ++ examples/review-assistant/src/app-scope.ts | 20 ++ .../src/capabilities/assistant.eval.ts | 76 ++++ .../src/capabilities/assistant.ts | 44 +++ .../src/collections/reviews.ts | 340 ++++++++++++++++++ examples/review-assistant/src/openai.ts | 6 + examples/review-assistant/src/schemas.ts | 32 ++ examples/review-assistant/src/server.ts | 21 ++ examples/review-assistant/tsconfig.json | 35 ++ pnpm-lock.yaml | 212 +++++++---- 13 files changed, 762 insertions(+), 70 deletions(-) create mode 100644 examples/review-assistant/.env.example create mode 100644 examples/review-assistant/axiom.config.ts create mode 100644 examples/review-assistant/package.json create mode 100644 examples/review-assistant/src/app-scope.ts create mode 100644 examples/review-assistant/src/capabilities/assistant.eval.ts create mode 100644 examples/review-assistant/src/capabilities/assistant.ts create mode 100644 examples/review-assistant/src/collections/reviews.ts create mode 100644 examples/review-assistant/src/openai.ts create mode 100644 examples/review-assistant/src/schemas.ts create mode 100644 examples/review-assistant/src/server.ts create mode 100644 examples/review-assistant/tsconfig.json diff --git a/.gitignore b/.gitignore index ec00b632..610eb4d9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ node_modules/ .DS_Store axiom-*.tgz coverage +.next +next-env.d.ts \ No newline at end of file diff --git a/examples/review-assistant/.env.example b/examples/review-assistant/.env.example new file mode 100644 index 00000000..07cb57b8 --- /dev/null +++ b/examples/review-assistant/.env.example @@ -0,0 +1,4 @@ +AXIOM_URL="https://api.axiom.co" +AXIOM_TOKEN="xaat-******" +AXIOM_EVALS_DATASET="product-evals-prod" +OPENAI_API_KEY="sk-******" \ No newline at end of file diff --git a/examples/review-assistant/axiom.config.ts b/examples/review-assistant/axiom.config.ts new file mode 100644 index 00000000..6a2cf95e --- /dev/null +++ b/examples/review-assistant/axiom.config.ts @@ -0,0 +1,16 @@ +import 'dotenv/config'; +import { defineConfig } from 'axiom/ai/config'; +import { flagSchema } from './src/app-scope'; + +export default defineConfig({ + eval: { + include: ['**/*.eval.{ts,js,mts,mjs,cts,cjs}'], + exclude: [], + dataset: process.env.AXIOM_EVALS_DATASET, + url: process.env.AXIOM_URL, + token: process.env.AXIOM_TOKEN, + orgId: process.env.AXIOM_ORG_ID, + flagSchema, + timeoutMs: 60_000, + }, +}); diff --git a/examples/review-assistant/package.json b/examples/review-assistant/package.json new file mode 100644 index 00000000..924b008a --- /dev/null +++ b/examples/review-assistant/package.json @@ -0,0 +1,24 @@ +{ + "name": "review-analysis", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "eval": "axiom eval", + "server": "tsx --watch src/server.ts" + }, + "dependencies": { + "@ai-sdk/openai": "^2.0.72", + "@hono/node-server": "^1.19.6", + "ai": "^5.0.102", + "axiom": "workspace:*", + "dotenv": "^16.4.5", + "hono": "^4.10.6", + "tsx": "^4.20.4", + "zod": "catalog:" + }, + "devDependencies": { + "@types/node": "^22", + "typescript": "^5" + } +} diff --git a/examples/review-assistant/src/app-scope.ts b/examples/review-assistant/src/app-scope.ts new file mode 100644 index 00000000..c23f8f0a --- /dev/null +++ b/examples/review-assistant/src/app-scope.ts @@ -0,0 +1,20 @@ +import { openai } from '@ai-sdk/openai'; +import { createAppScope } from 'axiom/ai'; +import { z } from 'zod'; + +type OpenAIModelId = Parameters[0]; + +export const flagSchema = z.object({ + reviewAssistant: z.object({ + sentiment: z.object({ + modelId: z.custom().default('gpt-4o-mini'), + }), + summarize: z.object({ + modelId: z.custom().default('gpt-4o-mini'), + }), + }), +}); + +const { flag, pickFlags } = createAppScope({ flagSchema }); + +export { flag, pickFlags }; diff --git a/examples/review-assistant/src/capabilities/assistant.eval.ts b/examples/review-assistant/src/capabilities/assistant.eval.ts new file mode 100644 index 00000000..c189dc6a --- /dev/null +++ b/examples/review-assistant/src/capabilities/assistant.eval.ts @@ -0,0 +1,76 @@ +import { Eval, Scorer } from 'axiom/ai/evals'; +import { pickFlags } from '@/app-scope'; +import { assistant } from './assistant'; +import { reviews } from '@/collections/reviews'; +import { generateObject } from 'ai'; +import { wrapAISDKModel } from 'axiom/ai'; +import { openai } from '@/openai'; +import { z } from 'zod'; +import { ReviewCollection } from '@/schemas'; + +const scoringGuidelines = ` +Score from 0.0 to 1.0 based on: +- Captures key sentiment and main points from the original review +- 20 words or less +- Clear and readable + +Scoring bands: +1.0 = Perfect summary - accurate, concise, clear +0.8 = Good - minor issues with brevity or completeness +0.6 = Acceptable - misses some details or slightly unclear +0.4 = Poor - significant inaccuracies or far too long +0.2 = Very poor - mostly wrong or incomprehensible +0.0 = Complete failure`; + +type AssistantOutput = Awaited>; + +const sentimentAccuracy = Scorer( + 'sentiment-accuracy', + ({ expected, output }: { expected: { sentiment: string }; output: AssistantOutput }) => { + return expected.sentiment === output.sentiment; + }, +); + +const summaryQuality = Scorer( + 'summary-quality', + async ({ + input, + expected, + output, + }: { + input: { review: string }; + expected: { summary: string }; + output: AssistantOutput; + }) => { + const model = wrapAISDKModel(openai('gpt-4o-mini')); + + const { object: evaluation } = await generateObject({ + model, + schema: z.object({ + score: z.number().min(0).max(1), + reasoning: z.string(), + }), + prompt: `You are scoring a review summary. +Original review: \`${input.review}\` +Generated summary: \`${output.summary}\` +Reference summary: \`${expected.summary}\` +${scoringGuidelines} +Return only a number between 0.0 and 1.0.`, + }); + + return { + score: evaluation.score, + metadata: { reasoning: evaluation.reasoning }, + }; + }, +); + +Eval('review-assistant', { + capability: 'review-assistant', + configFlags: pickFlags('reviewAssistant'), + data: reviews, + task: async (task) => { + return await assistant(task.input); + }, + scorers: [sentimentAccuracy, summaryQuality], +}); diff --git a/examples/review-assistant/src/capabilities/assistant.ts b/examples/review-assistant/src/capabilities/assistant.ts new file mode 100644 index 00000000..559bcbd3 --- /dev/null +++ b/examples/review-assistant/src/capabilities/assistant.ts @@ -0,0 +1,44 @@ +import { generateObject } from 'ai'; +import { withSpan, wrapAISDKModel } from 'axiom/ai'; +import { openai } from '@/openai'; +import { z } from 'zod'; +import type { UserReview } from '@/schemas'; +import { flag } from '@/app-scope'; + +export async function assistant(input: UserReview) { + return withSpan({ capability: 'review_assistant', step: 'assistant' }, async () => { + const [sentiment, summary] = await Promise.all([ + withSpan({ capability: 'review_assistant', step: 'sentiment' }, async () => { + const modelId = flag('reviewAssistant.sentiment.modelId'); + const model = wrapAISDKModel(openai(modelId)); + + const { object: sentiment } = await generateObject({ + model, + output: 'enum', + enum: ['positive', 'negative', 'neutral', 'unknown'], + prompt: `Classify the sentiment of the following review: \`${input.review}\``, + }); + + return sentiment; + }), + + withSpan({ capability: 'review_assistant', step: 'summarize' }, async () => { + const modelId = flag('reviewAssistant.summarize.modelId'); + const model = wrapAISDKModel(openai(modelId)); + + const { object: summary } = await generateObject({ + model, + schema: z.object({ summary: z.string() }), + prompt: `Summarize the following review in 20 words or less: \`${input.review}\``, + }); + + return summary.summary; + }), + ]); + + return { + sentiment, + summary, + }; + }); +} diff --git a/examples/review-assistant/src/collections/reviews.ts b/examples/review-assistant/src/collections/reviews.ts new file mode 100644 index 00000000..e7fd0c3c --- /dev/null +++ b/examples/review-assistant/src/collections/reviews.ts @@ -0,0 +1,340 @@ +import { type ReviewCollection } from '../schemas'; + +export const reviews: ReviewCollection = [ + { + input: { + review: + 'Absolutely brilliant kettle. Boils water super quick and looks gorgeous on the worktop. Had it for three months now and no issues whatsoever. The temperature control is a game changer for my green tea.', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'positive', + summary: + 'Excellent kettle with fast boiling, attractive design, and useful temperature control for tea preparation.', + }, + metadata: { + id: 'B09X7KL2M4', + profile: 'A2HJKF8DN3PLQ9R7TM6ZW1XE4YBS', + title: 'Electric Kettle with Variable Temperature Control, 1.7L Stainless Steel', + }, + }, + { + input: { + review: + 'dont waste ur money total garbage the handle broke after 2 weeks and customer service was useless', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'negative', + summary: + 'Poor quality product with handle breaking after two weeks and unhelpful customer service.', + }, + metadata: { + id: 'B0B3M8TQ5W', + profile: 'A9KL2P4NT6H8RXQZ7YC3FW1JV5MG', + title: 'Non-Stick Frying Pan Set, 3-Piece Aluminum Cookware', + }, + }, + { + input: { + review: + 'It works fine I guess. Nothing special really. Does what it says on the tin. Probably would look elsewhere next time but its not terrible.', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'neutral', + summary: 'Functional product that meets basic expectations but lacks standout features.', + }, + metadata: { + id: 'B0C7R4HN9K', + profile: 'A5XT9V2KJ4MW8PLQN7FC6YH3R1BZ', + title: 'USB-C Cable 6ft, Fast Charging Braided Cord', + }, + }, + { + input: { + review: + 'Received this as part of review program. The packaging seemed okay and the product looks decent but havent actually used it yet for anything serious so cant really comment on performance or durability at this stage.', + purchase_context: 'gifted_product', + }, + expected: { + sentiment: 'unknown', + summary: + 'Product appears well-packaged and visually acceptable but has not been tested for performance yet.', + }, + metadata: { + id: 'B0D8W5TJ2N', + profile: 'A3CQ8F7NK9PL5HXWY2JM4RT6GVZB', + title: 'Wireless Bluetooth Earbuds with Charging Case, 24-Hour Playtime', + }, + }, + { + input: { + review: + 'We purchased these for our office and they have been fantastic. Very durable, easy to clean, and the staff love them. Great value for bulk ordering. Would definitely recommend for commercial use.', + purchase_context: 'business', + }, + expected: { + sentiment: 'positive', + summary: + 'Durable and easy to maintain product ideal for office use with excellent value for bulk orders.', + }, + metadata: { + id: 'B0A6V9RQ3F', + profile: 'A8WN2QZ5K7MC4TB6HP9FX3JL1YRV', + title: 'Stackable Office Chairs, Set of 4, Ergonomic Design with Padded Seat', + }, + }, + { + input: { + review: + 'This is the worst purchase Ive made all year. The motor started making horrible grinding noises on day one and it smells like burning plastic when you turn it on. Returned immediately.', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'negative', + summary: 'Defective product with motor issues and burning plastic smell from first use.', + }, + metadata: { + id: 'B0E2N7KV8M', + profile: 'A7PQ3XJ9W5MK2NC8TH4FB6YR1ZLG', + title: 'Food Processor 12-Cup, 700W Motor with Multiple Blade Attachments', + }, + }, + { + input: { + review: + 'Nice phone case sturdy feels premium but the buttons r bit stiff makes it hard to press volume controls otherwise good', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'positive', + summary: + 'Well-made and premium-feeling case with slightly stiff buttons affecting volume control access.', + }, + metadata: { + id: 'B0F9J3LT6R', + profile: 'A4FR8H6NL9QT2VCK7XW5ZY3PM1BJ', + title: 'Silicone Phone Case for iPhone 14 Pro, Shockproof Protective Cover', + }, + }, + { + input: { + review: + 'Got this free to review. Its a vacuum cleaner so yeah. Havent really tested it properly but it turned on and moved around a bit. Seems alright I suppose.', + purchase_context: 'gifted_product', + }, + expected: { + sentiment: 'unknown', + summary: + 'Product powers on and operates but lacks sufficient testing to assess overall performance.', + }, + metadata: { + id: 'B0G4M8QW7P', + profile: 'A6MQ9TB4H7NX5PC8JL2YW3FK1RVZ', + title: 'Robot Vacuum Cleaner with Smart Navigation and App Control', + }, + }, + { + input: { + review: + 'Absolutely love this thing Changed my morning routine completely Coffee tastes incredible and its so much easier than my old machine Highly highly recommend', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'positive', + summary: + 'Transformative coffee maker that significantly improves morning routine with excellent taste and ease of use.', + }, + metadata: { + id: 'B0H7Q2NW5K', + profile: 'A2YF9W4JX7KC5PL8NT6HB3VM1QRZ', + title: 'Espresso Machine with Milk Frother, 15-Bar Pressure Pump', + }, + }, + { + input: { + review: + 'Quality seems acceptable for the price point. Installation was straightforward. No major complaints but nothing particularly impressive either. Does its job adequately.', + purchase_context: 'business', + }, + expected: { + sentiment: 'neutral', + summary: + 'Adequate product with reasonable quality for cost and easy installation but no exceptional features.', + }, + metadata: { + id: 'B0J1T8PL6M', + profile: 'A5NJ7WR2Q9VK8FC3LH4YP6TX1BZM', + title: 'LED Desk Lamp with USB Charging Port, Adjustable Brightness', + }, + }, + { + input: { + review: + 'DO NOT BUY these leak everywhere made a massive mess all over my carpet and the company refused to refund me even with photos shocking service absolutely livid', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'negative', + summary: + 'Severely defective product that leaks causing damage with company refusing refund despite evidence.', + }, + metadata: { + id: 'B0K5V9TJ3N', + profile: 'A8QW3FM7P5NJ2KX6TB4CH9YL1RVZ', + title: 'Reusable Water Bottles, BPA-Free Plastic, Pack of 6', + }, + }, + { + input: { + review: + 'My daughter loves this toy. She plays with it every single day since we got it for her birthday. Very well made and the colours are vibrant. Worth every penny.', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'positive', + summary: + 'High-quality toy with vibrant colors that provides consistent enjoyment and excellent value.', + }, + metadata: { + id: 'B0L2W7QH4R', + profile: 'A9XK4PL6H2NT8WC7FY5JM3VQ1RBZ', + title: 'Wooden Building Blocks Set, 100 Pieces with Storage Bag', + }, + }, + { + input: { + review: + 'its ok i guess the size is smaller than expected from the pictures and description but it arrived quickly and works fine so thats something', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'neutral', + summary: 'Functional product that arrived quickly but smaller than advertised.', + }, + metadata: { + id: 'B0M9K3TL7W', + profile: 'A3LH8YF9N6VP2JQ4TX7KC5WM1RBZ', + title: 'Portable Bluetooth Speaker, Waterproof with 12-Hour Battery', + }, + }, + { + input: { + review: + 'We bought these for our restaurant kitchen. They have held up remarkably well under heavy daily use. Easy to maintain and clean which is crucial for us. Will be ordering more.', + purchase_context: 'business', + }, + expected: { + sentiment: 'positive', + summary: + 'Durable kitchen equipment performing excellently under intensive commercial use with easy maintenance.', + }, + metadata: { + id: 'B0N6L2VM8Q', + profile: 'A7TW2NJ9Q4KF6PL8VH5XC3YM1RBZ', + title: 'Commercial Kitchen Cutting Boards, Set of 3, Dishwasher Safe', + }, + }, + { + input: { + review: + 'Free product for review. Havent used this enough to form proper opinion. Design looks nice materials feel cheap though might update later if remember', + purchase_context: 'gifted_product', + }, + expected: { + sentiment: 'unknown', + summary: + 'Attractive design with questionable material quality but insufficient use to provide complete assessment.', + }, + metadata: { + id: 'B0P4H8QL9T', + profile: 'A5JC9WN7X4PM2FL6QK8VT3HY1RBZ', + title: 'Fitness Tracker Watch with Heart Rate Monitor and Sleep Tracking', + }, + }, + { + input: { + review: + 'Cannot fault this at all. Exactly what I needed. Fits perfectly works brilliantly and the customer service when I had a question was top notch. Five stars all the way.', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'positive', + summary: + 'Perfect product that meets all needs with excellent fit, performance, and outstanding customer service.', + }, + metadata: { + id: 'B0Q7M2TW5N', + profile: 'A2KB9QX6P7VN4JH8FC3TL5WY1RMZ', + title: 'Car Phone Mount, Dashboard Windshield Holder with Strong Suction', + }, + }, + { + input: { + review: + 'Terrible quality the fabric started pilling after just one wash and the colour faded dramatically not what I expected from this brand very disappointed wont buy again', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'negative', + summary: + 'Poor fabric quality with immediate pilling and significant color fading after single wash.', + }, + metadata: { + id: 'B0R1N5VK8J', + profile: 'A4TX8JL9N2WQ6PC7VH5FK3YM1RBZ', + title: 'Cotton Bed Sheet Set, King Size, Deep Pocket Fitted Sheet', + }, + }, + { + input: { + review: + 'Works as described no more no less arrived on time packaging was fine not sure what else to say about it really its just a cable', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'neutral', + summary: 'Basic functional cable that meets standard expectations with timely delivery.', + }, + metadata: { + id: 'B0S8P3QJ6L', + profile: 'A9WY2TL7K4NQ5HJ6PC8FM3VX1RBZ', + title: 'HDMI Cable 10ft, High Speed 4K Compatible with Gold-Plated Connectors', + }, + }, + { + input: { + review: + 'This is hands down the best purchase I have made this year. The quality is outstanding it exceeded all my expectations and my whole family uses it daily. Could not be happier honestly.', + purchase_context: 'verified_purchase', + }, + expected: { + sentiment: 'positive', + summary: + 'Exceptional quality product surpassing expectations and providing daily value for entire family.', + }, + metadata: { + id: 'B0T5K9WL2M', + profile: 'A6PL4HY8N7QJ2VW9TC5FX3KM1RBZ', + title: 'Air Fryer Oven, 12-in-1 Multi-Functional Countertop Convection Oven', + }, + }, + { + input: { + review: + 'Received as part of vine program. Not entirely sure what to make of this product yet. Instructions were confusing setup took forever. Maybe ill update once I actually figure out how to use it properly.', + purchase_context: 'gifted_product', + }, + expected: { + sentiment: 'unknown', + summary: + 'Unclear product assessment due to confusing instructions and difficult setup requiring further use.', + }, + metadata: { + id: 'B0U2J7PW4N', + profile: 'A8VN3QK6W9LJ2TX7FH4PC5YM1RBZ', + title: 'Smart Home Security Camera System, 4-Pack with Night Vision', + }, + }, +]; diff --git a/examples/review-assistant/src/openai.ts b/examples/review-assistant/src/openai.ts new file mode 100644 index 00000000..50e2dbac --- /dev/null +++ b/examples/review-assistant/src/openai.ts @@ -0,0 +1,6 @@ +import 'dotenv/config'; +import { createOpenAI } from '@ai-sdk/openai'; + +export const openai = createOpenAI({ + apiKey: process.env.OPENAI_API_KEY, +}); diff --git a/examples/review-assistant/src/schemas.ts b/examples/review-assistant/src/schemas.ts new file mode 100644 index 00000000..62039af0 --- /dev/null +++ b/examples/review-assistant/src/schemas.ts @@ -0,0 +1,32 @@ +import { z } from 'zod'; + +export const PurchaseContextSchema = z.enum(['verified_purchase', 'gifted_product', 'business']); + +export const SentimentSchema = z.enum(['positive', 'negative', 'neutral', 'unknown']); + +export const UserReviewSchema = z.object({ + review: z.string(), + purchase_context: PurchaseContextSchema, +}); + +export const UserReviewMetadataSchema = z.object({ + id: z.string(), + profile: z.string(), + title: z.string(), +}); + +export const ReviewAssistantSchema = z.object({ + sentiment: SentimentSchema, + summary: z.string(), +}); + +export const ReviewCollectionItemSchema = z.object({ + input: UserReviewSchema, + expected: ReviewAssistantSchema, + metadata: UserReviewMetadataSchema, +}); + +export const ReviewCollectionSchema = z.array(ReviewCollectionItemSchema); + +export type ReviewCollection = z.infer; +export type UserReview = z.infer; diff --git a/examples/review-assistant/src/server.ts b/examples/review-assistant/src/server.ts new file mode 100644 index 00000000..382153d0 --- /dev/null +++ b/examples/review-assistant/src/server.ts @@ -0,0 +1,21 @@ +import { Hono } from 'hono'; +import { serve } from '@hono/node-server'; +import { assistant } from '@/capabilities/assistant'; +import { UserReviewSchema } from '@/schemas'; + +const app = new Hono(); + +// curl -X POST http://localhost:4321/assistant -H "Content-Type: application/json" -d '{"review":"Absolutely brilliant kettle. Boils water super quick and looks gorgeous on the worktop. Had it for three months now and no issues whatsoever. The temperature control is a game changer for my green tea. I used to just boil water and wait for it to cool down, but now I can set it to exactly 80 degrees which is perfect. The build quality feels really solid and premium, especially compared to my old plastic kettle. It is quite a bit more expensive than basic models but I think its worth it for the features and design. My only minor complaint is that the water level window could be a bit easier to read, but thats really nitpicking. Would definitely recommend to anyone looking for a quality kettle.","purchase_context":"verified_purchase"}' +app.post('/assistant', async (c) => { + try { + const body = await c.req.json(); + const input = UserReviewSchema.parse(body); + const result = await assistant(input); + return c.json(result); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return c.text(message, 500); + } +}); + +serve({ fetch: app.fetch, port: 4321 }); diff --git a/examples/review-assistant/tsconfig.json b/examples/review-assistant/tsconfig.json new file mode 100644 index 00000000..8408f95a --- /dev/null +++ b/examples/review-assistant/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules"] +} + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85451163..4d8ba2c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,6 +131,65 @@ importers: specifier: ^5 version: 5.9.2 + examples/review-analysis: + dependencies: + '@ai-sdk/openai': + specifier: ^2.0.72 + version: 2.0.72(zod@4.1.5) + ai: + specifier: ^5.0.102 + version: 5.0.102(zod@4.1.5) + axiom: + specifier: workspace:* + version: link:../../packages/ai + dotenv: + specifier: ^16.4.5 + version: 16.6.1 + zod: + specifier: 'catalog:' + version: 4.1.5 + devDependencies: + tsx: + specifier: ^4.19.0 + version: 4.20.4 + typescript: + specifier: ^5 + version: 5.9.2 + + examples/review-assistant: + dependencies: + '@ai-sdk/openai': + specifier: ^2.0.72 + version: 2.0.72(zod@4.1.5) + '@hono/node-server': + specifier: ^1.19.6 + version: 1.19.6(hono@4.10.6) + ai: + specifier: ^5.0.102 + version: 5.0.102(zod@4.1.5) + axiom: + specifier: workspace:* + version: link:../../packages/ai + dotenv: + specifier: ^16.4.5 + version: 16.6.1 + hono: + specifier: ^4.10.6 + version: 4.10.6 + tsx: + specifier: ^4.20.4 + version: 4.20.4 + zod: + specifier: 'catalog:' + version: 4.1.5 + devDependencies: + '@types/node': + specifier: ^22 + version: 22.17.2 + typescript: + specifier: ^5 + version: 5.9.2 + examples/telemetry-express: dependencies: '@ai-sdk/openai': @@ -284,7 +343,7 @@ importers: version: link:../../packages/ai next: specifier: latest - version: 16.1.0-canary.2(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.1.0-canary.10(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.2.0 version: 18.3.1 @@ -351,7 +410,7 @@ importers: version: link:../../packages/ai next: specifier: latest - version: 16.1.0-canary.2(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 16.1.0-canary.10(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: specifier: ^19.2.0 version: 19.2.0 @@ -980,6 +1039,12 @@ packages: engines: {node: '>=6'} hasBin: true + '@hono/node-server@1.19.6': + resolution: {integrity: sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1220,8 +1285,8 @@ packages: '@next/env@16.0.4': resolution: {integrity: sha512-FDPaVoB1kYhtOz6Le0Jn2QV7RZJ3Ngxzqri7YX4yu3Ini+l5lciR7nA9eNDpKTmDm7LWZtxSju+/CQnwRBn2pA==} - '@next/env@16.1.0-canary.2': - resolution: {integrity: sha512-HKrr0SBoCkFHHItHZwdwdmnGqkR8Me3ptWTfI0FkJZVx1FfyA7AZILzgd1sgImd6eXcnk3kb/NVfQ4hmvvTruA==} + '@next/env@16.1.0-canary.10': + resolution: {integrity: sha512-YbPKreQNXf907NdN6AMARZjnNv9eMb/xs2PsW0FUew9MD3XI8tzHuXq97K2FG8UJCKXB5QWCVwoDZmn6Z47tTA==} '@next/eslint-plugin-next@16.0.4': resolution: {integrity: sha512-0emoVyL4Z5NEkRNb63ko/BqLC9OFULcY7mJ3lSerBCqgh/UFcjnvodyikV2bTl7XygwcamJxJAfxCo1oAVfH6g==} @@ -1232,8 +1297,8 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@16.1.0-canary.2': - resolution: {integrity: sha512-vXJ2/HY9Gu636ZuonD+AI+F+RDo/ruqXy0EyC107SuJIU97GliN1r4+eVXa84GdNPojPY7UkJ+ksWOTe1jVchw==} + '@next/swc-darwin-arm64@16.1.0-canary.10': + resolution: {integrity: sha512-IyzlDfqMUjHcFerupuaKXSRCA6hJ0snRm9gaH7RIS0pRD5z2+foM9ajCogjLGyweaphfO6QoMYxgfjpgcvEKKg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1244,8 +1309,8 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@16.1.0-canary.2': - resolution: {integrity: sha512-ilvPJ3C+OLq4tG6FqYFqvznBAFOy5gLGW+PYrrbLKx0i63Wgm2LMR50hCFxgR3JxnWLjx5iWN7WF3KLSWxKUxQ==} + '@next/swc-darwin-x64@16.1.0-canary.10': + resolution: {integrity: sha512-AIjbgmTuzi+pps099y5siIPSbVHP0ATBzhQRT/ZlVdxBgz18l9k046el+Qw9uXKAU18yE8A06SqcY0jWyQu8Pg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1257,8 +1322,8 @@ packages: os: [linux] libc: [glibc] - '@next/swc-linux-arm64-gnu@16.1.0-canary.2': - resolution: {integrity: sha512-9yQvrJwezXK4LpnuyN+7GiUFWH7Fjq6NgKsQcBfyMbsGwBdZ58ZhDDjRNFFmH/N0z9FqpX90wHpp3oMCRxWQQw==} + '@next/swc-linux-arm64-gnu@16.1.0-canary.10': + resolution: {integrity: sha512-35S6WOSqt5N5O32+H73YpTy7hMHy5kW+Gc8Ga6ehRI4l+OujZ8PoEWVYwlgJjdcerg8ke0ajBJLLpAu/K5uNiQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1271,8 +1336,8 @@ packages: os: [linux] libc: [musl] - '@next/swc-linux-arm64-musl@16.1.0-canary.2': - resolution: {integrity: sha512-8JFlki9yZPYoW2YILF+CIGCSC6o5/hiAiziVB+7xmP6gUDMbI8J28tjQJXY2da4r1N+vRtFWC+bAMHPK5gt8Pg==} + '@next/swc-linux-arm64-musl@16.1.0-canary.10': + resolution: {integrity: sha512-ozuBXBju2YDOu/VpTLV7ltNaLcek8Q7eI2jwC/LxMDP9QPYi6QTuOBS7tUYFMk70DT01r8DIH47C0aZmATHyAQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1285,8 +1350,8 @@ packages: os: [linux] libc: [glibc] - '@next/swc-linux-x64-gnu@16.1.0-canary.2': - resolution: {integrity: sha512-OcR8vklPkjZy+z6HhB6K/SQzUKlCBqP3N3n4t+yJFDn+BWctM6+UTvfN0xcSZXSAx0FliCuYCnpCbmHWCgzAJQ==} + '@next/swc-linux-x64-gnu@16.1.0-canary.10': + resolution: {integrity: sha512-+Pjqnd4gQzjIJ4w33zn+uGQwbjkPzuqvNYq3kx1TesW5OeGxf3S6HlbASSqQ3a6GBfSQmjKdOfKjUVVGaiiZTQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1299,8 +1364,8 @@ packages: os: [linux] libc: [musl] - '@next/swc-linux-x64-musl@16.1.0-canary.2': - resolution: {integrity: sha512-1DRRadvIONBfS8ehdIRnPOuFqTvz/PONJGhPtupy4t2KXD5dPRZ0TW+mTqTIS3i7f7a72rS4DH/jci/q8AryQw==} + '@next/swc-linux-x64-musl@16.1.0-canary.10': + resolution: {integrity: sha512-iuOZLXCuTXvQI2IlCVPX1Joypxerao15ROvUVaOo/dk15EEDe0cY7kxyoBwLeoVWAHnjet5RZC4fISQdM1Hp0w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1312,8 +1377,8 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@16.1.0-canary.2': - resolution: {integrity: sha512-l4VMrwqvVnWtToRJhs9KY27bG26D+qUrbkzk8DWQC6Vc+cqTjgf7fAJOr6s0N99yFuzFA/ZoPloeOp1mTXNrkA==} + '@next/swc-win32-arm64-msvc@16.1.0-canary.10': + resolution: {integrity: sha512-wtFIVVlyT6OpQKqn0KFY2Ci1GZorEsj4mYCqXnjsaZOKtDPFrdV6ep1rTti2j8j9z+LS0ZM5EK2+GTjMo/Z3Ag==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1324,8 +1389,8 @@ packages: cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@16.1.0-canary.2': - resolution: {integrity: sha512-UMrWJoAf2j/oaaX6JWOJIyCMUy8K+NVLQaCVHZiqGqvodD2m8Q0AYTB3RdAZt4z+r7iNveB/mCUbg/xqBkWHow==} + '@next/swc-win32-x64-msvc@16.1.0-canary.10': + resolution: {integrity: sha512-KN/CZeUlY4gEUlbxcg4vlUtIzZtu8d0JVajqCNfigs7ArYFg7gYtPEYimyz73FRVd0rCAap1tIlLDHMSeUBt6w==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -4052,6 +4117,7 @@ packages: next@16.0.4: resolution: {integrity: sha512-vICcxKusY8qW7QFOzTvnRL1ejz2ClTqDKtm1AcUjm2mPv/lVAdgpGNsftsPRIDJOXOjRQO68i1dM8Lp8GZnqoA==} engines: {node: '>=20.9.0'} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details. hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -4070,8 +4136,8 @@ packages: sass: optional: true - next@16.1.0-canary.2: - resolution: {integrity: sha512-P1BjzhzCSccHCpJzzxQnsvmKcYPdTa1RvDbYOZ/WUPjD9yuflI1g+XbfdUzIGUjyHrknlEAAqw4X9tVZK9WECg==} + next@16.1.0-canary.10: + resolution: {integrity: sha512-VCBY2HD1zCQi05hCO9q8b8sSoqdOnbeCCHQk8vGE8iiPQGxaiSfrfF1D+OibNLsaL+vtMfy4X3ngpvjUDByOHg==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -5594,6 +5660,10 @@ snapshots: protobufjs: 7.5.4 yargs: 17.7.2 + '@hono/node-server@1.19.6(hono@4.10.6)': + dependencies: + hono: 4.10.6 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -5788,7 +5858,7 @@ snapshots: '@next/env@16.0.4': {} - '@next/env@16.1.0-canary.2': {} + '@next/env@16.1.0-canary.10': {} '@next/eslint-plugin-next@16.0.4': dependencies: @@ -5797,49 +5867,49 @@ snapshots: '@next/swc-darwin-arm64@16.0.4': optional: true - '@next/swc-darwin-arm64@16.1.0-canary.2': + '@next/swc-darwin-arm64@16.1.0-canary.10': optional: true '@next/swc-darwin-x64@16.0.4': optional: true - '@next/swc-darwin-x64@16.1.0-canary.2': + '@next/swc-darwin-x64@16.1.0-canary.10': optional: true '@next/swc-linux-arm64-gnu@16.0.4': optional: true - '@next/swc-linux-arm64-gnu@16.1.0-canary.2': + '@next/swc-linux-arm64-gnu@16.1.0-canary.10': optional: true '@next/swc-linux-arm64-musl@16.0.4': optional: true - '@next/swc-linux-arm64-musl@16.1.0-canary.2': + '@next/swc-linux-arm64-musl@16.1.0-canary.10': optional: true '@next/swc-linux-x64-gnu@16.0.4': optional: true - '@next/swc-linux-x64-gnu@16.1.0-canary.2': + '@next/swc-linux-x64-gnu@16.1.0-canary.10': optional: true '@next/swc-linux-x64-musl@16.0.4': optional: true - '@next/swc-linux-x64-musl@16.1.0-canary.2': + '@next/swc-linux-x64-musl@16.1.0-canary.10': optional: true '@next/swc-win32-arm64-msvc@16.0.4': optional: true - '@next/swc-win32-arm64-msvc@16.1.0-canary.2': + '@next/swc-win32-arm64-msvc@16.1.0-canary.10': optional: true '@next/swc-win32-x64-msvc@16.0.4': optional: true - '@next/swc-win32-x64-msvc@16.1.0-canary.2': + '@next/swc-win32-x64-msvc@16.1.0-canary.10': optional: true '@nodelib/fs.scandir@2.1.5': @@ -6108,7 +6178,7 @@ snapshots: '@opentelemetry/instrumentation-amqplib@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: @@ -6126,7 +6196,7 @@ snapshots: '@opentelemetry/instrumentation-aws-sdk@0.54.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/propagation-utils': 0.31.3(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 @@ -6153,7 +6223,7 @@ snapshots: '@opentelemetry/instrumentation-connect@0.46.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@types/connect': 3.4.38 @@ -6185,7 +6255,7 @@ snapshots: '@opentelemetry/instrumentation-express@0.51.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: @@ -6203,7 +6273,7 @@ snapshots: '@opentelemetry/instrumentation-fastify@0.47.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: @@ -6212,7 +6282,7 @@ snapshots: '@opentelemetry/instrumentation-fs@0.22.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -6242,7 +6312,7 @@ snapshots: '@opentelemetry/instrumentation-hapi@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: @@ -6296,7 +6366,7 @@ snapshots: '@opentelemetry/instrumentation-koa@0.50.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: @@ -6329,7 +6399,7 @@ snapshots: '@opentelemetry/instrumentation-mongoose@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: @@ -6381,7 +6451,7 @@ snapshots: '@opentelemetry/instrumentation-pg@0.54.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/sql-common': 0.41.0(@opentelemetry/api@1.9.0) @@ -6394,7 +6464,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.202.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -6420,7 +6490,7 @@ snapshots: '@opentelemetry/instrumentation-restify@0.48.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: @@ -6461,7 +6531,7 @@ snapshots: '@opentelemetry/instrumentation-undici@0.13.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.202.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -6587,35 +6657,35 @@ snapshots: '@opentelemetry/resource-detector-alibaba-cloud@0.31.3(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/resource-detector-aws@2.3.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/resource-detector-azure@0.9.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/resource-detector-container@0.7.3(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/resource-detector-gcp@0.36.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.38.0 gcp-metadata: 6.1.1 @@ -6778,7 +6848,7 @@ snapshots: '@opentelemetry/sql-common@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@pkgjs/parseargs@0.11.0': optional: true @@ -8994,48 +9064,50 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.1.0-canary.2(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@16.1.0-canary.10(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 16.1.0-canary.2 + '@next/env': 16.1.0-canary.10 '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.8.31 caniuse-lite: 1.0.30001735 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.6(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 16.1.0-canary.2 - '@next/swc-darwin-x64': 16.1.0-canary.2 - '@next/swc-linux-arm64-gnu': 16.1.0-canary.2 - '@next/swc-linux-arm64-musl': 16.1.0-canary.2 - '@next/swc-linux-x64-gnu': 16.1.0-canary.2 - '@next/swc-linux-x64-musl': 16.1.0-canary.2 - '@next/swc-win32-arm64-msvc': 16.1.0-canary.2 - '@next/swc-win32-x64-msvc': 16.1.0-canary.2 + '@next/swc-darwin-arm64': 16.1.0-canary.10 + '@next/swc-darwin-x64': 16.1.0-canary.10 + '@next/swc-linux-arm64-gnu': 16.1.0-canary.10 + '@next/swc-linux-arm64-musl': 16.1.0-canary.10 + '@next/swc-linux-x64-gnu': 16.1.0-canary.10 + '@next/swc-linux-x64-musl': 16.1.0-canary.10 + '@next/swc-win32-arm64-msvc': 16.1.0-canary.10 + '@next/swc-win32-x64-msvc': 16.1.0-canary.10 '@opentelemetry/api': 1.9.0 sharp: 0.34.4 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - next@16.1.0-canary.2(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next@16.1.0-canary.10(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@next/env': 16.1.0-canary.2 + '@next/env': 16.1.0-canary.10 '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.8.31 caniuse-lite: 1.0.30001735 postcss: 8.4.31 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.0) optionalDependencies: - '@next/swc-darwin-arm64': 16.1.0-canary.2 - '@next/swc-darwin-x64': 16.1.0-canary.2 - '@next/swc-linux-arm64-gnu': 16.1.0-canary.2 - '@next/swc-linux-arm64-musl': 16.1.0-canary.2 - '@next/swc-linux-x64-gnu': 16.1.0-canary.2 - '@next/swc-linux-x64-musl': 16.1.0-canary.2 - '@next/swc-win32-arm64-msvc': 16.1.0-canary.2 - '@next/swc-win32-x64-msvc': 16.1.0-canary.2 + '@next/swc-darwin-arm64': 16.1.0-canary.10 + '@next/swc-darwin-x64': 16.1.0-canary.10 + '@next/swc-linux-arm64-gnu': 16.1.0-canary.10 + '@next/swc-linux-arm64-musl': 16.1.0-canary.10 + '@next/swc-linux-x64-gnu': 16.1.0-canary.10 + '@next/swc-linux-x64-musl': 16.1.0-canary.10 + '@next/swc-win32-arm64-msvc': 16.1.0-canary.10 + '@next/swc-win32-x64-msvc': 16.1.0-canary.10 '@opentelemetry/api': 1.9.0 sharp: 0.34.4 transitivePeerDependencies: From e3830d8d481b0110c0d149b6fb74fa4e6cc150ec Mon Sep 17 00:00:00 2001 From: Dominic Chapman Date: Sun, 14 Dec 2025 20:31:29 +0000 Subject: [PATCH 2/2] feat: add instrumentation for server --- examples/review-assistant/.env.example | 1 + examples/review-assistant/package.json | 8 +++ .../review-assistant/src/instrumentation.ts | 50 ++++++++++++++++ examples/review-assistant/src/server.ts | 9 +++ pnpm-lock.yaml | 59 +++++++++---------- 5 files changed, 97 insertions(+), 30 deletions(-) create mode 100644 examples/review-assistant/src/instrumentation.ts diff --git a/examples/review-assistant/.env.example b/examples/review-assistant/.env.example index 07cb57b8..6095e083 100644 --- a/examples/review-assistant/.env.example +++ b/examples/review-assistant/.env.example @@ -1,4 +1,5 @@ AXIOM_URL="https://api.axiom.co" AXIOM_TOKEN="xaat-******" +AXIOM_DATASET="product-telemetry-prod" AXIOM_EVALS_DATASET="product-evals-prod" OPENAI_API_KEY="sk-******" \ No newline at end of file diff --git a/examples/review-assistant/package.json b/examples/review-assistant/package.json index 924b008a..24953dbf 100644 --- a/examples/review-assistant/package.json +++ b/examples/review-assistant/package.json @@ -10,6 +10,14 @@ "dependencies": { "@ai-sdk/openai": "^2.0.72", "@hono/node-server": "^1.19.6", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.203.0", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/instrumentation-http": "^0.203.0", + "@opentelemetry/resources": "^2.0.1", + "@opentelemetry/sdk-trace-base": "^2.0.1", + "@opentelemetry/sdk-trace-node": "^2.0.1", + "@opentelemetry/semantic-conventions": "^1.36.0", "ai": "^5.0.102", "axiom": "workspace:*", "dotenv": "^16.4.5", diff --git a/examples/review-assistant/src/instrumentation.ts b/examples/review-assistant/src/instrumentation.ts new file mode 100644 index 00000000..f5cc42b9 --- /dev/null +++ b/examples/review-assistant/src/instrumentation.ts @@ -0,0 +1,50 @@ +import 'dotenv/config'; +import type { Tracer } from '@opentelemetry/api'; +import { trace } from '@opentelemetry/api'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; +import { resourceFromAttributes } from '@opentelemetry/resources'; +import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; +import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; +import { initAxiomAI, RedactionPolicy } from 'axiom/ai'; + +registerInstrumentations({ + instrumentations: [new HttpInstrumentation()], +}); + +export const setupTracing = (config: { + url: string; + token: string; + dataset: string; + serviceName: string; +}): Tracer => { + const exporter = new OTLPTraceExporter({ + url: `${config.url}/v1/traces`, + headers: { + Authorization: `Bearer ${config.token}`, + 'X-Axiom-Dataset': config.dataset, + }, + }); + const provider = new NodeTracerProvider({ + resource: resourceFromAttributes({ + [ATTR_SERVICE_NAME]: config.serviceName, + }), + spanProcessors: [new SimpleSpanProcessor(exporter)], + }); + + registerInstrumentations({ + tracerProvider: provider, + instrumentations: [], + }); + + provider.register(); + + const tracer = trace.getTracer(config.serviceName); + + // Initialize Axiom AI with the tracer + initAxiomAI({ tracer, redactionPolicy: RedactionPolicy.AxiomDefault }); + + return tracer; +}; diff --git a/examples/review-assistant/src/server.ts b/examples/review-assistant/src/server.ts index 382153d0..3b32e6d2 100644 --- a/examples/review-assistant/src/server.ts +++ b/examples/review-assistant/src/server.ts @@ -1,3 +1,12 @@ +import { setupTracing } from '@/instrumentation'; + +setupTracing({ + url: process.env['AXIOM_URL'] || 'https://api.axiom.co', + token: process.env['AXIOM_TOKEN']!, + dataset: process.env['AXIOM_DATASET']!, + serviceName: 'review-assistant', +}); + import { Hono } from 'hono'; import { serve } from '@hono/node-server'; import { assistant } from '@/capabilities/assistant'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d8ba2c1..be01002e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,31 +131,6 @@ importers: specifier: ^5 version: 5.9.2 - examples/review-analysis: - dependencies: - '@ai-sdk/openai': - specifier: ^2.0.72 - version: 2.0.72(zod@4.1.5) - ai: - specifier: ^5.0.102 - version: 5.0.102(zod@4.1.5) - axiom: - specifier: workspace:* - version: link:../../packages/ai - dotenv: - specifier: ^16.4.5 - version: 16.6.1 - zod: - specifier: 'catalog:' - version: 4.1.5 - devDependencies: - tsx: - specifier: ^4.19.0 - version: 4.20.4 - typescript: - specifier: ^5 - version: 5.9.2 - examples/review-assistant: dependencies: '@ai-sdk/openai': @@ -164,6 +139,30 @@ importers: '@hono/node-server': specifier: ^1.19.6 version: 1.19.6(hono@4.10.6) + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/exporter-trace-otlp-proto': + specifier: ^0.203.0 + version: 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': + specifier: ^0.203.0 + version: 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': + specifier: ^0.203.0 + version: 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': + specifier: ^2.0.1 + version: 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: ^2.0.1 + version: 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': + specifier: ^2.0.1 + version: 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': + specifier: ^1.36.0 + version: 1.38.0 ai: specifier: ^5.0.102 version: 5.0.102(zod@4.1.5) @@ -8029,7 +8028,7 @@ snapshots: '@next/eslint-plugin-next': 16.0.4 eslint: 9.33.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.6.1)))(eslint@9.33.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.6.1)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.6.1)) @@ -8056,7 +8055,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.6.1)))(eslint@9.33.0(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -8071,14 +8070,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.6.1)))(eslint@9.33.0(jiti@2.6.1)))(eslint@9.33.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2) eslint: 9.33.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.6.1)))(eslint@9.33.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.33.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -8093,7 +8092,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.33.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.6.1)))(eslint@9.33.0(jiti@2.6.1)))(eslint@9.33.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3