crewlayer 0.1.0__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.
- crewlayer/__init__.py +54 -0
- crewlayer/_actions.py +153 -0
- crewlayer/_client.py +87 -0
- crewlayer/_context.py +107 -0
- crewlayer/_exceptions.py +71 -0
- crewlayer/_http.py +101 -0
- crewlayer/_memory.py +192 -0
- crewlayer/_types.py +242 -0
- crewlayer/integrations/__init__.py +6 -0
- crewlayer/integrations/autogen.py +538 -0
- crewlayer/integrations/crewai.py +215 -0
- crewlayer/integrations/langchain.py +404 -0
- crewlayer/integrations/llamaindex.py +527 -0
- crewlayer/py.typed +0 -0
- crewlayer-0.1.0.dist-info/METADATA +232 -0
- crewlayer-0.1.0.dist-info/RECORD +17 -0
- crewlayer-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
"""LlamaIndex integration for the CrewLayer SDK.
|
|
2
|
+
|
|
3
|
+
Provides four adapters:
|
|
4
|
+
|
|
5
|
+
- ``CrewLayerMemoryBuffer`` — BaseMemory backed by CrewLayer short-term memory
|
|
6
|
+
- ``CrewLayerVectorIndex`` — index backed by CrewLayer semantic recall (pgvector)
|
|
7
|
+
- ``CrewLayerQueryEngine`` — query engine returned by ``index.as_query_engine()``,
|
|
8
|
+
logs every query as a ``llamaindex.query`` action
|
|
9
|
+
- ``CrewLayerCallbackManager`` — BaseCallbackHandler that logs LLM calls and
|
|
10
|
+
tool/function calls as CrewLayer actions
|
|
11
|
+
|
|
12
|
+
Install::
|
|
13
|
+
|
|
14
|
+
pip install crewlayer[llamaindex]
|
|
15
|
+
|
|
16
|
+
Usage::
|
|
17
|
+
|
|
18
|
+
from crewlayer import CrewLayerClient
|
|
19
|
+
from crewlayer.integrations.llamaindex import (
|
|
20
|
+
CrewLayerMemoryBuffer,
|
|
21
|
+
CrewLayerVectorIndex,
|
|
22
|
+
CrewLayerCallbackManager,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
client = CrewLayerClient(api_key="crwl_...")
|
|
26
|
+
|
|
27
|
+
# Chat memory
|
|
28
|
+
memory = CrewLayerMemoryBuffer(client=client, agent_id="agent-uuid")
|
|
29
|
+
|
|
30
|
+
# Vector index + query engine
|
|
31
|
+
index = CrewLayerVectorIndex(client=client, agent_id="agent-uuid")
|
|
32
|
+
index.insert(document)
|
|
33
|
+
engine = index.as_query_engine()
|
|
34
|
+
response = engine.query("¿qué recuerdas sobre el cliente?")
|
|
35
|
+
print(response.response)
|
|
36
|
+
|
|
37
|
+
# Callback handler (add to LlamaIndex's CallbackManager)
|
|
38
|
+
from llama_index.core.callbacks import CallbackManager
|
|
39
|
+
handler = CrewLayerCallbackManager(client=client, agent_id="agent-uuid")
|
|
40
|
+
llm = OpenAI(callback_manager=CallbackManager([handler]))
|
|
41
|
+
"""
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import time
|
|
45
|
+
from dataclasses import dataclass, field
|
|
46
|
+
from typing import Any
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Optional LlamaIndex imports — graceful fallback when not installed
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
from llama_index.core.memory import BaseMemory as _LIBaseMemory # type: ignore[import]
|
|
54
|
+
from llama_index.core.llms import ChatMessage, MessageRole # type: ignore[import]
|
|
55
|
+
_LI_MEMORY = True
|
|
56
|
+
except ImportError:
|
|
57
|
+
_LI_MEMORY = False
|
|
58
|
+
_LIBaseMemory = object # type: ignore[assignment, misc]
|
|
59
|
+
|
|
60
|
+
class MessageRole: # type: ignore[no-redef]
|
|
61
|
+
USER = "user"
|
|
62
|
+
ASSISTANT = "assistant"
|
|
63
|
+
SYSTEM = "system"
|
|
64
|
+
|
|
65
|
+
class ChatMessage: # type: ignore[no-redef]
|
|
66
|
+
"""Stub — requires llama-index-core."""
|
|
67
|
+
def __init__(self, role: Any = "user", content: str = "", **kwargs: Any) -> None:
|
|
68
|
+
self.role = role
|
|
69
|
+
self.content = content
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
from llama_index.core.callbacks.base_handler import ( # type: ignore[import]
|
|
74
|
+
BaseCallbackHandler as _LIBaseCallbackHandler,
|
|
75
|
+
)
|
|
76
|
+
from llama_index.core.callbacks.schema import CBEventType # type: ignore[import]
|
|
77
|
+
_LI_CALLBACKS = True
|
|
78
|
+
except ImportError:
|
|
79
|
+
_LI_CALLBACKS = False
|
|
80
|
+
_LIBaseCallbackHandler = object # type: ignore[assignment, misc]
|
|
81
|
+
|
|
82
|
+
class CBEventType: # type: ignore[no-redef]
|
|
83
|
+
"""Stub — requires llama-index-core."""
|
|
84
|
+
LLM = "llm"
|
|
85
|
+
FUNCTION_CALL = "function_call"
|
|
86
|
+
AGENT_STEP = "agent_step"
|
|
87
|
+
QUERY = "query"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Internal helpers
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
# Event names that are worth logging to CrewLayer (avoids noise from EMBEDDING etc.)
|
|
95
|
+
_LOGGED_EVENTS: frozenset[str] = frozenset({"llm", "function_call", "agent_step"})
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _role_str(role: Any) -> str:
|
|
99
|
+
"""Normalise a LlamaIndex MessageRole (enum or str) to a plain string."""
|
|
100
|
+
s = str(role.value if hasattr(role, "value") else role)
|
|
101
|
+
return "user" if s == "human" else s
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _event_name(event_type: Any) -> str:
|
|
105
|
+
return str(event_type.value if hasattr(event_type, "value") else event_type)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class QueryResponse:
|
|
110
|
+
"""Lightweight response returned by ``CrewLayerQueryEngine.query()``.
|
|
111
|
+
|
|
112
|
+
Compatible with direct use; also holds the raw ``source_nodes`` from
|
|
113
|
+
CrewLayer so callers can inspect similarity scores without needing
|
|
114
|
+
the full LlamaIndex response hierarchy.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
response: str
|
|
118
|
+
source_nodes: list[Any] = field(default_factory=list)
|
|
119
|
+
|
|
120
|
+
def __str__(self) -> str:
|
|
121
|
+
return self.response
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# CrewLayerMemoryBuffer
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class CrewLayerMemoryBuffer(_LIBaseMemory): # type: ignore[misc]
|
|
130
|
+
"""LlamaIndex ``BaseMemory`` backed by CrewLayer short-term memory.
|
|
131
|
+
|
|
132
|
+
Each ``put()`` call appends a message to the agent's Redis session store.
|
|
133
|
+
``get()`` fetches recent messages and converts them to ``ChatMessage`` objects.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
client: A ``CrewLayerClient`` (sync) instance.
|
|
137
|
+
agent_id: Target agent UUID.
|
|
138
|
+
session_id: Session key (default ``"default"``).
|
|
139
|
+
limit: Max messages returned by ``get()`` (default ``50``).
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
*,
|
|
145
|
+
client: Any,
|
|
146
|
+
agent_id: str,
|
|
147
|
+
session_id: str = "default",
|
|
148
|
+
limit: int = 50,
|
|
149
|
+
) -> None:
|
|
150
|
+
# Use __dict__ directly to bypass Pydantic's __setattr__ when
|
|
151
|
+
# llama_index's BaseMemory is a Pydantic model.
|
|
152
|
+
try:
|
|
153
|
+
super().__init__()
|
|
154
|
+
except TypeError:
|
|
155
|
+
pass
|
|
156
|
+
self.__dict__.update({
|
|
157
|
+
"_client": client,
|
|
158
|
+
"_agent_id": agent_id,
|
|
159
|
+
"_session_id": session_id,
|
|
160
|
+
"_limit": limit,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
# ------------------------------------------------------------------
|
|
164
|
+
# BaseMemory interface
|
|
165
|
+
# ------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
def get(self, input: str | None = None, **kwargs: Any) -> list[Any]:
|
|
168
|
+
"""Return recent messages as ``ChatMessage`` objects."""
|
|
169
|
+
sm = self._client.memory.messages(
|
|
170
|
+
self._agent_id,
|
|
171
|
+
session_id=self._session_id,
|
|
172
|
+
limit=self._limit,
|
|
173
|
+
)
|
|
174
|
+
return [
|
|
175
|
+
ChatMessage(role=_role_str(m.role), content=m.content)
|
|
176
|
+
for m in sm.messages
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
def get_all(self) -> list[Any]:
|
|
180
|
+
"""Return all stored messages — delegates to ``get()``."""
|
|
181
|
+
return self.get()
|
|
182
|
+
|
|
183
|
+
def put(self, message: Any) -> None:
|
|
184
|
+
"""Append a single ``ChatMessage`` to CrewLayer short-term memory."""
|
|
185
|
+
role = _role_str(getattr(message, "role", "user"))
|
|
186
|
+
content = str(getattr(message, "content", ""))
|
|
187
|
+
self._client.memory.append(
|
|
188
|
+
self._agent_id,
|
|
189
|
+
role,
|
|
190
|
+
content,
|
|
191
|
+
session_id=self._session_id,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def set(self, messages: list[Any]) -> None:
|
|
195
|
+
"""Overwrite history by appending each message in order."""
|
|
196
|
+
for m in messages:
|
|
197
|
+
self.put(m)
|
|
198
|
+
|
|
199
|
+
def reset(self) -> None:
|
|
200
|
+
"""No-op — use a fresh ``session_id`` or rely on Redis TTL."""
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
# CrewLayerVectorIndex
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class CrewLayerVectorIndex:
|
|
209
|
+
"""Vector index backed by CrewLayer semantic recall.
|
|
210
|
+
|
|
211
|
+
``insert()`` persists documents as long-term memories via the extract
|
|
212
|
+
endpoint. ``similarity_search()`` uses pgvector cosine similarity.
|
|
213
|
+
``as_query_engine()`` returns a :class:`CrewLayerQueryEngine` that logs
|
|
214
|
+
every query as a ``llamaindex.query`` action.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
client: A ``CrewLayerClient`` (sync) instance.
|
|
218
|
+
agent_id: Target agent UUID.
|
|
219
|
+
similarity_top_k: Default number of results (default ``4``).
|
|
220
|
+
min_similarity: Minimum cosine similarity threshold (default ``0.0``).
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
def __init__(
|
|
224
|
+
self,
|
|
225
|
+
*,
|
|
226
|
+
client: Any,
|
|
227
|
+
agent_id: str,
|
|
228
|
+
similarity_top_k: int = 4,
|
|
229
|
+
min_similarity: float = 0.0,
|
|
230
|
+
) -> None:
|
|
231
|
+
self._client = client
|
|
232
|
+
self._agent_id = agent_id
|
|
233
|
+
self._similarity_top_k = similarity_top_k
|
|
234
|
+
self._min_similarity = min_similarity
|
|
235
|
+
|
|
236
|
+
def insert(self, document: Any) -> list[str]:
|
|
237
|
+
"""Persist a document as long-term memory via the extract endpoint.
|
|
238
|
+
|
|
239
|
+
Accepts any object with a ``.text`` or ``.get_content()`` attribute,
|
|
240
|
+
or anything ``str()``-able.
|
|
241
|
+
|
|
242
|
+
Returns the list of new memory IDs.
|
|
243
|
+
"""
|
|
244
|
+
text = (
|
|
245
|
+
getattr(document, "text", None)
|
|
246
|
+
or (
|
|
247
|
+
document.get_content()
|
|
248
|
+
if callable(getattr(document, "get_content", None))
|
|
249
|
+
else None
|
|
250
|
+
)
|
|
251
|
+
or str(document)
|
|
252
|
+
)
|
|
253
|
+
result = self._client.memory.extract(
|
|
254
|
+
self._agent_id,
|
|
255
|
+
conversation=f"Remember the following:\n{text}",
|
|
256
|
+
)
|
|
257
|
+
return result.memory_ids
|
|
258
|
+
|
|
259
|
+
def similarity_search(self, query: str, top_k: int | None = None) -> list[Any]:
|
|
260
|
+
"""Return the top-k memories most similar to *query*.
|
|
261
|
+
|
|
262
|
+
Returns a list of ``MemoryItem`` objects (from ``_types.py``).
|
|
263
|
+
"""
|
|
264
|
+
limit = top_k if top_k is not None else self._similarity_top_k
|
|
265
|
+
result = self._client.memory.recall(
|
|
266
|
+
self._agent_id,
|
|
267
|
+
query,
|
|
268
|
+
limit=limit,
|
|
269
|
+
min_similarity=self._min_similarity,
|
|
270
|
+
)
|
|
271
|
+
return result.results
|
|
272
|
+
|
|
273
|
+
def as_query_engine(
|
|
274
|
+
self,
|
|
275
|
+
*,
|
|
276
|
+
session_id: str | None = None,
|
|
277
|
+
similarity_top_k: int | None = None,
|
|
278
|
+
) -> "CrewLayerQueryEngine":
|
|
279
|
+
"""Return a :class:`CrewLayerQueryEngine` backed by this index."""
|
|
280
|
+
return CrewLayerQueryEngine(
|
|
281
|
+
index=self,
|
|
282
|
+
session_id=session_id,
|
|
283
|
+
similarity_top_k=similarity_top_k,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
# CrewLayerQueryEngine
|
|
289
|
+
# ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class CrewLayerQueryEngine:
|
|
293
|
+
"""Query engine that retrieves from CrewLayer and logs each call as an action.
|
|
294
|
+
|
|
295
|
+
Returned by :meth:`CrewLayerVectorIndex.as_query_engine`. Can also be
|
|
296
|
+
instantiated directly if you already have an index reference.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
index: A :class:`CrewLayerVectorIndex` instance.
|
|
300
|
+
session_id: Optional session to associate actions with.
|
|
301
|
+
similarity_top_k: Override the index default ``similarity_top_k``.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
def __init__(
|
|
305
|
+
self,
|
|
306
|
+
*,
|
|
307
|
+
index: CrewLayerVectorIndex,
|
|
308
|
+
session_id: str | None = None,
|
|
309
|
+
similarity_top_k: int | None = None,
|
|
310
|
+
) -> None:
|
|
311
|
+
self._index = index
|
|
312
|
+
self._session_id = session_id
|
|
313
|
+
self._similarity_top_k = similarity_top_k
|
|
314
|
+
|
|
315
|
+
def query(self, query_str: str, **kwargs: Any) -> QueryResponse:
|
|
316
|
+
"""Retrieve memories for *query_str*, log the call, and return a response.
|
|
317
|
+
|
|
318
|
+
The action is always logged with ``tool_name="llamaindex.query"``.
|
|
319
|
+
The response text is the concatenation of the top result contents.
|
|
320
|
+
``source_nodes`` holds the raw ``MemoryItem`` list for further inspection.
|
|
321
|
+
"""
|
|
322
|
+
start = time.monotonic()
|
|
323
|
+
try:
|
|
324
|
+
nodes = self._index.similarity_search(
|
|
325
|
+
query_str,
|
|
326
|
+
top_k=self._similarity_top_k,
|
|
327
|
+
)
|
|
328
|
+
duration_ms = max(0, int((time.monotonic() - start) * 1000))
|
|
329
|
+
response_text = "\n\n".join(item.content for item in nodes) if nodes else ""
|
|
330
|
+
self._index._client.actions.log(
|
|
331
|
+
self._index._agent_id,
|
|
332
|
+
tool_name="llamaindex.query",
|
|
333
|
+
input_params={"query": query_str},
|
|
334
|
+
output_result={
|
|
335
|
+
"results_count": len(nodes),
|
|
336
|
+
"response": response_text[:500],
|
|
337
|
+
},
|
|
338
|
+
status="success",
|
|
339
|
+
session_id=self._session_id,
|
|
340
|
+
duration_ms=duration_ms,
|
|
341
|
+
)
|
|
342
|
+
return QueryResponse(response=response_text, source_nodes=nodes)
|
|
343
|
+
except Exception as exc:
|
|
344
|
+
duration_ms = max(0, int((time.monotonic() - start) * 1000))
|
|
345
|
+
self._index._client.actions.log(
|
|
346
|
+
self._index._agent_id,
|
|
347
|
+
tool_name="llamaindex.query",
|
|
348
|
+
input_params={"query": query_str},
|
|
349
|
+
output_result={},
|
|
350
|
+
status="error",
|
|
351
|
+
error_msg=str(exc),
|
|
352
|
+
session_id=self._session_id,
|
|
353
|
+
duration_ms=duration_ms,
|
|
354
|
+
)
|
|
355
|
+
raise
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# ---------------------------------------------------------------------------
|
|
359
|
+
# CrewLayerCallbackManager
|
|
360
|
+
# ---------------------------------------------------------------------------
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class CrewLayerCallbackManager(_LIBaseCallbackHandler): # type: ignore[misc]
|
|
364
|
+
"""LlamaIndex ``BaseCallbackHandler`` that logs LLM and tool calls to CrewLayer.
|
|
365
|
+
|
|
366
|
+
Add an instance of this class to LlamaIndex's ``CallbackManager`` to
|
|
367
|
+
automatically record every LLM call, function call, and agent step as
|
|
368
|
+
an immutable action entry in CrewLayer.
|
|
369
|
+
|
|
370
|
+
Tracked event types: ``llm``, ``function_call``, ``agent_step``.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
client: A ``CrewLayerClient`` (sync) instance.
|
|
374
|
+
agent_id: Target agent UUID.
|
|
375
|
+
session_id: Optional session to associate actions with.
|
|
376
|
+
|
|
377
|
+
Usage::
|
|
378
|
+
|
|
379
|
+
from llama_index.core.callbacks import CallbackManager
|
|
380
|
+
handler = CrewLayerCallbackManager(client=client, agent_id="agent-uuid")
|
|
381
|
+
callback_manager = CallbackManager([handler])
|
|
382
|
+
llm = OpenAI(callback_manager=callback_manager)
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
def __init__(
|
|
386
|
+
self,
|
|
387
|
+
*,
|
|
388
|
+
client: Any,
|
|
389
|
+
agent_id: str,
|
|
390
|
+
session_id: str | None = None,
|
|
391
|
+
) -> None:
|
|
392
|
+
# BaseCallbackHandler (when installed) expects event ignore lists.
|
|
393
|
+
try:
|
|
394
|
+
super().__init__(event_starts_to_ignore=[], event_ends_to_ignore=[])
|
|
395
|
+
except TypeError:
|
|
396
|
+
try:
|
|
397
|
+
super().__init__()
|
|
398
|
+
except TypeError:
|
|
399
|
+
pass
|
|
400
|
+
self.__dict__.update({
|
|
401
|
+
"_client": client,
|
|
402
|
+
"_agent_id": agent_id,
|
|
403
|
+
"_session_id": session_id,
|
|
404
|
+
"_start_times": {}, # event_id → monotonic start
|
|
405
|
+
"_start_payloads": {}, # event_id → payload dict at start
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
# ------------------------------------------------------------------
|
|
409
|
+
# BaseCallbackHandler interface
|
|
410
|
+
# ------------------------------------------------------------------
|
|
411
|
+
|
|
412
|
+
def on_event_start(
|
|
413
|
+
self,
|
|
414
|
+
event_type: Any,
|
|
415
|
+
payload: dict[str, Any] | None = None,
|
|
416
|
+
event_id: str = "",
|
|
417
|
+
parent_id: str = "",
|
|
418
|
+
**kwargs: Any,
|
|
419
|
+
) -> str:
|
|
420
|
+
"""Record the start time for tracked events."""
|
|
421
|
+
name = _event_name(event_type)
|
|
422
|
+
if name in _LOGGED_EVENTS and event_id:
|
|
423
|
+
self._start_times[event_id] = time.monotonic()
|
|
424
|
+
self._start_payloads[event_id] = payload or {}
|
|
425
|
+
return event_id
|
|
426
|
+
|
|
427
|
+
def on_event_end(
|
|
428
|
+
self,
|
|
429
|
+
event_type: Any,
|
|
430
|
+
payload: dict[str, Any] | None = None,
|
|
431
|
+
event_id: str = "",
|
|
432
|
+
**kwargs: Any,
|
|
433
|
+
) -> None:
|
|
434
|
+
"""Log the completed event as a CrewLayer action."""
|
|
435
|
+
name = _event_name(event_type)
|
|
436
|
+
if name not in _LOGGED_EVENTS:
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
payload = payload or {}
|
|
440
|
+
duration_ms = self._pop_duration(event_id)
|
|
441
|
+
tool_name = f"llamaindex.{name}"
|
|
442
|
+
|
|
443
|
+
# Extract a concise output summary from the payload
|
|
444
|
+
output = _extract_output(payload)
|
|
445
|
+
input_info = _extract_input(self._start_payloads.pop(event_id, {}))
|
|
446
|
+
|
|
447
|
+
try:
|
|
448
|
+
self._client.actions.log(
|
|
449
|
+
self._agent_id,
|
|
450
|
+
tool_name=tool_name,
|
|
451
|
+
input_params=input_info,
|
|
452
|
+
output_result=output,
|
|
453
|
+
status="success",
|
|
454
|
+
session_id=self._session_id,
|
|
455
|
+
duration_ms=duration_ms,
|
|
456
|
+
)
|
|
457
|
+
except Exception:
|
|
458
|
+
pass # Never block the LlamaIndex pipeline
|
|
459
|
+
|
|
460
|
+
def start_trace(self, trace_id: str | None = None) -> None:
|
|
461
|
+
"""No-op — CrewLayer tracing is handled at the action level."""
|
|
462
|
+
|
|
463
|
+
def end_trace(
|
|
464
|
+
self,
|
|
465
|
+
trace_id: str | None = None,
|
|
466
|
+
trace_map: dict[str, Any] | None = None,
|
|
467
|
+
) -> None:
|
|
468
|
+
"""No-op."""
|
|
469
|
+
|
|
470
|
+
# ------------------------------------------------------------------
|
|
471
|
+
# Internal helpers
|
|
472
|
+
# ------------------------------------------------------------------
|
|
473
|
+
|
|
474
|
+
def _pop_duration(self, event_id: str) -> int | None:
|
|
475
|
+
start = self._start_times.pop(event_id, None)
|
|
476
|
+
if start is None:
|
|
477
|
+
return None
|
|
478
|
+
return max(0, int((time.monotonic() - start) * 1000))
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _extract_output(payload: dict[str, Any]) -> dict[str, Any]:
|
|
482
|
+
"""Pull a loggable summary from a LlamaIndex event end payload."""
|
|
483
|
+
out: dict[str, Any] = {}
|
|
484
|
+
|
|
485
|
+
# LLM response
|
|
486
|
+
response = payload.get("response")
|
|
487
|
+
if response is not None:
|
|
488
|
+
# CompletionResponse / ChatResponse both have .text or .message.content
|
|
489
|
+
text = getattr(response, "text", None)
|
|
490
|
+
if text is None:
|
|
491
|
+
msg = getattr(response, "message", None)
|
|
492
|
+
text = getattr(msg, "content", None) if msg is not None else None
|
|
493
|
+
if text is not None:
|
|
494
|
+
out["response"] = str(text)[:500]
|
|
495
|
+
|
|
496
|
+
# Function call response
|
|
497
|
+
fc_response = payload.get("function_call_response")
|
|
498
|
+
if fc_response is not None:
|
|
499
|
+
out["function_call_response"] = str(fc_response)[:500]
|
|
500
|
+
|
|
501
|
+
# Token usage
|
|
502
|
+
for key in ("num_input_tokens", "num_output_tokens", "model_name"):
|
|
503
|
+
if key in payload:
|
|
504
|
+
out[key] = payload[key]
|
|
505
|
+
|
|
506
|
+
return out or {"raw": str(payload)[:200]}
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _extract_input(payload: dict[str, Any]) -> dict[str, Any]:
|
|
510
|
+
"""Pull a loggable summary from a LlamaIndex event start payload."""
|
|
511
|
+
inp: dict[str, Any] = {}
|
|
512
|
+
|
|
513
|
+
# LLM messages list
|
|
514
|
+
messages = payload.get("messages")
|
|
515
|
+
if messages:
|
|
516
|
+
inp["message_count"] = len(messages)
|
|
517
|
+
last = messages[-1]
|
|
518
|
+
inp["last_message"] = str(
|
|
519
|
+
getattr(last, "content", str(last))
|
|
520
|
+
)[:300]
|
|
521
|
+
|
|
522
|
+
# Function call info
|
|
523
|
+
for key in ("function_call", "function_name", "model_name"):
|
|
524
|
+
if key in payload:
|
|
525
|
+
inp[key] = str(payload[key])[:200]
|
|
526
|
+
|
|
527
|
+
return inp or {}
|
crewlayer/py.typed
ADDED
|
File without changes
|