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.
- freeplay/resources/prompts.py +77 -17
- freeplay/resources/recordings.py +3 -1
- freeplay/support.py +6 -0
- {freeplay-0.3.7.dist-info → freeplay-0.3.9.dist-info}/METADATA +2 -1
- {freeplay-0.3.7.dist-info → freeplay-0.3.9.dist-info}/RECORD +8 -8
- {freeplay-0.3.7.dist-info → freeplay-0.3.9.dist-info}/WHEEL +1 -1
- {freeplay-0.3.7.dist-info → freeplay-0.3.9.dist-info}/LICENSE +0 -0
- {freeplay-0.3.7.dist-info → freeplay-0.3.9.dist-info}/entry_points.txt +0 -0
freeplay/resources/prompts.py
CHANGED
@@ -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,
|
63
|
-
|
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,
|
freeplay/resources/recordings.py
CHANGED
@@ -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.
|
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=
|
12
|
-
freeplay/resources/recordings.py,sha256=
|
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=
|
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.
|
18
|
-
freeplay-0.3.
|
19
|
-
freeplay-0.3.
|
20
|
-
freeplay-0.3.
|
21
|
-
freeplay-0.3.
|
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,,
|
File without changes
|
File without changes
|