freeplay 0.3.7__py3-none-any.whl → 0.3.9__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,18 +1,19 @@
1
1
  import copy
2
2
  import json
3
3
  from abc import ABC, abstractmethod
4
- from dataclasses import dataclass
4
+ from dataclasses import asdict, dataclass
5
+ import logging
5
6
  from pathlib import Path
6
- from typing import Dict, Optional, List, cast, Any, Union
7
+ from typing import Dict, Optional, List, Protocol, cast, Any, Union, runtime_checkable
7
8
 
8
9
  from freeplay.errors import FreeplayConfigurationError, FreeplayClientError, log_freeplay_client_warning
9
10
  from freeplay.llm_parameters import LLMParameters
10
11
  from freeplay.model import InputVariables
11
- from freeplay.support import CallSupport
12
+ from freeplay.support import CallSupport, ToolSchema
12
13
  from freeplay.support import PromptTemplate, PromptTemplates, PromptTemplateMetadata
13
14
  from freeplay.utils import bind_template_variables
14
15
 
15
-
16
+ logger = logging.getLogger(__name__)
16
17
  class MissingFlavorError(FreeplayConfigurationError):
17
18
  def __init__(self, flavor_name: str):
18
19
  super().__init__(
@@ -20,6 +21,12 @@ class MissingFlavorError(FreeplayConfigurationError):
20
21
  'a different model in the Freeplay UI.'
21
22
  )
22
23
 
24
+ class UnsupportedToolSchemaError(FreeplayConfigurationError):
25
+ def __init__(self) -> None:
26
+ super().__init__(
27
+ f'Tool schema not supported for this model and provider.'
28
+ )
29
+
23
30
 
24
31
  # SDK-Exposed Classes
25
32
  @dataclass
@@ -35,6 +42,13 @@ class PromptInfo:
35
42
  flavor_name: str
36
43
  project_id: str
37
44
 
45
+ # Client SDKs (Anthropic and OpenAI) have pydantic types for messages that require strict structures.
46
+ # We want to avoid taking a dependency on the client SDKs, so we instead just ensure they can be converted to a dict,
47
+ # this will ultimately be validated at runtime by the server for any incompatibility.
48
+ @runtime_checkable
49
+ class GenericProviderMessage(Protocol):
50
+ def to_dict(self) -> Dict[str, Any]:
51
+ pass
38
52
 
39
53
  class FormattedPrompt:
40
54
  def __init__(
@@ -42,10 +56,14 @@ class FormattedPrompt:
42
56
  prompt_info: PromptInfo,
43
57
  messages: List[Dict[str, str]],
44
58
  formatted_prompt: Optional[List[Dict[str, str]]] = None,
45
- formatted_prompt_text: Optional[str] = None
59
+ formatted_prompt_text: Optional[str] = None,
60
+ tool_schema: Optional[List[Dict[str, Any]]] = None
46
61
  ):
62
+ # These two definitions allow us to operate on typed fields unitl we expose them as Any for client use.
63
+ self._llm_prompt = formatted_prompt
64
+ self._tool_schema = tool_schema
65
+
47
66
  self.prompt_info = prompt_info
48
- self.llm_prompt = formatted_prompt
49
67
  if formatted_prompt_text:
50
68
  self.llm_prompt_text = formatted_prompt_text
51
69
 
@@ -56,21 +74,35 @@ class FormattedPrompt:
56
74
  # Note: messages are **not formatted** for the provider.
57
75
  self.messages = messages
58
76
 
77
+ @property
78
+ # 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.
79
+ def llm_prompt(self) -> Any:
80
+ return self._llm_prompt
81
+
82
+ @property
83
+ def tool_schema(self) -> Any:
84
+ return self._tool_schema
85
+
59
86
  def all_messages(
60
87
  self,
61
- new_message: Dict[str, str]
62
- ) -> List[Dict[str, str]]:
63
- return self.messages + [new_message]
64
-
88
+ new_message: Union[Dict[str, str], GenericProviderMessage]
89
+ ) -> List[Dict[str, Any]]:
90
+ # Check if it's a OpenAI or Anthropic message: if it's a provider message object (has to_dict method)
91
+ if isinstance(new_message, GenericProviderMessage):
92
+ return self.messages + [new_message.to_dict()]
93
+ elif isinstance(new_message, dict):
94
+ return self.messages + [new_message]
65
95
 
66
96
  class BoundPrompt:
67
97
  def __init__(
68
98
  self,
69
99
  prompt_info: PromptInfo,
70
- messages: List[Dict[str, str]]
100
+ messages: List[Dict[str, str]],
101
+ tool_schema: Optional[List[ToolSchema]] = None
71
102
  ):
