osmosis-ai 0.1.8__py3-none-any.whl → 0.2.0__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,15 @@
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 the @osmosis_reward decorator that enforces standardized
5
+ function signatures for reward functions used in LLM applications.
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
+ from .utils import osmosis_reward
13
14
 
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()
15
+ __all__ = ["osmosis_reward"]
osmosis_ai/consts.py CHANGED
@@ -1,17 +1,3 @@
1
- from enum import Enum
2
-
3
- # Extract package metadata
1
+ # package metadata
4
2
  package_name = "osmosis-ai"
5
- package_version = "0.1.8"
6
-
7
- indent = 2 # Number of spaces to use for indentation in pretty print
8
- osmosis_api_url = "https://osmosis.gulp.dev"
9
-
10
- DEFAULT_LOG_DESTINATION = "none" # "none" or "stdout" or "stderr" or "file"
11
-
12
-
13
- class LogDestination(Enum):
14
- NONE = "none"
15
- STDOUT = "stdout"
16
- STDERR = "stderr"
17
- FILE = "file"
3
+ package_version = "0.2.0"
osmosis_ai/utils.py CHANGED
@@ -1,120 +1,62 @@
1
- """
2
- Utility functions for osmosisadapters
3
- """
4
1
 
5
- import json
6
- from datetime import datetime, timezone
7
- from typing import Any, Dict, Callable
8
- import xxhash
9
2
  import functools
10
-
11
- # Import constants
12
- from .consts import osmosis_api_url
13
-
14
- # Import logger
15
- from .logger import logger
16
-
17
- # Global configuration
18
- enabled = True
19
- osmosis_api_key = None # Will be set by init()
20
- _initialized = False
21
-
22
-
23
- def init(api_key: str) -> None:
24
- """
25
- Initialize osmosiswith the OSMOSIS API key.
26
-
27
- Args:
28
- api_key: The OSMOSIS API key for logging LLM usage
29
- """
30
- global osmosis_api_key, _initialized
31
- osmosis_api_key = api_key
32
- _initialized = True
33
-
34
-
35
- def disable_osmosis() -> None:
36
- global enabled
37
- enabled = False
38
-
39
-
40
- def enable_osmosis() -> None:
41
- global enabled
42
- enabled = True
43
-
44
-
45
- def send_to_osmosis(
46
- query: Dict[str, Any], response: Dict[str, Any], status: int = 200
47
- ) -> None:
48
- """
49
- Send query and response data to the OSMOSIS API using AWS Firehose.
50
-
51
- Args:
52
- query: The query/request data
53
- response: The response data
54
- status: The HTTP status code (default: 200)
55
- """
56
- if not enabled or not osmosis_api_key:
57
- return
58
-
59
- if not _initialized:
60
- logger.warning("osmosisnot initialized. Call osmosis_ai.init(api_key) first.")
61
- return
62
-
63
- try:
64
- # Import requests only when needed
65
- import requests
66
-
67
- # Create headers
68
- headers = {"Content-Type": "application/json", "x-api-key": osmosis_api_key}
69
-
70
- # Prepare main data payload
71
- data = {
72
- "owner": xxhash.xxh32(osmosis_api_key.encode("utf-8")).hexdigest(),
73
- "date": int(datetime.now(timezone.utc).timestamp()),
74
- "query": query,
75
- "response": response,
76
- "status": status,
77
- }
78
-
79
- logger.info(f"Sending data to OSMOSIS API: {data}")
80
-
81
- # Send main data payload
82
- response_data = requests.post(
83
- f"{osmosis_api_url}/ingest",
84
- headers=headers,
85
- data=json.dumps(data).replace("\n", "") + "\n",
86
- )
87
-
88
- if response_data.status_code != 200:
89
- logger.warning(
90
- f"OSMOSIS API returned status {response_data.status_code} for data with error: {response_data.text}"
91
- )
92
-
93
- except ImportError:
94
- logger.warning(
95
- "Requests library not installed. Please install it with 'pip install requests'."
96
- )
97
- except Exception as e:
98
- logger.warning(f"Failed to send data to OSMOSIS API: {str(e)}")
3
+ import inspect
4
+ from typing import Callable
99
5
 
100
6
 
101
7
  def osmosis_reward(func: Callable) -> Callable:
