graphlit-client 1.0.20250610007 → 1.0.20250610009
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 +314 -12
- package/dist/client.js +12 -2
- package/dist/streaming/providers.js +45 -1
- package/package.json +1 -1
package/README.md
CHANGED
@@ -2,7 +2,37 @@
|
|
2
2
|
|
3
3
|
## Overview
|
4
4
|
|
5
|
-
The Graphlit Client for Node.js enables straightforward interactions with the Graphlit API, allowing developers to execute GraphQL queries and mutations against the Graphlit service. This document outlines the setup process and provides examples of using the client, including
|
5
|
+
The Graphlit Client for Node.js enables straightforward interactions with the Graphlit API, allowing developers to execute GraphQL queries and mutations against the Graphlit service. This document outlines the setup process and provides examples of using the client, including advanced streaming capabilities with real-time token delivery and tool calling support.
|
6
|
+
|
7
|
+
## Quick Start
|
8
|
+
|
9
|
+
Get up and running in 2 minutes:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
# Install the client
|
13
|
+
npm install graphlit-client
|
14
|
+
|
15
|
+
# Set your credentials (get from https://portal.graphlit.dev)
|
16
|
+
export GRAPHLIT_ORGANIZATION_ID=your_org_id
|
17
|
+
export GRAPHLIT_ENVIRONMENT_ID=your_env_id
|
18
|
+
export GRAPHLIT_JWT_SECRET=your_secret
|
19
|
+
```
|
20
|
+
|
21
|
+
```typescript
|
22
|
+
import { Graphlit } from "graphlit-client";
|
23
|
+
|
24
|
+
const client = new Graphlit();
|
25
|
+
|
26
|
+
// Stream a conversation
|
27
|
+
await client.streamAgent(
|
28
|
+
"Hello! Tell me a joke",
|
29
|
+
(event) => {
|
30
|
+
if (event.type === "message_update") {
|
31
|
+
console.log(event.message.message);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
);
|
35
|
+
```
|
6
36
|
|
7
37
|
## Prerequisites
|
8
38
|
|
@@ -102,17 +132,17 @@ const contents = await client.queryContents({
|
|
102
132
|
|
103
133
|
### Streaming Conversations with streamAgent
|
104
134
|
|
105
|
-
The
|
135
|
+
The `streamAgent` method provides real-time streaming responses with automatic UI event handling. It supports both native SDK streaming (when LLM clients are configured) and fallback streaming through the Graphlit API:
|
106
136
|
|
107
137
|
```typescript
|
108
|
-
import { Graphlit,
|
138
|
+
import { Graphlit, AgentStreamEvent } from "graphlit-client";
|
109
139
|
|
110
140
|
const client = new Graphlit();
|
111
141
|
|
112
142
|
// Basic streaming conversation
|
113
143
|
await client.streamAgent(
|
114
144
|
"Tell me about artificial intelligence",
|
115
|
-
(event:
|
145
|
+
(event: AgentStreamEvent) => {
|
116
146
|
switch (event.type) {
|
117
147
|
case "conversation_started":
|
118
148
|
console.log(`Started conversation: ${event.conversationId}`);
|
@@ -135,9 +165,27 @@ await client.streamAgent(
|
|
135
165
|
);
|
136
166
|
```
|
137
167
|
|
168
|
+
### Non-Streaming Conversations with promptAgent
|
169
|
+
|
170
|
+
For simpler use cases without streaming, use `promptAgent`:
|
171
|
+
|
172
|
+
```typescript
|
173
|
+
const result = await client.promptAgent(
|
174
|
+
"What's the weather like?",
|
175
|
+
undefined, // conversationId (creates new)
|
176
|
+
{ id: "your-specification-id" }, // specification
|
177
|
+
tools, // optional tools array
|
178
|
+
toolHandlers, // optional tool handlers
|
179
|
+
{ timeout: 30000 } // optional timeout
|
180
|
+
);
|
181
|
+
|
182
|
+
console.log(result.message); // Complete response
|
183
|
+
console.log(result.conversationId); // For continuing the conversation
|
184
|
+
```
|
185
|
+
|
138
186
|
### Tool Calling with Streaming
|
139
187
|
|
140
|
-
`streamAgent` supports tool calling with automatic execution:
|
188
|
+
`streamAgent` supports sophisticated tool calling with automatic execution and parallel processing:
|
141
189
|
|
142
190
|
```typescript
|
143
191
|
// Define tools
|
@@ -164,12 +212,15 @@ const toolHandlers = {
|
|
164
212
|
// Stream with tools
|
165
213
|
await client.streamAgent(
|
166
214
|
"What's the weather in San Francisco?",
|
167
|
-
(event:
|
215
|
+
(event: AgentStreamEvent) => {
|
168
216
|
if (event.type === "tool_update") {
|
169
217
|
console.log(`Tool ${event.toolCall.name}: ${event.status}`);
|
170
218
|
if (event.result) {
|
171
219
|
console.log(`Result: ${JSON.stringify(event.result)}`);
|
172
220
|
}
|
221
|
+
if (event.error) {
|
222
|
+
console.error(`Tool error: ${event.error}`);
|
223
|
+
}
|
173
224
|
} else if (event.type === "conversation_completed") {
|
174
225
|
console.log(`Final: ${event.message.message}`);
|
175
226
|
}
|
@@ -306,17 +357,98 @@ client.streamAgent(
|
|
306
357
|
setTimeout(() => controller.abort(), 5000);
|
307
358
|
```
|
308
359
|
|
360
|
+
## Advanced Tool Calling
|
361
|
+
|
362
|
+
### Multiple Tool Calls
|
363
|
+
|
364
|
+
The agent can make multiple tool calls in a single response:
|
365
|
+
|
366
|
+
```typescript
|
367
|
+
const tools = [
|
368
|
+
{
|
369
|
+
name: "search_web",
|
370
|
+
description: "Search the web for information",
|
371
|
+
schema: JSON.stringify({
|
372
|
+
type: "object",
|
373
|
+
properties: {
|
374
|
+
query: { type: "string" }
|
375
|
+
},
|
376
|
+
required: ["query"]
|
377
|
+
})
|
378
|
+
},
|
379
|
+
{
|
380
|
+
name: "calculate",
|
381
|
+
description: "Perform calculations",
|
382
|
+
schema: JSON.stringify({
|
383
|
+
type: "object",
|
384
|
+
properties: {
|
385
|
+
expression: { type: "string" }
|
386
|
+
},
|
387
|
+
required: ["expression"]
|
388
|
+
})
|
389
|
+
}
|
390
|
+
];
|
391
|
+
|
392
|
+
const toolHandlers = {
|
393
|
+
search_web: async ({ query }) => {
|
394
|
+
// Implement web search
|
395
|
+
return { results: ["Result 1", "Result 2"] };
|
396
|
+
},
|
397
|
+
calculate: async ({ expression }) => {
|
398
|
+
// Implement calculation
|
399
|
+
return { result: eval(expression) }; // Use a proper math parser in production
|
400
|
+
}
|
401
|
+
};
|
402
|
+
|
403
|
+
// The agent can use multiple tools to answer complex queries
|
404
|
+
await client.streamAgent(
|
405
|
+
"Search for the current GDP of Japan and calculate 2.5% of it",
|
406
|
+
handleStreamEvent,
|
407
|
+
undefined,
|
408
|
+
{ id: specId },
|
409
|
+
tools,
|
410
|
+
toolHandlers,
|
411
|
+
{ maxToolRounds: 5 } // Allow multiple rounds of tool calls
|
412
|
+
);
|
413
|
+
```
|
414
|
+
|
415
|
+
### Tool Calling with Timeouts
|
416
|
+
|
417
|
+
Protect against hanging tools with timeouts:
|
418
|
+
|
419
|
+
```typescript
|
420
|
+
const toolHandlers = {
|
421
|
+
slow_operation: async (args) => {
|
422
|
+
// This will timeout after 30 seconds (default)
|
423
|
+
await someSlowOperation(args);
|
424
|
+
}
|
425
|
+
};
|
426
|
+
|
427
|
+
// Or set custom timeout in AgentOptions
|
428
|
+
await client.promptAgent(
|
429
|
+
"Run the slow operation",
|
430
|
+
undefined,
|
431
|
+
{ id: specId },
|
432
|
+
tools,
|
433
|
+
toolHandlers,
|
434
|
+
{
|
435
|
+
timeout: 60000, // 60 second timeout for entire operation
|
436
|
+
maxToolRounds: 3
|
437
|
+
}
|
438
|
+
);
|
439
|
+
```
|
440
|
+
|
309
441
|
## Stream Event Reference
|
310
442
|
|
311
|
-
###
|
443
|
+
### Agent Stream Events
|
312
444
|
|
313
445
|
| Event Type | Description | Properties |
|
314
446
|
|------------|-------------|------------|
|
315
|
-
| `conversation_started` | Conversation initialized | `conversationId`, `timestamp` |
|
447
|
+
| `conversation_started` | Conversation initialized | `conversationId`, `timestamp`, `model?` |
|
316
448
|
| `message_update` | Message text updated | `message` (complete text), `isStreaming` |
|
317
449
|
| `tool_update` | Tool execution status | `toolCall`, `status`, `result?`, `error?` |
|
318
450
|
| `conversation_completed` | Streaming finished | `message` (final) |
|
319
|
-
| `error` | Error occurred | `error` object with `message`, `code
|
451
|
+
| `error` | Error occurred | `error` object with `message`, `code?`, `recoverable` |
|
320
452
|
|
321
453
|
### Tool Execution Statuses
|
322
454
|
|
@@ -405,12 +537,111 @@ await client.streamAgent(
|
|
405
537
|
);
|
406
538
|
```
|
407
539
|
|
540
|
+
## Best Practices
|
541
|
+
|
542
|
+
### 1. Always Handle Errors
|
543
|
+
|
544
|
+
```typescript
|
545
|
+
await client.streamAgent(
|
546
|
+
prompt,
|
547
|
+
(event) => {
|
548
|
+
if (event.type === "error") {
|
549
|
+
// Check if error is recoverable
|
550
|
+
if (event.error.recoverable) {
|
551
|
+
// Could retry or fallback
|
552
|
+
console.warn("Recoverable error:", event.error.message);
|
553
|
+
} else {
|
554
|
+
// Fatal error
|
555
|
+
console.error("Fatal error:", event.error.message);
|
556
|
+
}
|
557
|
+
}
|
558
|
+
}
|
559
|
+
);
|
560
|
+
```
|
561
|
+
|
562
|
+
### 2. Clean Up Resources
|
563
|
+
|
564
|
+
```typescript
|
565
|
+
// Always clean up conversations when done
|
566
|
+
let conversationId: string;
|
567
|
+
|
568
|
+
try {
|
569
|
+
await client.streamAgent(
|
570
|
+
prompt,
|
571
|
+
(event) => {
|
572
|
+
if (event.type === "conversation_started") {
|
573
|
+
conversationId = event.conversationId;
|
574
|
+
}
|
575
|
+
}
|
576
|
+
);
|
577
|
+
} finally {
|
578
|
+
if (conversationId) {
|
579
|
+
await client.deleteConversation(conversationId);
|
580
|
+
}
|
581
|
+
}
|
582
|
+
```
|
583
|
+
|
584
|
+
### 3. Tool Handler Best Practices
|
585
|
+
|
586
|
+
```typescript
|
587
|
+
const toolHandlers = {
|
588
|
+
// Always validate inputs
|
589
|
+
calculate: async (args) => {
|
590
|
+
if (!args.expression || typeof args.expression !== 'string') {
|
591
|
+
throw new Error("Invalid expression");
|
592
|
+
}
|
593
|
+
|
594
|
+
// Return structured data
|
595
|
+
return {
|
596
|
+
expression: args.expression,
|
597
|
+
result: evaluateExpression(args.expression),
|
598
|
+
timestamp: new Date().toISOString()
|
599
|
+
};
|
600
|
+
},
|
601
|
+
|
602
|
+
// Handle errors gracefully
|
603
|
+
fetch_data: async (args) => {
|
604
|
+
try {
|
605
|
+
const data = await fetchFromAPI(args.url);
|
606
|
+
return { success: true, data };
|
607
|
+
} catch (error) {
|
608
|
+
return {
|
609
|
+
success: false,
|
610
|
+
error: error.message,
|
611
|
+
// Help the LLM understand what went wrong
|
612
|
+
suggestion: "The URL might be invalid or the service is down"
|
613
|
+
};
|
614
|
+
}
|
615
|
+
}
|
616
|
+
};
|
617
|
+
```
|
618
|
+
|
619
|
+
### 4. Optimize for Performance
|
620
|
+
|
621
|
+
```typescript
|
622
|
+
// Use appropriate chunking strategy for your use case
|
623
|
+
const options = {
|
624
|
+
// For code generation or technical content
|
625
|
+
chunkingStrategy: 'character' as const,
|
626
|
+
|
627
|
+
// For natural conversation (default)
|
628
|
+
chunkingStrategy: 'word' as const,
|
629
|
+
|
630
|
+
// For long-form content
|
631
|
+
chunkingStrategy: 'sentence' as const,
|
632
|
+
|
633
|
+
// Adjust smoothing delay for your UI
|
634
|
+
smoothingDelay: 20, // Faster updates
|
635
|
+
smoothingDelay: 50, // Smoother updates (default: 30)
|
636
|
+
};
|
637
|
+
```
|
638
|
+
|
408
639
|
## Migration Guide
|
409
640
|
|
410
|
-
If you're upgrading from `promptConversation` to `streamAgent`:
|
641
|
+
If you're upgrading from `promptConversation` to `streamAgent` or `promptAgent`:
|
411
642
|
|
412
643
|
```typescript
|
413
|
-
// Before
|
644
|
+
// Before (v1.0)
|
414
645
|
const response = await client.promptConversation(
|
415
646
|
"Your prompt",
|
416
647
|
undefined,
|
@@ -418,7 +649,15 @@ const response = await client.promptConversation(
|
|
418
649
|
);
|
419
650
|
console.log(response.promptConversation.message.message);
|
420
651
|
|
421
|
-
// After
|
652
|
+
// After (v1.1) - Non-streaming
|
653
|
+
const result = await client.promptAgent(
|
654
|
+
"Your prompt",
|
655
|
+
undefined,
|
656
|
+
{ id: specId }
|
657
|
+
);
|
658
|
+
console.log(result.message);
|
659
|
+
|
660
|
+
// After (v1.1) - Streaming
|
422
661
|
await client.streamAgent(
|
423
662
|
"Your prompt",
|
424
663
|
(event) => {
|
@@ -431,6 +670,69 @@ await client.streamAgent(
|
|
431
670
|
);
|
432
671
|
```
|
433
672
|
|
673
|
+
## Troubleshooting
|
674
|
+
|
675
|
+
### Common Issues
|
676
|
+
|
677
|
+
#### 1. Streaming Not Working
|
678
|
+
|
679
|
+
```typescript
|
680
|
+
// Check if streaming is supported
|
681
|
+
if (!client.supportsStreaming()) {
|
682
|
+
console.log("Streaming not supported - using fallback mode");
|
683
|
+
}
|
684
|
+
|
685
|
+
// Ensure LLM clients are properly configured
|
686
|
+
const hasNativeStreaming =
|
687
|
+
client.hasOpenAIClient() ||
|
688
|
+
client.hasAnthropicClient() ||
|
689
|
+
client.hasGoogleClient();
|
690
|
+
```
|
691
|
+
|
692
|
+
#### 2. Tool Calls Not Executing
|
693
|
+
|
694
|
+
```typescript
|
695
|
+
// Ensure tool schemas are valid JSON Schema
|
696
|
+
const validSchema = {
|
697
|
+
type: "object",
|
698
|
+
properties: {
|
699
|
+
param: { type: "string", description: "Parameter description" }
|
700
|
+
},
|
701
|
+
required: ["param"] // Don't forget required fields
|
702
|
+
};
|
703
|
+
|
704
|
+
// Tool names must match exactly
|
705
|
+
const tools = [{ name: "my_tool", /* ... */ }];
|
706
|
+
const toolHandlers = {
|
707
|
+
"my_tool": async (args) => { /* ... */ } // Name must match
|
708
|
+
};
|
709
|
+
```
|
710
|
+
|
711
|
+
#### 3. Incomplete Streaming Responses
|
712
|
+
|
713
|
+
Some LLM providers may truncate responses. The client handles this automatically, but you can enable debug logging:
|
714
|
+
|
715
|
+
```bash
|
716
|
+
DEBUG_STREAMING=true npm start
|
717
|
+
```
|
718
|
+
|
719
|
+
#### 4. Type Errors
|
720
|
+
|
721
|
+
Ensure you're importing the correct types:
|
722
|
+
|
723
|
+
```typescript
|
724
|
+
import {
|
725
|
+
Graphlit,
|
726
|
+
AgentStreamEvent, // For streaming events
|
727
|
+
AgentResult, // For promptAgent results
|
728
|
+
ToolHandler, // For tool handler functions
|
729
|
+
StreamAgentOptions // For streaming options
|
730
|
+
} from "graphlit-client";
|
731
|
+
|
732
|
+
// Also available if needed
|
733
|
+
import * as Types from "graphlit-client/generated/graphql-types";
|
734
|
+
```
|
735
|
+
|
434
736
|
## Support
|
435
737
|
|
436
738
|
Please refer to the [Graphlit API Documentation](https://docs.graphlit.dev/).
|
package/dist/client.js
CHANGED
@@ -1730,14 +1730,24 @@ class Graphlit {
|
|
1730
1730
|
}
|
1731
1731
|
// Try to fix truncated JSON by adding missing closing braces
|
1732
1732
|
if (isTruncated) {
|
1733
|
-
let fixedJson = toolCall.arguments;
|
1733
|
+
let fixedJson = toolCall.arguments.trim();
|
1734
1734
|
// Count open braces vs close braces to determine how many we need
|
1735
1735
|
const openBraces = (fixedJson.match(/\{/g) || []).length;
|
1736
1736
|
const closeBraces = (fixedJson.match(/\}/g) || []).length;
|
1737
1737
|
const missingBraces = openBraces - closeBraces;
|
1738
1738
|
if (missingBraces > 0) {
|
1739
|
+
// Check if we're mid-value (ends with number or boolean)
|
1740
|
+
if (fixedJson.match(/:\s*\d+$/) || fixedJson.match(/:\s*(true|false)$/)) {
|
1741
|
+
// Complete the current property and close
|
1742
|
+
fixedJson += ', "content": ""'; // Add empty content field
|
1743
|
+
}
|
1744
|
+
// Check if we're after a value but missing comma
|
1745
|
+
else if (fixedJson.match(/"\s*:\s*[^,}\s]+$/)) {
|
1746
|
+
// We have a complete value but no comma, add empty content
|
1747
|
+
fixedJson += ', "content": ""';
|
1748
|
+
}
|
1739
1749
|
// Add missing closing quote if the string ends with an unfinished string
|
1740
|
-
if (fixedJson.endsWith('"') === false && fixedJson.includes('"')) {
|
1750
|
+
else if (fixedJson.endsWith('"') === false && fixedJson.includes('"')) {
|
1741
1751
|
const lastQuoteIndex = fixedJson.lastIndexOf('"');
|
1742
1752
|
const afterLastQuote = fixedJson.slice(lastQuoteIndex + 1);
|
1743
1753
|
if (!afterLastQuote.includes('"')) {
|
@@ -141,8 +141,14 @@ onEvent, onComplete) {
|
|
141
141
|
}));
|
142
142
|
}
|
143
143
|
const stream = await anthropicClient.messages.create(streamConfig);
|
144
|
+
let activeContentBlock = false;
|
144
145
|
for await (const chunk of stream) {
|
146
|
+
// Debug log all chunk types
|
147
|
+
if (process.env.DEBUG_STREAMING) {
|
148
|
+
console.log(`[Anthropic] Received chunk type: ${chunk.type}`);
|
149
|
+
}
|
145
150
|
if (chunk.type === "content_block_start") {
|
151
|
+
activeContentBlock = true;
|
146
152
|
if (chunk.content_block.type === "tool_use") {
|
147
153
|
const toolCall = {
|
148
154
|
id: chunk.content_block.id,
|
@@ -186,6 +192,7 @@ onEvent, onComplete) {
|
|
186
192
|
}
|
187
193
|
}
|
188
194
|
else if (chunk.type === "content_block_stop") {
|
195
|
+
activeContentBlock = false;
|
189
196
|
// Tool call complete
|
190
197
|
const currentTool = toolCalls[toolCalls.length - 1];
|
191
198
|
if (currentTool) {
|
@@ -221,8 +228,45 @@ onEvent, onComplete) {
|
|
221
228
|
});
|
222
229
|
}
|
223
230
|
}
|
231
|
+
else if (chunk.type === "message_stop" && activeContentBlock) {
|
232
|
+
// Handle Anthropic bug: message_stop without content_block_stop
|
233
|
+
console.warn(`[Anthropic] Received message_stop without content_block_stop - handling as implicit block stop`);
|
234
|
+
activeContentBlock = false;
|
235
|
+
// Emit synthetic content_block_stop for the current tool
|
236
|
+
const currentTool = toolCalls[toolCalls.length - 1];
|
237
|
+
if (currentTool) {
|
238
|
+
// Log the incomplete tool
|
239
|
+
console.warn(`[Anthropic] Synthetic content_block_stop for incomplete tool ${currentTool.name} (${currentTool.arguments.length} chars)`);
|
240
|
+
// Only emit tool_call_complete if we have valid JSON
|
241
|
+
if (isValidJSON(currentTool.arguments)) {
|
242
|
+
onEvent({
|
243
|
+
type: "tool_call_complete",
|
244
|
+
toolCall: {
|
245
|
+
id: currentTool.id,
|
246
|
+
name: currentTool.name,
|
247
|
+
arguments: currentTool.arguments,
|
248
|
+
},
|
249
|
+
});
|
250
|
+
}
|
251
|
+
else {
|
252
|
+
console.error(`[Anthropic] Tool ${currentTool.name} has incomplete JSON, skipping tool_call_complete event`);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
}
|
224
256
|
}
|
225
|
-
|
257
|
+
// Final check: filter out any remaining incomplete tool calls
|
258
|
+
const validToolCalls = toolCalls.filter((tc, idx) => {
|
259
|
+
if (!isValidJSON(tc.arguments)) {
|
260
|
+
console.warn(`[Anthropic] Filtering out incomplete tool call ${idx} (${tc.name}) with INVALID JSON (${tc.arguments.length} chars)`);
|
261
|
+
return false;
|
262
|
+
}
|
263
|
+
return true;
|
264
|
+
});
|
265
|
+
if (toolCalls.length !== validToolCalls.length) {
|
266
|
+
console.log(`[Anthropic] Filtered out ${toolCalls.length - validToolCalls.length} incomplete tool calls`);
|
267
|
+
console.log(`[Anthropic] Successfully processed ${validToolCalls.length} valid tool calls`);
|
268
|
+
}
|
269
|
+
onComplete(fullMessage, validToolCalls);
|
226
270
|
}
|
227
271
|
catch (error) {
|
228
272
|
onEvent({
|