simforge-py 0.1.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,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: simforge-py
3
+ Version: 0.1.0
4
+ Summary: Simforge client for provider-based API calls
5
+ Author: Harvest Team
6
+ Requires-Python: >=3.11,<3.13
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Requires-Dist: minijinja (>=2.12.0,<3.0.0)
11
+ Requires-Dist: openai (>=1.42.0,<2.0.0)
12
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Kirby
16
+
17
+ Kirby client for provider-based API calls.
18
+
19
+ ## Installation
20
+
21
+ For local development:
22
+
23
+ ```bash
24
+ cd kirby
25
+ poetry install
26
+ ```
27
+
28
+ Or install as an editable package from the parent directory:
29
+
30
+ ```bash
31
+ poetry add --editable ../kirby
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ```python
37
+ from kirby import Kirby
38
+
39
+ client = Kirby(
40
+ provider="openAI",
41
+ key="your-api-key",
42
+ model="gpt-4",
43
+ kirby_url="https://your-kirby-instance.com", # Optional, can use KIRBY_URL env var
44
+ kirby_user_id="user-id", # Optional, can use KIRBY_USER_ID env var
45
+ )
46
+
47
+ result = client.call("method_name", arg1="value1", arg2="value2")
48
+ ```
49
+
50
+ ## Configuration
51
+
52
+ The `kirby_url` and `kirby_user_id` can be provided either:
53
+ - As constructor arguments
54
+ - As environment variables (`KIRBY_URL` and `KIRBY_USER_ID`)
55
+
@@ -0,0 +1,40 @@
1
+ # Kirby
2
+
3
+ Kirby client for provider-based API calls.
4
+
5
+ ## Installation
6
+
7
+ For local development:
8
+
9
+ ```bash
10
+ cd kirby
11
+ poetry install
12
+ ```
13
+
14
+ Or install as an editable package from the parent directory:
15
+
16
+ ```bash
17
+ poetry add --editable ../kirby
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```python
23
+ from kirby import Kirby
24
+
25
+ client = Kirby(
26
+ provider="openAI",
27
+ key="your-api-key",
28
+ model="gpt-4",
29
+ kirby_url="https://your-kirby-instance.com", # Optional, can use KIRBY_URL env var
30
+ kirby_user_id="user-id", # Optional, can use KIRBY_USER_ID env var
31
+ )
32
+
33
+ result = client.call("method_name", arg1="value1", arg2="value2")
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ The `kirby_url` and `kirby_user_id` can be provided either:
39
+ - As constructor arguments
40
+ - As environment variables (`KIRBY_URL` and `KIRBY_USER_ID`)
@@ -0,0 +1,27 @@
1
+ [tool.poetry]
2
+ name = "simforge-py"
3
+ version = "0.1.0"
4
+ description = "Simforge client for provider-based API calls"
5
+ authors = ["Harvest Team"]
6
+ readme = "README.md"
7
+ packages = [{include = "simforge"}]
8
+
9
+ [tool.poetry.dependencies]
10
+ python = ">=3.11,<3.13"
11
+ requests = "^2.32.3"
12
+ minijinja = "^2.12.0"
13
+ openai = "^1.42.0"
14
+
15
+ [build-system]
16
+ requires = ["poetry-core"]
17
+ build-backend = "poetry.core.masonry.api"
18
+
19
+ [tool.black]
20
+ line-length = 88
21
+ target-version = ['py311']
22
+ include = '\.pyi?$'
23
+
24
+ [tool.isort]
25
+ profile = "black"
26
+ multi_line_output = 3
27
+ line_length = 88
@@ -0,0 +1,5 @@
1
+ """Kirby client for provider-based API calls."""
2
+
3
+ from kirby.client import Kirby
4
+
5
+ __all__ = ["Kirby"]
@@ -0,0 +1,165 @@
1
+ """Kirby client for provider-based API calls."""
2
+
3
+ import logging
4
+ from typing import Any, Optional
5
+
6
+ import requests
7
+ from minijinja import Environment
8
+ from openai import OpenAI
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class Kirby:
14
+ """Client for making provider-based API calls."""
15
+
16
+ def __init__(
17
+ self,
18
+ provider: str,
19
+ key: str,
20
+ model: str,
21
+ kirby_url: Optional[str] = None,
22
+ kirby_user_id: Optional[str] = None,
23
+ ):
24
+ """Initialize the Kirby client.
25
+
26
+ Args:
27
+ provider: The provider name (e.g., 'openAI')
28
+ key: The API key for the provider
29
+ model: The model name to use
30
+ kirby_url: The base URL for the Kirby API (can also be set via KIRBY_URL env var)
31
+ kirby_user_id: The user ID for Kirby API (can also be set via KIRBY_USER_ID env var)
32
+ """
33
+ self.provider = provider
34
+ self.key = key
35
+ self.model = model
36
+ self.kirby_url = kirby_url
37
+ self.kirby_user_id = kirby_user_id
38
+
39
+ def call(self, method_name: str, **kwargs: Any) -> Any:
40
+ """Call a method with the given arguments.
41
+
42
+ Args:
43
+ method_name: The name of the method to call
44
+ **kwargs: Keyword arguments to pass to the method
45
+
46
+ Returns:
47
+ The result of the OpenAI API call
48
+
49
+ Raises:
50
+ ValueError: If KIRBY_URL or KIRBY_USER_ID is not set, or if no prompt is found
51
+ """
52
+ import os
53
+
54
+ # Get kirby_url and kirby_user_id from instance or environment
55
+ kirby_url = self.kirby_url or os.getenv("KIRBY_URL")
56
+ kirby_user_id = self.kirby_user_id or os.getenv("KIRBY_USER_ID")
57
+
58
+ if not kirby_url:
59
+ raise ValueError(
60
+ "KIRBY_URL must be provided or set as environment variable"
61
+ )
62
+ if not kirby_user_id:
63
+ raise ValueError(
64
+ "KIRBY_USER_ID must be provided or set as environment variable"
65
+ )
66
+
67
+ # Call find_or_create endpoint
68
+ url = f"{kirby_url}/api/functions/find_or_create"
69
+ payload = {
70
+ "userId": kirby_user_id,
71
+ "name": method_name,
72
+ "variables": kwargs,
73
+ }
74
+
75
+ try:
76
+ response = requests.post(
77
+ url,
78
+ json=payload,
79
+ headers={"Content-Type": "application/json"},
80
+ timeout=30,
81
+ )
82
+ response.raise_for_status()
83
+ function_result = response.json()
84
+ except requests.exceptions.RequestException as e:
85
+ logger.error(f"Error calling Kirby endpoint: {e}")
86
+ if hasattr(e, "response") and e.response is not None:
87
+ logger.error(f"Response status: {e.response.status_code}")
88
+ logger.error(f"Response body: {e.response.text}")
89
+ raise
90
+
91
+ # Check if we have a version prompt
92
+ version_prompt = function_result.get("versionPrompt")
93
+ function_id = function_result.get("id")
94
+
95
+ if not version_prompt:
96
+ # Build the function URL
97
+ function_url = f"{kirby_url}/functions/{function_id}" if function_id else kirby_url
98
+ raise ValueError(
99
+ f"No prompt found for this function. Please add a prompt at: {function_url}"
100
+ )
101
+
102
+ # Get variables from the function result metadata and merge with kwargs
103
+ variables = function_result.get("metadata", {}).get("initialVariables", {})
104
+ # Merge with kwargs passed to the function
105
+ variables.update(kwargs)
106
+
107
+ # Template the prompt using minijinja
108
+ env = Environment()
109
+ templated_prompt = env.render_str(version_prompt, **variables)
110
+ logger.info(f"Templated prompt: {templated_prompt}")
111
+
112
+ # Call OpenAI with the templated prompt
113
+ openai_client = OpenAI(api_key=self.key)
114
+ completion = openai_client.chat.completions.create(
115
+ model=self.model,
116
+ messages=[{"role": "user", "content": templated_prompt}],
117
+ )
118
+
119
+ result = completion.choices[0].message.content
120
+
121
+ # Create trace with the result
122
+ function_version_id = function_result.get("versionId")
123
+ if function_version_id:
124
+ trace_url = f"{kirby_url}/api/functions/{function_id}/traces"
125
+ trace_payload = {
126
+ "functionVersionId": function_version_id,
127
+ "result": result,
128
+ "variables": variables,
129
+ "provider": self.provider.lower(),
130
+ "model": completion.model,
131
+ "usage": (
132
+ completion.usage.model_dump()
133
+ if hasattr(completion.usage, "model_dump")
134
+ else (
135
+ {
136
+ "prompt_tokens": getattr(
137
+ completion.usage, "prompt_tokens", 0
138
+ ),
139
+ "completion_tokens": getattr(
140
+ completion.usage, "completion_tokens", 0
141
+ ),
142
+ "total_tokens": getattr(
143
+ completion.usage, "total_tokens", 0
144
+ ),
145
+ }
146
+ if completion.usage
147
+ else {}
148
+ )
149
+ ),
150
+ }
151
+
152
+ try:
153
+ trace_response = requests.post(
154
+ trace_url,
155
+ json=trace_payload,
156
+ headers={"Content-Type": "application/json"},
157
+ timeout=30,
158
+ )
159
+ trace_response.raise_for_status()
160
+ logger.info(f"Trace created: {trace_response.json().get('id')}")
161
+ except requests.exceptions.RequestException as e:
162
+ logger.warning(f"Failed to create trace: {e}")
163
+ # Don't fail the whole function call if trace creation fails
164
+
165
+ return result