freeplay 0.2.37__py3-none-any.whl → 0.2.38__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.
freeplay/flavors.py CHANGED
@@ -28,7 +28,7 @@ class Flavor(ABC):
28
28
  return AnthropicClaudeChat()
29
29
  else:
30
30
  raise FreeplayConfigurationError(
31
- 'Configured flavor not found in SDK. Please update your SDK version or configure '
31
+ f'Configured flavor ({flavor_name}) not found in SDK. Please update your SDK version or configure '
32
32
  'a different model in the Freeplay UI.')
33
33
 
34
34
  @property
freeplay/support.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import time
3
3
  from copy import copy
4
+ from dataclasses import dataclass
4
5
  from typing import Dict, Any, Optional, Union, List, Generator
5
6
  from uuid import uuid4
6
7
 
@@ -37,6 +38,25 @@ class TestRunResponse:
37
38
  self.test_run_id = test_run_id
38
39
 
39
40
 
41
+ @dataclass
42
+ class PromptTemplateMetadata:
43
+ provider: Optional[str]
44
+ flavor: Optional[str]
45
+ model: Optional[str]
46
+ params: Optional[Dict[str, Any]] = None
47
+ provider_info: Optional[Dict[str, Any]] = None
48
+
49
+
50
+ @dataclass
51
+ class PromptTemplate:
52
+ prompt_template_id: str
53
+ prompt_template_version_id: str
54
+ prompt_template_name: str
55
+ content: List[Dict[str, str]]
56
+ metadata: PromptTemplateMetadata
57
+ format_version: int
58
+
59
+
40
60
  class CallSupport:
41
61
  def __init__(
42
62
  self,
@@ -57,10 +77,12 @@ class CallSupport:
57
77
  raise FreeplayConfigurationError(f'Could not find template with name "{template_name}"')
58
78
  return templates[0]
59
79
 
60
- def create_session_id(self) -> str:
80
+ @staticmethod
81
+ def create_session_id() -> str:
61
82
  return str(uuid4())
62
83
 
63
- def check_all_values_string_or_number(self, metadata: Optional[Dict[str, Union[str, int, float]]]) -> None:
84
+ @staticmethod
85
+ def check_all_values_string_or_number(metadata: Optional[Dict[str, Union[str, int, float]]]) -> None:
64
86
  if metadata:
65
87
  for key, value in metadata.items():
66
88
  if not isinstance(value, (str, int, float)):
@@ -79,9 +101,27 @@ class CallSupport:
79
101
  if response.status_code != 201:
80
102
  raise freeplay_response_error("Error updating customer feedback", response)
81
103
 
82
- def get_prompt(self, project_id: str, template_name: str, environment: str) -> PromptTemplateWithMetadata:
83
- prompt_templates = self.get_prompts(project_id, environment)
84
- return self.find_template_by_name(prompt_templates, template_name)
104
+ def get_prompt(self, project_id: str, template_name: str, environment: str) -> PromptTemplate:
105
+ response = api_support.get_raw(
106
+ api_key=self.freeplay_api_key,
107
+ url=f'{self.api_base}/v2/projects/{project_id}/prompt-templates/name/{template_name}'
108
+ )
109
+
110
+ if response.status_code != 200:
111
+ raise freeplay_response_error(
112
+ f"Error getting prompt template {template_name} in project {project_id} "
113
+ f"and environment {environment}",
114
+ response
115
+ )
116
+
117
+ maybe_prompt = try_decode(PromptTemplate, response.content)
118
+ if maybe_prompt is None:
119
+ raise FreeplayServerError(
120
+ f"Error handling prompt {template_name} in project {project_id} "
121
+ f"and environment {environment}"
122
+ )
123
+
124
+ return maybe_prompt
85
125
 
86
126
  def get_prompts(self, project_id: str, tag: str) -> PromptTemplates:
87
127
  response = api_support.get_raw(
@@ -20,8 +20,11 @@ class Freeplay:
20
20
  if not freeplay_api_key or not freeplay_api_key.strip():
21
21
  raise FreeplayConfigurationError("Freeplay API key not set. It must be set to the Freeplay API.")
22
22
 
23
- self.call_support = CallSupport(freeplay_api_key, api_base,
24
- DefaultRecordProcessor(freeplay_api_key, api_base))
23
+ self.call_support = CallSupport(
24
+ freeplay_api_key,
25
+ api_base,
26
+ DefaultRecordProcessor(freeplay_api_key, api_base)
27
+ )
25
28
  self.freeplay_api_key = freeplay_api_key
26
29
  self.api_base = api_base
27
30
 
@@ -2,17 +2,18 @@ import json
2
2
  from abc import ABC, abstractmethod
3
3
  from dataclasses import dataclass
4
4
  from pathlib import Path
5
- from typing import Dict, Optional, List, Union, cast
5
+ from typing import Dict, Optional, List, Union, cast, Any
6
6
 
7
7
  from freeplay.completions import PromptTemplates, ChatMessage, PromptTemplateWithMetadata
8
- from freeplay.errors import FreeplayConfigurationError
8
+ from freeplay.errors import FreeplayConfigurationError, FreeplayClientError
9
9
  from freeplay.flavors import Flavor
10
10
  from freeplay.llm_parameters import LLMParameters
11
11
  from freeplay.model import InputVariables
12
- from freeplay.support import CallSupport
12
+ from freeplay.support import CallSupport, PromptTemplate, PromptTemplateMetadata
13
13
  from freeplay.utils import bind_template_variables
14
14
 
15
15
 
16
+ # SDK-Exposed Classes
16
17
  @dataclass
17
18
  class PromptInfo:
18
19
  prompt_template_id: str
@@ -20,6 +21,7 @@ class PromptInfo:
20
21
  template_name: str
21
22
  environment: str
22
23
  model_parameters: LLMParameters
24
+ provider_info: Optional[Dict[str, Any]]
23
25
  provider: str
24
26
  model: str
25
27
  flavor_name: str
@@ -89,8 +91,21 @@ class TemplateResolver(ABC):
89
91
  def get_prompts(self, project_id: str, environment: str) -> PromptTemplates:
90
92
  pass
91
93
 
94
+ @abstractmethod
95
+ def get_prompt(self, project_id: str, template_name: str, environment: str) -> PromptTemplate:
96
+ pass
97
+
92
98
 
93
99
  class FilesystemTemplateResolver(TemplateResolver):
100
+ # If you think you need a change here, be sure to check the server as the translations must match. Once we have
101
+ # all the SDKs and all customers on the new common format, this translation can go away.
102
+ __role_translations = {
103
+ 'system': 'system',
104
+ 'user': 'user',
105
+ 'assistant': 'assistant',
106
+ 'Assistant': 'assistant',
107
+ 'Human': 'user' # Don't think we ever store this, but in case...
108
+ }
94
109
 
95
110
  def __init__(self, freeplay_directory: Path):
96
111
  FilesystemTemplateResolver.__validate_freeplay_directory(freeplay_directory)
@@ -117,6 +132,52 @@ class FilesystemTemplateResolver(TemplateResolver):
117
132
 
118
133
  return PromptTemplates(prompt_list)
119
134
 
135
+ def get_prompt(self, project_id: str, template_name: str, environment: str) -> PromptTemplate:
136
+ self.__validate_prompt_directory(project_id, environment)
137
+
138
+ expected_file: Path = self.prompts_directory / project_id / environment / f"{template_name}.json"
139
+
140
+ if not expected_file.exists():
141
+ raise FreeplayClientError(
142
+ f"Could not find prompt with name {template_name} for project "
143
+ f"{project_id} in environment {environment}"
144
+ )
145
+
146
+ json_dom = json.loads(expected_file.read_text())
147
+
148
+ format_version = json_dom.get('format_version')
149
+
150
+ if format_version == 2:
151
+ raise NotImplementedError("Cannot yet handle new format bundled prompts")
152
+
153
+ flavor_name = json_dom.get('metadata').get('flavor_name')
154
+ flavor = Flavor.get_by_name(flavor_name)
155
+
156
+ params = json_dom.get('metadata').get('params')
157
+ model = params.pop('model') if 'model' in params else None
158
+
159
+ return PromptTemplate(
160
+ format_version=2,
161
+ prompt_template_id=json_dom.get('prompt_template_id'),
162
+ prompt_template_version_id=json_dom.get('prompt_template_version_id'),
163
+ prompt_template_name=json_dom.get('name'),
164
+ content=FilesystemTemplateResolver.__normalize_roles(json.loads(json_dom.get('content'))), # type: ignore
165
+ metadata=PromptTemplateMetadata(
166
+ provider=flavor.provider,
167
+ flavor=flavor_name,
168
+ model=model,
169
+ params=params
170
+ )
171
+ )
172
+
173
+ @staticmethod
174
+ def __normalize_roles(messages: List[ChatMessage]) -> List[ChatMessage]:
175
+ normalized = []
176
+ for message in messages:
177
+ role = FilesystemTemplateResolver.__role_translations.get(message['role']) or message['role']
178
+ normalized.append(ChatMessage(role=role, content=message['content']))
179
+ return normalized
180
+
120
181
  @staticmethod
121
182
  def __validate_freeplay_directory(freeplay_directory: Path) -> None:
122
183
  if not freeplay_directory.is_dir():
@@ -151,6 +212,13 @@ class APITemplateResolver(TemplateResolver):
151
212
  tag=environment
152
213
  )
153
214
 
215
+ def get_prompt(self, project_id: str, template_name: str, environment: str) -> PromptTemplate:
216
+ return self.call_support.get_prompt(
217
+ project_id=project_id,
218
+ template_name=template_name,
219
+ environment=environment
220
+ )
221
+
154
222
 
155
223
  class Prompts:
156
224
  def __init__(self, call_support: CallSupport, template_resolver: TemplateResolver) -> None:
@@ -161,32 +229,34 @@ class Prompts:
161
229
  return self.call_support.get_prompts(project_id=project_id, tag=environment)
162
230
 
163
231
  def get(self, project_id: str, template_name: str, environment: str) -> TemplatePrompt:
164
- prompt_templates = self.template_resolver.get_prompts(project_id, environment)
165
- prompt_template = self.call_support.find_template_by_name(prompt_templates, template_name)
232
+ prompt = self.template_resolver.get_prompt(project_id, template_name, environment)
166
233
 
167
- messages = json.loads(prompt_template.content)
234
+ params = prompt.metadata.params
235
+ model = prompt.metadata.model
168
236
 
169
- params = prompt_template.get_params()
170
- model = params.pop('model')
237
+ if not model:
238
+ raise FreeplayConfigurationError(
239
+ "Model must be configured in the Freeplay UI. Unable to fulfill request.")
171
240
 
172
- if not prompt_template.flavor_name:
241
+ if not prompt.metadata.flavor:
173
242
  raise FreeplayConfigurationError(
174
243
  "Flavor must be configured in the Freeplay UI. Unable to fulfill request.")
175
244
 
176
- flavor = Flavor.get_by_name(prompt_template.flavor_name)
245
+ flavor = Flavor.get_by_name(prompt.metadata.flavor)
177
246
 
178
247
  prompt_info = PromptInfo(
179
- prompt_template_id=prompt_template.prompt_template_id,
180
- prompt_template_version_id=prompt_template.prompt_template_version_id,
181
- template_name=prompt_template.name,
248
+ prompt_template_id=prompt.prompt_template_id,
249
+ prompt_template_version_id=prompt.prompt_template_version_id,
250
+ template_name=prompt.prompt_template_name,
182
251
  environment=environment,
183
- model_parameters=params,
252
+ model_parameters=cast(LLMParameters, params) or LLMParameters({}),
184
253
  provider=flavor.provider,
185
254
  model=model,
186
- flavor_name=prompt_template.flavor_name
255
+ flavor_name=prompt.metadata.flavor,
256
+ provider_info=prompt.metadata.provider_info
187
257
  )
188
258
 
189
- return TemplatePrompt(prompt_info, messages)
259
+ return TemplatePrompt(prompt_info, prompt.content)
190
260
 
191
261
  def get_formatted(
192
262
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: freeplay
3
- Version: 0.2.37
3
+ Version: 0.2.38
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: FreePlay Engineering
@@ -2,7 +2,7 @@ freeplay/__init__.py,sha256=74A9S9hmLq9BNHsdx0-37yDxlSukudNl9bJ0TE60Z30,61
2
2
  freeplay/api_support.py,sha256=u8b1e9prlR4_qOcqmFduU7U35ubQt1tWJ6bXhUdsv0c,2240
3
3
  freeplay/completions.py,sha256=F1rMBtQaCtn0rBQqvCurkV25g8gLtwnEod5rRvf-txY,1176
4
4
  freeplay/errors.py,sha256=bPqsw32YX-xSr7O-G49M0sSFF7mq-YF1WGq928UV47s,631
5
- freeplay/flavors.py,sha256=IytHc1C1q0ZTJn9T6Mt6oJn6eeTqQBc-9habH7p-T80,17242
5
+ freeplay/flavors.py,sha256=9jVvhym9L5zyWM3RTBViibIgG6f6Fh1_63uMEVZAFT4,17259
6
6
  freeplay/freeplay.py,sha256=BLlr4YXh3a624xcM93KvZGsiIGSMrIMFM07lLyVCIao,17089
7
7
  freeplay/freeplay_cli.py,sha256=k9QPBedcFqpy9Ec9k7DT_y-NpVxHqRF5jKPTaQBBs1Q,3863
8
8
  freeplay/llm_parameters.py,sha256=bQbfuC8EICF0XMZQa5pwI3FkQqxmCUVqHO3gYHy3Tg8,898
@@ -10,18 +10,18 @@ freeplay/model.py,sha256=7BRAuyxsLl8X-ue8wXPmAiKYp4OaYI-HkaGc3t5qtMU,534
10
10
  freeplay/provider_config.py,sha256=hruf3Khusrwb76_-hv7ouuxmvJuaRyC1UxIw7XlJx8A,1416
11
11
  freeplay/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  freeplay/record.py,sha256=69vO9vYGFL8gLrvRqLzlvodVNo05GOzKDJnCyhUha08,3615
13
- freeplay/support.py,sha256=KP_rRgn0gh6n5BOGGExHO9pip9Pj22_WSvF6v106kX8,13083
13
+ freeplay/support.py,sha256=tipac902gW1TRICUgxGhO4X645XRglxHHmaqYvqfifo,14197
14
14
  freeplay/thin/__init__.py,sha256=XgU_eMmTpOPCa9w5mVOyxPgwAgRFfVdRWDazYGYZtQ0,351
15
- freeplay/thin/freeplay_thin.py,sha256=raVESB8VWoCSeCsGPDr-DOx37WXi8D27odKnOeDWwV0,1613
15
+ freeplay/thin/freeplay_thin.py,sha256=0w_ECjassczsHJUcpzPxEHUDbiGo7Ey7Ba7vIaWc5ds,1619
16
16
  freeplay/thin/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  freeplay/thin/resources/customer_feedback.py,sha256=aTM7Eez7iYmjXSpRqkHxf4pi6xBrzVnMiQCEJVfPGvg,527
18
- freeplay/thin/resources/prompts.py,sha256=CRsvrIIe59WHTAA1PFvfNOK2x9S-C9zCz_z8XzsIg4w,7142
18
+ freeplay/thin/resources/prompts.py,sha256=3sfH1k1AAXRfgHxfV6Acws0fAiMswgE93auRKWLGVIQ,10135
19
19
  freeplay/thin/resources/recordings.py,sha256=utrGTjPPiNt1btI0Hiti9s7VvCYFWqitAx35zrXrOfg,5047
20
20
  freeplay/thin/resources/sessions.py,sha256=ioWdeTM9BSrEWKrFH66ysQIw5kCTlCVopJDmWtFgHz0,868
21
21
  freeplay/thin/resources/test_runs.py,sha256=L8A1tQHzYg9UmLgbNFtFa8pGXqyrcoyTtSE0sSnd9UE,1267
22
22
  freeplay/utils.py,sha256=cRCCIzVqWNDKlTI-DDhXGyCkplbd-X4qzDs__aUpvww,1840
23
- freeplay-0.2.37.dist-info/LICENSE,sha256=_jzIw45hB1XHGxiQ8leZ0GH_X7bR_a8qgxaqnHbCUOo,1064
24
- freeplay-0.2.37.dist-info/METADATA,sha256=0QabpMND4wK9yg-3bGxZZJzMKVTikH0v1iLf0HOgyiE,1676
25
- freeplay-0.2.37.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
26
- freeplay-0.2.37.dist-info/entry_points.txt,sha256=32s3rf2UUCqiJT4jnClEXZhdXlvl30uwpcxz-Gsy4UU,54
27
- freeplay-0.2.37.dist-info/RECORD,,
23
+ freeplay-0.2.38.dist-info/LICENSE,sha256=_jzIw45hB1XHGxiQ8leZ0GH_X7bR_a8qgxaqnHbCUOo,1064
24
+ freeplay-0.2.38.dist-info/METADATA,sha256=N9DOEBhiTSyDMCBm9MYBK7XserJj0JxlPhYN96NhrnI,1676
25
+ freeplay-0.2.38.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
26
+ freeplay-0.2.38.dist-info/entry_points.txt,sha256=32s3rf2UUCqiJT4jnClEXZhdXlvl30uwpcxz-Gsy4UU,54
27
+ freeplay-0.2.38.dist-info/RECORD,,