freeplay 0.3.22__tar.gz → 0.3.24__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.
Files changed (23) hide show
  1. {freeplay-0.3.22 → freeplay-0.3.24}/PKG-INFO +1 -1
  2. {freeplay-0.3.22 → freeplay-0.3.24}/pyproject.toml +1 -1
  3. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/__init__.py +2 -1
  4. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/model.py +6 -0
  5. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/resources/adapters.py +4 -1
  6. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/resources/recordings.py +2 -6
  7. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/resources/sessions.py +5 -2
  8. freeplay-0.3.24/src/freeplay/resources/test_runs.py +132 -0
  9. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/support.py +33 -14
  10. freeplay-0.3.22/src/freeplay/resources/test_runs.py +0 -88
  11. {freeplay-0.3.22 → freeplay-0.3.24}/LICENSE +0 -0
  12. {freeplay-0.3.22 → freeplay-0.3.24}/README.md +0 -0
  13. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/api_support.py +0 -0
  14. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/errors.py +0 -0
  15. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/freeplay.py +0 -0
  16. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/freeplay_cli.py +0 -0
  17. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/llm_parameters.py +0 -0
  18. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/py.typed +0 -0
  19. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/resources/__init__.py +0 -0
  20. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/resources/customer_feedback.py +0 -0
  21. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/resources/prompts.py +0 -0
  22. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/resources/test_cases.py +0 -0
  23. {freeplay-0.3.22 → freeplay-0.3.24}/src/freeplay/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: freeplay
3
- Version: 0.3.22
3
+ Version: 0.3.24
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: FreePlay Engineering
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "freeplay"
3
- version = "0.3.22"
3
+ version = "0.3.24"
4
4
  description = ""
5
5
  authors = ["FreePlay Engineering <engineering@freeplay.ai>"]
6
6
  license = "MIT"
@@ -1,6 +1,7 @@
1
1
  from .freeplay import Freeplay
2
2
  from .resources.prompts import PromptInfo
3
- from .resources.recordings import CallInfo, ResponseInfo, RecordPayload, TestRunInfo, UsageTokens
3
+ from .model import TestRunInfo
4
+ from .resources.recordings import CallInfo, ResponseInfo, RecordPayload, UsageTokens
4
5
  from .resources.sessions import SessionInfo, TraceInfo
5
6
  from .support import CustomMetadata
6
7
 
@@ -13,6 +13,12 @@ class TestRun:
13
13
  inputs: List[TestRunInput]
14
14
 
15
15
 
16
+ @dataclass
17
+ class TestRunInfo:
18
+ test_run_id: str
19
+ test_case_id: str
20
+
21
+
16
22
  class OpenAIFunctionCall(TypedDict):
17
23
  name: str
18
24
  arguments: str
@@ -35,6 +35,7 @@ class MissingFlavorError(FreeplayConfigurationError):
35
35
 
36
36
 
37
37
  class LLMAdapter(Protocol):
38
+ # This method must handle BOTH prompt template messages and provider specific messages.
38
39
  def to_llm_syntax(self, messages: List[Dict[str, Any]]) -> Union[str, List[Dict[str, Any]]]:
39
40
  pass
40
41
 
@@ -187,11 +188,13 @@ class GeminiAdapter(LLMAdapter):
187
188
  "role": self.__translate_role(message["role"]),
188
189
  "parts": [self.__map_content(content) for content in message['content']]
189
190
  })
190
- else:
191
+ elif "content" in message:
191
192
  gemini_messages.append({
192
193
  "role": self.__translate_role(message["role"]),
193
194
  "parts": [{"text": message["content"]}]
194
195
  })
196
+ else:
197
+ gemini_messages.append(copy.deepcopy(message))
195
198
 
196
199
  return gemini_messages
197
200
 
@@ -9,11 +9,12 @@ from requests import HTTPError
9
9
  from freeplay import api_support
10
10
  from freeplay.errors import FreeplayClientError, FreeplayError
11
11
  from freeplay.llm_parameters import LLMParameters
12
- from freeplay.model import InputVariables, OpenAIFunctionCall
12
+ from freeplay.model import InputVariables, OpenAIFunctionCall, TestRunInfo
13
13
  from freeplay.resources.prompts import PromptInfo, MediaInputMap, MediaInput, MediaInputUrl
14
14
  from freeplay.resources.sessions import SessionInfo, TraceInfo
15
15
  from freeplay.support import CallSupport
16
16
 
17
+
17
18
  logger = logging.getLogger(__name__)
18
19
 
19
20
 
