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.
- freeplay/resources/prompts.py +40 -16
- freeplay/resources/recordings.py +2 -2
- freeplay/resources/test_runs.py +5 -2
- freeplay/support.py +5 -3
- freeplay/utils.py +18 -2
- {freeplay-0.3.8.dist-info → freeplay-0.3.10.dist-info}/METADATA +1 -1
- {freeplay-0.3.8.dist-info → freeplay-0.3.10.dist-info}/RECORD +10 -10
- {freeplay-0.3.8.dist-info → freeplay-0.3.10.dist-info}/LICENSE +0 -0
- {freeplay-0.3.8.dist-info → freeplay-0.3.10.dist-info}/WHEEL +0 -0
- {freeplay-0.3.8.dist-info → freeplay-0.3.10.dist-info}/entry_points.txt +0 -0
freeplay/resources/prompts.py
CHANGED
@@ -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,
|
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:
|
98
|
+
new_message: GenericProviderMessage
|
73
99
|
) -> List[Dict[str, Any]]:
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
|
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
|
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,
|
56
|
+
all_messages: List[Dict[str, Any]]
|
57
57
|
inputs: InputVariables
|
58
58
|
|
59
59
|
session_info: SessionInfo
|
freeplay/resources/test_runs.py
CHANGED
@@ -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(
|
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,
|
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
|
@@ -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=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=
|
15
|
-
freeplay/support.py,sha256=
|
16
|
-
freeplay/utils.py,sha256=
|
17
|
-
freeplay-0.3.
|
18
|
-
freeplay-0.3.
|
19
|
-
freeplay-0.3.
|
20
|
-
freeplay-0.3.
|
21
|
-
freeplay-0.3.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|