osmosis-ai 0.1.9__py3-none-any.whl → 0.2.4__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.

Potentially problematic release.


This version of osmosis-ai might be problematic. Click here for more details.

osmosis_ai/__init__.py CHANGED
@@ -1,137 +1,24 @@
1
1
  """
2
- osmosis-ai: A library for monkey patching LLM APIs to print all prompts and responses.
2
+ osmosis-ai: A Python library for reward function validation with strict type enforcement.
3
3
 
4
- This module patches various LLM client libraries to send all prompts and responses
5
- to the OSMOSIS API for logging and monitoring.
4
+ This library provides decorators such as @osmosis_reward and @osmosis_rubric that
5
+ enforce standardized function signatures for LLM-centric workflows.
6
6
 
7
- Currently supported adapters:
8
- - Anthropic (both sync and async clients)
9
- - OpenAI (both sync and async clients, v1 and v2 API versions)
10
- - LangChain (LLMs, Chat Models, and Prompt Templates)
7
+ Features:
8
+ - Type-safe reward function decoration
9
+ - Parameter name and type validation
10
+ - Support for optional configuration parameters
11
11
  """
12
12
 
13
-
14
- # Use lazy imports to avoid importing modules during setup
15
- def _import_modules():
16
- global utils, logger, reconfigure_logger
17
- global set_log_destination, log_destination, LogDestination
18
- global init, enabled, disable_osmosis, enable_osmosis, osmosis_reward
19
-
20
- from . import utils
21
- from .logger import logger, reconfigure_logger, set_log_destination, log_destination
22
- from .consts import LogDestination
23
-
24
- # Re-export configuration flags for easy access
25
- enabled = utils.enabled
26
-
27
- # Export disable and enable functions
28
- disable_osmosis = utils.disable_osmosis
29
- enable_osmosis = utils.enable_osmosis
30
-
31
- # Re-export initialization function
32
- init = utils.init
33
-
34
- # Export the reward decorator
35
- osmosis_reward = utils.osmosis_reward
36
-
37
- # Initialize wrappers as None
38
- global wrap_anthropic, wrap_openai, wrap_langchain
39
- global wrap_langchain_openai, wrap_langchain_anthropic
40
-
41
- wrap_anthropic = None
42
- wrap_openai = None
43
- wrap_langchain = None
44
- wrap_langchain_openai = None
45
- wrap_langchain_anthropic = None
46
-
47
- # Lazily set up anthropic wrapper if available
48
- try:
49
- from .adapters import anthropic
50
-
51
- wrap_anthropic = anthropic.wrap_anthropic
52
- # Apply the wrapper, but don't fail if it doesn't work
53
- try:
54
- wrap_anthropic()
55
- except Exception as e:
56
- logger.warning(f"Failed to wrap Anthropic: {str(e)}")
57
- except ImportError:
58
-
59
- def wrap_anthropic():
60
- logger.warning(
61
- "Anthropic support not available. Install Anthropic to use this feature."
62
- )
63
-
64
- # Lazily set up OpenAI wrapper if available
65
- try:
66
- from .adapters import openai
67
-
68
- wrap_openai = openai.wrap_openai
69
- # Apply the wrapper, but don't fail if it doesn't work
70
- try:
71
- wrap_openai()
72
- except Exception as e:
73
- logger.warning(f"Failed to wrap OpenAI: {str(e)}")
74
- except ImportError:
75
-
76
- def wrap_openai():
77
- logger.warning(
78
- "OpenAI support not available. Install OpenAI to use this feature."
79
- )
80
-
81
- # Lazily set up LangChain wrapper if available
82
- try:
83
- from .adapters import langchain
84
-
85
- wrap_langchain = langchain.wrap_langchain
86
- # Apply the wrapper, but don't fail if it doesn't work
87
- try:
88
- wrap_langchain()
89
- except Exception as e:
90
- logger.warning(f"Failed to wrap LangChain: {str(e)}")
91
- except ImportError:
92
-
93
- def wrap_langchain():
94
- logger.warning(
95
- "LangChain support not available. Install LangChain to use this feature."
96
- )
97
-
98
- # Lazily set up LangChain-OpenAI wrapper if available
99
- try:
100
- from .adapters import langchain_openai
101
-
102
- wrap_langchain_openai = langchain_openai.wrap_langchain_openai
103
- # Apply the wrapper, but don't fail if it doesn't work
104
- try:
105
- wrap_langchain_openai()
106
- except Exception as e:
107
- logger.warning(f"Failed to wrap LangChain-OpenAI: {str(e)}")
108
- except ImportError:
109
-
110
- def wrap_langchain_openai():
111
- logger.warning(
112
- "LangChain-OpenAI support not available. Install LangChain and OpenAI to use this feature."
113
- )
114
-
115
- # Lazily set up LangChain-Anthropic wrapper if available
116
- try:
117
- from .adapters import langchain_anthropic
118
-
119
- wrap_langchain_anthropic = langchain_anthropic.wrap_langchain_anthropic
120
- # Apply the wrapper, but don't fail if it doesn't work
121
- try:
122
- wrap_langchain_anthropic()
123
- except Exception as e:
124
- logger.warning(f"Failed to wrap LangChain-Anthropic: {str(e)}")
125
- except ImportError:
126
-
127
- def wrap_langchain_anthropic():
128
- logger.warning(
129
- "LangChain-Anthropic support not available. Install LangChain and Anthropic to use this feature."
130
- )
131
-
132
-
133
- # Initialize the module on first import, but not during installation
134
- import sys
135
-
136
- if "pip" not in sys.modules:
137
- _import_modules()
13
+ from .rubric_eval import MissingAPIKeyError, evaluate_rubric
14
+ from .rubric_types import ModelNotFoundError, ProviderRequestError
15
+ from .utils import osmosis_reward, osmosis_rubric
16
+
17
+ __all__ = [
18
+ "osmosis_reward",
19
+ "osmosis_rubric",
20
+ "evaluate_rubric",
21
+ "MissingAPIKeyError",
22
+ "ProviderRequestError",
23
+ "ModelNotFoundError",
24
+ ]
osmosis_ai/cli.py ADDED
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import sys
5
+ from typing import Optional
6
+
7
+ from .cli_commands import EvalCommand, PreviewCommand
8
+ from .cli_services import CLIError
9
+
10
+
11
+ def main(argv: Optional[list[str]] = None) -> int:
12
+ """Entry point for the osmosis CLI."""
13
+ parser = _build_parser()
14
+ args = parser.parse_args(argv)
15
+
16
+ handler = getattr(args, "handler", None)
17
+ if handler is None:
18
+ parser.print_help()
19
+ return 1
20
+
21
+ try:
22
+ return handler(args)
23
+ except CLIError as exc:
24
+ print(f"Error: {exc}", file=sys.stderr)
25
+ return 1
26
+
27
+
28
+ def _build_parser() -> argparse.ArgumentParser:
29
+ parser = argparse.ArgumentParser(
30
+ prog="osmosis", description="Utilities for inspecting Osmosis rubric and test data files."
31
+ )
32
+ subparsers = parser.add_subparsers(dest="command")
33
+
34
+ preview_parser = subparsers.add_parser(
35
+ "preview",
36
+ help="Preview a rubric YAML file or test JSONL file and print its parsed contents.",
37
+ )
38
+ PreviewCommand().configure_parser(preview_parser)
39
+
40
+ eval_parser = subparsers.add_parser(
41
+ "eval",
42
+ help="Evaluate JSONL conversations against a rubric using remote providers.",
43
+ )
44
+ EvalCommand().configure_parser(eval_parser)
45
+
46
+ return parser
47
+
48
+
49
+ if __name__ == "__main__":
50
+ sys.exit(main())
@@ -0,0 +1,181 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Any, Callable, Optional
7
+
8
+ from .cli_services import (
9
+ CLIError,
10
+ ParsedItem,
11
+ BaselineComparator,
12
+ ConsoleReportRenderer,
13
+ DatasetLoader,
14
+ EvaluationSession,
15
+ EvaluationSessionRequest,
16
+ JsonReportWriter,
17
+ RubricEvaluationEngine,
18
+ RubricSuite,
19
+ discover_rubric_config_path,
20
+ load_jsonl_records,
21
+ load_rubric_configs,
22
+ load_rubric_suite,
23
+ render_json_records,
24
+ render_yaml_items,
25
+ )
26
+
27
+
28
+ class PreviewCommand:
29
+ """Handler for `osmosis preview`."""
30
+
31
+ def __init__(
32
+ self,
33
+ *,
34
+ yaml_loader: Callable[[Path], list[ParsedItem]] = load_rubric_configs,
35
+ json_loader: Callable[[Path], list[dict[str, Any]]] = load_jsonl_records,
36
+ ):
37
+ self._yaml_loader = yaml_loader
38
+ self._json_loader = json_loader
39
+
40
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
41
+ parser.set_defaults(handler=self.run)
42
+ parser.add_argument(
43
+ "-p",
44
+ "--path",
45
+ dest="path",
46
+ required=True,
47
+ help="Path to the YAML or JSONL file to inspect.",
48
+ )
49
+
50
+ def run(self, args: argparse.Namespace) -> int:
51
+ path = Path(args.path).expanduser()
52
+ if not path.exists():
53
+ raise CLIError(f"Path '{path}' does not exist.")
54
+ if path.is_dir():
55
+ raise CLIError(f"Expected a file path but got directory '{path}'.")
56
+
57
+ suffix = path.suffix.lower()
58
+ if suffix in {".yaml", ".yml"}:
59
+ items = self._yaml_loader(path)
60
+ print(f"Loaded {len(items)} rubric config(s) from {path}")
61
+ print(render_yaml_items(items, label="Rubric config"))
62
+ elif suffix == ".jsonl":
63
+ records = self._json_loader(path)
64
+ print(f"Loaded {len(records)} JSONL record(s) from {path}")
65
+ print(render_json_records(records))
66
+ else:
67
+ raise CLIError(f"Unsupported file extension '{suffix}'. Expected .yaml, .yml, or .jsonl.")
68
+
69
+ return 0
70
+
71
+
72
+ class EvalCommand:
73
+ """Handler for `osmosis eval`."""
74
+
75
+ def __init__(
76
+ self,
77
+ *,
78
+ session: Optional[EvaluationSession] = None,
79
+ config_locator: Callable[[Optional[str], Path], Path] = discover_rubric_config_path,
80
+ suite_loader: Callable[[Path], RubricSuite] = load_rubric_suite,
81
+ dataset_loader: Optional[DatasetLoader] = None,
82
+ engine: Optional[RubricEvaluationEngine] = None,
83
+ renderer: Optional[ConsoleReportRenderer] = None,
84
+ report_writer: Optional[JsonReportWriter] = None,
85
+ baseline_comparator: Optional[BaselineComparator] = None,
86
+ ):
87
+ self._renderer = renderer or ConsoleReportRenderer()
88
+ if session is not None:
89
+ self._session = session
90
+ else:
91
+ self._session = EvaluationSession(
92
+ config_locator=config_locator,
93
+ suite_loader=suite_loader,
94
+ dataset_loader=dataset_loader,
95
+ engine=engine,
96
+ baseline_comparator=baseline_comparator,
97
+ report_writer=report_writer,
98
+ identifier_factory=self._generate_output_identifier,
99
+ )
100
+
101
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
102
+ parser.set_defaults(handler=self.run)
103
+ parser.add_argument(
104
+ "-r",
105
+ "--rubric",
106
+ dest="rubric_id",
107
+ required=True,
108
+ help="Rubric identifier declared in the rubric config file.",
109
+ )
110
+ parser.add_argument(
111
+ "-d",
112
+ "--data",
113
+ dest="data_path",
114
+ required=True,
115
+ help="Path to the JSONL file containing evaluation records.",
116
+ )
117
+ parser.add_argument(
118
+ "-n",
119
+ "--number",
120
+ dest="number",
121
+ type=int,
122
+ default=1,
123
+ help="Run the evaluation multiple times to sample provider variance (default: 1).",
124
+ )
125
+ parser.add_argument(
126
+ "-c",
127
+ "--config",
128
+ dest="config_path",
129
+ help="Path to the rubric config YAML (defaults to searching near the data file).",
130
+ )
131
+ parser.add_argument(
132
+ "-o",
133
+ "--output",
134
+ dest="output_path",
135
+ help="Optional path to write evaluation results as JSON.",
136
+ )
137
+ parser.add_argument(
138
+ "-b",
139
+ "--baseline",
140
+ dest="baseline_path",
141
+ help="Optional path to a prior evaluation JSON to compare against.",
142
+ )
143
+
144
+ def run(self, args: argparse.Namespace) -> int:
145
+ rubric_id_raw = getattr(args, "rubric_id", "")
146
+ rubric_id = str(rubric_id_raw).strip()
147
+ if not rubric_id:
148
+ raise CLIError("Rubric identifier cannot be empty.")
149
+
150
+ data_path = Path(args.data_path).expanduser()
151
+ config_path_value = getattr(args, "config_path", None)
152
+ output_path_value = getattr(args, "output_path", None)
153
+ baseline_path_value = getattr(args, "baseline_path", None)
154
+
155
+ number_value = getattr(args, "number", None)
156
+ number = int(number_value) if number_value is not None else 1
157
+
158
+ request = EvaluationSessionRequest(
159
+ rubric_id=rubric_id,
160
+ data_path=data_path,
161
+ number=number,
162
+ config_path=Path(config_path_value).expanduser() if config_path_value else None,
163
+ output_path=Path(output_path_value).expanduser() if output_path_value else None,
164
+ baseline_path=Path(baseline_path_value).expanduser() if baseline_path_value else None,
165
+ )
166
+
167
+ try:
168
+ result = self._session.execute(request)
169
+ except KeyboardInterrupt:
170
+ print("Evaluation cancelled by user.")
171
+ return 1
172
+ self._renderer.render(result.report, result.baseline)
173
+
174
+ if result.written_path is not None:
175
+ print(f"Wrote evaluation results to {result.written_path}")
176
+
177
+ return 0
178
+
179
+ @staticmethod
180
+ def _generate_output_identifier() -> str:
181
+ return str(int(time.time()))
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from .config import (
4
+ ParsedItem,
5
+ RubricConfig,
6
+ RubricConfigParser,
7
+ RubricSuite,
8
+ discover_rubric_config_path,
9
+ load_rubric_configs,
10
+ load_rubric_suite,
11
+ render_yaml_items,
12
+ )
13
+ from .dataset import DatasetLoader, DatasetRecord, load_jsonl_records, render_json_records
14
+ from .engine import (
15
+ EvaluationRecordResult,
16
+ EvaluationReport,
17
+ EvaluationRun,
18
+ RubricEvaluationEngine,
19
+ RubricEvaluator,
20
+ )
21
+ from .errors import CLIError
22
+ from .reporting import (
23
+ BaselineComparator,
24
+ BaselineStatistics,
25
+ ConsoleReportRenderer,
26
+ JsonReportFormatter,
27
+ JsonReportWriter,
28
+ TextReportFormatter,
29
+ )
30
+ from .session import EvaluationSession, EvaluationSessionRequest, EvaluationSessionResult
31
+
32
+ __all__ = [
33
+ "BaselineComparator",
34
+ "BaselineStatistics",
35
+ "CLIError",
36
+ "ConsoleReportRenderer",
37
+ "DatasetLoader",
38
+ "DatasetRecord",
39
+ "EvaluationSession",
40
+ "EvaluationSessionRequest",
41
+ "EvaluationSessionResult",
42
+ "EvaluationRecordResult",
43
+ "EvaluationReport",
44
+ "EvaluationRun",
45
+ "JsonReportFormatter",
46
+ "JsonReportWriter",
47
+ "ParsedItem",
48
+ "RubricConfig",
49
+ "RubricConfigParser",
50
+ "RubricEvaluationEngine",
51
+ "RubricEvaluator",
52
+ "RubricSuite",
53
+ "TextReportFormatter",
54
+ "discover_rubric_config_path",
55
+ "load_jsonl_records",
56
+ "load_rubric_configs",
57
+ "load_rubric_suite",
58
+ "render_json_records",
59
+ "render_yaml_items",
60
+ ]