freeplay 0.3.9__tar.gz → 0.3.10__tar.gz
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-0.3.9 → freeplay-0.3.10}/PKG-INFO +1 -1
- {freeplay-0.3.9 → freeplay-0.3.10}/pyproject.toml +3 -2
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/resources/prompts.py +28 -21
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/resources/recordings.py +2 -2
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/resources/test_runs.py +5 -2
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/support.py +5 -3
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/utils.py +18 -2
- {freeplay-0.3.9 → freeplay-0.3.10}/LICENSE +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/README.md +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/__init__.py +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/api_support.py +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/errors.py +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/freeplay.py +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/freeplay_cli.py +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/llm_parameters.py +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/model.py +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/py.typed +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/resources/__init__.py +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/resources/customer_feedback.py +0 -0
- {freeplay-0.3.9 → freeplay-0.3.10}/src/freeplay/resources/sessions.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "freeplay"
|
3
|
-
version = "0.3.
|
3
|
+
version = "0.3.10"
|
4
4
|
description = ""
|
5
5
|
authors = ["FreePlay Engineering <engineering@freeplay.ai>"]
|
6
6
|
license = "MIT"
|
@@ -16,10 +16,11 @@ pystache = "^0.6.5"
|
|
16
16
|
[tool.poetry.group.dev.dependencies]
|
17
17
|
mypy = "^1"
|
18
18
|
types-requests = "^2.31"
|
19
|
-
anthropic = {extras = ["bedrock"], version = "^0.39.0"}
|
19
|
+
anthropic = { extras = ["bedrock"], version = "^0.39.0" }
|
20
20
|
openai = "^1"
|
21
21
|
boto3 = "^1.34.97"
|
22
22
|
google-cloud-aiplatform = "1.51.0"
|
23
|
+
httpx = "0.27.2"
|
23
24
|
|
24
25
|
[tool.poetry.group.test.dependencies]
|
25
26
|
responses = "^0.23.1"
|
@@ -1,19 +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,
|
7
|
+
from typing import Dict, Optional, List, cast, Any, Union, runtime_checkable, Protocol
|
8
8
|
|
9
9
|
from freeplay.errors import FreeplayConfigurationError, FreeplayClientError, log_freeplay_client_warning
|
10
10
|
from freeplay.llm_parameters import LLMParameters
|
11
11
|
from freeplay.model import InputVariables
|
12
12
|
from freeplay.support import CallSupport, ToolSchema
|
13
13
|
from freeplay.support import PromptTemplate, PromptTemplates, PromptTemplateMetadata
|
14
|
-
from freeplay.utils import bind_template_variables
|
14
|
+
from freeplay.utils import bind_template_variables, convert_provider_message_to_dict
|
15
15
|
|
16
16
|
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
17
19
|
class MissingFlavorError(FreeplayConfigurationError):
|
18
20
|
def __init__(self, flavor_name: str):
|
19
21
|
super().__init__(
|
@@ -21,6 +23,7 @@ class MissingFlavorError(FreeplayConfigurationError):
|
|
21
23
|
'a different model in the Freeplay UI.'
|
22
24
|
)
|
23
25
|
|
26
|
+
|
24
27
|
class UnsupportedToolSchemaError(FreeplayConfigurationError):
|
25
28
|
def __init__(self) -> None:
|
26
29
|
super().__init__(
|
@@ -28,6 +31,20 @@ class UnsupportedToolSchemaError(FreeplayConfigurationError):
|
|
28
31
|
)
|
29
32
|
|
30
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
|
+
|
31
48
|
# SDK-Exposed Classes
|
32
49
|
@dataclass
|
33
50
|
class PromptInfo:
|
@@ -42,13 +59,6 @@ class PromptInfo:
|
|
42
59
|
flavor_name: str
|
43
60
|
project_id: str
|
44
61
|
|
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
|
52
62
|
|
53
63
|
class FormattedPrompt:
|
54
64
|
def __init__(
|
@@ -78,20 +88,18 @@ class FormattedPrompt:
|
|
78
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.
|
79
89
|
def llm_prompt(self) -> Any:
|
80
90
|
return self._llm_prompt
|
81
|
-
|
91
|
+
|
82
92
|
@property
|
83
93
|
def tool_schema(self) -> Any:
|
84
94
|
return self._tool_schema
|
85
95
|
|
86
96
|
def all_messages(
|
87
97
|
self,
|
88
|
-
new_message:
|
98
|
+
new_message: GenericProviderMessage
|
89
99
|
) -> List[Dict[str, Any]]:
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
elif isinstance(new_message, dict):
|
94
|
-
return self.messages + [new_message]
|
100
|
+
converted_message = convert_provider_message_to_dict(new_message)
|
101
|
+
return self.messages + [converted_message]
|
102
|
+
|
95
103
|
|
96
104
|
class BoundPrompt:
|
97
105
|
def __init__(
|
@@ -145,7 +153,7 @@ class BoundPrompt:
|
|
145
153
|
return formatted
|
146
154
|
|
147
155
|
raise MissingFlavorError(flavor_name)
|
148
|
-
|
156
|
+
|
149
157
|
@staticmethod
|
150
158
|
def __format_tool_schema(flavor_name: str, tool_schema: List[ToolSchema]) -> List[Dict[str, Any]]:
|
151
159
|
if flavor_name == 'anthropic_chat':
|
@@ -161,9 +169,8 @@ class BoundPrompt:
|
|
161
169
|
'type': 'function'
|
162
170
|
} for tool_schema in tool_schema
|
163
171
|
]
|
164
|
-
|
165
|
-
raise UnsupportedToolSchemaError()
|
166
172
|
|
173
|
+
raise UnsupportedToolSchemaError()
|
167
174
|
|
168
175
|
def format(
|
169
176
|
self,
|
@@ -173,7 +180,7 @@ class BoundPrompt:
|
|
173
180
|
formatted_prompt = BoundPrompt.__format_messages_for_flavor(final_flavor, self.messages)
|
174
181
|
|
175
182
|
formatted_tool_schema = BoundPrompt.__format_tool_schema(
|
176
|
-
final_flavor,
|
183
|
+
final_flavor,
|
177
184
|
self.tool_schema
|
178
185
|
) if self.tool_schema else None
|
179
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
|
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
|
@@ -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,
|
@@ -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
|
|
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|