crewplus 0.2.40__py3-none-any.whl → 0.2.41__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.
Potentially problematic release.
This version of crewplus might be problematic. Click here for more details.
- crewplus/callbacks/__init__.py +1 -0
- crewplus/callbacks/async_langfuse_handler.py +95 -0
- crewplus/services/azure_chat_model.py +8 -5
- crewplus/services/tracing_manager.py +22 -7
- {crewplus-0.2.40.dist-info → crewplus-0.2.41.dist-info}/METADATA +1 -1
- {crewplus-0.2.40.dist-info → crewplus-0.2.41.dist-info}/RECORD +9 -7
- {crewplus-0.2.40.dist-info → crewplus-0.2.41.dist-info}/WHEEL +0 -0
- {crewplus-0.2.40.dist-info → crewplus-0.2.41.dist-info}/entry_points.txt +0 -0
- {crewplus-0.2.40.dist-info → crewplus-0.2.41.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file makes the 'callbacks' directory a Python package.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# File: crewplus/callbacks/async_langfuse_handler.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import contextvars
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Any, Dict, List, Union
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from langfuse.langchain import CallbackHandler as LangfuseCallbackHandler
|
|
9
|
+
from langchain_core.callbacks import AsyncCallbackHandler
|
|
10
|
+
from langchain_core.outputs import LLMResult
|
|
11
|
+
LANGFUSE_AVAILABLE = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
LANGFUSE_AVAILABLE = False
|
|
14
|
+
LangfuseCallbackHandler = None
|
|
15
|
+
AsyncCallbackHandler = object
|
|
16
|
+
|
|
17
|
+
# This token is a simple flag to indicate that we are in an async context.
|
|
18
|
+
# We use a context variable to make it available only within the async task.
|
|
19
|
+
_ASYNC_CONTEXT_TOKEN = "in_async_context"
|
|
20
|
+
in_async_context = contextvars.ContextVar(_ASYNC_CONTEXT_TOKEN, default=False)
|
|
21
|
+
|
|
22
|
+
@contextmanager
|
|
23
|
+
def async_context():
|
|
24
|
+
"""A context manager to signal that we are in an async execution context."""
|
|
25
|
+
token = in_async_context.set(True)
|
|
26
|
+
try:
|
|
27
|
+
yield
|
|
28
|
+
finally:
|
|
29
|
+
in_async_context.reset(token)
|
|
30
|
+
|
|
31
|
+
class AsyncLangfuseCallbackHandler(AsyncCallbackHandler):
|
|
32
|
+
"""
|
|
33
|
+
Wraps the synchronous LangfuseCallbackHandler to make it compatible with
|
|
34
|
+
LangChain's async methods.
|
|
35
|
+
|
|
36
|
+
This works by running the synchronous handler's methods in a separate thread
|
|
37
|
+
using `asyncio.to_thread`. This is crucial because `asyncio`'s default
|
|
38
|
+
executor can correctly propagate `contextvars`, which solves the
|
|
39
|
+
`ValueError: <Token ...> was created in a different Context` from OpenTelemetry.
|
|
40
|
+
"""
|
|
41
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
42
|
+
if not LANGFUSE_AVAILABLE:
|
|
43
|
+
raise ImportError("Langfuse is not available. Please install it with 'pip install langfuse'")
|
|
44
|
+
self.sync_handler = LangfuseCallbackHandler(*args, **kwargs)
|
|
45
|
+
|
|
46
|
+
def __getattr__(self, name: str) -> Any:
|
|
47
|
+
# Delegate any other attribute access to the sync handler
|
|
48
|
+
return getattr(self.sync_handler, name)
|
|
49
|
+
|
|
50
|
+
async def on_llm_start(
|
|
51
|
+
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
|
|
52
|
+
) -> None:
|
|
53
|
+
await asyncio.to_thread(
|
|
54
|
+
self.sync_handler.on_llm_start, serialized, prompts, **kwargs
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
|
|
58
|
+
await asyncio.to_thread(
|
|
59
|
+
self.sync_handler.on_llm_end, response, **kwargs
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
async def on_llm_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any) -> None:
|
|
63
|
+
await asyncio.to_thread(
|
|
64
|
+
self.sync_handler.on_llm_error, error, **kwargs
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
async def on_tool_start(self, serialized: Dict[str, Any], input_str: str, **kwargs: Any) -> Any:
|
|
68
|
+
await asyncio.to_thread(
|
|
69
|
+
self.sync_handler.on_tool_start, serialized, input_str, **kwargs
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
|
|
73
|
+
await asyncio.to_thread(
|
|
74
|
+
self.sync_handler.on_tool_end, output, **kwargs
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
async def on_tool_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any) -> Any:
|
|
78
|
+
await asyncio.to_thread(
|
|
79
|
+
self.sync_handler.on_tool_error, error, **kwargs
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
async def on_chain_start(self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any) -> Any:
|
|
83
|
+
await asyncio.to_thread(
|
|
84
|
+
self.sync_handler.on_chain_start, serialized, inputs, **kwargs
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
async def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> Any:
|
|
88
|
+
await asyncio.to_thread(
|
|
89
|
+
self.sync_handler.on_chain_end, outputs, **kwargs
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def on_chain_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any) -> Any:
|
|
93
|
+
await asyncio.to_thread(
|
|
94
|
+
self.sync_handler.on_chain_error, error, **kwargs
|
|
95
|
+
)
|
|
@@ -5,6 +5,7 @@ from typing import Any, Optional
|
|
|
5
5
|
from langchain_openai.chat_models.azure import AzureChatOpenAI
|
|
6
6
|
from pydantic import Field
|
|
7
7
|
from .tracing_manager import TracingManager, TracingContext
|
|
8
|
+
from ..callbacks.async_langfuse_handler import async_context
|
|
8
9
|
|
|
9
10
|
class TracedAzureChatOpenAI(AzureChatOpenAI):
|
|
10
11
|
"""
|
|
@@ -106,8 +107,9 @@ class TracedAzureChatOpenAI(AzureChatOpenAI):
|
|
|
106
107
|
return super().invoke(input, config=config, **kwargs)
|
|
107
108
|
|
|
108
109
|
async def ainvoke(self, input, config=None, **kwargs):
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
with async_context():
|
|
111
|
+
config = self._tracing_manager.add_callbacks_to_config(config)
|
|
112
|
+
return await super().ainvoke(input, config=config, **kwargs)
|
|
111
113
|
|
|
112
114
|
def stream(self, input, config=None, **kwargs):
|
|
113
115
|
# Add stream_options to get usage data for Langfuse
|
|
@@ -124,6 +126,7 @@ class TracedAzureChatOpenAI(AzureChatOpenAI):
|
|
|
124
126
|
stream_options["include_usage"] = True
|
|
125
127
|
kwargs["stream_options"] = stream_options
|
|
126
128
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
with async_context():
|
|
130
|
+
config = self._tracing_manager.add_callbacks_to_config(config)
|
|
131
|
+
async for chunk in super().astream(input, config=config, **kwargs):
|
|
132
|
+
yield chunk
|
|
@@ -8,10 +8,13 @@ import logging
|
|
|
8
8
|
# even if the langfuse library is not installed.
|
|
9
9
|
try:
|
|
10
10
|
from langfuse.langchain import CallbackHandler as LangfuseCallbackHandler
|
|
11
|
+
from ..callbacks.async_langfuse_handler import AsyncLangfuseCallbackHandler, in_async_context
|
|
11
12
|
LANGFUSE_AVAILABLE = True
|
|
12
13
|
except ImportError:
|
|
13
14
|
LANGFUSE_AVAILABLE = False
|
|
14
15
|
LangfuseCallbackHandler = None
|
|
16
|
+
AsyncLangfuseCallbackHandler = None
|
|
17
|
+
in_async_context = None
|
|
15
18
|
|
|
16
19
|
class TracingContext(Protocol):
|
|
17
20
|
"""
|
|
@@ -65,7 +68,8 @@ class TracingManager:
|
|
|
65
68
|
to the TracingContext protocol.
|
|
66
69
|
"""
|
|
67
70
|
self.context = context
|
|
68
|
-
self.
|
|
71
|
+
self._sync_handlers: List[Any] = []
|
|
72
|
+
self._async_handlers: List[Any] = []
|
|
69
73
|
self._initialize_handlers()
|
|
70
74
|
|
|
71
75
|
def _initialize_handlers(self):
|
|
@@ -73,7 +77,8 @@ class TracingManager:
|
|
|
73
77
|
Initializes all supported tracing handlers. This is the central point
|
|
74
78
|
for adding new observability tools.
|
|
75
79
|
"""
|
|
76
|
-
self.
|
|
80
|
+
self._sync_handlers = []
|
|
81
|
+
self._async_handlers = []
|
|
77
82
|
self._initialize_langfuse()
|
|
78
83
|
# To add a new handler (e.g., Helicone), you would add a call to
|
|
79
84
|
# self._initialize_helicone() here.
|
|
@@ -94,8 +99,14 @@ class TracingManager:
|
|
|
94
99
|
|
|
95
100
|
if enable_langfuse:
|
|
96
101
|
try:
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
# Create both sync and async handlers. We'll pick one at runtime.
|
|
103
|
+
sync_handler = LangfuseCallbackHandler()
|
|
104
|
+
self._sync_handlers.append(sync_handler)
|
|
105
|
+
|
|
106
|
+
if AsyncLangfuseCallbackHandler:
|
|
107
|
+
async_handler = AsyncLangfuseCallbackHandler()
|
|
108
|
+
self._async_handlers.append(async_handler)
|
|
109
|
+
|
|
99
110
|
self.context.logger.info(f"Langfuse tracing enabled for {self.context.get_model_identifier()}")
|
|
100
111
|
except Exception as e:
|
|
101
112
|
self.context.logger.warning(f"Failed to initialize Langfuse: {e}")
|
|
@@ -118,15 +129,19 @@ class TracingManager:
|
|
|
118
129
|
if config is None:
|
|
119
130
|
config = {}
|
|
120
131
|
|
|
132
|
+
# Decide which handlers to use based on the async context flag.
|
|
133
|
+
is_async = in_async_context.get() if in_async_context else False
|
|
134
|
+
handlers = self._async_handlers if is_async else self._sync_handlers
|
|
135
|
+
|
|
121
136
|
# Respect a global disable flag for this specific call.
|
|
122
|
-
if not
|
|
137
|
+
if not handlers or config.get("metadata", {}).get("tracing_disabled"):
|
|
123
138
|
return config
|
|
124
139
|
|
|
125
140
|
callbacks = config.get("callbacks")
|
|
126
141
|
|
|
127
142
|
# Case 1: The 'callbacks' key holds a CallbackManager instance
|
|
128
143
|
if hasattr(callbacks, 'add_handler') and hasattr(callbacks, 'handlers'):
|
|
129
|
-
for handler in
|
|
144
|
+
for handler in handlers:
|
|
130
145
|
if not any(isinstance(cb, type(handler)) for cb in callbacks.handlers):
|
|
131
146
|
callbacks.add_handler(handler, inherit=True)
|
|
132
147
|
return config # Return the original, now-mutated config
|
|
@@ -135,7 +150,7 @@ class TracingManager:
|
|
|
135
150
|
current_callbacks = callbacks or []
|
|
136
151
|
new_callbacks = list(current_callbacks)
|
|
137
152
|
|
|
138
|
-
for handler in
|
|
153
|
+
for handler in handlers:
|
|
139
154
|
if not any(isinstance(cb, type(handler)) for cb in new_callbacks):
|
|
140
155
|
new_callbacks.append(handler)
|
|
141
156
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
crewplus-0.2.
|
|
2
|
-
crewplus-0.2.
|
|
3
|
-
crewplus-0.2.
|
|
4
|
-
crewplus-0.2.
|
|
1
|
+
crewplus-0.2.41.dist-info/METADATA,sha256=JKgTyNze2KdlVY4GP_oInL_nc3m8wcxJtOOqf-sYvAI,5362
|
|
2
|
+
crewplus-0.2.41.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
|
3
|
+
crewplus-0.2.41.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
|
4
|
+
crewplus-0.2.41.dist-info/licenses/LICENSE,sha256=2_NHSHRTKB_cTcT_GXgcenOCtIZku8j343mOgAguTfc,1087
|
|
5
5
|
crewplus/__init__.py,sha256=m46HkZL1Y4toD619NL47Sn2Qe084WFFSFD7e6VoYKZc,284
|
|
6
|
+
crewplus/callbacks/__init__.py,sha256=YG7ieeb91qEjp1zF0-inEN7mjZ7yT_D2yzdWFT8Z1Ws,63
|
|
7
|
+
crewplus/callbacks/async_langfuse_handler.py,sha256=QBY2xP7G4LnyuW4HVbNuZwZ5dJtWZl97VX3gkqJA_tc,3887
|
|
6
8
|
crewplus/services/__init__.py,sha256=V1CG8b2NOmRzNgQH7BPl4KVxWSYJH5vfEsW1wVErKNE,375
|
|
7
|
-
crewplus/services/azure_chat_model.py,sha256=
|
|
9
|
+
crewplus/services/azure_chat_model.py,sha256=LXTd1g6OBV-YEaGokVNMddd1P5BU8nfV4k5tG_GcH04,5643
|
|
8
10
|
crewplus/services/gemini_chat_model.py,sha256=VsOB_st1qRmDkwLXzo-gCShhUsZHpk0V-G-ulQXGN3g,40081
|
|
9
11
|
crewplus/services/init_services.py,sha256=7oZ1GmesK32EDB_DYnTzW17MEpXjXK41_U_1pmqu_m4,2183
|
|
10
12
|
crewplus/services/model_load_balancer.py,sha256=Q9Gx3GrbKworU-Ytxeqp0ggHSgZ1Q6brtTk-nCl4sak,12095
|
|
11
|
-
crewplus/services/tracing_manager.py,sha256=
|
|
13
|
+
crewplus/services/tracing_manager.py,sha256=vT-7zerq6v0x-cwEBWAsB9NHdul4mPDnI60azcngTr8,7058
|
|
12
14
|
crewplus/utils/__init__.py,sha256=2Gk1n5srFJQnFfBuYTxktdtKOVZyNrFcNaZKhXk35Pw,142
|
|
13
15
|
crewplus/utils/schema_action.py,sha256=GDaBoVFQD1rXqrLVSMTfXYW1xcUu7eDcHsn57XBSnIg,422
|
|
14
16
|
crewplus/utils/schema_document_updater.py,sha256=frvffxn2vbi71fHFPoGb9hq7gH2azmmdq17p-Fumnvg,7322
|
|
@@ -20,4 +22,4 @@ docs/GeminiChatModel.md,sha256=zZYyl6RmjZTUsKxxMiC9O4yV70MC4TD-IGUmWhIDBKA,8677
|
|
|
20
22
|
docs/ModelLoadBalancer.md,sha256=aGHES1dcXPz4c7Y8kB5-vsCNJjriH2SWmjBkSGoYKiI,4398
|
|
21
23
|
docs/VDBService.md,sha256=Dw286Rrf_fsi13jyD3Bo4Sy7nZ_G7tYm7d8MZ2j9hxk,9375
|
|
22
24
|
docs/index.md,sha256=3tlc15uR8lzFNM5WjdoZLw0Y9o1P1gwgbEnOdIBspqc,1643
|
|
23
|
-
crewplus-0.2.
|
|
25
|
+
crewplus-0.2.41.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|