72
103
  self.prompt_info = prompt_info
73
104
  self.messages = messages
105
+ self.tool_schema = tool_schema
74
106
 
75
107
  @staticmethod
76
108
  def __format_messages_for_flavor(
@@ -113,6 +145,25 @@ class BoundPrompt:
113
145
  return formatted
114
146
 
115
147
  raise MissingFlavorError(flavor_name)
148
+
149
+ @staticmethod
150
+ def __format_tool_schema(flavor_name: str, tool_schema: List[ToolSchema]) -> List[Dict[str, Any]]:
151
+ if flavor_name == 'anthropic_chat':
152
+ return [{
153
+ 'name': tool_schema.name,
154
+ 'description': tool_schema.description,
155
+ 'input_schema': tool_schema.parameters
156
+ } for tool_schema in tool_schema]
157
+ elif flavor_name in ['openai_chat', 'azure_openai_chat']:
158
+ return [
159
+ {
160
+ 'function': asdict(tool_schema),
161
+ 'type': 'function'
162
+ } for tool_schema in tool_schema
163
+ ]
164
+
165
+ raise UnsupportedToolSchemaError()
166
+
116
167
 
117
168
  def format(
118
169
  self,
@@ -121,17 +172,24 @@ class BoundPrompt:
121
172
  final_flavor = flavor_name or self.prompt_info.flavor_name
122
173
  formatted_prompt = BoundPrompt.__format_messages_for_flavor(final_flavor, self.messages)
123
174
 
175
+ formatted_tool_schema = BoundPrompt.__format_tool_schema(
176
+ final_flavor,
177
+ self.tool_schema
178
+ ) if self.tool_schema else None
179
+
124
180
  if isinstance(formatted_prompt, str):
125
181
  return FormattedPrompt(
126
182
  prompt_info=self.prompt_info,
127
183
  messages=self.messages,
128
- formatted_prompt_text=formatted_prompt
184
+ formatted_prompt_text=formatted_prompt,
185
+ tool_schema=formatted_tool_schema
129
186
  )
130
187
  else:
131
188
  return FormattedPrompt(
132
189
  prompt_info=self.prompt_info,
133
190
  messages=self.messages,
134
- formatted_prompt=formatted_prompt
191
+ formatted_prompt=formatted_prompt,
192
+ tool_schema=formatted_tool_schema
135
193
  )
136
194
 
137
195
 
@@ -139,9 +197,11 @@ class TemplatePrompt:
139
197
  def __init__(
140
198
  self,
141
199
  prompt_info: PromptInfo,
142
- messages: List[Dict[str, str]]
200
+ messages: List[Dict[str, str]],
201
+ tool_schema: Optional[List[ToolSchema]] = None
143
202
  ):
144
203
  self.prompt_info = prompt_info
204
+ self.tool_schema = tool_schema
145
205
  self.messages = messages
146
206
 
147
207
  def bind(self, variables: InputVariables, history: Optional[List[Dict[str, str]]] = None) -> BoundPrompt:
@@ -172,7 +232,7 @@ class TemplatePrompt:
172
232
  'content': bind_template_variables(msg['content'], variables)},
173
233
  )
174
234
 
175
- return BoundPrompt(self.prompt_info, bound_messages)
235
+ return BoundPrompt(self.prompt_info, bound_messages, self.tool_schema)
176
236
 
177
237
 
178
238
  class TemplateResolver(ABC):
@@ -410,7 +470,7 @@ class Prompts:
410
470
  project_id=prompt.project_id
411
471
  )
412
472
 
413
- return TemplatePrompt(prompt_info, prompt.content)
473
+ return TemplatePrompt(prompt_info, prompt.content, prompt.tool_schema)
414
474
 
415
475
  def get_by_version_id(self, project_id: str, template_id: str, version_id: str) -> TemplatePrompt:
416
476
  prompt = self.template_resolver.get_prompt_version_id(project_id, template_id, version_id)
@@ -443,7 +503,7 @@ class Prompts:
443
503
  project_id=prompt.project_id
444
504
  )
445
505
 
446
- return TemplatePrompt(prompt_info, prompt.content)
506
+ return TemplatePrompt(prompt_info, prompt.content, prompt.tool_schema)
447
507
 
