freeplay 0.3.8__py3-none-any.whl → 0.3.10__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.
@@ -1,21 +1,21 @@
1
1
  import copy
2
2
  import json
3
+ import logging
3
4
  from abc import ABC, abstractmethod
4
5
  from dataclasses import asdict, dataclass
5
- import logging
6
6
  from pathlib import Path
7
- from typing import Dict, Optional, List, cast, Any, Union, overload
8
- from anthropic.types.message import Message as AnthropicMessage
9
- from openai.types.chat import ChatCompletionMessage as OpenAIMessage
7
+ from typing import Dict, Optional, List, cast, Any, Union, runtime_checkable, Protocol
10
8
 
11
9
  from freeplay.errors import FreeplayConfigurationError, FreeplayClientError, log_freeplay_client_warning
12
10
  from freeplay.llm_parameters import LLMParameters
13
11
  from freeplay.model import InputVariables
14
12
  from freeplay.support import CallSupport, ToolSchema
15
13
  from freeplay.support import PromptTemplate, PromptTemplates, PromptTemplateMetadata
16
- from freeplay.utils import bind_template_variables
14
+ from freeplay.utils import bind_template_variables, convert_provider_message_to_dict
17
15
 
18
16
  logger = logging.getLogger(__name__)
17
+
18
+
19
19
  class MissingFlavorError(FreeplayConfigurationError):
20
20
  def __init__(self, flavor_name: str):
21
21
  super().__init__(
@@ -23,6 +23,7 @@ class MissingFlavorError(FreeplayConfigurationError):
23
23
  'a different model in the Freeplay UI.'
24
24
  )
25
25
 
26
+
26
27
  class UnsupportedToolSchemaError(FreeplayConfigurationError):
27
28
  def __init__(self) -> None:
28
29
  super().__init__(
@@ -30,6 +31,20 @@ class UnsupportedToolSchemaError(FreeplayConfigurationError):
30
31
  )
31
32
 
32
33
 
34
+ # Models ==
35
+
36
+ # A content block a la OpenAI or Anthropic. Intentionally over-permissive to allow schema evolution by the providers.
37
+ @runtime_checkable
38
+ class ContentBlock(Protocol):
39
+ def model_dump(self) -> Dict[str, Any]:
40
+ pass
41
+
42
+
43
+ # A content/role pair with a type-safe content for common provider recording. If not using a common provider,
44
+ # use {'content': str, 'role': str} to record. If using a common provider, this is usually the `.content` field.
45
+ GenericProviderMessage = Union[Dict[str, Any], ContentBlock]
46
+
47
+
33
48
  # SDK-Exposed Classes
34
49
  @dataclass
35
50
  class PromptInfo:
@@ -54,9 +69,11 @@ class FormattedPrompt:
54
69
  formatted_prompt_text: Optional[str] = None,
55
70
  tool_schema: Optional[List[Dict[str, Any]]] = None
56
71
  ):
72
+ # These two definitions allow us to operate on typed fields unitl we expose them as Any for client use.
73
+ self._llm_prompt = formatted_prompt
74
+ self._tool_schema = tool_schema
75
+
57
76
  self.prompt_info = prompt_info
58
- self.llm_prompt = formatted_prompt
59
- self.tool_schema = tool_schema
60
77
  if formatted_prompt_text:
61
78
  self.llm_prompt_text = formatted_prompt_text
62
79
 
@@ -67,14 +84,22 @@ class FormattedPrompt:
67
84
  # Note: messages are **not formatted** for the provider.
68
85
  self.messages = messages
69
86
 
87
+ @property
88
+ # We know this is a list of dict[str,str], but we use Any to avoid typing issues with client SDK libraries, which require strict TypedDict.
89
+ def llm_prompt(self) -> Any:
90
+ return self._llm_prompt
91
+
92
+ @property
93
+ def tool_schema(self) -> Any:
94
+ return self._tool_schema
95
+
70
96
  def all_messages(
71
97
  self,
72
- new_message: Union[Dict[str, str], AnthropicMessage, OpenAIMessage]
98
+ new_message: GenericProviderMessage
73
99
  ) -> List[Dict[str, Any]]:
74
- if isinstance(new_message, AnthropicMessage) or isinstance(new_message, OpenAIMessage):
75
- return self.messages + [new_message.to_dict()]
76
- elif isinstance(new_message, dict):
77
- return self.messages + [new_message]
100
+ converted_message = convert_provider_message_to_dict(new_message)
101
+ return self.messages + [converted_message]
102
+
78
103
 
79
104
  class BoundPrompt:
80
105
  def __init__(
@@ -128,7 +153,7 @@ class BoundPrompt:
128
153
  return formatted
129
154
 
130
155
  raise MissingFlavorError(flavor_name)
131
-
156
+
132
157
  @staticmethod
133
158
  def __format_tool_schema(flavor_name: str, tool_schema: List[ToolSchema]) -> List[Dict[str, Any]]:
134
159
  if flavor_name == 'anthropic_chat':
@@ -144,9 +169,8 @@ class BoundPrompt:
144
169
  'type': 'function'
145
170
  } for tool_schema in tool_schema
146
171
  ]
147
-
148
- raise UnsupportedToolSchemaError()
149
172
 
173
+ raise UnsupportedToolSchemaError()
150
174
 
151
175
  def format(
152
176
  self,
@@ -156,7 +180,7 @@ class BoundPrompt:
156
180
  formatted_prompt = BoundPrompt.__format_messages_for_flavor(final_flavor, self.messages)
157
181
 
158
182
  formatted_tool_schema = BoundPrompt.__format_tool_schema(
159
- final_flavor,
183
+ final_flavor,
160
184
  self.tool_schema
161
185
  ) if self.tool_schema else None
162
186
 
@@ -9,7 +9,7 @@ from freeplay import api_support
9
9
  from freeplay.errors import FreeplayClientError, FreeplayError
10
10
  from freeplay.llm_parameters import LLMParameters
11
11
  from freeplay.model import InputVariables, OpenAIFunctionCall
12
- from freeplay.resources.prompts import FormattedPrompt, PromptInfo
12
+ from freeplay.resources.prompts import PromptInfo
13
13
  from freeplay.resources.sessions import SessionInfo, TraceInfo
14
14
  from freeplay.support import CallSupport
15
15
 
@@ -53,7 +53,7 @@ class TestRunInfo:
53
53
 
54
54
  @dataclass
55
55
  class RecordPayload:
56
- all_messages: List[Dict[str, str]]
56
+ all_messages: List[Dict[str, Any]]
57
57
  inputs: InputVariables
58
58
 
59
59
  session_info: SessionInfo
@@ -37,6 +37,7 @@ class TestRun:
37
37
  def get_test_run_info(self, test_case_id: str) -> TestRunInfo:
38
38
  return TestRunInfo(self.test_run_id, test_case_id)
39
39
 
40
+
40
41
  @dataclass
41
42
  class TestRunResults:
42
43
  def __init__(
@@ -62,9 +63,11 @@ class TestRuns:
62
63
  testlist: str,
63
64
  include_outputs: bool = False,
64
65
  name: Optional[str] = None,
65
- description: Optional[str] = None
66
+ description: Optional[str] = None,
67
+ flavor_name: Optional[str] = None
66
68
  ) -> TestRun:
67
- test_run = self.call_support.create_test_run(project_id, testlist, include_outputs, name, description)
69
+ test_run = self.call_support.create_test_run(
70
+ project_id, testlist, include_outputs, name, description, flavor_name)
68
71
  test_cases = [
69
72
  TestCase(test_case_id=test_case.id,
70
73
  variables=test_case.variables,
freeplay/support.py CHANGED
@@ -55,7 +55,7 @@ class TestCaseTestRunResponse:
55
55
  self.variables: InputVariables = test_case['variables']
56
56
  self.id: str = test_case['test_case_id']
57
57
  self.output: Optional[str] = test_case.get('output')
58
- self.history: Optional[List[Dict[str, str]]] = test_case.get('history')
58
+ self.history: Optional[List[Dict[str, Any]]] = test_case.get('history')
59
59
 
60
60
 
61
61
  class TestRunResponse:
@@ -190,7 +190,8 @@ class CallSupport:
190
190
  testlist: str,
191
191
  include_outputs: bool = False,
192
192
  name: Optional[str] = None,
193
- description: Optional[str] = None
193
+ description: Optional[str] = None,
194
+ flavor_name: Optional[str] = None
194
195
  ) -> TestRunResponse:
195
196
  response = api_support.post_raw(
196
197
  api_key=self.freeplay_api_key,
@@ -199,7 +200,8 @@ class CallSupport:
199
200
  'dataset_name': testlist,
200
201
  'include_outputs': include_outputs,
201
202
  'test_run_name': name,
202
- 'test_run_description': description
203
+ 'test_run_description': description,
204
+ 'flavor_name': flavor_name
203
205
  },
204
206
  )
205
207
 
freeplay/utils.py CHANGED
@@ -1,7 +1,7 @@
1
- import json
2
- from typing import Dict, Union, Optional, Any
3
1
  import importlib.metadata
2
+ import json
4
3
  import platform
4
+ from typing import Dict, Union, Optional, Any
5
5
 
6
6
  import pystache # type: ignore
7
7
 
@@ -70,3 +70,19 @@ def get_user_agent() -> str:
70
70
  # Output format
71
71
  # Freeplay/0.2.30 (Python/3.11.4; Darwin/23.2.0)
72
72
  return f"{sdk_name}/{sdk_version} ({language}/{language_version}; {os_name}/{os_version})"
73
+
74
+
75
+ # Recursively convert Pydantic models, lists, and dicts to dict compatible format -- used to allow us to accept
76
+ # provider message shapes (usually generated types) or the default {'content': ..., 'role': ...} shape.
77
+ def convert_provider_message_to_dict(obj: Any) -> Any:
78
+ if hasattr(obj, 'model_dump'):
79
+ # Pydantic v2
80
+ return obj.model_dump(mode='json')
81
+ elif hasattr(obj, 'dict'):
82
+ # Pydantic v1
83
+ return obj.dict(encode_json=True)
84
+ elif isinstance(obj, dict):
85
+ return {k: convert_provider_message_to_dict(v) for k, v in obj.items()}
86
+ elif isinstance(obj, list):
87
+ return [convert_provider_message_to_dict(item) for item in obj]
88
+ return obj
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: freeplay
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: FreePlay Engineering
@@ -8,14 +8,14 @@ freeplay/model.py,sha256=bh3TmINOxvKFxeVO8Uz7ybX28eD1tmO0XLewwLOtS7I,436
8
8
  freeplay/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  freeplay/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  freeplay/resources/customer_feedback.py,sha256=bw8MfEOKbGgn4FOyvcADrcs9GhcpNXNTgxKjBjIzywE,899
11
- freeplay/resources/prompts.py,sha256=g0TPjHdr1A83Vkn-PUrTk0UN2sN9eRq5KBvbLtKKyIs,20880
12
- freeplay/resources/recordings.py,sha256=28P77nVqPObICUeWFJKY4nTOAqNC6VBmR5_CmSbCRNc,6065
11
+ freeplay/resources/prompts.py,sha256=woSQcJ6QKpiVyhnrp-t9vx6UND6bxDtWjdbMHO3o9sk,21581
12
+ freeplay/resources/recordings.py,sha256=nECoZb159POpOm-pZnJuFrmvFFWSrea665I5YXEYMFY,6048
13
13
  freeplay/resources/sessions.py,sha256=Qz5v7VOf1DmQTd1wCOFXnrizlW5WFJT5V8-pq22Ifvg,2793
14
- freeplay/resources/test_runs.py,sha256=qF4CE4XiX_6epcs5bFKJg73C94YgrJQTxnCJLBERkos,2549
15
- freeplay/support.py,sha256=57n-Rv3yRBh3Zpg67uIkXZDLtFtk_bGj91IeheqjoDM,8595
16
- freeplay/utils.py,sha256=roDF_4QYKt93XO_hRdQC_mFAORVuqUWgCVAPXTj_vsU,2488
17
- freeplay-0.3.8.dist-info/LICENSE,sha256=_jzIw45hB1XHGxiQ8leZ0GH_X7bR_a8qgxaqnHbCUOo,1064
18
- freeplay-0.3.8.dist-info/METADATA,sha256=oBCXIMM2M-38pzGPblMCglPrmG-0Wro6BE1NC2fxVMQ,1653
19
- freeplay-0.3.8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
20
- freeplay-0.3.8.dist-info/entry_points.txt,sha256=32s3rf2UUCqiJT4jnClEXZhdXlvl30uwpcxz-Gsy4UU,54
21
- freeplay-0.3.8.dist-info/RECORD,,
14
+ freeplay/resources/test_runs.py,sha256=Tp2N-odInT5XEEWrEsVhdgfnsclOE8n92_C8gTwO2MI,2623
15
+ freeplay/support.py,sha256=RgC-EDMdxKu7iQEHQ16gxt9VGmjHLUbaKi_k0U5YR1I,8686
16
+ freeplay/utils.py,sha256=Xvt4mNLXLL7E6MI2hTuDLV5cl5Y83DgdjCZSyDGMjR0,3187
17
+ freeplay-0.3.10.dist-info/LICENSE,sha256=_jzIw45hB1XHGxiQ8leZ0GH_X7bR_a8qgxaqnHbCUOo,1064
18
+ freeplay-0.3.10.dist-info/METADATA,sha256=hxD6Q6S1B0KN52AxYHanuWOOog_foMudTjIQurIvrQk,1654
19
+ freeplay-0.3.10.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
20
+ freeplay-0.3.10.dist-info/entry_points.txt,sha256=32s3rf2UUCqiJT4jnClEXZhdXlvl30uwpcxz-Gsy4UU,54
21
+ freeplay-0.3.10.dist-info/RECORD,,