raindrop-azure-openai 0.0.1__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,74 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: raindrop-azure-openai
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Raindrop integration for Azure OpenAI
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Raindrop AI
|
|
7
|
+
Author-email: sdk@raindrop.ai
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: openai (>=1.0.0)
|
|
15
|
+
Requires-Dist: raindrop-ai (>=0.0.42)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# raindrop-azure-openai
|
|
19
|
+
|
|
20
|
+
Raindrop integration for Azure OpenAI (Python). Automatically captures `chat.completions.create()` calls by wrapping the AzureOpenAI client.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install raindrop-azure-openai openai
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from raindrop_azure_openai import create_raindrop_azure_openai
|
|
32
|
+
from openai import AzureOpenAI
|
|
33
|
+
|
|
34
|
+
raindrop = create_raindrop_azure_openai(
|
|
35
|
+
api_key="rk_...",
|
|
36
|
+
user_id="user-123",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
client = AzureOpenAI(
|
|
40
|
+
azure_endpoint="https://your-resource.openai.azure.com",
|
|
41
|
+
api_key="...",
|
|
42
|
+
api_version="2024-10-21",
|
|
43
|
+
)
|
|
44
|
+
wrapped = raindrop["wrap"](client)
|
|
45
|
+
|
|
46
|
+
response = wrapped.chat.completions.create(
|
|
47
|
+
model="gpt-4o-mini",
|
|
48
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
raindrop["shutdown"]()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## What gets captured
|
|
55
|
+
|
|
56
|
+
- **Chat completions**: input messages, output text, model, token usage
|
|
57
|
+
- **Errors**: tracked and re-raised to caller
|
|
58
|
+
|
|
59
|
+
## Options
|
|
60
|
+
|
|
61
|
+
| Option | Type | Default | Description |
|
|
62
|
+
|--------|------|---------|-------------|
|
|
63
|
+
| `api_key` | `str` | required | Raindrop API key |
|
|
64
|
+
| `user_id` | `str` | `None` | Associate all events with a user |
|
|
65
|
+
| `convo_id` | `str` | `None` | Group events into a conversation |
|
|
66
|
+
|
|
67
|
+
## Testing
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cd packages/azure-openai-python
|
|
71
|
+
pip install -e .
|
|
72
|
+
python -m pytest tests/ -v
|
|
73
|
+
```
|
|
74
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# raindrop-azure-openai
|
|
2
|
+
|
|
3
|
+
Raindrop integration for Azure OpenAI (Python). Automatically captures `chat.completions.create()` calls by wrapping the AzureOpenAI client.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install raindrop-azure-openai openai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from raindrop_azure_openai import create_raindrop_azure_openai
|
|
15
|
+
from openai import AzureOpenAI
|
|
16
|
+
|
|
17
|
+
raindrop = create_raindrop_azure_openai(
|
|
18
|
+
api_key="rk_...",
|
|
19
|
+
user_id="user-123",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
client = AzureOpenAI(
|
|
23
|
+
azure_endpoint="https://your-resource.openai.azure.com",
|
|
24
|
+
api_key="...",
|
|
25
|
+
api_version="2024-10-21",
|
|
26
|
+
)
|
|
27
|
+
wrapped = raindrop["wrap"](client)
|
|
28
|
+
|
|
29
|
+
response = wrapped.chat.completions.create(
|
|
30
|
+
model="gpt-4o-mini",
|
|
31
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
raindrop["shutdown"]()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## What gets captured
|
|
38
|
+
|
|
39
|
+
- **Chat completions**: input messages, output text, model, token usage
|
|
40
|
+
- **Errors**: tracked and re-raised to caller
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
| Option | Type | Default | Description |
|
|
45
|
+
|--------|------|---------|-------------|
|
|
46
|
+
| `api_key` | `str` | required | Raindrop API key |
|
|
47
|
+
| `user_id` | `str` | `None` | Associate all events with a user |
|
|
48
|
+
| `convo_id` | `str` | `None` | Group events into a conversation |
|
|
49
|
+
|
|
50
|
+
## Testing
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd packages/azure-openai-python
|
|
54
|
+
pip install -e .
|
|
55
|
+
python -m pytest tests/ -v
|
|
56
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "raindrop-azure-openai"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Raindrop integration for Azure OpenAI"
|
|
5
|
+
authors = ["Raindrop AI <sdk@raindrop.ai>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [{ include = "raindrop_azure_openai" }]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = ">=3.10,<4.0"
|
|
12
|
+
raindrop-ai = ">=0.0.42"
|
|
13
|
+
openai = ">=1.0.0"
|
|
14
|
+
|
|
15
|
+
[tool.poetry.group.dev.dependencies]
|
|
16
|
+
pytest = "^8.0"
|
|
17
|
+
|
|
18
|
+
[tool.pytest.ini_options]
|
|
19
|
+
testpaths = ["tests"]
|
|
20
|
+
python_files = ["test_*.py"]
|
|
21
|
+
python_classes = ["Test*"]
|
|
22
|
+
python_functions = ["test_*"]
|
|
23
|
+
addopts = ["-v", "--tb=short"]
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["poetry-core"]
|
|
27
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Azure OpenAI client wrapper for Raindrop observability.
|
|
3
|
+
|
|
4
|
+
Wraps an AzureOpenAI client to capture chat.completions.create() calls.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from raindrop_azure_openai import create_raindrop_azure_openai
|
|
8
|
+
from openai import AzureOpenAI
|
|
9
|
+
|
|
10
|
+
raindrop = create_raindrop_azure_openai(api_key="rk_...")
|
|
11
|
+
client = AzureOpenAI(azure_endpoint="...", api_key="...", api_version="...")
|
|
12
|
+
wrapped = raindrop["wrap"](client)
|
|
13
|
+
|
|
14
|
+
response = wrapped.chat.completions.create(model="gpt-4o", messages=[...])
|
|
15
|
+
raindrop["flush"]()
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import uuid
|
|
22
|
+
from typing import Any, Dict, Optional
|
|
23
|
+
|
|
24
|
+
import raindrop.analytics as raindrop
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_raindrop_azure_openai(
|
|
28
|
+
api_key: str,
|
|
29
|
+
user_id: Optional[str] = None,
|
|
30
|
+
convo_id: Optional[str] = None,
|
|
31
|
+
) -> dict:
|
|
32
|
+
"""Create a Raindrop-instrumented Azure OpenAI wrapper."""
|
|
33
|
+
raindrop.init(api_key=api_key)
|
|
34
|
+
|
|
35
|
+
def wrap(client: Any) -> Any:
|
|
36
|
+
return wrap_azure_client(client, user_id=user_id, convo_id=convo_id)
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
"wrap": wrap,
|
|
40
|
+
"flush": raindrop.flush,
|
|
41
|
+
"shutdown": raindrop.shutdown,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def wrap_azure_client(
|
|
46
|
+
client: Any,
|
|
47
|
+
user_id: Optional[str] = None,
|
|
48
|
+
convo_id: Optional[str] = None,
|
|
49
|
+
) -> Any:
|
|
50
|
+
"""Wrap an AzureOpenAI client to capture chat completions."""
|
|
51
|
+
try:
|
|
52
|
+
completions = client.chat.completions
|
|
53
|
+
original_create = completions.create
|
|
54
|
+
|
|
55
|
+
def wrapped_create(**kwargs: Any) -> Any:
|
|
56
|
+
return _instrument_create(original_create, kwargs, user_id, convo_id)
|
|
57
|
+
|
|
58
|
+
completions.create = wrapped_create
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
return client
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _instrument_create(
|
|
66
|
+
original_fn: Any,
|
|
67
|
+
kwargs: Dict[str, Any],
|
|
68
|
+
user_id: Optional[str],
|
|
69
|
+
convo_id: Optional[str],
|
|
70
|
+
) -> Any:
|
|
71
|
+
try:
|
|
72
|
+
model_id = kwargs.get("model")
|
|
73
|
+
input_text = _extract_input(kwargs.get("messages"))
|
|
74
|
+
event_id = str(uuid.uuid4())
|
|
75
|
+
except Exception:
|
|
76
|
+
return original_fn(**kwargs)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
response = original_fn(**kwargs)
|
|
80
|
+
except Exception:
|
|
81
|
+
try:
|
|
82
|
+
raindrop.track_ai(
|
|
83
|
+
user_id=user_id or "unknown",
|
|
84
|
+
event="ai_generation",
|
|
85
|
+
event_id=event_id,
|
|
86
|
+
input=input_text,
|
|
87
|
+
model=model_id,
|
|
88
|
+
convo_id=convo_id,
|
|
89
|
+
)
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
raise
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
output_text = _extract_output(response)
|
|
96
|
+
response_model = getattr(response, "model", model_id)
|
|
97
|
+
usage = getattr(response, "usage", None)
|
|
98
|
+
prompt_tokens = getattr(usage, "prompt_tokens", None) if usage else None
|
|
99
|
+
completion_tokens = getattr(usage, "completion_tokens", None) if usage else None
|
|
100
|
+
|
|
101
|
+
properties: Dict[str, Any] = {}
|
|
102
|
+
if prompt_tokens is not None:
|
|
103
|
+
properties["ai.usage.prompt_tokens"] = prompt_tokens
|
|
104
|
+
if completion_tokens is not None:
|
|
105
|
+
properties["ai.usage.completion_tokens"] = completion_tokens
|
|
106
|
+
|
|
107
|
+
raindrop.track_ai(
|
|
108
|
+
user_id=user_id or "unknown",
|
|
109
|
+
event="ai_generation",
|
|
110
|
+
event_id=event_id,
|
|
111
|
+
input=input_text,
|
|
112
|
+
output=output_text,
|
|
113
|
+
model=response_model,
|
|
114
|
+
properties=properties or None,
|
|
115
|
+
convo_id=convo_id,
|
|
116
|
+
)
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
return response
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _extract_input(messages: Any) -> Optional[str]:
|
|
124
|
+
if not isinstance(messages, list):
|
|
125
|
+
return None
|
|
126
|
+
try:
|
|
127
|
+
user_msgs = [m for m in messages if isinstance(m, dict) and m.get("role") == "user"]
|
|
128
|
+
to_extract = user_msgs if user_msgs else messages[-1:]
|
|
129
|
+
texts = []
|
|
130
|
+
for msg in to_extract:
|
|
131
|
+
content = msg.get("content", "") if isinstance(msg, dict) else ""
|
|
132
|
+
if isinstance(content, str):
|
|
133
|
+
texts.append(content)
|
|
134
|
+
else:
|
|
135
|
+
try:
|
|
136
|
+
texts.append(json.dumps(content, default=str))
|
|
137
|
+
except Exception:
|
|
138
|
+
texts.append(str(content))
|
|
139
|
+
return "\n".join(t for t in texts if t) or None
|
|
140
|
+
except Exception:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _extract_output(response: Any) -> Optional[str]:
|
|
145
|
+
try:
|
|
146
|
+
choices = getattr(response, "choices", None)
|
|
147
|
+
if choices and len(choices) > 0:
|
|
148
|
+
message = getattr(choices[0], "message", None)
|
|
149
|
+
if message:
|
|
150
|
+
return getattr(message, "content", None)
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
153
|
+
return None
|