divi 0.0.1b0__py3-none-any.whl → 0.0.1b2__py3-none-any.whl

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.
divi/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # DIVI SDK
2
+
3
+ > this is the Python SDK for the divine-agent project.
divi/__init__.py CHANGED
@@ -1,10 +1,9 @@
1
1
  from typing import Optional
2
2
 
3
3
  from .decorators import obs_openai, observable
4
- from .evaluation import Evaluator, Score
4
+ from .evaluation import Evaluator
5
5
  from .services import Auth, Core, DataPark
6
6
  from .session import Session
7
- from .signals import Kind
8
7
 
9
8
  name: str = "divi"
10
9
 
@@ -14,5 +13,5 @@ _auth: Optional[Auth] = None
14
13
  _datapark: Optional[DataPark] = None
15
14
  _evaluator: Optional[Evaluator] = None
16
15
 
17
- __version__ = "0.0.1b0"
18
- __all__ = ["obs_openai", "observable", "Score", "Kind"]
16
+ __version__ = "0.0.1b2"
17
+ __all__ = ["obs_openai", "observable"]
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, TypeVar, Union
5
5
  from typing_extensions import Optional
6
6
 
7
7
  from divi.decorators.observable import observable
8
+ from divi.evaluation.evaluator import EvaluatorConfig
8
9
  from divi.evaluation.scores import Score
9
10
  from divi.signals.span import Kind
10
11
  from divi.utils import is_async
@@ -19,10 +20,13 @@ def _get_observable_create(
19
20
  create: Callable,
20
21
  name: Optional[str] = None,
21
22
  scores: Optional[list[Score]] = None,
23
+ eval: Optional[EvaluatorConfig] = None,
22
24
  ) -> Callable:
23
25
  @functools.wraps(create)
24
26
  def observable_create(*args, stream: bool = False, **kwargs):
25
- decorator = observable(kind=Kind.llm, name=name, scores=scores)
27
+ decorator = observable(
28
+ kind=Kind.llm, name=name, scores=scores, eval=eval
29
+ )
26
30
  return decorator(create)(*args, stream=stream, **kwargs)
27
31
 
28
32
  # TODO Async Observable Create
@@ -33,16 +37,13 @@ def obs_openai(
33
37
  client: C,
34
38
  name: Optional[str] = "Agent",
35
39
  scores: Optional[list[Score]] = None,
40
+ eval: Optional[EvaluatorConfig] = None,
36
41
  ) -> C:
37
42
  """Make OpenAI client observable."""
38
43
  client.chat.completions.create = _get_observable_create(
39
44
  client.chat.completions.create,
40
45
  name=name,
41
46
  scores=scores,
42
- )
43
- client.completions.create = _get_observable_create(
44
- client.completions.create,
45
- name=name,
46
- scores=scores,
47
+ eval=eval,
47
48
  )
48
49
  return client
