@raindrop-ai/langchain 0.0.8 → 0.1.0

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 CHANGED
@@ -47,8 +47,9 @@ await raindrop.flush();
47
47
  | `userId` | `string` | - | Associate all events with a user |
48
48
  | `convoId` | `string` | - | Group events into a conversation |
49
49
  | `projectId` | `string` | - | Route events to a specific project (slug); omit for the default **Production** project |
50
+ | `eventName` | `string` | `ai_generation` | Event name applied to every event (the `event` field in the dashboard) |
50
51
  | `debug` | `boolean` | `false` | Enable verbose logging |
51
- | `traceChains` | `boolean` | `true` | Create spans for chain execution |
52
+ | `traceChains` | `boolean` | `false` | Create spans for chain/runnable execution. Off by default because LangGraph agents emit many structural "chain" spans that bury the LLM and tool spans. |
52
53
  | `traceRetrievers` | `boolean` | `true` | Create spans for retriever calls |
53
54
  | `filterLangGraphInternals` | `boolean` | `true` | Filter LangGraph-internal chain events and deduplicate LLM callbacks |
54
55
  | `maxTextFieldChars` | `number` | `1000000` | Per-field cap for event input/output and serialized span payloads, enforced before/during serialization (truncated values end with `...[truncated by raindrop]`; a stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored) |
