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,3 @@
1
+ from .wrapper import create_raindrop_azure_openai
2
+
3
+ __all__ = ["create_raindrop_azure_openai"]
@@ -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