turingpulse-sdk-mistral 1.0.0__tar.gz
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,42 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Virtual environments
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
ENV/
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
|
|
16
|
+
# Database files
|
|
17
|
+
*.db
|
|
18
|
+
*.sqlite3
|
|
19
|
+
|
|
20
|
+
# Environment variables
|
|
21
|
+
.env
|
|
22
|
+
.env.local
|
|
23
|
+
|
|
24
|
+
# IDE
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
*.swp
|
|
28
|
+
*.swo
|
|
29
|
+
|
|
30
|
+
# Testing
|
|
31
|
+
.pytest_cache/
|
|
32
|
+
.coverage
|
|
33
|
+
htmlcov/
|
|
34
|
+
.tox/
|
|
35
|
+
|
|
36
|
+
# Logs
|
|
37
|
+
*.log
|
|
38
|
+
logs/
|
|
39
|
+
|
|
40
|
+
# OS files
|
|
41
|
+
.DS_Store
|
|
42
|
+
Thumbs.db
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: turingpulse-sdk-mistral
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: TuringPulse SDK integration for Mistral AI
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: mistralai>=0.4.0
|
|
8
|
+
Requires-Dist: turingpulse-sdk>=1.0.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "turingpulse-sdk-mistral"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "TuringPulse SDK integration for Mistral AI"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"turingpulse-sdk>=1.0.0",
|
|
13
|
+
"mistralai>=0.4.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23"]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Mistral AI monkey-patch instrumentation for TuringPulse."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from contextvars import ContextVar
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
from turingpulse_sdk import instrument, GovernanceDirective
|
|
11
|
+
from turingpulse_sdk.config import MAX_FIELD_SIZE
|
|
12
|
+
from turingpulse_sdk.context import current_context
|
|
13
|
+
from turingpulse_sdk.exceptions import ConfigurationError
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("turingpulse.sdk.mistral")
|
|
16
|
+
|
|
17
|
+
_INSTRUMENTING: ContextVar[bool] = ContextVar("_tp_mistral_instrumenting", default=False)
|
|
18
|
+
|
|
19
|
+
_ORIGINAL_COMPLETE: Any = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def patch_mistral(
|
|
23
|
+
*,
|
|
24
|
+
name: str | None = None,
|
|
25
|
+
governance: Optional[GovernanceDirective] = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Monkey-patch ``mistralai.Mistral.chat.complete``."""
|
|
28
|
+
global _ORIGINAL_COMPLETE
|
|
29
|
+
|
|
30
|
+
effective_name = name or os.getenv("TP_WORKFLOW_NAME", "")
|
|
31
|
+
if not effective_name:
|
|
32
|
+
raise ConfigurationError(
|
|
33
|
+
"patch_mistral() requires name= or TP_WORKFLOW_NAME to be set."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from mistralai import Mistral
|
|
38
|
+
except ImportError as exc:
|
|
39
|
+
raise ImportError("mistralai package is required: pip install mistralai>=0.4.0") from exc
|
|
40
|
+
|
|
41
|
+
if _ORIGINAL_COMPLETE is not None:
|
|
42
|
+
logger.warning("Mistral is already patched — skipping")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
chat_cls = getattr(Mistral, "chat", None)
|
|
46
|
+
if chat_cls is None:
|
|
47
|
+
try:
|
|
48
|
+
from mistralai.chat import Chat
|
|
49
|
+
chat_cls = Chat
|
|
50
|
+
except ImportError:
|
|
51
|
+
logger.warning(
|
|
52
|
+
"Cannot locate Mistral.chat or Chat class — Mistral SDK version may be incompatible. Skipping patch."
|
|
53
|
+
)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
_ORIGINAL_COMPLETE = getattr(chat_cls, "complete", None)
|
|
57
|
+
if _ORIGINAL_COMPLETE is None or not callable(_ORIGINAL_COMPLETE):
|
|
58
|
+
_ORIGINAL_COMPLETE = None
|
|
59
|
+
logger.warning(
|
|
60
|
+
"Cannot locate Chat.complete — Mistral SDK version may be incompatible. Skipping patch."
|
|
61
|
+
)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
@instrument(name=effective_name, governance=governance)
|
|
65
|
+
def _patched_complete(self_chat, *args: Any, **kwargs: Any) -> Any:
|
|
66
|
+
if _INSTRUMENTING.get(False):
|
|
67
|
+
return _ORIGINAL_COMPLETE(self_chat, *args, **kwargs)
|
|
68
|
+
token = _INSTRUMENTING.set(True)
|
|
69
|
+
try:
|
|
70
|
+
response = _ORIGINAL_COMPLETE(self_chat, *args, **kwargs)
|
|
71
|
+
_record_mistral_span(response, kwargs)
|
|
72
|
+
return response
|
|
73
|
+
finally:
|
|
74
|
+
_INSTRUMENTING.reset(token)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
chat_cls.complete = _patched_complete
|
|
78
|
+
except (AttributeError, TypeError) as exc:
|
|
79
|
+
_ORIGINAL_COMPLETE = None
|
|
80
|
+
logger.warning("Failed to patch Chat.complete: %s", type(exc).__name__)
|
|
81
|
+
return
|
|
82
|
+
logger.info("Mistral patched for TuringPulse instrumentation")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def unpatch_mistral() -> None:
|
|
86
|
+
"""Restore original Mistral methods."""
|
|
87
|
+
global _ORIGINAL_COMPLETE
|
|
88
|
+
if _ORIGINAL_COMPLETE is None:
|
|
89
|
+
return
|
|
90
|
+
try:
|
|
91
|
+
from mistralai.chat import Chat
|
|
92
|
+
Chat.complete = _ORIGINAL_COMPLETE
|
|
93
|
+
except ImportError:
|
|
94
|
+
try:
|
|
95
|
+
from mistralai import Mistral
|
|
96
|
+
if hasattr(Mistral, "chat"):
|
|
97
|
+
Mistral.chat.complete = _ORIGINAL_COMPLETE
|
|
98
|
+
except ImportError:
|
|
99
|
+
pass
|
|
100
|
+
_ORIGINAL_COMPLETE = None
|
|
101
|
+
logger.info("Mistral unpatched — original methods restored")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _record_mistral_span(response: Any, kwargs: dict) -> None:
|
|
105
|
+
ctx = current_context()
|
|
106
|
+
if not ctx:
|
|
107
|
+
return
|
|
108
|
+
ctx.framework = "mistral"
|
|
109
|
+
ctx.node_type = "llm"
|
|
110
|
+
|
|
111
|
+
usage = getattr(response, "usage", None)
|
|
112
|
+
if usage:
|
|
113
|
+
ctx.set_tokens(
|
|
114
|
+
getattr(usage, "prompt_tokens", 0) or 0,
|
|
115
|
+
getattr(usage, "completion_tokens", 0) or 0,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
ctx.set_model(kwargs.get("model", getattr(response, "model", "unknown")), "mistral")
|
|
119
|
+
|
|
120
|
+
messages = kwargs.get("messages", [])
|
|
121
|
+
if messages:
|
|
122
|
+
last_user = next(
|
|
123
|
+
(m for m in reversed(messages) if (m.get("role") if isinstance(m, dict) else getattr(m, "role", "")) == "user"),
|
|
124
|
+
None,
|
|
125
|
+
)
|
|
126
|
+
if last_user:
|
|
127
|
+
content = last_user.get("content", "") if isinstance(last_user, dict) else getattr(last_user, "content", "")
|
|
128
|
+
ctx.set_prompt(str(content)[:MAX_FIELD_SIZE])
|
|
129
|
+
|
|
130
|
+
choices = getattr(response, "choices", [])
|
|
131
|
+
if choices:
|
|
132
|
+
message = getattr(choices[0], "message", None)
|
|
133
|
+
if message:
|
|
134
|
+
content = getattr(message, "content", "")
|
|
135
|
+
if content:
|
|
136
|
+
ctx.set_io(output_data=str(content)[:MAX_FIELD_SIZE])
|
|
137
|
+
|
|
138
|
+
tool_calls = getattr(message, "tool_calls", None)
|
|
139
|
+
if tool_calls:
|
|
140
|
+
for tc in tool_calls:
|
|
141
|
+
func = getattr(tc, "function", None)
|
|
142
|
+
if func:
|
|
143
|
+
ctx.add_tool_call(
|
|
144
|
+
tool_name=getattr(func, "name", "unknown"),
|
|
145
|
+
tool_args={"arguments": getattr(func, "arguments", "")},
|
|
146
|
+
tool_id=getattr(tc, "id", None),
|
|
147
|
+
)
|