opik-gemini 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +293 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +2 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Opik Gemini Integration
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/opik-gemini)
|
|
4
|
+
[](https://github.com/comet-ml/opik/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
Seamlessly integrate [Opik](https://www.comet.com/docs/opik/) observability with your [Google Gemini](https://ai.google.dev/) applications.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🔍 **Comprehensive Tracing**: Automatically trace Gemini API calls
|
|
11
|
+
- 📊 **Hierarchical Visualization**: View execution as structured traces and spans
|
|
12
|
+
- 📝 **Detailed Metadata**: Record model names, prompts, completions, token usage
|
|
13
|
+
- 🚨 **Error Handling**: Capture and visualize errors with full context
|
|
14
|
+
- 🏷️ **Custom Tagging**: Add custom tags and metadata to organize traces
|
|
15
|
+
- 🔄 **Streaming Support**: Full support for streamed responses
|
|
16
|
+
- ⚡ **Non-blocking**: Minimal performance impact with async batching
|
|
17
|
+
- 🎯 **Type-Safe**: Full TypeScript support with comprehensive types
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install opik-gemini @google/genai
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Requirements
|
|
26
|
+
|
|
27
|
+
- Node.js ≥ 18
|
|
28
|
+
- @google/genai SDK (≥ 1.0.0)
|
|
29
|
+
- Opik SDK (automatically installed as peer dependency)
|
|
30
|
+
|
|
31
|
+
**Note**: The official Google GenAI SDK package is `@google/genai` (not `@google/generative-ai`). This is Google Deepmind's unified SDK for both Gemini Developer API and Vertex AI.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### Basic Usage
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { GoogleGenAI } from "@google/genai";
|
|
39
|
+
import { trackGemini } from "opik-gemini";
|
|
40
|
+
|
|
41
|
+
// Initialize Gemini client
|
|
42
|
+
const genAI = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
|
43
|
+
|
|
44
|
+
// Wrap with Opik tracking
|
|
45
|
+
const trackedGenAI = trackGemini(genAI, {
|
|
46
|
+
traceMetadata: {
|
|
47
|
+
tags: ["production", "my-app"],
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Use normally - all calls are automatically tracked
|
|
52
|
+
async function main() {
|
|
53
|
+
const response = await trackedGenAI.models.generateContent({
|
|
54
|
+
model: "gemini-2.0-flash-001",
|
|
55
|
+
contents: "What is the capital of France?",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log(response.text);
|
|
59
|
+
|
|
60
|
+
// Ensure all traces are sent before exit
|
|
61
|
+
await trackedGenAI.flush();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Streaming Support
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { GoogleGenAI } from "@google/genai";
|
|
71
|
+
import { trackGemini } from "opik-gemini";
|
|
72
|
+
|
|
73
|
+
const genAI = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
|
74
|
+
const trackedGenAI = trackGemini(genAI);
|
|
75
|
+
|
|
76
|
+
async function streamExample() {
|
|
77
|
+
const response = await trackedGenAI.models.generateContentStream({
|
|
78
|
+
model: "gemini-2.0-flash-001",
|
|
79
|
+
contents: "Write a haiku about AI",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Stream is automatically tracked
|
|
83
|
+
let streamedContent = "";
|
|
84
|
+
for await (const chunk of response) {
|
|
85
|
+
const chunkText = chunk.text;
|
|
86
|
+
if (chunkText) {
|
|
87
|
+
process.stdout.write(chunkText);
|
|
88
|
+
streamedContent += chunkText;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log("\n");
|
|
93
|
+
await trackedGenAI.flush();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
streamExample();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Using with Existing Opik Client
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { Opik } from "opik";
|
|
103
|
+
import { GoogleGenAI } from "@google/genai";
|
|
104
|
+
import { trackGemini } from "opik-gemini";
|
|
105
|
+
|
|
106
|
+
const opikClient = new Opik({
|
|
107
|
+
projectName: "gemini-project",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const genAI = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
|
111
|
+
const trackedGenAI = trackGemini(genAI, {
|
|
112
|
+
client: opikClient,
|
|
113
|
+
traceMetadata: {
|
|
114
|
+
tags: ["gemini", "production"],
|
|
115
|
+
environment: "prod",
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// All calls will be logged to "gemini-project"
|
|
120
|
+
const response = await trackedGenAI.models.generateContent({
|
|
121
|
+
model: "gemini-2.0-flash-001",
|
|
122
|
+
contents: "Hello, Gemini!",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
console.log(response.text);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Custom Generation Names
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { trackGemini } from "opik-gemini";
|
|
132
|
+
|
|
133
|
+
const trackedGenAI = trackGemini(genAI, {
|
|
134
|
+
generationName: "MyCustomGeminiCall",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Traces will appear as "MyCustomGeminiCall" in Opik
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Nested Tracing
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { Opik } from "opik";
|
|
144
|
+
import { trackGemini } from "opik-gemini";
|
|
145
|
+
|
|
146
|
+
const opikClient = new Opik();
|
|
147
|
+
const trackedGenAI = trackGemini(genAI, { client: opikClient });
|
|
148
|
+
|
|
149
|
+
async function processQuery(query: string) {
|
|
150
|
+
// Create parent trace
|
|
151
|
+
const trace = opikClient.trace({
|
|
152
|
+
name: "ProcessUserQuery",
|
|
153
|
+
input: { query },
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Gemini call will be nested under this trace
|
|
157
|
+
const trackedGenAIWithParent = trackGemini(genAI, {
|
|
158
|
+
parent: trace,
|
|
159
|
+
client: opikClient,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const response = await trackedGenAIWithParent.models.generateContent({
|
|
163
|
+
model: "gemini-2.0-flash-001",
|
|
164
|
+
contents: query,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
trace.update({
|
|
168
|
+
output: { response: response.text },
|
|
169
|
+
});
|
|
170
|
+
trace.end();
|
|
171
|
+
|
|
172
|
+
return response;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Configuration
|
|
177
|
+
|
|
178
|
+
### TrackOpikConfig Options
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
interface TrackOpikConfig {
|
|
182
|
+
/** Opik client instance (optional, creates singleton if not provided) */
|
|
183
|
+
client?: Opik;
|
|
184
|
+
|
|
185
|
+
/** Custom name for the generation (optional, defaults to method name) */
|
|
186
|
+
generationName?: string;
|
|
187
|
+
|
|
188
|
+
/** Parent trace or span for nested tracing (optional) */
|
|
189
|
+
parent?: Trace | Span;
|
|
190
|
+
|
|
191
|
+
/** Additional metadata for traces (optional) */
|
|
192
|
+
traceMetadata?: {
|
|
193
|
+
tags?: string[];
|
|
194
|
+
[key: string]: unknown;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## What Gets Tracked
|
|
200
|
+
|
|
201
|
+
The integration automatically captures:
|
|
202
|
+
|
|
203
|
+
- **Input**: Prompt contents and generation config
|
|
204
|
+
- **Output**: Generated text, candidates, and safety ratings
|
|
205
|
+
- **Model**: Model name/version (e.g., "gemini-pro", "gemini-1.5-flash")
|
|
206
|
+
- **Usage**: Token counts (prompt, completion, total)
|
|
207
|
+
- **Metadata**: Provider info, model settings, safety settings
|
|
208
|
+
- **Errors**: Error messages and stack traces
|
|
209
|
+
- **Timing**: Start/end times and duration
|
|
210
|
+
|
|
211
|
+
## Best Practices
|
|
212
|
+
|
|
213
|
+
1. **Always call `flush()` before process exit** (especially in short-lived scripts):
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
await trackedGenAI.flush();
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
2. **Use descriptive tags** for easier filtering:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
trackGemini(genAI, {
|
|
223
|
+
traceMetadata: {
|
|
224
|
+
tags: ["production", "customer-support", "v2"],
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
3. **Reuse Opik client** across your application for consistency:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const opikClient = new Opik({ projectName: "my-project" });
|
|
233
|
+
const trackedGenAI = trackGemini(genAI, { client: opikClient });
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
4. **Use nested tracing** for complex workflows to understand call hierarchies.
|
|
237
|
+
|
|
238
|
+
## Supported Gemini Models
|
|
239
|
+
|
|
240
|
+
This integration supports all Google Gemini models including:
|
|
241
|
+
|
|
242
|
+
- `gemini-2.0-flash-001` (Latest, recommended for most use cases)
|
|
243
|
+
- `gemini-1.5-pro`
|
|
244
|
+
- `gemini-1.5-flash`
|
|
245
|
+
- `gemini-pro`
|
|
246
|
+
- `gemini-pro-vision`
|
|
247
|
+
- Any future Gemini models
|
|
248
|
+
|
|
249
|
+
Refer to [Google's official documentation](https://ai.google.dev/models/gemini) for the complete list of available models and their capabilities.
|
|
250
|
+
|
|
251
|
+
## Development
|
|
252
|
+
|
|
253
|
+
### Building
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm run build
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Type Checking
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npm run typecheck
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Testing
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
npm test
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Examples
|
|
272
|
+
|
|
273
|
+
Check out the [examples directory](../../../../../../examples) for more usage examples.
|
|
274
|
+
|
|
275
|
+
## Documentation
|
|
276
|
+
|
|
277
|
+
- [Opik Documentation](https://www.comet.com/docs/opik/)
|
|
278
|
+
- [Google Gemini Documentation](https://ai.google.dev/)
|
|
279
|
+
- [TypeScript SDK Guide](https://www.comet.com/docs/opik/typescript-sdk)
|
|
280
|
+
|
|
281
|
+
## Support
|
|
282
|
+
|
|
283
|
+
- [GitHub Issues](https://github.com/comet-ml/opik/issues)
|
|
284
|
+
- [Comet Support](mailto:support@comet.com)
|
|
285
|
+
- [Community Slack](https://www.comet.com/docs/opik/community/)
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
Apache-2.0
|
|
290
|
+
|
|
291
|
+
## Contributing
|
|
292
|
+
|
|
293
|
+
Contributions are welcome! Please see our [Contributing Guide](https://github.com/comet-ml/opik/blob/main/CONTRIBUTING.md).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var opik=require('opik');var R=class R{static getInstance(e){return R.instance||(R.instance=new opik.Opik(e)),R.instance}};R.instance=null;var A=R;var x=a=>{if(a)return a.replace(/^models\//,"")};var G=a=>a!==null&&typeof a=="object"&&Symbol.asyncIterator in a&&typeof a[Symbol.asyncIterator]=="function",P=(a,e="")=>{let o={};for(let[n,t]of Object.entries(a)){let i=e?`${e}.${n}`:n;t&&typeof t=="object"&&!Array.isArray(t)&&!(t instanceof Date)?Object.assign(o,P(t,i)):o[i]=t;}return o};var E=a=>{let e=a.model;e&&(e=x(e));let o={};"contents"in a&&(o.contents=a.contents),"prompt"in a&&(o.prompt=a.prompt),"config"in a&&(o.config=a.config);let n={};if("config"in a&&typeof a.config=="object"&&a.config!==null){let t=a.config;"systemInstruction"in t&&(n.systemInstruction=t.systemInstruction),"generationConfig"in t&&(n.generationConfig=t.generationConfig),"safetySettings"in t&&(n.safetySettings=t.safetySettings),"tools"in t&&(n.tools=t.tools),"toolConfig"in t&&(n.toolConfig=t.toolConfig);let i=["temperature","maxOutputTokens","topP","topK","candidateCount","stopSequences"];for(let c of i)c in t&&(n[c]=t[c]);}return {model:e,input:o,modelParameters:n}},O=a=>{if(!a||typeof a!="object")return;let e=a;if("candidates"in e&&Array.isArray(e.candidates))return e.candidates.length===0?void 0:{candidates:e.candidates}},D=a=>{if(!a||typeof a!="object")return;let e=a,o="usageMetadata"in e&&e.usageMetadata||"usage_metadata"in e&&e.usage_metadata||"usage"in e&&e.usage;if(!o||typeof o!="object")return;let n=o,t={};(typeof n.promptTokenCount=="number"||typeof n.prompt_token_count=="number")&&(t.prompt_tokens=n.promptTokenCount||n.prompt_token_count),(typeof n.candidatesTokenCount=="number"||typeof n.candidates_token_count=="number")&&(t.completion_tokens=n.candidatesTokenCount||n.candidates_token_count),(typeof n.totalTokenCount=="number"||typeof n.total_token_count=="number")&&(t.total_tokens=n.totalTokenCount||n.total_token_count),(typeof n.cachedContentTokenCount=="number"||typeof n.cached_content_token_count=="number")&&(t.cached_content_tokens=n.cachedContentTokenCount||n.cached_content_token_count);let i={...n};delete i.promptTokensDetails,delete i.prompt_tokens_details,delete i.candidatesTokensDetails,delete i.candidates_tokens_details;let c=P(i,"original_usage");for(let[l,s]of Object.entries(c))typeof s=="number"&&(t[l]=s);return Object.keys(t).length>0?t:void 0},M=a=>{if(!a||typeof a!="object")return {isToolCall:false,data:""};let e=a;if("candidates"in e&&Array.isArray(e.candidates)&&e.candidates.length>0){let o=e.candidates[0];if(o&&"content"in o&&o.content&&typeof o.content=="object"){let n=o.content;if("parts"in n&&Array.isArray(n.parts)&&n.parts.length>0){let t=n.parts[0];if("functionCall"in t||"function_call"in t)return {isToolCall:true,data:JSON.stringify(t.functionCall||t.function_call)};if("text"in t&&typeof t.text=="string")return {isToolCall:false,data:t.text}}}}return {isToolCall:false,data:""}},C=a=>{if(!a||typeof a!="object")return {model:void 0,metadata:void 0};let e=a,o,n={};if("modelVersion"in e&&typeof e.modelVersion=="string"?o=x(e.modelVersion):"model_version"in e&&typeof e.model_version=="string"&&(o=x(e.model_version)),"candidates"in e&&Array.isArray(e.candidates)&&e.candidates.length>0){let t=e.candidates[0];"safetyRatings"in t?n.safety_ratings=t.safetyRatings:"safety_ratings"in t&&(n.safety_ratings=t.safety_ratings),"finishReason"in t?n.finish_reason=t.finishReason:"finish_reason"in t&&(n.finish_reason=t.finish_reason);}return "promptFeedback"in e?n.prompt_feedback=e.promptFeedback:"prompt_feedback"in e&&(n.prompt_feedback=e.prompt_feedback),n.created_from="genai",{model:o,metadata:Object.keys(n).length>0?n:void 0}};var v=(a,e)=>(...o)=>{var g;let{model:n,input:t,modelParameters:i}=E(o[0]),{tags:c=[],...l}=(g=e==null?void 0:e.traceMetadata)!=null?g:{},s={name:e.generationName,startTime:new Date,input:t,model:n,provider:e.provider,metadata:{...l,...i},tags:["genai",...c]},r,y=!!(e!=null&&e.parent);e!=null&&e.parent?r=e.parent:r=e.client.trace(s);try{let d=a(...o);if(G(d))return N(d,r,y,s);if(d instanceof Promise)return d.then(p=>{if(G(p))return N(p,r,y,s);let F=O(p),S=D(p),{model:$,metadata:K}=C(p),L=r.span({...s,output:F,endTime:new Date,usage:S,model:$||s.model,type:opik.OpikSpanType.Llm,metadata:{...s.metadata,...K,usage:S}}),j=p;return j.functionCalls&&Array.isArray(j.functionCalls)&&j.functionCalls.forEach(h=>{L.span({name:`function_call: ${h.name}`,startTime:new Date,endTime:new Date,input:{arguments:h.args},output:{function_name:h.name},type:opik.OpikSpanType.Tool,metadata:{function_name:h.name,function_arguments:h.args}});}),y||r.update({output:F,endTime:new Date}),p}).catch(p=>{throw I(p,r,s),p});let k=O(d),m=D(d),{model:b,metadata:f}=C(d),u=r.span({...s,output:k,endTime:new Date,usage:m,model:b||s.model,type:opik.OpikSpanType.Llm,metadata:{...s.metadata,...f,usage:m}}),_=d;return _.functionCalls&&Array.isArray(_.functionCalls)&&_.functionCalls.forEach(w=>{u.span({name:`function_call: ${w.name}`,startTime:new Date,endTime:new Date,input:{arguments:w.args},output:{function_name:w.name},type:opik.OpikSpanType.Tool,metadata:{function_name:w.name,function_arguments:w.args}});}),y||r.update({output:k,endTime:new Date}),d}catch(d){throw I(d,r,s),d}};function N(a,e,o,n){async function*t(){var y,g,d,k;let i=[],c=[],l,s={},r;try{for await(let f of a){i.push(f);let u=M(f);u.isToolCall||c.push(u.data),yield f;}let m=i[i.length-1];if(m){l=D(m),r=C(m);let f=c.join(""),u=O(m);if(u!=null&&u.candidates){s={candidates:structuredClone(u.candidates)};let _=s;(k=(d=(g=(y=_.candidates)==null?void 0:y[0])==null?void 0:g.content)==null?void 0:d.parts)!=null&&k[0]&&(_.candidates[0].content.parts[0].text=f);}}let b=e.span({...n,output:s,endTime:new Date,usage:l,model:(r==null?void 0:r.model)||n.model,type:opik.OpikSpanType.Llm,metadata:{...n.metadata,...r==null?void 0:r.metadata,usage:l}});if(m&&typeof m=="object"){let f=m;f.functionCalls&&Array.isArray(f.functionCalls)&&f.functionCalls.forEach(u=>{b.span({name:`function_call: ${u.name}`,startTime:new Date,endTime:new Date,input:{arguments:u.args},output:{function_name:u.name},type:opik.OpikSpanType.Tool,metadata:{function_name:u.name,function_arguments:u.args}});});}o||e.update({output:s,endTime:new Date});}catch(m){throw I(m,e,n),m}}return t()}var I=(a,e,o)=>{var n;e.span({...o,endTime:new Date,type:opik.OpikSpanType.Llm,errorInfo:{message:a.message,exceptionType:a.name,traceback:(n=a.stack)!=null?n:""}}),e.end();};var V=a=>"vertexai"in a&&a.vertexai?"google_vertexai":"google_ai",z=(a,e)=>{let o=V(a);return new Proxy(a,{get(n,t,i){var g,d,k;let c=n[t],l=((g=a.constructor)==null?void 0:g.name)||"Gemini",r={generationName:(d=e==null?void 0:e.generationName)!=null?d:`${l}.${t.toString()}`,client:(k=e==null?void 0:e.client)!=null?k:A.getInstance(),parent:e==null?void 0:e.parent,traceMetadata:e==null?void 0:e.traceMetadata,provider:o};return t==="flush"?r.client.flush.bind(r.client):typeof c=="function"?v(c.bind(n),r):c&&!Array.isArray(c)&&!(c instanceof Date)&&typeof c=="object"?new Proxy(c,{get(m,b,f){let u=m[b];return typeof u=="function"?v(u.bind(m),r):Reflect.get(m,b,f)}}):Reflect.get(n,t,i)}})};
|
|
2
|
+
exports.trackGemini=z;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Trace, Span, Opik } from 'opik';
|
|
2
|
+
|
|
3
|
+
type OpikParent = Trace | Span;
|
|
4
|
+
type TrackOpikConfig = {
|
|
5
|
+
/**
|
|
6
|
+
* Project name for this trace
|
|
7
|
+
*/
|
|
8
|
+
projectName?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Parent span or trace for this generation
|
|
11
|
+
*/
|
|
12
|
+
parent?: OpikParent;
|
|
13
|
+
/**
|
|
14
|
+
* Generation name (defaults to "ClassName.methodName")
|
|
15
|
+
*/
|
|
16
|
+
generationName?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Trace metadata (tags, custom fields)
|
|
19
|
+
*/
|
|
20
|
+
traceMetadata?: Record<string, unknown> & {
|
|
21
|
+
tags?: string[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Opik client instance
|
|
25
|
+
*/
|
|
26
|
+
client?: Opik;
|
|
27
|
+
};
|
|
28
|
+
type OpikExtension = {
|
|
29
|
+
/**
|
|
30
|
+
* Flush all pending traces and spans to Opik backend
|
|
31
|
+
*/
|
|
32
|
+
flush: () => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Track Gemini client with Opik observability
|
|
37
|
+
*
|
|
38
|
+
* Wraps a Gemini SDK instance (GoogleGenerativeAI or any Gemini client) to automatically
|
|
39
|
+
* create traces and spans for all generation calls.
|
|
40
|
+
*
|
|
41
|
+
* @param sdk - The Gemini SDK instance to track
|
|
42
|
+
* @param opikConfig - Configuration for Opik tracking
|
|
43
|
+
* @returns Proxied SDK instance with flush() method
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
46
|
+
declare const trackGemini: <SDKType extends object>(sdk: SDKType, opikConfig?: TrackOpikConfig) => SDKType & OpikExtension;
|
|
47
|
+
|
|
48
|
+
export { trackGemini };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Trace, Span, Opik } from 'opik';
|
|
2
|
+
|
|
3
|
+
type OpikParent = Trace | Span;
|
|
4
|
+
type TrackOpikConfig = {
|
|
5
|
+
/**
|
|
6
|
+
* Project name for this trace
|
|
7
|
+
*/
|
|
8
|
+
projectName?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Parent span or trace for this generation
|
|
11
|
+
*/
|
|
12
|
+
parent?: OpikParent;
|
|
13
|
+
/**
|
|
14
|
+
* Generation name (defaults to "ClassName.methodName")
|
|
15
|
+
*/
|
|
16
|
+
generationName?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Trace metadata (tags, custom fields)
|
|
19
|
+
*/
|
|
20
|
+
traceMetadata?: Record<string, unknown> & {
|
|
21
|
+
tags?: string[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Opik client instance
|
|
25
|
+
*/
|
|
26
|
+
client?: Opik;
|
|
27
|
+
};
|
|
28
|
+
type OpikExtension = {
|
|
29
|
+
/**
|
|
30
|
+
* Flush all pending traces and spans to Opik backend
|
|
31
|
+
*/
|
|
32
|
+
flush: () => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Track Gemini client with Opik observability
|
|
37
|
+
*
|
|
38
|
+
* Wraps a Gemini SDK instance (GoogleGenerativeAI or any Gemini client) to automatically
|
|
39
|
+
* create traces and spans for all generation calls.
|
|
40
|
+
*
|
|
41
|
+
* @param sdk - The Gemini SDK instance to track
|
|
42
|
+
* @param opikConfig - Configuration for Opik tracking
|
|
43
|
+
* @returns Proxied SDK instance with flush() method
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
46
|
+
declare const trackGemini: <SDKType extends object>(sdk: SDKType, opikConfig?: TrackOpikConfig) => SDKType & OpikExtension;
|
|
47
|
+
|
|
48
|
+
export { trackGemini };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {Opik,OpikSpanType}from'opik';var R=class R{static getInstance(e){return R.instance||(R.instance=new Opik(e)),R.instance}};R.instance=null;var A=R;var x=a=>{if(a)return a.replace(/^models\//,"")};var G=a=>a!==null&&typeof a=="object"&&Symbol.asyncIterator in a&&typeof a[Symbol.asyncIterator]=="function",P=(a,e="")=>{let o={};for(let[n,t]of Object.entries(a)){let i=e?`${e}.${n}`:n;t&&typeof t=="object"&&!Array.isArray(t)&&!(t instanceof Date)?Object.assign(o,P(t,i)):o[i]=t;}return o};var E=a=>{let e=a.model;e&&(e=x(e));let o={};"contents"in a&&(o.contents=a.contents),"prompt"in a&&(o.prompt=a.prompt),"config"in a&&(o.config=a.config);let n={};if("config"in a&&typeof a.config=="object"&&a.config!==null){let t=a.config;"systemInstruction"in t&&(n.systemInstruction=t.systemInstruction),"generationConfig"in t&&(n.generationConfig=t.generationConfig),"safetySettings"in t&&(n.safetySettings=t.safetySettings),"tools"in t&&(n.tools=t.tools),"toolConfig"in t&&(n.toolConfig=t.toolConfig);let i=["temperature","maxOutputTokens","topP","topK","candidateCount","stopSequences"];for(let c of i)c in t&&(n[c]=t[c]);}return {model:e,input:o,modelParameters:n}},O=a=>{if(!a||typeof a!="object")return;let e=a;if("candidates"in e&&Array.isArray(e.candidates))return e.candidates.length===0?void 0:{candidates:e.candidates}},D=a=>{if(!a||typeof a!="object")return;let e=a,o="usageMetadata"in e&&e.usageMetadata||"usage_metadata"in e&&e.usage_metadata||"usage"in e&&e.usage;if(!o||typeof o!="object")return;let n=o,t={};(typeof n.promptTokenCount=="number"||typeof n.prompt_token_count=="number")&&(t.prompt_tokens=n.promptTokenCount||n.prompt_token_count),(typeof n.candidatesTokenCount=="number"||typeof n.candidates_token_count=="number")&&(t.completion_tokens=n.candidatesTokenCount||n.candidates_token_count),(typeof n.totalTokenCount=="number"||typeof n.total_token_count=="number")&&(t.total_tokens=n.totalTokenCount||n.total_token_count),(typeof n.cachedContentTokenCount=="number"||typeof n.cached_content_token_count=="number")&&(t.cached_content_tokens=n.cachedContentTokenCount||n.cached_content_token_count);let i={...n};delete i.promptTokensDetails,delete i.prompt_tokens_details,delete i.candidatesTokensDetails,delete i.candidates_tokens_details;let c=P(i,"original_usage");for(let[l,s]of Object.entries(c))typeof s=="number"&&(t[l]=s);return Object.keys(t).length>0?t:void 0},M=a=>{if(!a||typeof a!="object")return {isToolCall:false,data:""};let e=a;if("candidates"in e&&Array.isArray(e.candidates)&&e.candidates.length>0){let o=e.candidates[0];if(o&&"content"in o&&o.content&&typeof o.content=="object"){let n=o.content;if("parts"in n&&Array.isArray(n.parts)&&n.parts.length>0){let t=n.parts[0];if("functionCall"in t||"function_call"in t)return {isToolCall:true,data:JSON.stringify(t.functionCall||t.function_call)};if("text"in t&&typeof t.text=="string")return {isToolCall:false,data:t.text}}}}return {isToolCall:false,data:""}},C=a=>{if(!a||typeof a!="object")return {model:void 0,metadata:void 0};let e=a,o,n={};if("modelVersion"in e&&typeof e.modelVersion=="string"?o=x(e.modelVersion):"model_version"in e&&typeof e.model_version=="string"&&(o=x(e.model_version)),"candidates"in e&&Array.isArray(e.candidates)&&e.candidates.length>0){let t=e.candidates[0];"safetyRatings"in t?n.safety_ratings=t.safetyRatings:"safety_ratings"in t&&(n.safety_ratings=t.safety_ratings),"finishReason"in t?n.finish_reason=t.finishReason:"finish_reason"in t&&(n.finish_reason=t.finish_reason);}return "promptFeedback"in e?n.prompt_feedback=e.promptFeedback:"prompt_feedback"in e&&(n.prompt_feedback=e.prompt_feedback),n.created_from="genai",{model:o,metadata:Object.keys(n).length>0?n:void 0}};var v=(a,e)=>(...o)=>{var g;let{model:n,input:t,modelParameters:i}=E(o[0]),{tags:c=[],...l}=(g=e==null?void 0:e.traceMetadata)!=null?g:{},s={name:e.generationName,startTime:new Date,input:t,model:n,provider:e.provider,metadata:{...l,...i},tags:["genai",...c]},r,y=!!(e!=null&&e.parent);e!=null&&e.parent?r=e.parent:r=e.client.trace(s);try{let d=a(...o);if(G(d))return N(d,r,y,s);if(d instanceof Promise)return d.then(p=>{if(G(p))return N(p,r,y,s);let F=O(p),S=D(p),{model:$,metadata:K}=C(p),L=r.span({...s,output:F,endTime:new Date,usage:S,model:$||s.model,type:OpikSpanType.Llm,metadata:{...s.metadata,...K,usage:S}}),j=p;return j.functionCalls&&Array.isArray(j.functionCalls)&&j.functionCalls.forEach(h=>{L.span({name:`function_call: ${h.name}`,startTime:new Date,endTime:new Date,input:{arguments:h.args},output:{function_name:h.name},type:OpikSpanType.Tool,metadata:{function_name:h.name,function_arguments:h.args}});}),y||r.update({output:F,endTime:new Date}),p}).catch(p=>{throw I(p,r,s),p});let k=O(d),m=D(d),{model:b,metadata:f}=C(d),u=r.span({...s,output:k,endTime:new Date,usage:m,model:b||s.model,type:OpikSpanType.Llm,metadata:{...s.metadata,...f,usage:m}}),_=d;return _.functionCalls&&Array.isArray(_.functionCalls)&&_.functionCalls.forEach(w=>{u.span({name:`function_call: ${w.name}`,startTime:new Date,endTime:new Date,input:{arguments:w.args},output:{function_name:w.name},type:OpikSpanType.Tool,metadata:{function_name:w.name,function_arguments:w.args}});}),y||r.update({output:k,endTime:new Date}),d}catch(d){throw I(d,r,s),d}};function N(a,e,o,n){async function*t(){var y,g,d,k;let i=[],c=[],l,s={},r;try{for await(let f of a){i.push(f);let u=M(f);u.isToolCall||c.push(u.data),yield f;}let m=i[i.length-1];if(m){l=D(m),r=C(m);let f=c.join(""),u=O(m);if(u!=null&&u.candidates){s={candidates:structuredClone(u.candidates)};let _=s;(k=(d=(g=(y=_.candidates)==null?void 0:y[0])==null?void 0:g.content)==null?void 0:d.parts)!=null&&k[0]&&(_.candidates[0].content.parts[0].text=f);}}let b=e.span({...n,output:s,endTime:new Date,usage:l,model:(r==null?void 0:r.model)||n.model,type:OpikSpanType.Llm,metadata:{...n.metadata,...r==null?void 0:r.metadata,usage:l}});if(m&&typeof m=="object"){let f=m;f.functionCalls&&Array.isArray(f.functionCalls)&&f.functionCalls.forEach(u=>{b.span({name:`function_call: ${u.name}`,startTime:new Date,endTime:new Date,input:{arguments:u.args},output:{function_name:u.name},type:OpikSpanType.Tool,metadata:{function_name:u.name,function_arguments:u.args}});});}o||e.update({output:s,endTime:new Date});}catch(m){throw I(m,e,n),m}}return t()}var I=(a,e,o)=>{var n;e.span({...o,endTime:new Date,type:OpikSpanType.Llm,errorInfo:{message:a.message,exceptionType:a.name,traceback:(n=a.stack)!=null?n:""}}),e.end();};var V=a=>"vertexai"in a&&a.vertexai?"google_vertexai":"google_ai",z=(a,e)=>{let o=V(a);return new Proxy(a,{get(n,t,i){var g,d,k;let c=n[t],l=((g=a.constructor)==null?void 0:g.name)||"Gemini",r={generationName:(d=e==null?void 0:e.generationName)!=null?d:`${l}.${t.toString()}`,client:(k=e==null?void 0:e.client)!=null?k:A.getInstance(),parent:e==null?void 0:e.parent,traceMetadata:e==null?void 0:e.traceMetadata,provider:o};return t==="flush"?r.client.flush.bind(r.client):typeof c=="function"?v(c.bind(n),r):c&&!Array.isArray(c)&&!(c instanceof Date)&&typeof c=="object"?new Proxy(c,{get(m,b,f){let u=m[b];return typeof u=="function"?v(u.bind(m),r):Reflect.get(m,b,f)}}):Reflect.get(n,t,i)}})};
|
|
2
|
+
export{z as trackGemini};
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opik-gemini",
|
|
3
|
+
"description": "Opik TypeScript and JavaScript SDK integration with Google Gemini AI",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=18"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/comet-ml/opik.git",
|
|
11
|
+
"directory": "sdks/typescript/src/opik/integrations/opik-gemini"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://www.comet.com/docs/opik/",
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "Comet",
|
|
16
|
+
"email": "support@comet.com",
|
|
17
|
+
"url": "https://github.com/comet-ml"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/comet-ml/opik/issues",
|
|
21
|
+
"email": "support@comet.com"
|
|
22
|
+
},
|
|
23
|
+
"license": "Apache-2.0",
|
|
24
|
+
"keywords": [
|
|
25
|
+
"opik",
|
|
26
|
+
"gemini",
|
|
27
|
+
"google-ai",
|
|
28
|
+
"google-generative-ai",
|
|
29
|
+
"genai",
|
|
30
|
+
"gemini-integration",
|
|
31
|
+
"sdk",
|
|
32
|
+
"javascript",
|
|
33
|
+
"javascript-sdk",
|
|
34
|
+
"typescript",
|
|
35
|
+
"typescript-sdk",
|
|
36
|
+
"llm",
|
|
37
|
+
"tracing",
|
|
38
|
+
"observability",
|
|
39
|
+
"comet"
|
|
40
|
+
],
|
|
41
|
+
"exports": {
|
|
42
|
+
"./package.json": "./package.json",
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"import": "./dist/index.js",
|
|
46
|
+
"require": "./dist/index.cjs"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"main": "dist/index.cjs",
|
|
50
|
+
"module": "dist/index.js",
|
|
51
|
+
"types": "dist/index.d.ts",
|
|
52
|
+
"type": "module",
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"watch": "tsup --watch",
|
|
56
|
+
"lint": "eslint '**/*.{ts,tsx}'",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"format": "prettier --write 'src/**/*.{ts,tsx,js,jsx,json,md}'",
|
|
59
|
+
"test": "vitest"
|
|
60
|
+
},
|
|
61
|
+
"files": [
|
|
62
|
+
"dist/**/*",
|
|
63
|
+
"README.md"
|
|
64
|
+
],
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@google/genai": ">=1.0.0",
|
|
67
|
+
"opik": "^1.7.25"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@eslint/js": "^9.20.0",
|
|
71
|
+
"eslint": "^9.34.0",
|
|
72
|
+
"globals": "^15.14.0",
|
|
73
|
+
"prettier": "^3.6.2",
|
|
74
|
+
"tsup": "^8.5.0",
|
|
75
|
+
"typescript": "^5.9.2",
|
|
76
|
+
"typescript-eslint": "^8.42.0",
|
|
77
|
+
"vitest": "^3.0.5"
|
|
78
|
+
}
|
|
79
|
+
}
|