turingpulse-sdk-bedrock 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-bedrock
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: TuringPulse SDK integration for AWS Bedrock
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: boto3>=1.35.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-bedrock"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "TuringPulse SDK integration for AWS Bedrock"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"turingpulse-sdk>=1.0.0",
|
|
13
|
+
"boto3>=1.35.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23"]
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""AWS Bedrock monkey-patch instrumentation for TuringPulse."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from contextvars import ContextVar
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
from turingpulse_sdk import instrument, GovernanceDirective
|
|
12
|
+
from turingpulse_sdk.config import MAX_FIELD_SIZE
|
|
13
|
+
from turingpulse_sdk.context import current_context
|
|
14
|
+
from turingpulse_sdk.exceptions import ConfigurationError
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("turingpulse.sdk.bedrock")
|
|
17
|
+
|
|
18
|
+
_INSTRUMENTING: ContextVar[bool] = ContextVar("_tp_bedrock_instrumenting", default=False)
|
|
19
|
+
|
|
20
|
+
_ORIGINAL_CONVERSE: Any = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def patch_bedrock(
|
|
24
|
+
*,
|
|
25
|
+
name: str | None = None,
|
|
26
|
+
governance: Optional[GovernanceDirective] = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Monkey-patch ``botocore`` to intercept ``bedrock-runtime.converse`` calls."""
|
|
29
|
+
global _ORIGINAL_CONVERSE
|
|
30
|
+
|
|
31
|
+
effective_name = name or os.getenv("TP_WORKFLOW_NAME", "")
|
|
32
|
+
if not effective_name:
|
|
33
|
+
raise ConfigurationError(
|
|
34
|
+
"patch_bedrock() requires name= or TP_WORKFLOW_NAME to be set."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import botocore.client
|
|
39
|
+
except ImportError as exc:
|
|
40
|
+
raise ImportError("boto3 package is required: pip install boto3") from exc
|
|
41
|
+
|
|
42
|
+
if _ORIGINAL_CONVERSE is not None:
|
|
43
|
+
logger.warning("Bedrock is already patched — skipping")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
_ORIGINAL_CONVERSE = botocore.client.ClientCreator.create_client
|
|
47
|
+
|
|
48
|
+
original_create = _ORIGINAL_CONVERSE
|
|
49
|
+
|
|
50
|
+
def _patched_create_client(self_creator, *args: Any, **kwargs: Any) -> Any:
|
|
51
|
+
client = original_create(self_creator, *args, **kwargs)
|
|
52
|
+
service_name = args[0] if args else kwargs.get("service_name", "")
|
|
53
|
+
if service_name == "bedrock-runtime":
|
|
54
|
+
_wrap_converse(client, effective_name, governance)
|
|
55
|
+
return client
|
|
56
|
+
|
|
57
|
+
botocore.client.ClientCreator.create_client = _patched_create_client
|
|
58
|
+
logger.info("Bedrock patched for TuringPulse instrumentation")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def unpatch_bedrock() -> None:
|
|
62
|
+
"""Restore original botocore client creator."""
|
|
63
|
+
global _ORIGINAL_CONVERSE
|
|
64
|
+
if _ORIGINAL_CONVERSE is None:
|
|
65
|
+
return
|
|
66
|
+
try:
|
|
67
|
+
import botocore.client
|
|
68
|
+
botocore.client.ClientCreator.create_client = _ORIGINAL_CONVERSE
|
|
69
|
+
except ImportError:
|
|
70
|
+
pass
|
|
71
|
+
_ORIGINAL_CONVERSE = None
|
|
72
|
+
logger.info("Bedrock unpatched — original methods restored")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _wrap_converse(client: Any, name: str, governance: Optional[GovernanceDirective]) -> None:
|
|
76
|
+
"""Wrap the converse method of a bedrock-runtime client."""
|
|
77
|
+
original_converse = client.converse
|
|
78
|
+
|
|
79
|
+
@instrument(name=name, governance=governance)
|
|
80
|
+
def _patched_converse(**kwargs: Any) -> Any:
|
|
81
|
+
if _INSTRUMENTING.get(False):
|
|
82
|
+
return original_converse(**kwargs)
|
|
83
|
+
token = _INSTRUMENTING.set(True)
|
|
84
|
+
try:
|
|
85
|
+
response = original_converse(**kwargs)
|
|
86
|
+
_record_bedrock_span(response, kwargs)
|
|
87
|
+
return response
|
|
88
|
+
finally:
|
|
89
|
+
_INSTRUMENTING.reset(token)
|
|
90
|
+
|
|
91
|
+
client.converse = _patched_converse
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _record_bedrock_span(response: Any, kwargs: dict) -> None:
|
|
95
|
+
ctx = current_context()
|
|
96
|
+
if not ctx:
|
|
97
|
+
return
|
|
98
|
+
ctx.framework = "bedrock"
|
|
99
|
+
ctx.node_type = "llm"
|
|
100
|
+
|
|
101
|
+
usage = response.get("usage", {}) if isinstance(response, dict) else {}
|
|
102
|
+
ctx.set_tokens(
|
|
103
|
+
usage.get("inputTokens", 0),
|
|
104
|
+
usage.get("outputTokens", 0),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
model_id = kwargs.get("modelId", "unknown")
|
|
108
|
+
ctx.set_model(model_id, "bedrock")
|
|
109
|
+
|
|
110
|
+
messages = kwargs.get("messages", [])
|
|
111
|
+
if messages:
|
|
112
|
+
last_user = next(
|
|
113
|
+
(m for m in reversed(messages) if m.get("role") == "user"),
|
|
114
|
+
None,
|
|
115
|
+
)
|
|
116
|
+
if last_user:
|
|
117
|
+
content = last_user.get("content", [])
|
|
118
|
+
if isinstance(content, list):
|
|
119
|
+
texts = [c.get("text", "") for c in content if "text" in c]
|
|
120
|
+
ctx.set_prompt("\n".join(texts)[:MAX_FIELD_SIZE])
|
|
121
|
+
elif isinstance(content, str):
|
|
122
|
+
ctx.set_prompt(content[:MAX_FIELD_SIZE])
|
|
123
|
+
|
|
124
|
+
output = response.get("output", {}) if isinstance(response, dict) else {}
|
|
125
|
+
message = output.get("message", {})
|
|
126
|
+
content = message.get("content", [])
|
|
127
|
+
if content:
|
|
128
|
+
texts = [c.get("text", "") for c in content if "text" in c]
|
|
129
|
+
if texts:
|
|
130
|
+
ctx.set_io(output_data="\n".join(texts)[:MAX_FIELD_SIZE])
|
|
131
|
+
|
|
132
|
+
for c in content:
|
|
133
|
+
if "toolUse" in c:
|
|
134
|
+
tool = c["toolUse"]
|
|
135
|
+
ctx.add_tool_call(
|
|
136
|
+
tool_name=tool.get("name", "unknown"),
|
|
137
|
+
tool_args=tool.get("input", {}),
|
|
138
|
+
tool_id=tool.get("toolUseId"),
|
|
139
|
+
)
|