graphlit-client 1.0.20250611019 ā 1.0.20250612001
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/dist/client.js
CHANGED
@@ -1419,26 +1419,28 @@ class Graphlit {
|
|
1419
1419
|
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1420
1420
|
console.log("[supportsStreaming] Checking support for:", {
|
1421
1421
|
serviceType,
|
1422
|
-
hasOpenAI: this.openaiClient !== undefined,
|
1423
|
-
hasAnthropic: this.anthropicClient !== undefined,
|
1424
|
-
hasGoogle: this.googleClient !== undefined,
|
1422
|
+
hasOpenAI: OpenAI !== undefined || this.openaiClient !== undefined,
|
1423
|
+
hasAnthropic: Anthropic !== undefined || this.anthropicClient !== undefined,
|
1424
|
+
hasGoogle: GoogleGenerativeAI !== undefined || this.googleClient !== undefined,
|
1425
1425
|
});
|
1426
1426
|
}
|
1427
1427
|
switch (serviceType) {
|
1428
1428
|
case Types.ModelServiceTypes.OpenAi:
|
1429
|
-
return this.openaiClient !== undefined;
|
1429
|
+
return OpenAI !== undefined || this.openaiClient !== undefined;
|
1430
1430
|
case Types.ModelServiceTypes.Anthropic:
|
1431
|
-
return this.anthropicClient !== undefined;
|
1431
|
+
return Anthropic !== undefined || this.anthropicClient !== undefined;
|
1432
1432
|
case Types.ModelServiceTypes.Google:
|
1433
|
-
return this.googleClient !== undefined;
|
1433
|
+
return GoogleGenerativeAI !== undefined || this.googleClient !== undefined;
|
1434
1434
|
default:
|
1435
1435
|
return false;
|
1436
1436
|
}
|
1437
1437
|
}
|
1438
1438
|
// If we have no specification, check if any client is available
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1439
|
+
// Check both module-level SDKs and instance-level clients
|
1440
|
+
const hasOpenAI = OpenAI !== undefined || this.openaiClient !== undefined;
|
1441
|
+
const hasAnthropic = Anthropic !== undefined || this.anthropicClient !== undefined;
|
1442
|
+
const hasGoogle = GoogleGenerativeAI !== undefined || this.googleClient !== undefined;
|
1443
|
+
return hasOpenAI || hasAnthropic || hasGoogle;
|
1442
1444
|
}
|
1443
1445
|
/**
|
1444
1446
|
* Execute an agent with non-streaming response
|
@@ -1640,9 +1642,7 @@ class Graphlit {
|
|
1640
1642
|
throw new Error("Failed to format conversation");
|
1641
1643
|
}
|
1642
1644
|
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1643
|
-
console.log("\nš [formatConversation] Response
|
1644
|
-
console.log("Formatted message:", formattedMessage.message);
|
1645
|
-
console.log("Full formatConversation response:", JSON.stringify(formatResponse.formatConversation, null, 2));
|
1645
|
+
console.log("\nš [formatConversation] Response", formattedMessage.message);
|
1646
1646
|
}
|
1647
1647
|
// Build message array with conversation history
|
1648
1648
|
const messages = [];
|
@@ -1658,12 +1658,21 @@ class Graphlit {
|
|
1658
1658
|
// Use the formatted message from formatConversation which already includes
|
1659
1659
|
// all context, RAG results, and conversation history
|
1660
1660
|
if (formattedMessage) {
|
1661
|
-
|
1661
|
+
const messageToAdd = {
|
1662
1662
|
__typename: "ConversationMessage",
|
1663
1663
|
role: formattedMessage.role || Types.ConversationRoleTypes.User,
|
1664
1664
|
message: formattedMessage.message,
|
1665
1665
|
timestamp: formattedMessage.timestamp || new Date().toISOString(),
|
1666
|
-
}
|
1666
|
+
};
|
1667
|
+
// Add image data if provided
|
1668
|
+
if (mimeType && data) {
|
1669
|
+
messageToAdd.mimeType = mimeType;
|
1670
|
+
messageToAdd.data = data;
|
1671
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1672
|
+
console.log(`\nš¼ļø [Streaming] Adding image data to message: ${mimeType}, ${data.length} chars`);
|
1673
|
+
}
|
1674
|
+
}
|
1675
|
+
messages.push(messageToAdd);
|
1667
1676
|
}
|
1668
1677
|
else {
|
1669
1678
|
throw new Error("No formatted message returned from formatConversation");
|
@@ -1677,7 +1686,16 @@ class Graphlit {
|
|
1677
1686
|
let toolCalls = [];
|
1678
1687
|
let roundMessage = "";
|
1679
1688
|
// Stream with appropriate provider
|
1680
|
-
if (
|
1689
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1690
|
+
console.log(`\nš [Streaming Decision] Service: ${serviceType}, Round: ${currentRound}`);
|
1691
|
+
console.log(` OpenAI available: ${!!(OpenAI || this.openaiClient)}`);
|
1692
|
+
console.log(` Anthropic available: ${!!(Anthropic || this.anthropicClient)}`);
|
1693
|
+
console.log(` Google available: ${!!(GoogleGenerativeAI || this.googleClient)}`);
|
1694
|
+
}
|
1695
|
+
if (serviceType === Types.ModelServiceTypes.OpenAi && (OpenAI || this.openaiClient)) {
|
1696
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1697
|
+
console.log(`\nā
[Streaming] Using OpenAI native streaming (Round ${currentRound})`);
|
1698
|
+
}
|
1681
1699
|
const openaiMessages = formatMessagesForOpenAI(messages);
|
1682
1700
|
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1683
1701
|
console.log("\nš [OpenAI] Formatted messages being sent to LLM:");
|
@@ -1688,9 +1706,15 @@ class Graphlit {
|
|
1688
1706
|
roundMessage = message;
|
1689
1707
|
toolCalls = calls;
|
1690
1708
|
});
|
1709
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1710
|
+
console.log(`\nš [Streaming] OpenAI native streaming completed (Round ${currentRound})`);
|
1711
|
+
}
|
1691
1712
|
}
|
1692
1713
|
else if (serviceType === Types.ModelServiceTypes.Anthropic &&
|
1693
|
-
Anthropic) {
|
1714
|
+
(Anthropic || this.anthropicClient)) {
|
1715
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1716
|
+
console.log(`\nā
[Streaming] Using Anthropic native streaming (Round ${currentRound})`);
|
1717
|
+
}
|
1694
1718
|
const { system, messages: anthropicMessages } = formatMessagesForAnthropic(messages);
|
1695
1719
|
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1696
1720
|
console.log("\nš [Anthropic] Formatted messages being sent to LLM:");
|
@@ -1702,9 +1726,15 @@ class Graphlit {
|
|
1702
1726
|
roundMessage = message;
|
1703
1727
|
toolCalls = calls;
|
1704
1728
|
});
|
1729
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1730
|
+
console.log(`\nš [Streaming] Anthropic native streaming completed (Round ${currentRound})`);
|
1731
|
+
}
|
1705
1732
|
}
|
1706
1733
|
else if (serviceType === Types.ModelServiceTypes.Google &&
|
1707
|
-
GoogleGenerativeAI) {
|
1734
|
+
(GoogleGenerativeAI || this.googleClient)) {
|
1735
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1736
|
+
console.log(`\nā
[Streaming] Using Google native streaming (Round ${currentRound})`);
|
1737
|
+
}
|
1708
1738
|
const googleMessages = formatMessagesForGoogle(messages);
|
1709
1739
|
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1710
1740
|
console.log("\nš [Google] Formatted messages being sent to LLM:");
|
@@ -1717,10 +1747,21 @@ class Graphlit {
|
|
1717
1747
|
roundMessage = message;
|
1718
1748
|
toolCalls = calls;
|
1719
1749
|
});
|
1750
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1751
|
+
console.log(`\nš [Streaming] Google native streaming completed (Round ${currentRound})`);
|
1752
|
+
}
|
1720
1753
|
}
|
1721
1754
|
else {
|
1722
1755
|
// Fallback to non-streaming
|
1756
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1757
|
+
console.log(`\nā ļø [Fallback] No native streaming available for ${serviceType} (Round ${currentRound})`);
|
1758
|
+
console.log(` Falling back to non-streaming promptConversation`);
|
1759
|
+
console.log(` This should NOT happen if clients are properly set!`);
|
1760
|
+
}
|
1723
1761
|
await this.fallbackToNonStreaming(prompt, conversationId, specification, tools, mimeType, data, uiAdapter, correlationId);
|
1762
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
1763
|
+
console.log(`\nš [Fallback] Non-streaming fallback completed (Round ${currentRound})`);
|
1764
|
+
}
|
1724
1765
|
break;
|
1725
1766
|
}
|
1726
1767
|
// Update the full message
|
@@ -1964,8 +2005,21 @@ class Graphlit {
|
|
1964
2005
|
* Fallback to non-streaming when streaming is not available
|
1965
2006
|
*/
|
1966
2007
|
async fallbackToNonStreaming(prompt, conversationId, specification, tools, mimeType, data, uiAdapter, correlationId) {
|
2008
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
2009
|
+
console.log(`\nš [Fallback] Starting non-streaming fallback`);
|
2010
|
+
console.log(` Conversation ID: ${conversationId}`);
|
2011
|
+
console.log(` Specification: ${specification.name} (${specification.serviceType})`);
|
2012
|
+
console.log(` Prompt: "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"`);
|
2013
|
+
console.log(` About to call promptConversation...`);
|
2014
|
+
}
|
1967
2015
|
const response = await this.promptConversation(prompt, conversationId, { id: specification.id }, mimeType, data, tools, false, false, correlationId);
|
1968
2016
|
const message = response.promptConversation?.message;
|
2017
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
2018
|
+
console.log(`\nā
[Fallback] promptConversation completed`);
|
2019
|
+
console.log(` Response message length: ${message?.message?.length || 0} chars`);
|
2020
|
+
console.log(` Response preview: "${message?.message?.substring(0, 100) || 'NO MESSAGE'}${(message?.message?.length || 0) > 100 ? '...' : ''}"`);
|
2021
|
+
console.log(` Now simulating streaming by splitting into tokens...`);
|
2022
|
+
}
|
1969
2023
|
if (message?.message) {
|
1970
2024
|
// Simulate streaming by emitting tokens
|
1971
2025
|
const words = message.message.split(" ");
|
@@ -1974,46 +2028,72 @@ class Graphlit {
|
|
1974
2028
|
uiAdapter.handleEvent({ type: "token", token });
|
1975
2029
|
}
|
1976
2030
|
uiAdapter.handleEvent({ type: "message", message: message.message });
|
2031
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
2032
|
+
console.log(`\nšÆ [Fallback] Completed token simulation (${words.length} tokens)`);
|
2033
|
+
}
|
1977
2034
|
}
|
1978
2035
|
}
|
1979
2036
|
/**
|
1980
2037
|
* Stream with OpenAI client
|
1981
2038
|
*/
|
1982
2039
|
async streamWithOpenAI(specification, messages, tools, uiAdapter, onComplete) {
|
1983
|
-
if
|
2040
|
+
// Check if we have either the OpenAI module or a provided client
|
2041
|
+
if (!OpenAI && !this.openaiClient) {
|
1984
2042
|
throw new Error("OpenAI client not available");
|
1985
2043
|
}
|
1986
2044
|
// Use provided client or create a new one
|
1987
2045
|
const openaiClient = this.openaiClient ||
|
1988
|
-
new OpenAI({
|
2046
|
+
(OpenAI ? new OpenAI({
|
1989
2047
|
apiKey: process.env.OPENAI_API_KEY || "",
|
1990
|
-
});
|
2048
|
+
}) : (() => { throw new Error("OpenAI module not available"); })());
|
2049
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
2050
|
+
console.log("\nš [Graphlit SDK] Routing to OpenAI streaming provider");
|
2051
|
+
console.log(` š Specification: ${specification.name} (${specification.id})`);
|
2052
|
+
console.log(` š Messages: ${messages.length}`);
|
2053
|
+
console.log(` š§ Tools: ${tools?.length || 0}`);
|
2054
|
+
}
|
1991
2055
|
await streamWithOpenAI(specification, messages, tools, openaiClient, (event) => uiAdapter.handleEvent(event), onComplete);
|
1992
2056
|
}
|
1993
2057
|
/**
|
1994
2058
|
* Stream with Anthropic client
|
1995
2059
|
*/
|
1996
2060
|
async streamWithAnthropic(specification, messages, systemPrompt, tools, uiAdapter, onComplete) {
|
1997
|
-
if
|
2061
|
+
// Check if we have either the Anthropic module or a provided client
|
2062
|
+
if (!Anthropic && !this.anthropicClient) {
|
1998
2063
|
throw new Error("Anthropic client not available");
|
1999
2064
|
}
|
2000
2065
|
// Use provided client or create a new one
|
2001
2066
|
const anthropicClient = this.anthropicClient ||
|
2002
|
-
new Anthropic({
|
2067
|
+
(Anthropic ? new Anthropic({
|
2003
2068
|
apiKey: process.env.ANTHROPIC_API_KEY || "",
|
2004
|
-
});
|
2069
|
+
}) : (() => { throw new Error("Anthropic module not available"); })());
|
2070
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
2071
|
+
console.log("\nš [Graphlit SDK] Routing to Anthropic streaming provider");
|
2072
|
+
console.log(` š Specification: ${specification.name} (${specification.id})`);
|
2073
|
+
console.log(` š Messages: ${messages.length}`);
|
2074
|
+
console.log(` š§ Tools: ${tools?.length || 0}`);
|
2075
|
+
console.log(` š¬ System Prompt: ${systemPrompt ? 'Yes' : 'No'}`);
|
2076
|
+
}
|
2005
2077
|
await streamWithAnthropic(specification, messages, systemPrompt, tools, anthropicClient, (event) => uiAdapter.handleEvent(event), onComplete);
|
2006
2078
|
}
|
2007
2079
|
/**
|
2008
2080
|
* Stream with Google client
|
2009
2081
|
*/
|
2010
2082
|
async streamWithGoogle(specification, messages, systemPrompt, tools, uiAdapter, onComplete) {
|
2011
|
-
if
|
2083
|
+
// Check if we have either the Google module or a provided client
|
2084
|
+
if (!GoogleGenerativeAI && !this.googleClient) {
|
2012
2085
|
throw new Error("Google GenerativeAI client not available");
|
2013
2086
|
}
|
2014
2087
|
// Use provided client or create a new one
|
2015
2088
|
const googleClient = this.googleClient ||
|
2016
|
-
new GoogleGenerativeAI(process.env.GOOGLE_API_KEY || "");
|
2089
|
+
(GoogleGenerativeAI ? new GoogleGenerativeAI(process.env.GOOGLE_API_KEY || "") : (() => { throw new Error("Google GenerativeAI module not available"); })());
|
2090
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
2091
|
+
console.log("\nš [Graphlit SDK] Routing to Google streaming provider");
|
2092
|
+
console.log(` š Specification: ${specification.name} (${specification.id})`);
|
2093
|
+
console.log(` š Messages: ${messages.length}`);
|
2094
|
+
console.log(` š§ Tools: ${tools?.length || 0}`);
|
2095
|
+
console.log(` š¬ System Prompt: ${systemPrompt ? 'Yes' : 'No'}`);
|
2096
|
+
}
|
2017
2097
|
await streamWithGoogle(specification, messages, systemPrompt, tools, googleClient, (event) => uiAdapter.handleEvent(event), onComplete);
|
2018
2098
|
}
|
2019
2099
|
// Helper method to execute tools for promptAgent
|
@@ -4,7 +4,13 @@ import { ConversationMessage } from "../generated/graphql-types.js";
|
|
4
4
|
*/
|
5
5
|
export interface OpenAIMessage {
|
6
6
|
role: "system" | "user" | "assistant" | "tool";
|
7
|
-
content?: string
|
7
|
+
content?: string | Array<{
|
8
|
+
type: "text" | "image_url";
|
9
|
+
text?: string;
|
10
|
+
image_url?: {
|
11
|
+
url: string;
|
12
|
+
};
|
13
|
+
}>;
|
8
14
|
tool_calls?: Array<{
|
9
15
|
id: string;
|
10
16
|
type: "function";
|
@@ -21,8 +27,13 @@ export interface OpenAIMessage {
|
|
21
27
|
export interface AnthropicMessage {
|
22
28
|
role: "user" | "assistant";
|
23
29
|
content: string | Array<{
|
24
|
-
type: "text" | "tool_use" | "tool_result";
|
30
|
+
type: "text" | "image" | "tool_use" | "tool_result";
|
25
31
|
text?: string;
|
32
|
+
source?: {
|
33
|
+
type: "base64";
|
34
|
+
media_type: string;
|
35
|
+
data: string;
|
36
|
+
};
|
26
37
|
id?: string;
|
27
38
|
name?: string;
|
28
39
|
input?: unknown;
|
@@ -37,6 +48,10 @@ export interface GoogleMessage {
|
|
37
48
|
role: "user" | "model";
|
38
49
|
parts: Array<{
|
39
50
|
text?: string;
|
51
|
+
inlineData?: {
|
52
|
+
mimeType: string;
|
53
|
+
data: string;
|
54
|
+
};
|
40
55
|
functionCall?: {
|
41
56
|
name: string;
|
42
57
|
args: unknown;
|
@@ -53,10 +53,36 @@ export function formatMessagesForOpenAI(messages) {
|
|
53
53
|
});
|
54
54
|
break;
|
55
55
|
default: // User messages
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
// Check if this message has image data
|
57
|
+
if (message.mimeType && message.data) {
|
58
|
+
// Multi-modal message with image
|
59
|
+
const contentParts = [];
|
60
|
+
// Add text content if present
|
61
|
+
if (trimmedMessage) {
|
62
|
+
contentParts.push({
|
63
|
+
type: "text",
|
64
|
+
text: trimmedMessage,
|
65
|
+
});
|
66
|
+
}
|
67
|
+
// Add image content
|
68
|
+
contentParts.push({
|
69
|
+
type: "image_url",
|
70
|
+
image_url: {
|
71
|
+
url: `data:${message.mimeType};base64,${message.data}`,
|
72
|
+
},
|
73
|
+
});
|
74
|
+
formattedMessages.push({
|
75
|
+
role: "user",
|
76
|
+
content: contentParts,
|
77
|
+
});
|
78
|
+
}
|
79
|
+
else {
|
80
|
+
// Text-only message
|
81
|
+
formattedMessages.push({
|
82
|
+
role: "user",
|
83
|
+
content: trimmedMessage,
|
84
|
+
});
|
85
|
+
}
|
60
86
|
break;
|
61
87
|
}
|
62
88
|
}
|
@@ -122,10 +148,38 @@ export function formatMessagesForAnthropic(messages) {
|
|
122
148
|
});
|
123
149
|
break;
|
124
150
|
default: // User messages
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
151
|
+
// Check if this message has image data
|
152
|
+
if (message.mimeType && message.data) {
|
153
|
+
// Multi-modal message with image
|
154
|
+
const contentParts = [];
|
155
|
+
// Add text content if present
|
156
|
+
if (trimmedMessage) {
|
157
|
+
contentParts.push({
|
158
|
+
type: "text",
|
159
|
+
text: trimmedMessage,
|
160
|
+
});
|
161
|
+
}
|
162
|
+
// Add image content
|
163
|
+
contentParts.push({
|
164
|
+
type: "image",
|
165
|
+
source: {
|
166
|
+
type: "base64",
|
167
|
+
media_type: message.mimeType,
|
168
|
+
data: message.data,
|
169
|
+
},
|
170
|
+
});
|
171
|
+
formattedMessages.push({
|
172
|
+
role: "user",
|
173
|
+
content: contentParts,
|
174
|
+
});
|
175
|
+
}
|
176
|
+
else {
|
177
|
+
// Text-only message
|
178
|
+
formattedMessages.push({
|
179
|
+
role: "user",
|
180
|
+
content: trimmedMessage,
|
181
|
+
});
|
182
|
+
}
|
129
183
|
break;
|
130
184
|
}
|
131
185
|
}
|
@@ -138,9 +192,14 @@ export function formatMessagesForAnthropic(messages) {
|
|
138
192
|
export function formatMessagesForGoogle(messages) {
|
139
193
|
const formattedMessages = [];
|
140
194
|
for (const message of messages) {
|
141
|
-
if (!message.role
|
195
|
+
if (!message.role)
|
142
196
|
continue;
|
143
|
-
|
197
|
+
// Allow messages with image data even if they have no text content
|
198
|
+
const hasContent = message.message?.trim();
|
199
|
+
const hasImageData = message.mimeType && message.data;
|
200
|
+
if (!hasContent && !hasImageData)
|
201
|
+
continue;
|
202
|
+
const trimmedMessage = message.message?.trim() || "";
|
144
203
|
switch (message.role) {
|
145
204
|
case ConversationRoleTypes.System:
|
146
205
|
// Google handles system prompts differently, usually as part of the first user message
|
@@ -176,10 +235,33 @@ export function formatMessagesForGoogle(messages) {
|
|
176
235
|
});
|
177
236
|
break;
|
178
237
|
default: // User messages
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
238
|
+
// Check if this message has image data
|
239
|
+
if (message.mimeType && message.data) {
|
240
|
+
// Multi-modal message with image
|
241
|
+
const parts = [];
|
242
|
+
// Add text content if present
|
243
|
+
if (trimmedMessage) {
|
244
|
+
parts.push({ text: trimmedMessage });
|
245
|
+
}
|
246
|
+
// Add image content
|
247
|
+
parts.push({
|
248
|
+
inlineData: {
|
249
|
+
mimeType: message.mimeType,
|
250
|
+
data: message.data,
|
251
|
+
},
|
252
|
+
});
|
253
|
+
formattedMessages.push({
|
254
|
+
role: "user",
|
255
|
+
parts,
|
256
|
+
});
|
257
|
+
}
|
258
|
+
else {
|
259
|
+
// Text-only message
|
260
|
+
formattedMessages.push({
|
261
|
+
role: "user",
|
262
|
+
parts: [{ text: trimmedMessage }],
|
263
|
+
});
|
264
|
+
}
|
183
265
|
break;
|
184
266
|
}
|
185
267
|
}
|
@@ -18,6 +18,12 @@ export async function streamWithOpenAI(specification, messages, tools, openaiCli
|
|
18
18
|
onEvent, onComplete) {
|
19
19
|
let fullMessage = "";
|
20
20
|
let toolCalls = [];
|
21
|
+
// Performance metrics
|
22
|
+
const startTime = Date.now();
|
23
|
+
let firstTokenTime = 0;
|
24
|
+
let tokenCount = 0;
|
25
|
+
let lastEventTime = 0;
|
26
|
+
const interTokenDelays = [];
|
21
27
|
try {
|
22
28
|
const modelName = getModelName(specification);
|
23
29
|
if (!modelName) {
|
@@ -55,6 +61,9 @@ onEvent, onComplete) {
|
|
55
61
|
},
|
56
62
|
}));
|
57
63
|
}
|
64
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
65
|
+
console.log("\nā±ļø [OpenAI] Starting LLM call at:", new Date().toISOString());
|
66
|
+
}
|
58
67
|
const stream = await openaiClient.chat.completions.create(streamConfig);
|
59
68
|
for await (const chunk of stream) {
|
60
69
|
const delta = chunk.choices[0]?.delta;
|
@@ -73,9 +82,23 @@ onEvent, onComplete) {
|
|
73
82
|
}
|
74
83
|
if (delta?.content) {
|
75
84
|
fullMessage += delta.content;
|
85
|
+
tokenCount++;
|
86
|
+
const currentTime = Date.now();
|
87
|
+
// Track TTFT
|
88
|
+
if (firstTokenTime === 0) {
|
89
|
+
firstTokenTime = currentTime - startTime;
|
90
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
91
|
+
console.log(`\nā” [OpenAI] Time to First Token (TTFT): ${firstTokenTime}ms`);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
// Track inter-token delays
|
95
|
+
if (lastEventTime > 0) {
|
96
|
+
const delay = currentTime - lastEventTime;
|
97
|
+
interTokenDelays.push(delay);
|
98
|
+
}
|
99
|
+
lastEventTime = currentTime;
|
76
100
|
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
77
|
-
console.log(`[OpenAI]
|
78
|
-
console.log(`[OpenAI] Current full message: "${fullMessage}"`);
|
101
|
+
console.log(`[OpenAI] Token #${tokenCount}: "${delta.content}" | Accumulated: ${fullMessage.length} chars`);
|
79
102
|
}
|
80
103
|
onEvent({
|
81
104
|
type: "token",
|
@@ -153,8 +176,27 @@ onEvent, onComplete) {
|
|
153
176
|
if (process.env.DEBUG_GRAPHLIT_STREAMING && toolCalls.length > 0) {
|
154
177
|
console.log(`[OpenAI] Successfully processed ${toolCalls.length} tool calls`);
|
155
178
|
}
|
179
|
+
// Calculate final metrics
|
180
|
+
const totalTime = Date.now() - startTime;
|
181
|
+
const tokensPerSecond = tokenCount > 0 ? tokenCount / (totalTime / 1000) : 0;
|
156
182
|
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
157
|
-
console.log(
|
183
|
+
console.log("\nš [OpenAI] Performance Metrics:");
|
184
|
+
console.log(` ā±ļø Total Time: ${totalTime}ms`);
|
185
|
+
console.log(` ā” Time to First Token (TTFT): ${firstTokenTime}ms`);
|
186
|
+
console.log(` š Tokens Generated: ${tokenCount}`);
|
187
|
+
console.log(` šØ Tokens Per Second (TPS): ${tokensPerSecond.toFixed(2)}`);
|
188
|
+
if (interTokenDelays.length > 0) {
|
189
|
+
const avgDelay = interTokenDelays.reduce((a, b) => a + b, 0) / interTokenDelays.length;
|
190
|
+
const sortedDelays = [...interTokenDelays].sort((a, b) => a - b);
|
191
|
+
const p50Delay = sortedDelays[Math.floor(sortedDelays.length * 0.5)];
|
192
|
+
const p95Delay = sortedDelays[Math.floor(sortedDelays.length * 0.95)];
|
193
|
+
const p99Delay = sortedDelays[Math.floor(sortedDelays.length * 0.99)];
|
194
|
+
console.log(` ā³ Average Inter-Token Delay: ${avgDelay.toFixed(2)}ms`);
|
195
|
+
console.log(` š P50 Delay: ${p50Delay}ms`);
|
196
|
+
console.log(` ā ļø P95 Delay: ${p95Delay}ms`);
|
197
|
+
console.log(` šØ P99 Delay: ${p99Delay}ms`);
|
198
|
+
}
|
199
|
+
console.log(`\nā
[OpenAI] Final message (${fullMessage.length} chars): "${fullMessage}"`);
|
158
200
|
}
|
159
201
|
onComplete(fullMessage, toolCalls);
|
160
202
|
}
|
@@ -173,6 +215,12 @@ export async function streamWithAnthropic(specification, messages, systemPrompt,
|
|
173
215
|
onEvent, onComplete) {
|
174
216
|
let fullMessage = "";
|
175
217
|
let toolCalls = [];
|
218
|
+
// Performance metrics
|
219
|
+
const startTime = Date.now();
|
220
|
+
let firstTokenTime = 0;
|
221
|
+
let tokenCount = 0;
|
222
|
+
let lastEventTime = 0;
|
223
|
+
const interTokenDelays = [];
|
176
224
|
try {
|
177
225
|
const modelName = getModelName(specification);
|
178
226
|
if (!modelName) {
|
@@ -207,6 +255,9 @@ onEvent, onComplete) {
|
|
207
255
|
input_schema: tool.schema ? JSON.parse(tool.schema) : {},
|
208
256
|
}));
|
209
257
|
}
|
258
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
259
|
+
console.log("\nā±ļø [Anthropic] Starting LLM call at:", new Date().toISOString());
|
260
|
+
}
|
210
261
|
const stream = await anthropicClient.messages.create(streamConfig);
|
211
262
|
let activeContentBlock = false;
|
212
263
|
for await (const chunk of stream) {
|
@@ -234,10 +285,25 @@ onEvent, onComplete) {
|
|
234
285
|
}
|
235
286
|
else if (chunk.type === "content_block_delta") {
|
236
287
|
if (chunk.delta.type === "text_delta") {
|
288
|
+
fullMessage += chunk.delta.text;
|
289
|
+
tokenCount++;
|
290
|
+
const currentTime = Date.now();
|
291
|
+
// Track TTFT
|
292
|
+
if (firstTokenTime === 0) {
|
293
|
+
firstTokenTime = currentTime - startTime;
|
294
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
295
|
+
console.log(`\nā” [Anthropic] Time to First Token (TTFT): ${firstTokenTime}ms`);
|
296
|
+
}
|
297
|
+
}
|
298
|
+
// Track inter-token delays
|
299
|
+
if (lastEventTime > 0) {
|
300
|
+
const delay = currentTime - lastEventTime;
|
301
|
+
interTokenDelays.push(delay);
|
302
|
+
}
|
303
|
+
lastEventTime = currentTime;
|
237
304
|
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
238
|
-
console.log(`[Anthropic]
|
305
|
+
console.log(`[Anthropic] Token #${tokenCount}: "${chunk.delta.text}" | Accumulated: ${fullMessage.length} chars`);
|
239
306
|
}
|
240
|
-
fullMessage += chunk.delta.text;
|
241
307
|
onEvent({
|
242
308
|
type: "token",
|
243
309
|
token: chunk.delta.text,
|
@@ -336,6 +402,28 @@ onEvent, onComplete) {
|
|
336
402
|
console.log(`[Anthropic] Filtered out ${toolCalls.length - validToolCalls.length} incomplete tool calls`);
|
337
403
|
console.log(`[Anthropic] Successfully processed ${validToolCalls.length} valid tool calls`);
|
338
404
|
}
|
405
|
+
// Calculate final metrics
|
406
|
+
const totalTime = Date.now() - startTime;
|
407
|
+
const tokensPerSecond = tokenCount > 0 ? tokenCount / (totalTime / 1000) : 0;
|
408
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
409
|
+
console.log("\nš [Anthropic] Performance Metrics:");
|
410
|
+
console.log(` ā±ļø Total Time: ${totalTime}ms`);
|
411
|
+
console.log(` ā” Time to First Token (TTFT): ${firstTokenTime}ms`);
|
412
|
+
console.log(` š Tokens Generated: ${tokenCount}`);
|
413
|
+
console.log(` šØ Tokens Per Second (TPS): ${tokensPerSecond.toFixed(2)}`);
|
414
|
+
if (interTokenDelays.length > 0) {
|
415
|
+
const avgDelay = interTokenDelays.reduce((a, b) => a + b, 0) / interTokenDelays.length;
|
416
|
+
const sortedDelays = [...interTokenDelays].sort((a, b) => a - b);
|
417
|
+
const p50Delay = sortedDelays[Math.floor(sortedDelays.length * 0.5)];
|
418
|
+
const p95Delay = sortedDelays[Math.floor(sortedDelays.length * 0.95)];
|
419
|
+
const p99Delay = sortedDelays[Math.floor(sortedDelays.length * 0.99)];
|
420
|
+
console.log(` ā³ Average Inter-Token Delay: ${avgDelay.toFixed(2)}ms`);
|
421
|
+
console.log(` š P50 Delay: ${p50Delay}ms`);
|
422
|
+
console.log(` ā ļø P95 Delay: ${p95Delay}ms`);
|
423
|
+
console.log(` šØ P99 Delay: ${p99Delay}ms`);
|
424
|
+
}
|
425
|
+
console.log(`\nā
[Anthropic] Final message (${fullMessage.length} chars): "${fullMessage}"`);
|
426
|
+
}
|
339
427
|
onComplete(fullMessage, validToolCalls);
|
340
428
|
}
|
341
429
|
catch (error) {
|
@@ -353,6 +441,12 @@ export async function streamWithGoogle(specification, messages, systemPrompt, to
|
|
353
441
|
onEvent, onComplete) {
|
354
442
|
let fullMessage = "";
|
355
443
|
let toolCalls = [];
|
444
|
+
// Performance metrics
|
445
|
+
const startTime = Date.now();
|
446
|
+
let firstTokenTime = 0;
|
447
|
+
let tokenCount = 0;
|
448
|
+
let lastEventTime = 0;
|
449
|
+
const interTokenDelays = [];
|
356
450
|
try {
|
357
451
|
const modelName = getModelName(specification);
|
358
452
|
if (!modelName) {
|
@@ -559,6 +653,28 @@ onEvent, onComplete) {
|
|
559
653
|
if (process.env.DEBUG_GRAPHLIT_STREAMING && toolCalls.length > 0) {
|
560
654
|
console.log(`[Google] Successfully processed ${toolCalls.length} tool calls`);
|
561
655
|
}
|
656
|
+
// Calculate final metrics
|
657
|
+
const totalTime = Date.now() - startTime;
|
658
|
+
const tokensPerSecond = tokenCount > 0 ? tokenCount / (totalTime / 1000) : 0;
|
659
|
+
if (process.env.DEBUG_GRAPHLIT_STREAMING) {
|
660
|
+
console.log("\nš [Google] Performance Metrics:");
|
661
|
+
console.log(` ā±ļø Total Time: ${totalTime}ms`);
|
662
|
+
console.log(` ā” Time to First Token (TTFT): ${firstTokenTime}ms`);
|
663
|
+
console.log(` š Tokens Generated: ${tokenCount}`);
|
664
|
+
console.log(` šØ Tokens Per Second (TPS): ${tokensPerSecond.toFixed(2)}`);
|
665
|
+
if (interTokenDelays.length > 0) {
|
666
|
+
const avgDelay = interTokenDelays.reduce((a, b) => a + b, 0) / interTokenDelays.length;
|
667
|
+
const sortedDelays = [...interTokenDelays].sort((a, b) => a - b);
|
668
|
+
const p50Delay = sortedDelays[Math.floor(sortedDelays.length * 0.5)];
|
669
|
+
const p95Delay = sortedDelays[Math.floor(sortedDelays.length * 0.95)];
|
670
|
+
const p99Delay = sortedDelays[Math.floor(sortedDelays.length * 0.99)];
|
671
|
+
console.log(` ā³ Average Inter-Token Delay: ${avgDelay.toFixed(2)}ms`);
|
672
|
+
console.log(` š P50 Delay: ${p50Delay}ms`);
|
673
|
+
console.log(` ā ļø P95 Delay: ${p95Delay}ms`);
|
674
|
+
console.log(` šØ P99 Delay: ${p99Delay}ms`);
|
675
|
+
}
|
676
|
+
console.log(`\nā
[Google] Final message (${fullMessage.length} chars): "${fullMessage}"`);
|
677
|
+
}
|
562
678
|
onComplete(fullMessage, toolCalls);
|
563
679
|
}
|
564
680
|
catch (error) {
|
@@ -150,21 +150,17 @@ export class UIEventAdapter {
|
|
150
150
|
}
|
151
151
|
}
|
152
152
|
handleComplete() {
|
153
|
-
// Flush any remaining chunks from buffer
|
154
|
-
if (this.chunkBuffer) {
|
155
|
-
const remaining = this.chunkBuffer.flush();
|
156
|
-
this.chunkQueue.push(...remaining);
|
157
|
-
}
|
158
153
|
// Clear any pending updates
|
159
154
|
if (this.updateTimer) {
|
160
155
|
globalThis.clearTimeout(this.updateTimer);
|
161
156
|
this.updateTimer = undefined;
|
162
157
|
}
|
163
|
-
//
|
164
|
-
|
165
|
-
|
166
|
-
this.
|
158
|
+
// DO NOT re-process chunks here - they should already be in currentMessage
|
159
|
+
// Just clear any remaining state
|
160
|
+
if (this.chunkBuffer) {
|
161
|
+
this.chunkBuffer.flush(); // Clear the buffer but don't use the result
|
167
162
|
}
|
163
|
+
this.chunkQueue.length = 0; // Clear any remaining queue
|
168
164
|
this.isStreaming = false;
|
169
165
|
// Create final message
|
170
166
|
const finalMessage = {
|