@@ -65,11 +66,6 @@ class ResponseInfo:
65
66
  response_tokens: Optional[int] = None
66
67
 
67
68
 
68
- @dataclass
69
- class TestRunInfo:
70
- test_run_id: str
71
- test_case_id: str
72
-
73
69
 
74
70
  @dataclass
75
71
  class RecordPayload:
@@ -3,6 +3,7 @@ from dataclasses import dataclass
3
3
  from typing import Optional, Dict, Union
4
4
 
5
5
  from freeplay.errors import FreeplayClientError
6
+ from freeplay.model import TestRunInfo
6
7
  from freeplay.support import CallSupport, CustomMetadata
7
8
 
8
9
 
@@ -40,7 +41,8 @@ class TraceInfo:
40
41
  self,
41
42
  project_id: str,
42
43
  output: str,
43
- eval_results: Optional[Dict[str, Union[bool, float]]] = None
44
+ eval_results: Optional[Dict[str, Union[bool, float]]] = None,
45
+ test_run_info: Optional[TestRunInfo] = None
44
46
  ) -> None:
45
47
  if self.input is None:
46
48
  raise FreeplayClientError("Input must be set before recording output")
@@ -52,7 +54,8 @@ class TraceInfo:
52
54
  output,
53
55
  agent_name=self.agent_name,
54
56
  custom_metadata=self.custom_metadata,
55
- eval_results=eval_results
57
+ eval_results=eval_results,
58
+ test_run_info=test_run_info
56
59
  )
57
60
 
58
61
 
@@ -0,0 +1,132 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Optional, Dict, Any
3
+ import warnings
4
+
5
+ from freeplay.model import InputVariables, TestRunInfo
6
+ from freeplay.support import CallSupport, SummaryStatistics
7
+
8
+ @dataclass
9
+ class CompletionTestCase:
10
+ def __init__(
11
+ self,
12
+ test_case_id: str,
13
+ variables: InputVariables,
14
+ output: Optional[str],
15
+ history: Optional[List[Dict[str, str]]],
16
+ custom_metadata: Optional[Dict[str, str]]
17
+ ):
18
+ self.id = test_case_id
19
+ self.variables = variables
20
+ self.output = output
21
+ self.history = history
22
+ self.custom_metadata = custom_metadata
23
+
24
+ class TestCase(CompletionTestCase):
25
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
26
+ warnings.warn(
27
+ "'TestCase' is deprecated; use 'CompletionTestCase' instead.",
28
+ DeprecationWarning,
29
+ stacklevel=2,
30
+ )
31
+ super().__init__(*args, **kwargs)
32
+
33
+ class TraceTestCase:
34
+ def __init__(
35
+ self,
36
+ test_case_id: str,
37
+ input: str,
38
+ output: Optional[str],
39
+ custom_metadata: Optional[Dict[str, str]]
40
+ ):
41
+ self.id = test_case_id
42
+ self.input = input
43
+ self.output = output
44
+ self.custom_metadata = custom_metadata
45
+ @dataclass
46
+ class TestRun:
47
+ def __init__(
48
+ self,
49
+ test_run_id: str,
50
+ test_cases: List[CompletionTestCase] = [],
51
+ trace_test_cases: List[TraceTestCase] = []
52
+ ):
53
+ self.test_run_id = test_run_id
54
+ self.test_cases = test_cases
55
+ self.trace_test_cases = trace_test_cases
56
+
57
+ def __must_not_be_both_trace_and_completion(self) -> None:
58
+ if self.test_cases and len(self.test_cases) > 0 and self.trace_test_cases and len(self.trace_test_cases) > 0:
59
+ raise ValueError("Test case and trace test case cannot both be present")
60
+
61
+ def get_test_cases(self) -> List[CompletionTestCase]:
62
+ self.__must_not_be_both_trace_and_completion()
63
+ if len(self.trace_test_cases) > 0:
64
+ raise ValueError("Completion test cases are not present. Please use get_trace_test_cases() instead.")
65
+ return self.test_cases
66
+
67
+ def get_trace_test_cases(self) -> List[TraceTestCase]:
68
+ self.__must_not_be_both_trace_and_completion()
69
+ if len(self.test_cases) > 0:
70
+ raise ValueError("Trace test cases are not present. Please use get_test_cases() instead.")
71
+ return self.trace_test_cases
72
+
73
+ def get_test_run_info(self, test_case_id: str) -> TestRunInfo:
74
+ return TestRunInfo(self.test_run_id, test_case_id)
75
+
76
+
77
+ @dataclass
78
+ class TestRunResults:
79
+ def __init__(
80
+ self,
81
+ name: str,
82
+ description: str,
83
+ test_run_id: str,
84
+ summary_statistics: SummaryStatistics,
85
+ ):
86
+ self.name = name
87
+ self.description = description
88
+ self.test_run_id = test_run_id
89
+ self.summary_statistics = summary_statistics
90
+
91
+
92
+ class TestRuns:
93
+ def __init__(self, call_support: CallSupport) -> None:
94
+ self.call_support = call_support
95
+
96
+ def create(
97
+ self,
98
+ project_id: str,
99
+ testlist: str,
100
+ include_outputs: bool = False,
101
+ name: Optional[str] = None,
102
+ description: Optional[str] = None,
103
+ flavor_name: Optional[str] = None
104
+ ) -> TestRun:
105
+ test_run = self.call_support.create_test_run(
106
+ project_id, testlist, include_outputs, name, description, flavor_name)
107
+ test_cases = [
108
+ CompletionTestCase(test_case_id=test_case.id,
109
+ variables=test_case.variables,
110
+ output=test_case.output,
111
+ history=test_case.history,
112
+ custom_metadata=test_case.custom_metadata)
113
+ for test_case in test_run.test_cases
114
+ ]
115
+ trace_test_cases = [
116
+ TraceTestCase(test_case_id=test_case.id,
117
+ input=test_case.input,
118
+ output=test_case.output,
119
+ custom_metadata=test_case.custom_metadata)
120
+ for test_case in test_run.trace_test_cases
121
+ ]
122
+
123
+ return TestRun(test_run.test_run_id, test_cases, trace_test_cases)
124
+
125
+ def get(self, project_id: str, test_run_id: str) -> TestRunResults:
126
+ test_run_results = self.call_support.get_test_run_results(project_id, test_run_id)
127
+ return TestRunResults(
128
+ test_run_results.name,
129
+ test_run_results.description,
130
+ test_run_results.test_run_id,
131
+ test_run_results.summary_statistics
132
+ )
@@ -1,11 +1,11 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass, field, asdict
2
2
  from json import JSONEncoder