448
508
  def get_formatted(
449
509
  self,
@@ -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 PromptInfo
12
+ from freeplay.resources.prompts import FormattedPrompt, PromptInfo
13
13
  from freeplay.resources.sessions import SessionInfo, TraceInfo
14
14
  from freeplay.support import CallSupport
15
15
 
@@ -59,6 +59,7 @@ class RecordPayload:
59
59
  session_info: SessionInfo
60
60
  prompt_info: PromptInfo
61
61
  call_info: CallInfo
62
+ tool_schema: Optional[List[Dict[str, Any]]] = None
62
63
  response_info: Optional[ResponseInfo] = None
63
64
  test_run_info: Optional[TestRunInfo] = None
64
65
  eval_results: Optional[Dict[str, Union[bool, float]]] = None
@@ -82,6 +83,7 @@ class Recordings:
82
83
  record_api_payload = {
83
84
  "messages": record_payload.all_messages,
84
85
  "inputs": record_payload.inputs,
86
+ "tool_schema": record_payload.tool_schema,
85
87
  "session_info": {"custom_metadata": record_payload.session_info.custom_metadata},
86
88
  "prompt_info": {
87
89
  "environment": record_payload.prompt_info.environment,
freeplay/support.py CHANGED
@@ -16,6 +16,11 @@ class PromptTemplateMetadata:
16
16
  params: Optional[Dict[str, Any]] = None
17
17
  provider_info: Optional[Dict[str, Any]] = None
18
18
 
19
+ @dataclass
20
+ class ToolSchema:
21
+ name: str
22
+ description: str
23
+ parameters: Dict[str, Any]
19
24
 
20
25
  @dataclass
21
26
  class PromptTemplate:
@@ -27,6 +32,7 @@ class PromptTemplate:
27
32
  project_id: str
28
33
  format_version: int
29
34
  environment: Optional[str] = None
35
+ tool_schema: Optional[List[ToolSchema]] = None
30
36
 
31
37
 
32
38
  @dataclass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: freeplay
3
- Version: 0.3.7
3
+ Version: 0.3.9
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: FreePlay Engineering
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
16
17
  Requires-Dist: click (==8.1.7)
17
18
  Requires-Dist: dacite (>=1.8.0,<2.0.0)
18
19
  Requires-Dist: pystache (>=0.6.5,<0.7.0)
@@ -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=Iv-f1o-cBgwzyLfWhcBybTn2zJdc006kf_bNSCMl54o,18895
12
- freeplay/resources/recordings.py,sha256=kUElo6Yvc6lyo221ghbElx75uxftw7HpgBV_R9gYlE4,5938
11
+ freeplay/resources/prompts.py,sha256=YPjM0YibT-ONWya9PEcllyAeUYOi5rFl_pN0JY-vz6E,21675
12
+ freeplay/resources/recordings.py,sha256=28P77nVqPObICUeWFJKY4nTOAqNC6VBmR5_CmSbCRNc,6065
13
13
  freeplay/resources/sessions.py,sha256=Qz5v7VOf1DmQTd1wCOFXnrizlW5WFJT5V8-pq22Ifvg,2793
14
14
  freeplay/resources/test_runs.py,sha256=qF4CE4XiX_6epcs5bFKJg73C94YgrJQTxnCJLBERkos,2549
15
- freeplay/support.py,sha256=nuly_GCWEAEz0CmjlviadKK2_r4X6RdxxiQ6yo78wdk,8449
15
+ freeplay/support.py,sha256=57n-Rv3yRBh3Zpg67uIkXZDLtFtk_bGj91IeheqjoDM,8595
16
16
  freeplay/utils.py,sha256=roDF_4QYKt93XO_hRdQC_mFAORVuqUWgCVAPXTj_vsU,2488
17
- freeplay-0.3.7.dist-info/LICENSE,sha256=_jzIw45hB1XHGxiQ8leZ0GH_X7bR_a8qgxaqnHbCUOo,1064
18
- freeplay-0.3.7.dist-info/METADATA,sha256=Br__sIy9olEIWT4tFGfZVXGgDYKMJMpM4NgHXoxhSGw,1602
19
- freeplay-0.3.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
20
- freeplay-0.3.7.dist-info/entry_points.txt,sha256=32s3rf2UUCqiJT4jnClEXZhdXlvl30uwpcxz-Gsy4UU,54
21
- freeplay-0.3.7.dist-info/RECORD,,
17
+ freeplay-0.3.9.dist-info/LICENSE,sha256=_jzIw45hB1XHGxiQ8leZ0GH_X7bR_a8qgxaqnHbCUOo,1064
18
+ freeplay-0.3.9.dist-info/METADATA,sha256=BQh3WgxEhS5U2tYgb8pRwFH6fyI35Mdw-lD_neIce_8,1653
19
+ freeplay-0.3.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
20
+ freeplay-0.3.9.dist-info/entry_points.txt,sha256=32s3rf2UUCqiJT4jnClEXZhdXlvl30uwpcxz-Gsy4UU,54
21
+ freeplay-0.3.9.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any