trace-agent-langchain 0.0.1__py3-none-any.whl

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.
@@ -0,0 +1,5 @@
1
+ __all__ = ["__version__", "TraceAgentLangChainCallback"]
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from trace_agent_langchain.callback import TraceAgentLangChainCallback
@@ -0,0 +1,654 @@
1
+ """LangChain callback handler that streams execution events into TraceAgent.
2
+
3
+ Usage:
4
+ from trace_agent_sdk import TraceAgentClient
5
+ from trace_agent_langchain import TraceAgentLangChainCallback
6
+
7
+ client = TraceAgentClient(base_url="http://127.0.0.1:8000")
8
+ run = client.start_run(agent_name="my-agent", goal="Do something")
9
+
10
+ callback = run.as_langchain_callback()
11
+ chain.invoke({"input": "..."}, config={"callbacks": [callback]})
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import time
17
+ import traceback
18
+ from typing import Any
19
+
20
+ try:
21
+ from langchain_core.callbacks import BaseCallbackHandler
22
+ from langchain_core.outputs import ChatResult, LLMResult
23
+ except ImportError: # pragma: no cover
24
+ BaseCallbackHandler = None # type: ignore[misc,assignment]
25
+ LLMResult = None # type: ignore[misc,assignment]
26
+ ChatResult = None # type: ignore[misc,assignment]
27
+
28
+
29
+ class TraceAgentLangChainCallback(BaseCallbackHandler):
30
+ """Record LangChain execution as TraceAgent observations.
31
+
32
+ The callback translates LangChain lifecycle events into HTTP POSTs to the
33
+ TraceAgent backend. If the backend is unreachable the callback logs a warning
34
+ but **never** crashes the calling chain/agent.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ run_session: Any,
40
+ *,
41
+ record_chain_events: bool = True,
42
+ record_retriever_events: bool = True,
43
+ ) -> None:
44
+ if BaseCallbackHandler is None:
45
+ raise RuntimeError(
46
+ "langchain-core is not installed. "
47
+ "Install it with: uv pip install -e '.[langchain]'"
48
+ )
49
+ self.run_session = run_session
50
+ self.record_chain_events = record_chain_events
51
+ self.record_retriever_events = record_retriever_events
52
+ # langchain run_id -> start timestamp (for duration calculation)
53
+ self._starts: dict[str, float] = {}
54
+
55
+ # ── Helpers ─────────────────────────────────────────────────────────────
56
+
57
+ def _post_observations(self, observations: list[dict[str, Any]]) -> None:
58
+ """Send observations to the TraceAgent backend."""
59
+ if not observations:
60
+ return
61
+ try:
62
+ self.run_session.client._request(
63
+ "POST",
64
+ f"/api/runs/{self.run_session.run_id}/observations",
65
+ {"observations": observations},
66
+ )
67
+ except Exception:
68
+ # Silently ignore backend errors so the agent keeps running.
69
+ traceback.print_exc()
70
+
71
+ def _post_tool_results(self, results: list[dict[str, Any]]) -> None:
72
+ """Send tool results to the TraceAgent backend."""
73
+ if not results:
74
+ return
75
+ try:
76
+ self.run_session.client._request(
77
+ "POST",
78
+ f"/api/runs/{self.run_session.run_id}/tool-results",
79
+ {"results": results},
80
+ )
81
+ except Exception:
82
+ traceback.print_exc()
83
+
84
+ @staticmethod
85
+ def _extract_token_usage(result: LLMResult | ChatResult | None) -> dict[str, Any]:
86
+ """Try to extract token usage from an LLM / Chat result."""
87
+ if result is None:
88
+ return {}
89
+ llm_output = getattr(result, "llm_output", None) or {}
90
+ usage = llm_output.get("token_usage") or {}
91
+ # OpenAI format
92
+ if "prompt_tokens" in usage:
93
+ return {
94
+ "prompt_tokens": usage.get("prompt_tokens"),
95
+ "completion_tokens": usage.get("completion_tokens"),
96
+ "total_tokens": usage.get("total_tokens"),
97
+ }
98
+ # Anthropic format
99
+ if "input_tokens" in usage:
100
+ return {
101
+ "prompt_tokens": usage.get("input_tokens"),
102
+ "completion_tokens": usage.get("output_tokens"),
103
+ "total_tokens": (
104
+ (usage.get("input_tokens") or 0)
105
+ + (usage.get("output_tokens") or 0)
106
+ ),
107
+ }
108
+ return dict(usage)
109
+
110
+ # ── Chain lifecycle ─────────────────────────────────────────────────────
111
+
112
+ def on_chain_start(
113
+ self,
114
+ serialized: dict[str, Any] | None,
115
+ inputs: dict[str, Any],
116
+ *,
117
+ run_id: str,
118
+ parent_run_id: str | None = None,
119
+ tags: list[str] | None = None,
120
+ metadata: dict[str, Any] | None = None,
121
+ **kwargs: Any,
122
+ ) -> None:
123
+ if not self.record_chain_events:
124
+ return
125
+ self._starts[run_id] = time.time()
126
+ chain_type = (serialized or {}).get("name", "unknown")
127
+ inputs_keys = list(inputs.keys()) if isinstance(inputs, dict) else []
128
+ self._post_observations(
129
+ [
130
+ {
131
+ "kind": "chain_step",
132
+ "step_id": str(run_id),
133
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
134
+ "status": "running",
135
+ "input_summary": f"Chain '{chain_type}' started",
136
+ "metadata": {
137
+ "chain_type": chain_type,
138
+ "inputs_keys": inputs_keys,
139
+ "tags": tags,
140
+ **(metadata or {}),
141
+ },
142
+ }
143
+ ]
144
+ )
145
+
146
+ def on_chain_end(
147
+ self,
148
+ outputs: dict[str, Any],
149
+ *,
150
+ run_id: str,
151
+ parent_run_id: str | None = None,
152
+ tags: list[str] | None = None,
153
+ metadata: dict[str, Any] | None = None,
154
+ **kwargs: Any,
155
+ ) -> None:
156
+ if not self.record_chain_events:
157
+ return
158
+ duration_ms = int((time.time() - self._starts.pop(run_id, time.time())) * 1000)
159
+ output_keys = list(outputs.keys()) if isinstance(outputs, dict) else []
160
+ self._post_observations(
161
+ [
162
+ {
163
+ "kind": "chain_step",
164
+ "step_id": str(run_id),
165
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
166
+ "status": "succeeded",
167
+ "duration_ms": duration_ms,
168
+ "input_summary": f"Chain completed",
169
+ "metadata": {
170
+ "output_keys": output_keys,
171
+ "tags": tags,
172
+ **(metadata or {}),
173
+ },
174
+ }
175
+ ]
176
+ )
177
+
178
+ def on_chain_error(
179
+ self,
180
+ error: BaseException,
181
+ *,
182
+ run_id: str,
183
+ parent_run_id: str | None = None,
184
+ tags: list[str] | None = None,
185
+ metadata: dict[str, Any] | None = None,
186
+ **kwargs: Any,
187
+ ) -> None:
188
+ if not self.record_chain_events:
189
+ return
190
+ duration_ms = int((time.time() - self._starts.pop(run_id, time.time())) * 1000)
191
+ self._post_observations(
192
+ [
193
+ {
194
+ "kind": "chain_step",
195
+ "step_id": str(run_id),
196
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
197
+ "status": "failed",
198
+ "duration_ms": duration_ms,
199
+ "error_code": error.__class__.__name__,
200
+ "error_summary": str(error),
201
+ "metadata": {"tags": tags, **(metadata or {})},
202
+ }
203
+ ]
204
+ )
205
+
206
+ # ── LLM / Chat model lifecycle ──────────────────────────────────────────
207
+
208
+ def _on_llm_start(
209
+ self,
210
+ *,
211
+ run_id: str,
212
+ parent_run_id: str | None,
213
+ provider: str | None,
214
+ model: str | None,
215
+ metadata: dict[str, Any] | None,
216
+ ) -> None:
217
+ self._starts[run_id] = time.time()
218
+ self._post_observations(
219
+ [
220
+ {
221
+ "kind": "llm_call",
222
+ "step_id": str(run_id),
223
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
224
+ "status": "running",
225
+ "provider": provider,
226
+ "model": model,
227
+ "metadata": metadata or {},
228
+ }
229
+ ]
230
+ )
231
+
232
+ def _on_llm_end(
233
+ self,
234
+ result: LLMResult | ChatResult | None,
235
+ *,
236
+ run_id: str,
237
+ parent_run_id: str | None,
238
+ provider: str | None,
239
+ model: str | None,
240
+ metadata: dict[str, Any] | None,
241
+ ) -> None:
242
+ duration_ms = int((time.time() - self._starts.pop(run_id, time.time())) * 1000)
243
+ usage = self._extract_token_usage(result)
244
+ self._post_observations(
245
+ [
246
+ {
247
+ "kind": "llm_call",
248
+ "step_id": str(run_id),
249
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
250
+ "status": "succeeded",
251
+ "duration_ms": duration_ms,
252
+ "provider": provider,
253
+ "model": model,
254
+ "metadata": {**usage, **(metadata or {})},
255
+ }
256
+ ]
257
+ )
258
+
259
+ def _on_llm_error(
260
+ self,
261
+ error: BaseException,
262
+ *,
263
+ run_id: str,
264
+ parent_run_id: str | None,
265
+ provider: str | None,
266
+ model: str | None,
267
+ metadata: dict[str, Any] | None,
268
+ ) -> None:
269
+ duration_ms = int((time.time() - self._starts.pop(run_id, time.time())) * 1000)
270
+ self._post_observations(
271
+ [
272
+ {
273
+ "kind": "llm_call",
274
+ "step_id": str(run_id),
275
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
276
+ "status": "failed",
277
+ "duration_ms": duration_ms,
278
+ "provider": provider,
279
+ "model": model,
280
+ "error_code": error.__class__.__name__,
281
+ "error_summary": str(error),
282
+ "metadata": metadata or {},
283
+ }
284
+ ]
285
+ )
286
+
287
+ def on_llm_start(
288
+ self,
289
+ serialized: dict[str, Any] | None,
290
+ prompts: list[str],
291
+ *,
292
+ run_id: str,
293
+ parent_run_id: str | None = None,
294
+ tags: list[str] | None = None,
295
+ metadata: dict[str, Any] | None = None,
296
+ **kwargs: Any,
297
+ ) -> None:
298
+ provider = (serialized or {}).get("name", "unknown")
299
+ self._on_llm_start(
300
+ run_id=run_id,
301
+ parent_run_id=parent_run_id,
302
+ provider=provider,
303
+ model=None,
304
+ metadata={"tags": tags, **(metadata or {})},
305
+ )
306
+
307
+ def on_llm_end(
308
+ self,
309
+ response: LLMResult,
310
+ *,
311
+ run_id: str,
312
+ parent_run_id: str | None = None,
313
+ tags: list[str] | None = None,
314
+ metadata: dict[str, Any] | None = None,
315
+ **kwargs: Any,
316
+ ) -> None:
317
+ provider = (metadata or {}).get("name", "unknown")
318
+ self._on_llm_end(
319
+ response,
320
+ run_id=run_id,
321
+ parent_run_id=parent_run_id,
322
+ provider=provider,
323
+ model=None,
324
+ metadata={"tags": tags, **(metadata or {})},
325
+ )
326
+
327
+ def on_llm_error(
328
+ self,
329
+ error: BaseException,
330
+ *,
331
+ run_id: str,
332
+ parent_run_id: str | None = None,
333
+ tags: list[str] | None = None,
334
+ metadata: dict[str, Any] | None = None,
335
+ **kwargs: Any,
336
+ ) -> None:
337
+ provider = (metadata or {}).get("name", "unknown")
338
+ self._on_llm_error(
339
+ error,
340
+ run_id=run_id,
341
+ parent_run_id=parent_run_id,
342
+ provider=provider,
343
+ model=None,
344
+ metadata={"tags": tags, **(metadata or {})},
345
+ )
346
+
347
+ def on_chat_model_start(
348
+ self,
349
+ serialized: dict[str, Any] | None,
350
+ messages: list[list[Any]],
351
+ *,
352
+ run_id: str,
353
+ parent_run_id: str | None = None,
354
+ tags: list[str] | None = None,
355
+ metadata: dict[str, Any] | None = None,
356
+ **kwargs: Any,
357
+ ) -> None:
358
+ provider = (serialized or {}).get("name", "unknown")
359
+ model = (kwargs.get("invocation_params") or {}).get("model")
360
+ self._on_llm_start(
361
+ run_id=run_id,
362
+ parent_run_id=parent_run_id,
363
+ provider=provider,
364
+ model=model,
365
+ metadata={"tags": tags, **(metadata or {})},
366
+ )
367
+
368
+ def on_chat_model_end(
369
+ self,
370
+ response: ChatResult,
371
+ *,
372
+ run_id: str,
373
+ parent_run_id: str | None = None,
374
+ tags: list[str] | None = None,
375
+ metadata: dict[str, Any] | None = None,
376
+ **kwargs: Any,
377
+ ) -> None:
378
+ provider = (metadata or {}).get("name", "unknown")
379
+ model = (kwargs.get("invocation_params") or {}).get("model")
380
+ self._on_llm_end(
381
+ response,
382
+ run_id=run_id,
383
+ parent_run_id=parent_run_id,
384
+ provider=provider,
385
+ model=model,
386
+ metadata={"tags": tags, **(metadata or {})},
387
+ )
388
+
389
+ # ── Tool lifecycle ──────────────────────────────────────────────────────
390
+
391
+ def on_tool_start(
392
+ self,
393
+ serialized: dict[str, Any] | None,
394
+ input_str: str,
395
+ *,
396
+ run_id: str,
397
+ parent_run_id: str | None = None,
398
+ tags: list[str] | None = None,
399
+ metadata: dict[str, Any] | None = None,
400
+ **kwargs: Any,
401
+ ) -> None:
402
+ self._starts[run_id] = time.time()
403
+ tool_name = (serialized or {}).get("name", "unknown")
404
+ self._post_observations(
405
+ [
406
+ {
407
+ "kind": "tool_call",
408
+ "step_id": str(run_id),
409
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
410
+ "status": "running",
411
+ "tool_name": tool_name,
412
+ "input_summary": f"Tool '{tool_name}' started",
413
+ "metadata": {
414
+ "input": input_str,
415
+ "tags": tags,
416
+ **(metadata or {}),
417
+ },
418
+ }
419
+ ]
420
+ )
421
+
422
+ def on_tool_end(
423
+ self,
424
+ output: Any,
425
+ *,
426
+ run_id: str,
427
+ parent_run_id: str | None = None,
428
+ tags: list[str] | None = None,
429
+ metadata: dict[str, Any] | None = None,
430
+ **kwargs: Any,
431
+ ) -> None:
432
+ duration_ms = int((time.time() - self._starts.pop(run_id, time.time())) * 1000)
433
+ tool_name = (metadata or {}).get("name", "unknown")
434
+ output_str = str(output) if output is not None else ""
435
+ self._post_tool_results(
436
+ [
437
+ {
438
+ "call_id": run_id,
439
+ "name": tool_name,
440
+ "step_id": run_id,
441
+ "status": "succeeded",
442
+ "output_summary": output_str[:500],
443
+ "output_hash": None,
444
+ "duration_ms": duration_ms,
445
+ }
446
+ ]
447
+ )
448
+ self._post_observations(
449
+ [
450
+ {
451
+ "kind": "tool_call",
452
+ "step_id": str(run_id),
453
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
454
+ "status": "succeeded",
455
+ "tool_name": tool_name,
456
+ "duration_ms": duration_ms,
457
+ "input_summary": f"Tool '{tool_name}' succeeded",
458
+ "metadata": {"tags": tags, **(metadata or {})},
459
+ }
460
+ ]
461
+ )
462
+
463
+ def on_tool_error(
464
+ self,
465
+ error: BaseException,
466
+ *,
467
+ run_id: str,
468
+ parent_run_id: str | None = None,
469
+ tags: list[str] | None = None,
470
+ metadata: dict[str, Any] | None = None,
471
+ **kwargs: Any,
472
+ ) -> None:
473
+ duration_ms = int((time.time() - self._starts.pop(run_id, time.time())) * 1000)
474
+ tool_name = (metadata or {}).get("name", "unknown")
475
+ error_summary = str(error)
476
+ self._post_tool_results(
477
+ [
478
+ {
479
+ "call_id": run_id,
480
+ "name": tool_name,
481
+ "step_id": run_id,
482
+ "status": "failed",
483
+ "output_summary": error_summary[:500],
484
+ "output_hash": None,
485
+ "error_code": error.__class__.__name__,
486
+ "error_summary": error_summary,
487
+ "duration_ms": duration_ms,
488
+ }
489
+ ]
490
+ )
491
+ self._post_observations(
492
+ [
493
+ {
494
+ "kind": "tool_call",
495
+ "step_id": str(run_id),
496
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
497
+ "status": "failed",
498
+ "tool_name": tool_name,
499
+ "duration_ms": duration_ms,
500
+ "error_code": error.__class__.__name__,
501
+ "error_summary": error_summary,
502
+ "metadata": {"tags": tags, **(metadata or {})},
503
+ }
504
+ ]
505
+ )
506
+
507
+ # ── Retriever lifecycle ─────────────────────────────────────────────────
508
+
509
+ def on_retriever_start(
510
+ self,
511
+ serialized: dict[str, Any] | None,
512
+ query: str,
513
+ *,
514
+ run_id: str,
515
+ parent_run_id: str | None = None,
516
+ tags: list[str] | None = None,
517
+ metadata: dict[str, Any] | None = None,
518
+ **kwargs: Any,
519
+ ) -> None:
520
+ if not self.record_retriever_events:
521
+ return
522
+ self._starts[run_id] = time.time()
523
+ self._post_observations(
524
+ [
525
+ {
526
+ "kind": "retriever_query",
527
+ "step_id": str(run_id),
528
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
529
+ "status": "running",
530
+ "input_summary": f"Retriever query: {query[:200]}",
531
+ "metadata": {"query": query, "tags": tags, **(metadata or {})},
532
+ }
533
+ ]
534
+ )
535
+
536
+ def on_retriever_end(
537
+ self,
538
+ documents: list[Any],
539
+ *,
540
+ run_id: str,
541
+ parent_run_id: str | None = None,
542
+ tags: list[str] | None = None,
543
+ metadata: dict[str, Any] | None = None,
544
+ **kwargs: Any,
545
+ ) -> None:
546
+ if not self.record_retriever_events:
547
+ return
548
+ duration_ms = int((time.time() - self._starts.pop(run_id, time.time())) * 1000)
549
+ self._post_observations(
550
+ [
551
+ {
552
+ "kind": "retriever_query",
553
+ "step_id": str(run_id),
554
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
555
+ "status": "succeeded",
556
+ "duration_ms": duration_ms,
557
+ "input_summary": f"Retriever returned {len(documents)} documents",
558
+ "metadata": {
559
+ "document_count": len(documents),
560
+ "tags": tags,
561
+ **(metadata or {}),
562
+ },
563
+ }
564
+ ]
565
+ )
566
+
567
+ def on_retriever_error(
568
+ self,
569
+ error: BaseException,
570
+ *,
571
+ run_id: str,
572
+ parent_run_id: str | None = None,
573
+ tags: list[str] | None = None,
574
+ metadata: dict[str, Any] | None = None,
575
+ **kwargs: Any,
576
+ ) -> None:
577
+ if not self.record_retriever_events:
578
+ return
579
+ duration_ms = int((time.time() - self._starts.pop(run_id, time.time())) * 1000)
580
+ self._post_observations(
581
+ [
582
+ {
583
+ "kind": "retriever_query",
584
+ "step_id": str(run_id),
585
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
586
+ "status": "failed",
587
+ "duration_ms": duration_ms,
588
+ "error_code": error.__class__.__name__,
589
+ "error_summary": str(error),
590
+ "metadata": {"tags": tags, **(metadata or {})},
591
+ }
592
+ ]
593
+ )
594
+
595
+ # ── Agent lifecycle ─────────────────────────────────────────────────────
596
+
597
+ def on_agent_action(
598
+ self,
599
+ action: Any,
600
+ *,
601
+ run_id: str,
602
+ parent_run_id: str | None = None,
603
+ tags: list[str] | None = None,
604
+ metadata: dict[str, Any] | None = None,
605
+ **kwargs: Any,
606
+ ) -> None:
607
+ tool = getattr(action, "tool", None) or "unknown"
608
+ tool_input = getattr(action, "tool_input", None) or {}
609
+ self._post_observations(
610
+ [
611
+ {
612
+ "kind": "agent_action",
613
+ "step_id": str(run_id),
614
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
615
+ "status": "running",
616
+ "tool_name": tool,
617
+ "input_summary": f"Agent action: {tool}",
618
+ "metadata": {
619
+ "tool": tool,
620
+ "tool_input": tool_input,
621
+ "tags": tags,
622
+ **(metadata or {}),
623
+ },
624
+ }
625
+ ]
626
+ )
627
+
628
+ def on_agent_finish(
629
+ self,
630
+ finish: Any,
631
+ *,
632
+ run_id: str,
633
+ parent_run_id: str | None = None,
634
+ tags: list[str] | None = None,
635
+ metadata: dict[str, Any] | None = None,
636
+ **kwargs: Any,
637
+ ) -> None:
638
+ output = getattr(finish, "return_values", {})
639
+ self._post_observations(
640
+ [
641
+ {
642
+ "kind": "agent_action",
643
+ "step_id": str(run_id),
644
+ "parent_step_id": str(parent_run_id) if parent_run_id else None,
645
+ "status": "succeeded",
646
+ "input_summary": "Agent finished",
647
+ "metadata": {
648
+ "output_keys": list(output.keys()),
649
+ "tags": tags,
650
+ **(metadata or {}),
651
+ },
652
+ }
653
+ ]
654
+ )
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: trace-agent-langchain
3
+ Version: 0.0.1
4
+ Summary: LangChain callback integration for TraceAgent.
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: trace-agent-sdk
7
+ Requires-Dist: langchain-core<0.4.0,>=0.3.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest<9.0.0,>=8.3.2; extra == "dev"
10
+ Requires-Dist: langchain<0.4.0,>=0.3.0; extra == "dev"
11
+ Requires-Dist: langchain-openai<0.3.0,>=0.2.0; extra == "dev"
12
+ Requires-Dist: trace-agent-server; extra == "dev"
@@ -0,0 +1,6 @@
1
+ trace_agent_langchain/__init__.py,sha256=DYp3DMO2WajFK1dmu_J-aOyiv31el5iekHUEQlAkRRQ,152
2
+ trace_agent_langchain/callback.py,sha256=lMntz9RMSjWFAOyythm3GYAJKe_hpVxfKXej-g2fH2E,22716
3
+ trace_agent_langchain-0.0.1.dist-info/METADATA,sha256=kPo2xlXEgfnB7kUp8c9apnyDHMVwFaOCW-QJQftaF5Y,459
4
+ trace_agent_langchain-0.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
5
+ trace_agent_langchain-0.0.1.dist-info/top_level.txt,sha256=gzLKO-AMGL2ZY03u3wCA70IMHodevfzUVGjdh512yrI,22
6
+ trace_agent_langchain-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ trace_agent_langchain