3
3
  from typing import Optional, Dict, Any, List, Union, Literal
4
4
 
5
5
  from freeplay import api_support
6
6
  from freeplay.api_support import try_decode
7
7
  from freeplay.errors import freeplay_response_error, FreeplayServerError
8
- from freeplay.model import InputVariables, FeedbackValue, NormalizedMessage
8
+ from freeplay.model import InputVariables, FeedbackValue, NormalizedMessage, TestRunInfo
9
9
 
10
10
  CustomMetadata = Optional[Dict[str, Union[str, int, float, bool]]]
11
11
 
@@ -83,22 +83,38 @@ class PromptTemplateEncoder(JSONEncoder):
83
83
 
84
84
  class TestCaseTestRunResponse:
85
85
  def __init__(self, test_case: Dict[str, Any]):
86
- self.variables: InputVariables = test_case['variables']
87
86
  self.id: str = test_case['test_case_id']
87
+ self.variables: InputVariables = test_case['variables']
88
88
  self.output: Optional[str] = test_case.get('output')
89
89
  self.history: Optional[List[Dict[str, Any]]] = test_case.get('history')
90
+ self.custom_metadata: Optional[Dict[str, str]] = test_case.get('custom_metadata')
90
91
 
92
+ class TraceTestCaseTestRunResponse:
93
+ def __init__(self, test_case: Dict[str, Any]):
94
+ self.id: str = test_case['test_case_id']
95
+ self.input: str = test_case['input']
96
+ self.output: Optional[str] = test_case.get('output')
97
+ self.custom_metadata: Optional[Dict[str, str]] = test_case.get('custom_metadata')
91
98
 
92
99
  class TestRunResponse:
93
100
  def __init__(
94
101
  self,
95
102
  test_run_id: str,
96
- test_cases: List[Dict[str, Any]]
103
+ test_cases: Optional[List[Dict[str, Any]]],
104
+ trace_test_cases: Optional[List[Dict[str, Any]]]
97
105
  ):
106
+ if test_cases and trace_test_cases:
107
+ raise ValueError("Test cases and trace test cases cannot both be present.")
108
+
98
109
  self.test_cases = [
99
110
  TestCaseTestRunResponse(test_case)
100
- for test_case in test_cases
111
+ for test_case in (test_cases or []) if test_case is not None
112
+ ]
113
+ self.trace_test_cases = [
114
+ TraceTestCaseTestRunResponse(test_case)
115
+ for test_case in (trace_test_cases or []) if test_case is not None
101
116
  ]