@@ -15,6 +15,7 @@ from typing import (
15
15
 
16
16
  from divi.decorators.observe import observe
17
17
  from divi.evaluation.evaluate import evaluate_scores
18
+ from divi.evaluation.evaluator import EvaluatorConfig
18
19
  from divi.evaluation.scores import Score
19
20
  from divi.session import SessionExtra
20
21
  from divi.signals.span import Kind, Span
@@ -43,6 +44,7 @@ def observable(
43
44
  *,
44
45
  name: Optional[str] = None,
45
46
  scores: Optional[list[Score]] = None,
47
+ eval: Optional[EvaluatorConfig] = None,
46
48
  metadata: Optional[Mapping[str, Any]] = None,
47
49
  ) -> Callable[[Callable[P, R]], WithSessionExtra[P, R]]: ...
48
50
 
@@ -56,6 +58,7 @@ def observable(
56
58
  name = kwargs.pop("name", None)
57
59
  metadata = kwargs.pop("metadata", None)
58
60
  scores: list[Score] = kwargs.pop("scores", None)
61
+ eval: EvaluatorConfig = kwargs.pop("eval", None)
59
62
 
60
63
  def decorator(func):
61
64
  @functools.wraps(func)
@@ -78,7 +81,9 @@ def observable(
78
81
 
79
82
  # 3. evaluate the scores if they are provided
80
83
  messages = kwargs.get("messages", [])
81
- evaluate_scores(messages, outputs=result, scores=scores)
84
+ evaluate_scores(
85
+ messages, outputs=result, scores=scores, config=eval
86
+ )
82
87
 
83
88
  return result
84
89
 
@@ -1,4 +1,4 @@
1
- from .evaluator import Evaluator
1
+ from .evaluator import Evaluator, EvaluatorConfig
2
2
  from .scores import Score
3
3
 
4
- __all__ = ["Evaluator", "Score"]
4
+ __all__ = ["Evaluator", "Score", "EvaluatorConfig"]
@@ -13,8 +13,9 @@ from divi.evaluation.scores import Score
13
13
  class EvaluatorConfig:
14
14
  def __init__(
15
15
  self,
16
- model: str = "gpt-4.1-nano",
16
+ model: str = "gpt-4o",
17
17
  temperature: float = 0.5,
18
+ n_rounds: int = 5,
18
19
  max_concurrency: int = 10,
19
20
  api_key: Optional[str] = None,
20
21
  base_url: Optional[str] = None,
@@ -23,6 +24,7 @@ class EvaluatorConfig:
23
24
  self.api_key = api_key
24
25
  self.base_url = base_url
25
26
  self.temperature = temperature
27
+ self.n_rounds = n_rounds
26
28
  self.max_concurrency = max_concurrency
27
29
 
28
30
 
@@ -123,7 +125,11 @@ class Evaluator:
123
125
  return aggregated_results
124
126
 
125
127
  def evaluate_sync(
126
- self, target: str, conversation: str, scores: list[Score], n_rounds: int
128
+ self,
129
+ target: str,
130
+ conversation: str,
131
+ scores: list[Score],
132
+ n_rounds: Optional[int] = None,
127
133
  ) -> List[EvaluationScore]:
128
134
  with concurrent.futures.ThreadPoolExecutor(
129
135
  max_workers=self.config.max_concurrency
@@ -132,7 +138,7 @@ class Evaluator:
132
138
  executor.submit(
133
139
  self._sync_evaluate_once, target, conversation, score
134
140
  )
135
- for _ in range(n_rounds)
141
+ for _ in range(n_rounds if n_rounds else self.config.n_rounds)
136
142
  for score in scores
137
143
  ]
138
144
  evaluations = [
@@ -143,7 +149,11 @@ class Evaluator:
143
149
  )
144
150
 
145
151
  async def evaluate_async(
146
- self, target: str, conversation: str, scores: list[Score], n_rounds: int
152
+ self,
153
+ target: str,
154
+ conversation: str,
155
+ scores: list[Score],
156
+ n_rounds: Optional[int] = None,
147
157
  ) -> List[EvaluationScore]:
148
158
  semaphore = asyncio.Semaphore(self.config.max_concurrency)
149
159
 
@@ -153,7 +163,11 @@ class Evaluator:
153
163
  target, conversation, score
154
164
  )
155
165
 
156
- tasks = [sem_task(score) for _ in range(n_rounds) for score in scores]
166
+ tasks = [
167
+ sem_task(score)
168
+ for _ in range(n_rounds if n_rounds else self.config.n_rounds)
169
+ for score in scores
170
+ ]
157
171
  evaluations = await asyncio.gather(*tasks)
158
172
  return self._aggregate_results(
159
173
  [e for e in evaluations if e is not None]
@@ -164,7 +178,7 @@ class Evaluator:
164
178
  target: str,
165
179
  conversation: str,
166
180
  scores: list[Score],
167
- n_rounds: int = 5,
181
+ n_rounds: Optional[int] = None,
168
182
  mode: Literal["sync", "async"] = "sync",
169
183
  ) -> List[EvaluationScore]:
170
184
  if mode == "async":
divi/signals/span.py CHANGED
@@ -1,3 +1,4 @@
1
+ import atexit
1
2
  import os
2
3
  import time
3
4
  from enum import Enum
@@ -62,6 +63,8 @@ class Span:
62
63
  """Start the span by recording the current time in nanoseconds."""
63
64
  self.start_time_unix_nano = time.time_ns()
64
65
  self.upsert_span()
66
+ # Register the end method to be called at exit
67
+ atexit.register(self.end)
65
68
 
66
69
  def end(self):
67
70
  """End the span by recording the end time in nanoseconds."""
@@ -69,6 +72,8 @@ class Span:
69
72
  raise ValueError("Span must be started before ending.")
70
73
  self.end_time_unix_nano = time.time_ns()
71
74
  self.upsert_span()
75
+ # Unregister the end method
76
+ atexit.unregister(self.end)
72
77
 
73
78
  def _add_node(self, trace_id: UUID4, parent_id: Optional[bytes] = None):
74
79
  """Add node for obs tree."""
divi/signals/trace.py CHANGED
@@ -1,3 +1,4 @@
1
+ import atexit
1
2
  from datetime import UTC, datetime
2
3
  from typing import Optional
3
4
  from uuid import uuid4
@@ -63,6 +64,8 @@ class Trace:
63
64
  """Start the trace by recording the current time in nanoseconds."""
64
65
  self.start_time = datetime.now(UTC).isoformat()
65
66
  self.upsert_trace()
67
+ # Register the end method to be called on exit
68
+ atexit.register(self.end)
66
69
 
67
70
  def end(self):
68
71
  """End the trace by recording the end time in nanoseconds."""
@@ -70,6 +73,8 @@ class Trace:
70
73
  raise ValueError("Span must be started before ending.")
71
74
  self.end_time = datetime.now(UTC).isoformat()
72
75
  self.upsert_trace()
76
+ # Unregister the end method to prevent multiple calls
77
+ atexit.unregister(self.end)
73
78
 
74
79
  def upsert_trace(self):
75
80
  """Upsert trace with datapark."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: divi
3
- Version: 0.0.1b0
3
+ Version: 0.0.1b2
4
4
  Summary: The Agent Platform for Observability & Evaluation
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -1,13 +1,14 @@
1
- divi/__init__.py,sha256=Mxu0UYIKgegwUU7gTe5jMHYZ0wf3rzwtiHfHyLcTysA,486
1
+ divi/README.md,sha256=hdw10Mkebd1SGMeA3tPJtlOwbWZzuQ8J-N9YPEHGuXg,67
2
+ divi/__init__.py,sha256=FNz-nbMRHLhT-JWowhQTK7zMajy2XY0TFPD3Jjar1sw,436
2
3
  divi/utils.py,sha256=fXkjoyo_Lh8AZliKICOP460m0czUcNQjcEcceJbaOVA,1439
3
4
  divi/decorators/__init__.py,sha256=HkyWdC1ctTsVFucCWCkj57JB4NmwONus1d2S2dUbvs4,110
4
5
  divi/decorators/collect.py,sha256=5iUxAnbHYx4ISkFg64IK_4miGdrWgbOXLJxKz8lGIv8,1074
5
- divi/decorators/obs_openai.py,sha256=ouw3GYDFg6S27tcUzY0dIqz8JX_JM8IOXttzo7HK7nk,1359
6
- divi/decorators/observable.py,sha256=isUS3P_07wbZBj2UcRAoYNDceQTIn6zdein3-PWVsi8,2289
6
+ divi/decorators/obs_openai.py,sha256=eJkPL7T9ribA8Utl8RLvVsp1dRKbS3-Q4vwDfPz16Sg,1414
7
+ divi/decorators/observable.py,sha256=orDNCP1_uOTwgg71lg2c0qXOv6DtcxufRzuQZqXOrH4,2483
7
8
  divi/decorators/observe.py,sha256=I2RVsp2WQep6iTLSxkAlMP8wiRsSYiiYrxR2hJzPxcI,1211
8
- divi/evaluation/__init__.py,sha256=3qMHWu_zBh6FJa6-1dZZEWiAblQZurn5doa0OjGvDGs,93
9
+ divi/evaluation/__init__.py,sha256=SVu4tYZLgx4juEa1JtkXGcwB_Xuwvh60lbVhYKMseQo,129
9
10
  divi/evaluation/evaluate.py,sha256=lVMCw5vHGa5sJvUyhVDZ9m3Sgl4baCjWhw2OKazhvgM,1861
10
- divi/evaluation/evaluator.py,sha256=ulTyfSg2JXxzCCL7hRsn-EBb9UKcpQFA6rVT42mouVQ,5819
11
+ divi/evaluation/evaluator.py,sha256=D1s6upIOiGTjf24WAQfygYYUI8Lcc65vRgJ2wW2_3Uo,6108
11
12
  divi/evaluation/prompts.py,sha256=4oL8K8X2-zdmTDuAetc53o7Ys_vph-eWYhPmvRKYAng,960
12
13
  divi/evaluation/scores.py,sha256=ZgSxfve-ZivX3WU4TGcgPOSpUQVMbG5a15IQNPeq_bQ,173
13
14
  divi/proto/common/v1/common.proto,sha256=Rx8wr0_tOtQ1NseTMnsav4ApD1MDALzQDBA2IvLRTU0,1775
@@ -43,9 +44,9 @@ divi/session/session.py,sha256=QxtEezI447PbtKG2U6cxL1ACae55e8nFfTufAY8pEYI,811
43
44
  divi/session/setup.py,sha256=rC1QdCxpdCOaRXmcLEQs4Yuu5UC_aRzKSaqWRPJN4Og,1390
44
45
  divi/session/teardown.py,sha256=YiBz_3yCiljMFEofZ60VmRL5sb8WA5GT7EYF8nFznZ4,133
45
46
  divi/signals/__init__.py,sha256=wfSkkCwkRsFP4aLj8aGHk_k6Y50P5yN44WWlO3XyW18,43
46
- divi/signals/span.py,sha256=FQWql6ivAeXGk1HPZCsCjL5mXW6S6Nn9SmOiKH4aXik,2629
47
- divi/signals/trace.py,sha256=IoYeTfd6x_Xmxcp4HbFSEne0d48hol4ng2Mb_AO8hZw,2144
48
- divi-0.0.1b0.dist-info/METADATA,sha256=BW1GBcbxovWLvVJGoDylvomnBVaApGtJkTSgHIRBtuw,493
49
- divi-0.0.1b0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
50
- divi-0.0.1b0.dist-info/licenses/LICENSE,sha256=5OJuZ4wMMEV0DgF0tofhAlS_KLkaUsZwwwDS2U_GwQ0,1063
51
- divi-0.0.1b0.dist-info/RECORD,,
47
+ divi/signals/span.py,sha256=AQ24C8EKzGceryybX0rBe9B0z43kKaULdR6eOeOCtno,2804
48
+ divi/signals/trace.py,sha256=US_M2CAYNGUhEa5pppRPJN8dfNA5owgHMFDK2AOrFXA,2345
49
+ divi-0.0.1b2.dist-info/METADATA,sha256=IS0W_tN3EECkWRlX18GiUtX2idT3lYS2sAF2Z4jmoKA,493
50
+ divi-0.0.1b2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
51
+ divi-0.0.1b2.dist-info/licenses/LICENSE,sha256=5OJuZ4wMMEV0DgF0tofhAlS_KLkaUsZwwwDS2U_GwQ0,1063
52
+ divi-0.0.1b2.dist-info/RECORD,,
File without changes