@@ -66,17 +67,58 @@ const raindrop = createRaindropLangChain({
66
67
 
67
68
  This sets the `X-Raindrop-Project-Id` header on every event. Omit it (or pass `"default"`) to use your org's default **Production** project — the existing behavior. Single-project orgs need nothing new.
68
69
 
69
- ## LangGraph Support
70
+ ## Agents (LangGraph / ReAct)
70
71
 
71
- Works with LangGraph out of the box. The handler automatically:
72
+ For a prebuilt agent (`createReactAgent`) or any LangGraph graph, pass the handler
73
+ **once** to the top-level `invoke` call. LangChain propagates the callback down to
74
+ every LLM step and tool call in the run, and the handler threads them onto a single
75
+ Raindrop event (with a span tree) via each callback's `runId`/`parentRunId`.
76
+
77
+ ```typescript
78
+ import { createRaindropLangChain } from "@raindrop-ai/langchain";
79
+ import { ChatOpenAI } from "@langchain/openai";
80
+ import { tool } from "@langchain/core/tools";
81
+ import { createReactAgent } from "@langchain/langgraph/prebuilt";
82
+ import { z } from "zod";
83
+
84
+ const raindrop = createRaindropLangChain({
85
+ writeKey: "your-write-key",
86
+ userId: "user-123",
87
+ eventName: "support-agent",
88
+ });
89
+
90
+ const getWeather = tool(async ({ city }) => `It is sunny and 72F in ${city}.`, {
91
+ name: "get_weather",
92
+ description: "Get the current weather for a city.",
93
+ schema: z.object({ city: z.string() }),
94
+ });
95
+
96
+ const agent = createReactAgent({
97
+ llm: new ChatOpenAI({ model: "gpt-4o-mini" }),
98
+ tools: [getWeather],
99
+ });
100
+
101
+ // One handler on the top-level invoke captures the whole agent run:
102
+ // LLM steps + tool calls, all on one event.
103
+ await agent.invoke(
104
+ { messages: [{ role: "user", content: "What's the weather in SF?" }] },
105
+ { callbacks: [raindrop.handler] },
106
+ );
107
+
108
+ await raindrop.flush(); // await before the process/lambda exits
109
+ ```
110
+
111
+ See `examples/langchain-agent-basic/` for a full runnable example.
112
+
113
+ The handler works with LangGraph out of the box and automatically:
72
114
  - Filters LangGraph-internal chain events (graph executor, `__start__`, `__end__`, channel nodes)
73
115
  - Deduplicates LLM callbacks that LangGraph fires multiple times with the same `runId`
74
116
 
75
- Pass the handler both at `graph.invoke()` and inside your LLM node. See `examples/langchain-langgraph-basic/` for a full example.
76
-
77
117
  **Best practices:**
78
- - Pass callbacks to the **model inside your node**, not to `graph.invoke()` this avoids duplicate events from LangGraph's internal callback propagation
79
- - Create a new handler instance per request in server environments
118
+ - **Prebuilt agents / graphs**: attach the handler to the top-level `invoke(...)` (as above). This is the one place that reliably captures the full run.
119
+ - **Hand-built graphs with custom callback wiring**: attach the handler to the **model inside your node** rather than duplicating it at each layer, to avoid double events from manual callback propagation.
120
+ - Create a new handler instance per request in server environments.
121
+ - Always `await raindrop.flush()` (or `shutdown()`) before exit — in short scripts and serverless the process can freeze before the batched spans ship.
80
122
 
81
123
  ## LangSmith Coexistence
82
124
 
package/dist/index.d.mts CHANGED
@@ -61,6 +61,7 @@ type Patch = {
61
61
  output?: string;
62
62
  model?: string;
63
63
  properties?: Record<string, unknown>;
64
+ featureFlags?: Record<string, string>;
64
65
  attachments?: Attachment[];
65
66
  isPending?: boolean;
66
67
  timestamp?: string;
@@ -161,6 +162,7 @@ declare class EventShipper {
161
162
  output?: string;
162
163
  model?: string;
163
164
  properties?: Record<string, unknown>;
165
+ featureFlags?: Record<string, string>;
164
166
  userId?: string;
165
167
  }): Promise<void>;
166
168
  flush(): Promise<void>;
@@ -433,6 +435,12 @@ interface RaindropCallbackHandlerOptions {
433
435
  traceShipper: TraceShipper;
434
436
  userId?: string;
435
437
  convoId?: string;
438
+ /**
439
+ * Emit a span for every chain / runnable event. For LangGraph agents this
440
+ * produces many structural "chain" spans (per-node runnable wrappers) that
441
+ * bury the meaningful LLM and tool spans, so it defaults to `false`. The
442
+ * event is still finalized correctly when a chain is the root run.
443
+ */
436
444
  traceChains?: boolean;
437
445
  traceRetrievers?: boolean;
438
446
  /**
@@ -501,6 +509,18 @@ interface LangChainOptions {
501
509
  * resolves to `default` server-side; byte-identical to prior behavior).
502
510
  */
503
511
  projectId?: string;
512
+ /**
513
+ * Event name applied to every event produced by this integration (the
514
+ * `event` field in the Raindrop dashboard). Lets you distinguish LangChain
515
+ * traffic — e.g. `"support_agent"` vs `"billing_agent"` — instead of the
516
+ * generic default. Defaults to `"ai_generation"`.
517
+ */
518
+ eventName?: string;
519
+ /**
520
+ * Emit a span for every chain / runnable event. LangGraph agents produce many
521
+ * structural "chain" spans (per-node runnable wrappers) that bury the LLM and
522
+ * tool spans, so this defaults to `false`. Event threading is unaffected.
523
+ */
504
524
  traceChains?: boolean;
505
525
  traceRetrievers?: boolean;
506
526
  /**
package/dist/index.d.ts CHANGED
@@ -61,6 +61,7 @@ type Patch = {
61
61
  output?: string;
62
62
  model?: string;
63
63
  properties?: Record<string, unknown>;
64
+ featureFlags?: Record<string, string>;
64
65
  attachments?: Attachment[];
65
66
  isPending?: boolean;
66
67
  timestamp?: string;
@@ -161,6 +162,7 @@ declare class EventShipper {
161
162
  output?: string;
162
163
  model?: string;
163
164
  properties?: Record<string, unknown>;
165
+ featureFlags?: Record<string, string>;
164
166
  userId?: string;
165
167
  }): Promise<void>;
166
168
  flush(): Promise<void>;
@@ -433,6 +435,12 @@ interface RaindropCallbackHandlerOptions {
433
435
  traceShipper: TraceShipper;
434
436
  userId?: string;
435
437
  convoId?: string;
438
+ /**
439
+ * Emit a span for every chain / runnable event. For LangGraph agents this
440
+ * produces many structural "chain" spans (per-node runnable wrappers) that
441
+ * bury the meaningful LLM and tool spans, so it defaults to `false`. The
442
+ * event is still finalized correctly when a chain is the root run.
443
+ */
436
444
  traceChains?: boolean;
437
445
  traceRetrievers?: boolean;
438
446
  /**
@@ -501,6 +509,18 @@ interface LangChainOptions {
501
509
  * resolves to `default` server-side; byte-identical to prior behavior).
502
510
  */
503
511
  projectId?: string;
512
+ /**
513
+ * Event name applied to every event produced by this integration (the
514
+ * `event` field in the Raindrop dashboard). Lets you distinguish LangChain
515
+ * traffic — e.g. `"support_agent"` vs `"billing_agent"` — instead of the
516
+ * generic default. Defaults to `"ai_generation"`.
517
+ */
518
+ eventName?: string;
519
+ /**
520
+ * Emit a span for every chain / runnable event. LangGraph agents produce many
521
+ * structural "chain" spans (per-node runnable wrappers) that bury the LLM and
522
+ * tool spans, so this defaults to `false`. Event threading is unaffected.
523
+ */
504
524
  traceChains?: boolean;
505
525
  traceRetrievers?: boolean;
506
526
  /**
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ module.exports = __toCommonJS(index_exports);
28
28
  // src/callback-handler.ts
29
29
  var import_base = require("@langchain/core/callbacks/base");
30
30
 
31
- // ../core/dist/chunk-WKRW55KX.js
31
+ // ../core/dist/chunk-ITAZONWL.js
32
32
  function getCrypto() {
33
33
  const c = globalThis.crypto;
34
34
  return c;
@@ -418,13 +418,16 @@ function projectIdHeaders(projectId) {
418
418
  var SHUTDOWN_DEADLINE_MS = 1e4;
419
419
  var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
420
420
  function mergePatches(target, source) {
421
- var _a, _b, _c, _d;
421
+ var _a, _b, _c, _d, _e, _f;
422
422
  const out = { ...target, ...source };
423
423
  if (target.properties || source.properties) {
424
424
  out.properties = { ...(_a = target.properties) != null ? _a : {}, ...(_b = source.properties) != null ? _b : {} };
425
425
  }
426
+ if (target.featureFlags || source.featureFlags) {
427
+ out.featureFlags = { ...(_c = target.featureFlags) != null ? _c : {}, ...(_d = source.featureFlags) != null ? _d : {} };
428
+ }
426
429
  if (target.attachments || source.attachments) {
427
- out.attachments = [...(_c = target.attachments) != null ? _c : [], ...(_d = source.attachments) != null ? _d : []];
430
+ out.attachments = [...(_e = target.attachments) != null ? _e : [], ...(_f = source.attachments) != null ? _f : []];
428
431
  }
429
432
  return out;
430
433
  }
@@ -694,6 +697,7 @@ var EventShipper = class {
694
697
  ...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
695
698
  $context: this.context
696
699
  },
700
+ ...accumulated.featureFlags && Object.keys(accumulated.featureFlags).length > 0 ? { feature_flags: accumulated.featureFlags } : {},
697
701
  attachments: accumulated.attachments,
698
702
  is_pending: isPending
699
703
  };
@@ -1319,6 +1323,7 @@ function boundedStringify(value, limit) {
1319
1323
  }
1320
1324
 
1321
1325
  // src/callback-handler.ts
1326
+ var MESSAGE_CONTENT_CAP = 4096;
1322
1327
  var LANGGRAPH_INTERNAL_NAMES = /* @__PURE__ */ new Set([
1323
1328
  "LangGraph",
1324
1329
  "__start__",
@@ -1338,7 +1343,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
1338
1343
  this.traceShipper = opts.traceShipper;
1339
1344
  this.userId = opts.userId;
1340
1345
  this.convoId = opts.convoId;
1341
- this.traceChains = (_a = opts.traceChains) != null ? _a : true;
1346
+ this.traceChains = (_a = opts.traceChains) != null ? _a : false;
1342
1347
  this.traceRetrievers = (_b = opts.traceRetrievers) != null ? _b : true;
1343
1348
  this.filterLangGraphInternals = (_c = opts.filterLangGraphInternals) != null ? _c : true;
1344
1349
  }
@@ -1398,9 +1403,20 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
1398
1403
  name: "llm",
1399
1404
  parent: this.getParent(parentRunId),
1400
1405
  eventId,
1406
+ // `ai.generateText` classifies the span as an LLM generation at ingest
1407
+ // (a bare `llm`/`chat` operationId maps to INTERNAL and is skipped by
1408
+ // the rich-interaction extractor). `ai.prompt.messages` carries the
1409
+ // per-step trajectory so the Convo view can weave in tool calls
1410
+ // inline, matching the shape the ai-sdk / strands SDKs emit.
1401
1411
  attributes: [
1402
1412
  attrString("ai.model.provider", modelId),
1403
- attrString("ai.operationId", "llm")
1413
+ attrString("ai.operationId", "ai.generateText"),
1414
+ attrString(
1415
+ "ai.prompt.messages",
1416
+ boundedStringify([
1417
+ { role: "user", content: capText2(prompts.join("\n"), MESSAGE_CONTENT_CAP) }
1418
+ ])
1419
+ )
1404
1420
  ]
1405
1421
  });
1406
1422
  this.spans.set(runId, span);
@@ -1420,17 +1436,21 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
1420
1436
  if (this.filterLangGraphInternals && this.spans.has(runId)) return;
1421
1437
  const eventId = this.getEventId(runId, parentRunId);
1422
1438
  const modelId = getName(llm, runName);
1439
+ const allMessages = messages.flat();
1423
1440
  const span = this.traceShipper.startSpan({
1424
1441
  name: "llm",
1425
1442
  parent: this.getParent(parentRunId),
1426
1443
  eventId,
1444
+ // See handleLLMStart: `ai.generateText` + `ai.prompt.messages` let the
1445
+ // rich-interaction extractor render this step (and any tool calls
1446
+ // between it and the next step) inline in the Convo view.
1427
1447
  attributes: [
1428
1448
  attrString("ai.model.provider", modelId),
1429
- attrString("ai.operationId", "chat")
1449
+ attrString("ai.operationId", "ai.generateText"),
1450
+ attrString("ai.prompt.messages", serializeChatMessages(allMessages))
1430
1451
  ]
1431
1452
  });
1432
1453
  this.spans.set(runId, span);
1433
- const allMessages = messages.flat();
1434
1454
  const userMessages = allMessages.filter((m) => {
1435
1455
  var _a, _b;
1436
1456
  const role = (_b = (_a = m._getType) == null ? void 0 : _a.call(m)) != null ? _b : "unknown";
@@ -1459,6 +1479,10 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
1459
1479
  this.traceShipper.endSpan(span, {
1460
1480
  attributes: [
1461
1481
  attrString("ai.response.model", model),
1482
+ // The assistant completion for this step; paired with
1483
+ // `ai.prompt.messages` this is what the Convo view renders as the
1484
+ // assistant turn (empty on tool-only steps, so no blank bubble).
1485
+ attrString("ai.response.text", capText2(outputText, MESSAGE_CONTENT_CAP)),
1462
1486
  attrInt("ai.usage.prompt_tokens", promptTokens),
1463
1487
  attrInt("ai.usage.completion_tokens", completionTokens)
1464
1488
  ]
@@ -1543,12 +1567,13 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
1543
1567
  const eventId = this.getEventId(runId, parentRunId);
1544
1568
  const toolName = getName(tool, runName);
1545
1569
  const span = this.traceShipper.startSpan({
1546
- name: "tool",
1570
+ name: "ai.toolCall",
1547
1571
  parent: this.getParent(parentRunId),
1548
1572
  eventId,
1573
+ operationId: "ai.toolCall",
1549
1574
  attributes: [
1550
- attrString("tool.name", toolName),
1551
- attrString("tool.input", truncate(input, 4096))
1575
+ attrString("ai.toolCall.name", toolName),
1576
+ attrString("ai.toolCall.args", truncate(input, 4096))
1552
1577
  ]
1553
1578
  });
1554
1579
  this.spans.set(runId, span);
@@ -1562,7 +1587,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
1562
1587
  if (span) {
1563
1588
  const outputStr = typeof output === "string" ? output : safeStringify(output, 4096);
1564
1589
  this.traceShipper.endSpan(span, {
1565
- attributes: [attrString("tool.output", truncate(outputStr, 4096))]
1590
+ attributes: [attrString("ai.toolCall.result", truncate(outputStr, 4096))]
1566
1591
  });
1567
1592
  }
1568
1593
  await this.finalizeEventIfRoot(runId);
@@ -1595,6 +1620,9 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
1595
1620
  name: "retriever",
1596
1621
  parent: this.getParent(parentRunId),
1597
1622
  eventId,
1623
+ // Ingest drops spans without an `ai.operationId` (or traceloop/gen_ai)
1624
+ // attribute; stamp one so retriever spans are persisted.
1625
+ operationId: "retriever",
1598
1626
  attributes: [
1599
1627
  attrString("retriever.name", retrieverName),
1600
1628
  attrString("retriever.query", truncate(query, 2048))
@@ -1642,6 +1670,9 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
1642
1670
  name: "agent_action",
1643
1671
  parent: this.getParent(parentRunId),
1644
1672
  eventId,
1673
+ // Ingest drops spans without an `ai.operationId` (or traceloop/gen_ai)
1674
+ // attribute; stamp one so agent_action spans are persisted.
1675
+ operationId: "agent_action",
1645
1676
  attributes: [
1646
1677
  attrString("agent.tool", action.tool),
1647
1678
  attrString("agent.tool_input", truncate(
@@ -1696,6 +1727,30 @@ function truncate(str, maxLen) {
1696
1727
  if (str.length <= maxLen) return str;
1697
1728
  return str.slice(0, maxLen - 3) + "...";
1698
1729
  }
1730
+ function messageRole(message) {
1731
+ var _a;
1732
+ switch ((_a = message._getType) == null ? void 0 : _a.call(message)) {
1733
+ case "human":
1734
+ return "user";
1735
+ case "ai":
1736
+ return "assistant";
1737
+ case "system":
1738
+ return "system";
1739
+ case "tool":
1740
+ case "function":
1741
+ return "tool";
1742
+ default:
1743
+ return "user";
1744
+ }
1745
+ }
1746
+ function serializeChatMessages(messages) {
1747
+ if (messages.length === 0) return void 0;
1748
+ const shaped = messages.map((message) => ({
1749
+ role: messageRole(message),
1750
+ content: typeof message.content === "string" ? capText2(message.content, MESSAGE_CONTENT_CAP) : message.content
1751
+ }));
1752
+ return boundedStringify(shaped);
1753
+ }
1699
1754
  function extractLLMMetadata(output) {
1700
1755
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
1701
1756
  const gen = (_b = (_a = output.generations) == null ? void 0 : _a[0]) == null ? void 0 : _b[0];
@@ -1716,7 +1771,7 @@ function extractLLMMetadata(output) {
1716
1771
  // package.json
1717
1772
  var package_default = {
1718
1773
  name: "@raindrop-ai/langchain",
1719
- version: "0.0.8",
1774
+ version: "0.1.0",
1720
1775
  description: "Raindrop integration for LangChain",
1721
1776
  main: "dist/index.js",
1722
1777
  module: "dist/index.mjs",
@@ -1797,7 +1852,8 @@ function createRaindropLangChain(opts) {
1797
1852
  projectId: opts.projectId,
1798
1853
  sdkName: "langchain",
1799
1854
  libraryName,
1800
- libraryVersion
1855
+ libraryVersion,
1856
+ defaultEventName: opts.eventName
1801
1857
  });
1802
1858
  const traceShipper = new TraceShipper({
1803
1859
  writeKey,
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/callback-handler.ts
2
2
  import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
3
3
 
4
- // ../core/dist/chunk-WKRW55KX.js
4
+ // ../core/dist/chunk-ITAZONWL.js
5
5
  function getCrypto() {
6
6
  const c = globalThis.crypto;
7
7
  return c;
@@ -391,13 +391,16 @@ function projectIdHeaders(projectId) {
391
391
  var SHUTDOWN_DEADLINE_MS = 1e4;
392
392
  var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
393
393
  function mergePatches(target, source) {
394
- var _a, _b, _c, _d;
394
+ var _a, _b, _c, _d, _e, _f;
395
395
  const out = { ...target, ...source };
396
396
  if (target.properties || source.properties) {
397
397
  out.properties = { ...(_a = target.properties) != null ? _a : {}, ...(_b = source.properties) != null ? _b : {} };
398
398
  }
399
+ if (target.featureFlags || source.featureFlags) {
400
+ out.featureFlags = { ...(_c = target.featureFlags) != null ? _c : {}, ...(_d = source.featureFlags) != null ? _d : {} };
401
+ }
399
402
  if (target.attachments || source.attachments) {
400
- out.attachments = [...(_c = target.attachments) != null ? _c : [], ...(_d = source.attachments) != null ? _d : []];
403
+ out.attachments = [...(_e = target.attachments) != null ? _e : [], ...(_f = source.attachments) != null ? _f : []];
401
404
  }
402
405
  return out;
403
406
  }
@@ -667,6 +670,7 @@ var EventShipper = class {
667
670
  ...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
668
671
  $context: this.context
669
672
  },
673
+ ...accumulated.featureFlags && Object.keys(accumulated.featureFlags).length > 0 ? { feature_flags: accumulated.featureFlags } : {},
670
674
  attachments: accumulated.attachments,
671
675
  is_pending: isPending
672
676
  };
@@ -1292,6 +1296,7 @@ function boundedStringify(value, limit) {
1292
1296
  }
1293
1297
 
1294
1298
  // src/callback-handler.ts
1299
+ var MESSAGE_CONTENT_CAP = 4096;
1295
1300
  var LANGGRAPH_INTERNAL_NAMES = /* @__PURE__ */ new Set([
1296
1301
  "LangGraph",
1297
1302
  "__start__",
@@ -1311,7 +1316,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
1311
1316
  this.traceShipper = opts.traceShipper;
1312
1317
  this.userId = opts.userId;
1313
1318
  this.convoId = opts.convoId;
1314
- this.traceChains = (_a = opts.traceChains) != null ? _a : true;
1319
+ this.traceChains = (_a = opts.traceChains) != null ? _a : false;
1315
1320
  this.traceRetrievers = (_b = opts.traceRetrievers) != null ? _b : true;
1316
1321
  this.filterLangGraphInternals = (_c = opts.filterLangGraphInternals) != null ? _c : true;
1317
1322
  }
@@ -1371,9 +1376,20 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
1371
1376
  name: "llm",
1372
1377
  parent: this.getParent(parentRunId),
1373
1378
  eventId,
1379
+ // `ai.generateText` classifies the span as an LLM generation at ingest
1380
+ // (a bare `llm`/`chat` operationId maps to INTERNAL and is skipped by
1381
+ // the rich-interaction extractor). `ai.prompt.messages` carries the
1382
+ // per-step trajectory so the Convo view can weave in tool calls
1383
+ // inline, matching the shape the ai-sdk / strands SDKs emit.
1374
1384
  attributes: [
1375
1385
  attrString("ai.model.provider", modelId),
1376
- attrString("ai.operationId", "llm")
1386
+ attrString("ai.operationId", "ai.generateText"),
1387
+ attrString(
1388
+ "ai.prompt.messages",
1389
+ boundedStringify([
1390
+ { role: "user", content: capText2(prompts.join("\n"), MESSAGE_CONTENT_CAP) }
1391
+ ])
1392
+ )
1377
1393
  ]
1378
1394
  });
1379
1395
  this.spans.set(runId, span);
@@ -1393,17 +1409,21 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
1393
1409
  if (this.filterLangGraphInternals && this.spans.has(runId)) return;
1394
1410
  const eventId = this.getEventId(runId, parentRunId);
1395
1411
  const modelId = getName(llm, runName);
1412
+ const allMessages = messages.flat();
1396
1413
  const span = this.traceShipper.startSpan({
1397
1414
  name: "llm",
1398
1415
  parent: this.getParent(parentRunId),
1399
1416
  eventId,
1417
+ // See handleLLMStart: `ai.generateText` + `ai.prompt.messages` let the
1418
+ // rich-interaction extractor render this step (and any tool calls
1419
+ // between it and the next step) inline in the Convo view.
1400
1420
  attributes: [
1401
1421
  attrString("ai.model.provider", modelId),
1402
- attrString("ai.operationId", "chat")
1422
+ attrString("ai.operationId", "ai.generateText"),
1423
+ attrString("ai.prompt.messages", serializeChatMessages(allMessages))
1403
1424
  ]
1404
1425
  });
1405
1426
  this.spans.set(runId, span);
1406
- const allMessages = messages.flat();
1407
1427
  const userMessages = allMessages.filter((m) => {
1408
1428
  var _a, _b;
1409
1429
  const role = (_b = (_a = m._getType) == null ? void 0 : _a.call(m)) != null ? _b : "unknown";
@@ -1432,6 +1452,10 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
1432
1452
  this.traceShipper.endSpan(span, {
1433
1453
  attributes: [
1434
1454
  attrString("ai.response.model", model),
1455
+ // The assistant completion for this step; paired with
1456
+ // `ai.prompt.messages` this is what the Convo view renders as the
1457
+ // assistant turn (empty on tool-only steps, so no blank bubble).
1458
+ attrString("ai.response.text", capText2(outputText, MESSAGE_CONTENT_CAP)),
1435
1459
  attrInt("ai.usage.prompt_tokens", promptTokens),
1436
1460
  attrInt("ai.usage.completion_tokens", completionTokens)
1437
1461
  ]
@@ -1516,12 +1540,13 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
1516
1540
  const eventId = this.getEventId(runId, parentRunId);
1517
1541
  const toolName = getName(tool, runName);
1518
1542
  const span = this.traceShipper.startSpan({
1519
- name: "tool",
1543
+ name: "ai.toolCall",
1520
1544
  parent: this.getParent(parentRunId),
1521
1545
  eventId,
1546
+ operationId: "ai.toolCall",
1522
1547
  attributes: [
1523
- attrString("tool.name", toolName),
1524
- attrString("tool.input", truncate(input, 4096))
1548
+ attrString("ai.toolCall.name", toolName),
1549
+ attrString("ai.toolCall.args", truncate(input, 4096))
1525
1550
  ]
1526
1551
  });
1527
1552
  this.spans.set(runId, span);
@@ -1535,7 +1560,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
1535
1560
  if (span) {
1536
1561
  const outputStr = typeof output === "string" ? output : safeStringify(output, 4096);
1537
1562
  this.traceShipper.endSpan(span, {
1538
- attributes: [attrString("tool.output", truncate(outputStr, 4096))]
1563
+ attributes: [attrString("ai.toolCall.result", truncate(outputStr, 4096))]
1539
1564
  });
1540
1565
  }
1541
1566
  await this.finalizeEventIfRoot(runId);
@@ -1568,6 +1593,9 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
1568
1593
  name: "retriever",
1569
1594
  parent: this.getParent(parentRunId),
1570
1595
  eventId,
1596
+ // Ingest drops spans without an `ai.operationId` (or traceloop/gen_ai)
1597
+ // attribute; stamp one so retriever spans are persisted.
1598
+ operationId: "retriever",
1571
1599
  attributes: [
1572
1600
  attrString("retriever.name", retrieverName),
1573
1601
  attrString("retriever.query", truncate(query, 2048))
@@ -1615,6 +1643,9 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
1615
1643
  name: "agent_action",
1616
1644
  parent: this.getParent(parentRunId),
1617
1645
  eventId,
1646
+ // Ingest drops spans without an `ai.operationId` (or traceloop/gen_ai)
1647
+ // attribute; stamp one so agent_action spans are persisted.
1648
+ operationId: "agent_action",
1618
1649
  attributes: [
1619
1650
  attrString("agent.tool", action.tool),
1620
1651
  attrString("agent.tool_input", truncate(
@@ -1669,6 +1700,30 @@ function truncate(str, maxLen) {
1669
1700
  if (str.length <= maxLen) return str;
1670
1701
  return str.slice(0, maxLen - 3) + "...";
1671
1702
  }
1703
+ function messageRole(message) {
1704
+ var _a;
1705
+ switch ((_a = message._getType) == null ? void 0 : _a.call(message)) {
1706
+ case "human":
1707
+ return "user";
1708
+ case "ai":
1709
+ return "assistant";
1710
+ case "system":
1711
+ return "system";
1712
+ case "tool":
1713
+ case "function":
1714
+ return "tool";
1715
+ default:
1716
+ return "user";
1717
+ }
1718
+ }
1719
+ function serializeChatMessages(messages) {
1720
+ if (messages.length === 0) return void 0;
1721
+ const shaped = messages.map((message) => ({
1722
+ role: messageRole(message),
1723
+ content: typeof message.content === "string" ? capText2(message.content, MESSAGE_CONTENT_CAP) : message.content
1724
+ }));
1725
+ return boundedStringify(shaped);
1726
+ }
1672
1727
  function extractLLMMetadata(output) {
1673
1728
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
1674
1729
  const gen = (_b = (_a = output.generations) == null ? void 0 : _a[0]) == null ? void 0 : _b[0];
@@ -1689,7 +1744,7 @@ function extractLLMMetadata(output) {
1689
1744
  // package.json
1690
1745
  var package_default = {
1691
1746
  name: "@raindrop-ai/langchain",
1692
- version: "0.0.8",
1747
+ version: "0.1.0",
1693
1748
  description: "Raindrop integration for LangChain",
1694
1749
  main: "dist/index.js",
1695
1750
  module: "dist/index.mjs",
@@ -1770,7 +1825,8 @@ function createRaindropLangChain(opts) {
1770
1825
  projectId: opts.projectId,
1771
1826
  sdkName: "langchain",
1772
1827
  libraryName,
1773
- libraryVersion
1828
+ libraryVersion,
1829
+ defaultEventName: opts.eventName
1774
1830
  });
1775
1831
  const traceShipper = new TraceShipper({
1776
1832
  writeKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raindrop-ai/langchain",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
4
4
  "description": "Raindrop integration for LangChain",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -31,7 +31,7 @@
31
31
  "tsup": "^8.4.0",
32
32
  "typescript": "^5.3.3",
33
33
  "vitest": "^2.1.9",
34
- "@raindrop-ai/core": "0.0.5"
34
+ "@raindrop-ai/core": "0.1.1"
35
35
  },
36
36
  "tsup": {
37
37
  "entry": [