turingpulse-sdk-dspy 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-dspy
3
+ Version: 1.0.0
4
+ Summary: TuringPulse SDK integration for DSPy
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: dspy>=3.1.3
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-dspy"
7
+ version = "1.0.0"
8
+ description = "TuringPulse SDK integration for DSPy"
9
+ requires-python = ">=3.11"
10
+ license = "Apache-2.0"
11
+ dependencies = [
12
+ "turingpulse-sdk>=1.0.0",
13
+ "dspy>=3.1.3",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.23"]
@@ -0,0 +1,6 @@
1
+ """TuringPulse SDK integration for DSPy."""
2
+
3
+ from ._wrapper import instrument_dspy
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["instrument_dspy"]
@@ -0,0 +1,97 @@
1
+ """DSPy instrumentation for TuringPulse.
2
+
3
+ Wraps DSPy module forward passes to capture LLM calls,
4
+ optimization traces, and retrieval steps.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from contextvars import ContextVar
11
+ from typing import Any, Dict, Optional, Sequence
12
+
13
+ from turingpulse_sdk import instrument, GovernanceDirective
14
+ from turingpulse_sdk.config import MAX_FIELD_SIZE
15
+ from turingpulse_sdk.context import current_context
16
+
17
+ logger = logging.getLogger("turingpulse.sdk.dspy")
18
+
19
+ _INSTRUMENTING: ContextVar[bool] = ContextVar("_tp_dspy_instrumenting", default=False)
20
+
21
+
22
+ def instrument_dspy(
23
+ module,
24
+ *,
25
+ name: str,
26
+ governance: Optional[GovernanceDirective] = None,
27
+ model: Optional[str] = None,
28
+ provider: str = "openai",
29
+ kpis: Optional[Sequence["KPIConfig"]] = None,
30
+ metadata: Optional[Dict[str, str]] = None,
31
+ ):
32
+ """Wrap a DSPy module for TuringPulse observability.
33
+
34
+ Returns a callable wrapping ``module.forward()`` / ``module()``.
35
+
36
+ Args:
37
+ module: A ``dspy.Module`` instance (e.g., ``dspy.ChainOfThought``).
38
+ name: Workflow display name.
39
+ governance: Optional governance directive.
40
+ model: LLM model name override.
41
+ provider: LLM provider name.
42
+
43
+ Returns:
44
+ A callable wrapping the module's forward pass.
45
+ """
46
+
47
+ @instrument(name=name, governance=governance, kpis=kpis, metadata=metadata or {})
48
+ def _run(*args: Any, **kwargs: Any) -> Any:
49
+ token = _INSTRUMENTING.set(True)
50
+ try:
51
+ result = module(*args, **kwargs)
52
+
53
+ ctx = current_context()
54
+ if ctx:
55
+ ctx.framework = "dspy"
56
+ ctx.node_type = "workflow"
57
+
58
+ ctx.set_io(
59
+ input_data=str(kwargs)[:MAX_FIELD_SIZE] if kwargs else str(args)[:MAX_FIELD_SIZE],
60
+ output_data=str(result)[:MAX_FIELD_SIZE],
61
+ )
62
+
63
+ if model:
64
+ ctx.set_model(model, provider)
65
+
66
+ try:
67
+ import dspy
68
+ lm = dspy.settings.lm
69
+ if lm is not None:
70
+ hist = getattr(lm, "history", [])
71
+ if hist:
72
+ total_prompt = 0
73
+ total_completion = 0
74
+ for entry in hist:
75
+ usage = None
76
+ if isinstance(entry, dict):
77
+ resp = entry.get("response")
78
+ if resp and hasattr(resp, "usage"):
79
+ usage = resp.usage
80
+ elif isinstance(resp, dict):
81
+ usage_d = resp.get("usage", {})
82
+ total_prompt += usage_d.get("prompt_tokens", 0) or 0
83
+ total_completion += usage_d.get("completion_tokens", 0) or 0
84
+ continue
85
+ if usage:
86
+ total_prompt += getattr(usage, "prompt_tokens", 0) or 0
87
+ total_completion += getattr(usage, "completion_tokens", 0) or 0
88
+ if total_prompt or total_completion:
89
+ ctx.set_tokens(total_prompt, total_completion)
90
+ except Exception:
91
+ pass
92
+
93
+ return result
94
+ finally:
95
+ _INSTRUMENTING.reset(token)
96
+
97
+ return _run