102
8
  """
103
- Decorator for reward functions.
104
-
9
+ Decorator for reward functions that enforces the signature:
10
+ (solution_str: str, ground_truth: str, extra_info: dict = None) -> float
11
+
105
12
  Args:
106
13
  func: The reward function to be wrapped
107
-
14
+
108
15
  Returns:
109
16
  The wrapped function
110
-
17
+
18
+ Raises:
19
+ TypeError: If the function doesn't have the required signature or doesn't return a float
20
+
111
21
  Example:
112
22
  @osmosis_reward
113
- def calculate_reward(state, action):
114
- return state.score + action.value
23
+ def calculate_reward(solution_str: str, ground_truth: str, extra_info: dict = None) -> float:
24
+ return some_calculation(solution_str, ground_truth)
115
25
  """
26
+ # Validate function signature
27
+ sig = inspect.signature(func)
28
+ params = list(sig.parameters.values())
29
+
30
+ # Check parameter count
31
+ if len(params) < 2 or len(params) > 3:
32
+ raise TypeError(f"Function {func.__name__} must have 2-3 parameters, got {len(params)}")
33
+
34
+ # Check first parameter: solution_str: str
35
+ if params[0].name != 'solution_str':
36
+ raise TypeError(f"First parameter must be named 'solution_str', got '{params[0].name}'")
37
+ if params[0].annotation != str:
38
+ raise TypeError(f"First parameter 'solution_str' must be annotated as str, got {params[0].annotation}")
39
+
40
+ # Check second parameter: ground_truth: str
41
+ if params[1].name != 'ground_truth':
42
+ raise TypeError(f"Second parameter must be named 'ground_truth', got '{params[1].name}'")
43
+ if params[1].annotation != str:
44
+ raise TypeError(f"Second parameter 'ground_truth' must be annotated as str, got {params[1].annotation}")
45
+
46
+ # Check third parameter if present: extra_info: dict = None
47
+ if len(params) == 3:
48
+ if params[2].name != 'extra_info':
49
+ raise TypeError(f"Third parameter must be named 'extra_info', got '{params[2].name}'")
50
+ if params[2].annotation != dict:
51
+ raise TypeError(f"Third parameter 'extra_info' must be annotated as dict, got {params[2].annotation}")
52
+ if params[2].default is inspect.Parameter.empty:
53
+ raise TypeError("Third parameter 'extra_info' must have a default value of None")
54
+
116
55
  @functools.wraps(func)
117
56
  def wrapper(*args, **kwargs):
