Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(core): Support stream responses and tool calls for Google GenAI #17664

New issue

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

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

Already on GitHub? Sign in to your account

Open
RulaKhaled wants to merge 21 commits into develop
base: develop
Choose a base branch
Loading
from instrument-genai-google-stream
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7aaece3
feat: Add instrumentation for google genai
RulaKhaled Sep 11, 2025
f3d5940
some refacotor
RulaKhaled Sep 12, 2025
251fccd
fix lint issues
RulaKhaled Sep 15, 2025
0c5648e
add missing imports
RulaKhaled Sep 15, 2025
275a9a7
simplify promise handle
RulaKhaled Sep 15, 2025
6fb278a
fix lint
RulaKhaled Sep 18, 2025
afa472c
lint again
RulaKhaled Sep 18, 2025
b1db2bb
adding a version to package.json
RulaKhaled Sep 18, 2025
7f9b83a
try to fix yarn lock
RulaKhaled Sep 19, 2025
d3053a3
quick comments
RulaKhaled Sep 19, 2025
72c104d
feat(core): Support stream responses
RulaKhaled Sep 16, 2025
61f204d
Update with tool calls
RulaKhaled Sep 16, 2025
25d87ea
resolve conflicts
RulaKhaled Sep 19, 2025
945920b
Merge branch 'develop' into instrument-genai-google-stream
RulaKhaled Sep 19, 2025
39ca9c3
update lint
RulaKhaled Sep 19, 2025
c6c2071
update lint
RulaKhaled Sep 19, 2025
1baed6e
quick refactor
RulaKhaled Sep 19, 2025
cace9b7
fix port issues
RulaKhaled Sep 22, 2025
c63680f
lint
RulaKhaled Sep 22, 2025
6c7272f
fix port issue2
RulaKhaled Sep 22, 2025
6eb8690
Merge branch 'develop' into instrument-genai-google-stream
RulaKhaled Sep 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import { GoogleGenAI } from '@google/genai';
import * as Sentry from '@sentry/node';
import express from 'express';

function startMockGoogleGenAIServer() {
const app = express();
app.use(express.json());

// Streaming endpoint for models.generateContentStream and chat.sendMessageStream
app.post('/v1beta/models/:model\\:streamGenerateContent', (req, res) => {
const model = req.params.model;

if (model === 'error-model') {
res.status(404).set('x-request-id', 'mock-request-123').end('Model not found');
return;
}

// Set headers for streaming response
res.setHeader('Content-Type', 'application/json');
res.setHeader('Transfer-Encoding', 'chunked');

// Create a mock stream
const mockStream = createMockStream(model);

// Send chunks
const sendChunk = async () => {
const { value, done } = await mockStream.next();
if (done) {
res.end();
return;
}

res.write(`data: ${JSON.stringify(value)}\n\n`);
setTimeout(sendChunk, 10); // Small delay between chunks
};

sendChunk();
});

return new Promise(resolve => {
const server = app.listen(0, () => {
resolve(server);
});
});
}

// Helper function to create mock stream
async function* createMockStream(model) {
if (model === 'blocked-model') {
// First chunk: Contains promptFeedback with blockReason
yield {
promptFeedback: {
blockReason: 'SAFETY',
blockReasonMessage: 'The prompt was blocked due to safety concerns',
},
responseId: 'mock-blocked-response-streaming-id',
modelVersion: 'gemini-1.5-pro',
};

// Note: In a real blocked scenario, there would typically be no more chunks
// But we'll add one more to test that processing stops after the error
yield {
candidates: [
{
content: {
parts: [{ text: 'This should not be processed' }],
role: 'model',
},
index: 0,
},
],
};
return;
}

// First chunk: Start of response with initial text
yield {
candidates: [
{
content: {
parts: [{ text: 'Hello! ' }],
role: 'model',
},
index: 0,
},
],
responseId: 'mock-response-streaming-id',
modelVersion: 'gemini-1.5-pro',
};

// Second chunk: More text content
yield {
candidates: [
{
content: {
parts: [{ text: 'This is a streaming ' }],
role: 'model',
},
index: 0,
},
],
};

// Third chunk: Final text content
yield {
candidates: [
{
content: {
parts: [{ text: 'response from Google GenAI!' }],
role: 'model',
},
index: 0,
},
],
};

// Final chunk: End with finish reason and usage metadata
yield {
candidates: [
{
content: {
parts: [{ text: '' }], // Empty text in final chunk
role: 'model',
},
finishReason: 'STOP',
index: 0,
},
],
usageMetadata: {
promptTokenCount: 10,
candidatesTokenCount: 12,
totalTokenCount: 22,
},
};
}

async function run() {
const server = await startMockGoogleGenAIServer();

await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
const client = new GoogleGenAI({
apiKey: 'mock-api-key',
httpOptions: { baseUrl: `http://localhost:${server.address().port}` },
});

// Test 1: models.generateContentStream (streaming)
const streamResponse = await client.models.generateContentStream({
model: 'gemini-1.5-flash',
config: {
temperature: 0.7,
topP: 0.9,
maxOutputTokens: 100,
},
contents: [
{
role: 'user',
parts: [{ text: 'Tell me about streaming' }],
},
],
});

// Consume the stream
for await (const _ of streamResponse) {
void _;
}

// Test 2: chat.sendMessageStream (streaming)
const streamingChat = client.chats.create({
model: 'gemini-1.5-pro',
config: {
temperature: 0.8,
topP: 0.9,
maxOutputTokens: 150,
},
});

const chatStreamResponse = await streamingChat.sendMessageStream({
message: 'Tell me a streaming joke',
});

// Consume the chat stream
for await (const _ of chatStreamResponse) {
void _;
}

// Test 3: Blocked content streaming (should trigger error handling)
try {
const blockedStreamResponse = await client.models.generateContentStream({
model: 'blocked-model',
config: {
temperature: 0.7,
},
contents: [
{
role: 'user',
parts: [{ text: 'This should be blocked' }],
},
],
});

// Consume the blocked stream
for await (const _ of blockedStreamResponse) {
void _;
}
} catch (error) {
// Expected: The stream should be processed, but the span should be marked with error status
// The error handling happens in the streaming instrumentation, not as a thrown error
}

// Test 4: Error handling for streaming
try {
const errorStreamResponse = await client.models.generateContentStream({
model: 'error-model',
config: {
temperature: 0.7,
},
contents: [
{
role: 'user',
parts: [{ text: 'This will fail' }],
},
],
});

// Consume the error stream
for await (const _ of errorStreamResponse) {
void _;
}
} catch (error) {
// Expected error
}
});

server.close();
}

run();
Loading

AltStyle によって変換されたページ (->オリジナル) /