117
+
102
118
  self.test_run_id = test_run_id
103
119
 
104
120
 
@@ -267,7 +283,7 @@ class CallSupport:
267
283
 
268
284
  json_dom = response.json()
269
285
 
270
- return TestRunResponse(json_dom['test_run_id'], json_dom['test_cases'])
286
+ return TestRunResponse(json_dom['test_run_id'], json_dom['test_cases'], json_dom['trace_test_cases'])
271
287
 
272
288
  def get_test_run_results(
273
289
  self,
@@ -299,18 +315,21 @@ class CallSupport:
299
315
  output: str,
300
316
  agent_name: Optional[str] = None,
301
317
  custom_metadata: CustomMetadata = None,
302
- eval_results: Optional[Dict[str, Union[bool, float]]] = None
318
+ eval_results: Optional[Dict[str, Union[bool, float]]] = None,
319
+ test_run_info: Optional[TestRunInfo] = None
303
320
  ) -> None:
321
+ payload = {
322
+ 'agent_name': agent_name,
323
+ 'input': input,
324
+ 'output': output,
325
+ 'custom_metadata': custom_metadata,
326
+ 'eval_results': eval_results,
327
+ 'test_run_info': asdict(test_run_info) if test_run_info else None
328
+ }
304
329
  response = api_support.post_raw(
305
330
  self.freeplay_api_key,
306
331
  f'{self.api_base}/v2/projects/{project_id}/sessions/{session_id}/traces/id/{trace_id}',
307
- {
308
- 'agent_name': agent_name,
309
- 'input': input,
310
- 'output': output,
311
- 'custom_metadata': custom_metadata,
312
- 'eval_results': eval_results,
313
- }
332
+ payload
314
333
  )
315
334
  if response.status_code != 201:
316
335
  raise freeplay_response_error('Error while recording trace.', response)
@@ -1,88 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import List, Optional, Dict
3
-
4
- from freeplay.model import InputVariables
5
- from freeplay.resources.recordings import TestRunInfo
6
- from freeplay.support import CallSupport, SummaryStatistics
7
-
8
-
9
- @dataclass
10
- class TestCase:
11
- def __init__(
12
- self,
13
- test_case_id: str,
14
- variables: InputVariables,
15
- output: Optional[str],
16
- history: Optional[List[Dict[str, str]]]
17
- ):
18
- self.id = test_case_id
19
- self.variables = variables
20
- self.output = output
21
- self.history = history
22
-
23
-
24
- @dataclass
25
- class TestRun:
26
- def __init__(
27
- self,
28
- test_run_id: str,
29
- test_cases: List[TestCase]
30
- ):
31
- self.test_run_id = test_run_id
32
- self.test_cases = test_cases
33
-
34
- def get_test_cases(self) -> List[TestCase]:
35
- return self.test_cases
36
-
37
- def get_test_run_info(self, test_case_id: str) -> TestRunInfo:
38
- return TestRunInfo(self.test_run_id, test_case_id)
39
-
40
-
41
- @dataclass
42
- class TestRunResults:
43
- def __init__(
44
- self,
45
- name: str,
46
- description: str,
47
- test_run_id: str,
48
- summary_statistics: SummaryStatistics,
49
- ):
50
- self.name = name
51
- self.description = description
52
- self.test_run_id = test_run_id
53
- self.summary_statistics = summary_statistics
54
-
55
-
56
- class TestRuns:
57
- def __init__(self, call_support: CallSupport) -> None:
58
- self.call_support = call_support
59
-
60
- def create(
61
- self,
62
- project_id: str,
63
- testlist: str,
64
- include_outputs: bool = False,
65
- name: Optional[str] = None,
66
- description: Optional[str] = None,
67
- flavor_name: Optional[str] = None
68
- ) -> TestRun:
69
- test_run = self.call_support.create_test_run(
70
- project_id, testlist, include_outputs, name, description, flavor_name)
71
- test_cases = [
72
- TestCase(test_case_id=test_case.id,
73
- variables=test_case.variables,
74
- output=test_case.output,
75
- history=test_case.history)
76
- for test_case in test_run.test_cases
77
- ]
78
-
79
- return TestRun(test_run.test_run_id, test_cases)
80
-
81
- def get(self, project_id: str, test_run_id: str) -> TestRunResults:
82
- test_run_results = self.call_support.get_test_run_results(project_id, test_run_id)
83
- return TestRunResults(
84
- test_run_results.name,
85
- test_run_results.description,
86
- test_run_results.test_run_id,
87
- test_run_results.summary_statistics
88
- )
File without changes
File without changes