118
- return func(*args, **kwargs)
119
-
57
+ result = func(*args, **kwargs)
58
+ if not isinstance(result, float):
59
+ raise TypeError(f"Function {func.__name__} must return a float, got {type(result).__name__}")
60
+ return result
61
+
120
62
  return wrapper
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: osmosis-ai
3
+ Version: 0.2.0
4
+ Summary: Adds reward functionality to LLM client libraries
5
+ Author-email: Osmosis AI <jake@osmosis.ai>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Gulp AI
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ Project-URL: Homepage, https://github.com/Osmosis-AI/osmosis-sdk-python
28
+ Project-URL: Issues, https://github.com/Osmosis-AI/osmosis-sdk-python/issues
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: OS Independent
32
+ Requires-Python: >=3.6
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Dynamic: license-file
36
+
37
+ # osmosis-ai
38
+
39
+ A Python library that provides reward functionality for LLM applications with strict type enforcement.
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install osmosis-ai
45
+ ```
46
+
47
+ For development:
48
+ ```bash
49
+ git clone https://github.com/Osmosis-AI/osmosis-sdk-python
50
+ cd osmosis-sdk-python
51
+ pip install -e .
52
+ ```
53
+
54
+ ## Quick Start
55
+
56
+ ```python
57
+ from osmosis_ai import osmosis_reward
58
+
59
+ @osmosis_reward
60
+ def simple_reward(solution_str: str, ground_truth: str, extra_info: dict = None) -> float:
61
+ """Basic exact match reward function."""
62
+ return 1.0 if solution_str.strip() == ground_truth.strip() else 0.0
63
+
64
+ # Use the reward function
65
+ score = simple_reward("hello world", "hello world") # Returns 1.0
66
+ ```
67
+
68
+ ## Required Function Signature
69
+
70
+ All functions decorated with `@osmosis_reward` must have exactly this signature:
71
+
72
+ ```python
73
+ @osmosis_reward
74
+ def your_function(solution_str: str, ground_truth: str, extra_info: dict = None) -> float:
75
+ # Your reward logic here
76
+ return float_score
77
+ ```
78
+
79
+ ### Parameters
80
+
81
+ - **`solution_str: str`** - The solution string to evaluate (required)
82
+ - **`ground_truth: str`** - The correct/expected answer (required)
83
+ - **`extra_info: dict = None`** - Optional dictionary for additional configuration
84
+
85
+ ### Return Value
86
+
87
+ - **`-> float`** - Must return a float value representing the reward score
88
+
89
+ The decorator will raise a `TypeError` if the function doesn't match this exact signature or doesn't return a float.
90
+
91
+ ## Examples
92
+
93
+ See the [`examples/`](examples/) directory for complete examples:
94
+
95
+ ```python
96
+ @osmosis_reward
97
+ def case_insensitive_match(solution_str: str, ground_truth: str, extra_info: dict = None) -> float:
98
+ """Case-insensitive string matching with partial credit."""
99
+ match = solution_str.lower().strip() == ground_truth.lower().strip()
100
+
101
+ if extra_info and 'partial_credit' in extra_info:
102
+ if not match and extra_info['partial_credit']:
103
+ len_diff = abs(len(solution_str) - len(ground_truth))
104
+ if len_diff <= 2:
105
+ return 0.5
106
+
107
+ return 1.0 if match else 0.0
108
+
109
+ @osmosis_reward
110
+ def numeric_tolerance(solution_str: str, ground_truth: str, extra_info: dict = None) -> float:
111
+ """Numeric comparison with configurable tolerance."""
112
+ try:
113
+ solution_num = float(solution_str.strip())
114
+ truth_num = float(ground_truth.strip())
115
+
116
+ tolerance = extra_info.get('tolerance', 0.01) if extra_info else 0.01
117
+ return 1.0 if abs(solution_num - truth_num) <= tolerance else 0.0
118
+ except ValueError:
119
+ return 0.0
120
+ ```
121
+
122
+ ## Running Examples
123
+
124
+ ```bash
125
+ python examples/reward_functions.py
126
+ ```
127
+
128
+ ## License
129
+
130
+ MIT License - see [LICENSE](LICENSE) file for details.
131
+
132
+ ## Contributing
133
+
134
+ 1. Fork the repository
135
+ 2. Create a feature branch
136
+ 3. Make your changes
137
+ 4. Run tests and examples
138
+ 5. Submit a pull request
139
+
140
+ ## Links
141
+
142
+ - [Homepage](https://github.com/Osmosis-AI/osmosis-sdk-python)
143
+ - [Issues](https://github.com/Osmosis-AI/osmosis-sdk-python/issues)
@@ -0,0 +1,8 @@
1
+ osmosis_ai/__init__.py,sha256=P1w65jE23XhFLW6BEOaiMat-EoANgKwSYBIQFPCS3Xc,445
2
+ osmosis_ai/consts.py,sha256=Ojd7wWs0Xc7vJQqfjtxYNoWiaFbgk3ChHV5fPk3l6MI,72
3
+ osmosis_ai/utils.py,sha256=y6b-qy3nl_xFvURaDWKHFzkCJCFyzI8L0Wc1dHHZBm4,2465
4
+ osmosis_ai-0.2.0.dist-info/licenses/LICENSE,sha256=FV2ZmyhdCYinoLLvU_ci-7pZ3DeNYY9XqZjVjOd3h94,1064
5
+ osmosis_ai-0.2.0.dist-info/METADATA,sha256=7MzdHQmlJjnN5Mf-KSTVExh99aoxJu-91mM_KB6lEhM,4725
6
+ osmosis_ai-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ osmosis_ai-0.2.0.dist-info/top_level.txt,sha256=UPNRTKIBSrxsJVNxwXnLCqSoBS4bAiL_3jMtjvf5zEY,11
8
+ osmosis_ai-0.2.0.dist-info/RECORD,,
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Gulp AI
3
+ Copyright (c) 2025 Gulp AI
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,9 +0,0 @@
1
- """
2
- Adapters package for Osmosis
3
-
4
- This package contains various adapters for different LLM providers.
5
- Each adapter is conditionally imported based on installed dependencies.
6
- """
7
-
8
- # Don't include adapters in __all__ as they're imported conditionally
9
- __all__ = []