freeplay 0.3.15__py3-none-any.whl → 0.3.17__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/__init__.py CHANGED
@@ -2,9 +2,11 @@ from .freeplay import Freeplay
2
2
  from .resources.prompts import PromptInfo
3
3
  from .resources.recordings import CallInfo, ResponseInfo, RecordPayload, TestRunInfo, UsageTokens
4
4
  from .resources.sessions import SessionInfo, TraceInfo
5
+ from .support import CustomMetadata
5
6
 
6
7
  __all__ = [
7
8
  'CallInfo',
9
+ 'CustomMetadata',
8
10
  'Freeplay',
9
11
  'PromptInfo',
10
12
  'RecordPayload',
@@ -1,16 +1,37 @@
1
1
  import copy
2
2
  import json
3
3
  import logging
4
+ import warnings
4
5
  from abc import ABC, abstractmethod
5
6
  from dataclasses import asdict, dataclass
6
7
  from pathlib import Path
7
- from typing import Dict, Optional, List, Sequence, cast, Any, Union, runtime_checkable, Protocol
8
-
9
- from freeplay.errors import FreeplayConfigurationError, FreeplayClientError, log_freeplay_client_warning
8
+ from typing import (
9
+ Any,
10
+ Dict,
11
+ List,
12
+ Optional,
13
+ Protocol,
14
+ Sequence,
15
+ TypedDict,
16
+ Union,
17
+ cast,
18
+ runtime_checkable,
19
+ )
20
+
21
+ from freeplay.errors import (
22
+ FreeplayClientError,
23
+ FreeplayConfigurationError,
24
+ log_freeplay_client_warning,
25
+ )
10
26
  from freeplay.llm_parameters import LLMParameters
11
27
  from freeplay.model import InputVariables
12
- from freeplay.support import CallSupport, ToolSchema
13
- from freeplay.support import PromptTemplate, PromptTemplates, PromptTemplateMetadata
28
+ from freeplay.support import (
29
+ CallSupport,
30
+ PromptTemplate,
31
+ PromptTemplateMetadata,
32
+ PromptTemplates,
33
+ ToolSchema,
34
+ )
14
35
  from freeplay.utils import bind_template_variables, convert_provider_message_to_dict
15
36
 
16
37
  logger = logging.getLogger(__name__)
@@ -33,16 +54,30 @@ class UnsupportedToolSchemaError(FreeplayConfigurationError):
33
54
 
34
55
  # Models ==
35
56
 
36
- # A content block a la OpenAI or Anthropic. Intentionally over-permissive to allow schema evolution by the providers.
57
+ # A content block compatible with stainless generated SDKs (such as Anthropic
58
+ # and OpenAI). This lets us generate a dictionary from the stainless classes
59
+ # correctly. Intentionally over-permissive to allow schema evolution by the
60
+ # providers.
37
61
  @runtime_checkable
38
- class ProviderMessageContentBlock(Protocol):
62
+ class ProviderMessageProtocol(Protocol):
39
63
  def model_dump(self) -> Dict[str, Any]:
40
64
  pass
41
65
 
42
66
 
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], ProviderMessageContentBlock]
67
+ class MessageDict(TypedDict):
68
+ role: str
69
+ content: Any
70
+
71
+
72
+ # This type represents a struct or dict containing a role and content. The role
73
+ # should be one of user, assistant or system. This type should be compatible
74
+ # with OpenAI and Anthropic's message format, as well as most other SDKs. If
75
+ # not using a common provider, use {'content': str, 'role': str} to record. If
76
+ # using a common provider, this is usually the `.content` field.
77
+ ProviderMessage = Union[MessageDict, Dict[str, Any], ProviderMessageProtocol]
78
+
79
+ # DEPRECATED: Use ProviderMessage instead
80
+ GenericProviderMessage = ProviderMessage
46
81
 
47
82
 
48
83
  # SDK-Exposed Classes
@@ -69,7 +104,7 @@ class FormattedPrompt:
69
104
  formatted_prompt_text: Optional[str] = None,
70
105
  tool_schema: Optional[List[Dict[str, Any]]] = None
71
106
  ):
72
- # These two definitions allow us to operate on typed fields unitl we expose them as Any for client use.
107
+ # These two definitions allow us to operate on typed fields until we expose them as Any for client use.
73
108
  self._llm_prompt = formatted_prompt
74
109
  self._tool_schema = tool_schema
75
110
 
@@ -81,11 +116,18 @@ class FormattedPrompt:
81
116
  (message['content'] for message in messages if message['role'] == 'system'), None)
82
117
  self.system_content = maybe_system_content
83
118
 
84
- # Note: messages are **not formatted** for the provider.
85
- self.messages = messages
119
+ self._messages = messages
120
+
121
+ @property
122
+ def messages(self) -> List[Dict[str, str]]:
123
+ warnings.warn(
124
+ "The 'messages' attribute is deprecated and will be removed in a future version. It is not formatted for the provider. Use 'llm_prompt' instead.",
125
+ DeprecationWarning,
126
+ stacklevel=2,
127
+ )
128
+ return self._messages
86
129
 
87
130
  @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
131
  def llm_prompt(self) -> Any:
90
132
  return self._llm_prompt
91
133
 
@@ -93,12 +135,9 @@ class FormattedPrompt:
93
135
  def tool_schema(self) -> Any:
94
136
  return self._tool_schema
95
137
 
96
- def all_messages(
97
- self,
98
- new_message: GenericProviderMessage
99
- ) -> List[Dict[str, Any]]:
138
+ def all_messages(self, new_message: ProviderMessage) -> List[Dict[str, Any]]:
100
139
  converted_message = convert_provider_message_to_dict(new_message)
101
- return self.messages + [converted_message]
140
+ return self._messages + [converted_message]
102
141
 
103
142
 
104
143
  class BoundPrompt:
@@ -211,7 +250,11 @@ class TemplatePrompt:
211
250
  self.tool_schema = tool_schema
212
251
  self.messages = messages
213
252
 
214
- def bind(self, variables: InputVariables, history: Optional[Sequence[GenericProviderMessage]] = None) -> BoundPrompt:
253
+ def bind(
254
+ self,
255
+ variables: InputVariables,
256
+ history: Optional[Sequence[ProviderMessage]] = None,
257
+ ) -> BoundPrompt:
215
258
  # check history for a system message
216
259
  history_clean = []
217
260
  if history:
@@ -521,13 +564,13 @@ class Prompts:
521
564
  return TemplatePrompt(prompt_info, prompt.content, prompt.tool_schema)
522
565
 
523
566
  def get_formatted(
524
- self,
525
- project_id: str,
526
- template_name: str,
527
- environment: str,
528
- variables: InputVariables,
529
- history: Optional[Sequence[GenericProviderMessage]] = None,
530
- flavor_name: Optional[str] = None
567
+ self,
568
+ project_id: str,
569
+ template_name: str,
570
+ environment: str,
571
+ variables: InputVariables,
572
+ history: Optional[Sequence[ProviderMessage]] = None,
573
+ flavor_name: Optional[str] = None,
531
574
  ) -> FormattedPrompt:
532
575
  bound_prompt = self.get(
533
576
  project_id=project_id,
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  from dataclasses import dataclass
4
- from typing import Any, Dict, List, Optional, Union
4
+ from typing import Any, Dict, List, Optional, Union, Literal
5
5
  from uuid import UUID
6
6
 
7
7
  from requests import HTTPError
@@ -23,6 +23,9 @@ class UsageTokens:
23
23
  completion_tokens: int
24
24
 
25
25
 
26
+ ApiStyle = Union[Literal['batch'], Literal['default']]
27
+
28
+
26
29
  @dataclass
27
30
  class CallInfo:
28
31
  provider: str
@@ -32,13 +35,15 @@ class CallInfo:
32
35
  model_parameters: LLMParameters
33
36
  provider_info: Optional[Dict[str, Any]] = None
34
37
  usage: Optional[UsageTokens] = None
38
+ api_style: Optional[ApiStyle] = None
35
39
 
36
40
  @staticmethod
37
41
  def from_prompt_info(
38
42
  prompt_info: PromptInfo,
39
43
  start_time: float,
40
44
  end_time: float,
41
- usage: Optional[UsageTokens] = None
45
+ usage: Optional[UsageTokens] = None,
46
+ api_style: Optional[ApiStyle] = None
42
47
  ) -> 'CallInfo':
43
48
  return CallInfo(
44
49
  provider=prompt_info.provider,
@@ -47,7 +52,8 @@ class CallInfo:
47
52
  end_time=end_time,
48
53
  model_parameters=prompt_info.model_parameters,
49
54
  provider_info=prompt_info.provider_info,
50
- usage=usage
55
+ usage=usage,
56
+ api_style=api_style
51
57
  )
52
58
 
53
59
 
@@ -157,6 +163,9 @@ class Recordings:
157
163
  "completion_tokens": record_payload.call_info.usage.completion_tokens,
158
164
  }
159
165
 
166
+ if record_payload.call_info.api_style is not None:
167
+ record_api_payload['call_info']['api_style'] = record_payload.call_info.api_style
168
+
160
169
  try:
161
170
  recorded_response = api_support.post_raw(
162
171
  api_key=self.call_support.freeplay_api_key,
@@ -3,9 +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.support import CallSupport
7
-
8
- CustomMetadata = Optional[Dict[str, Union[str, int, float, bool]]]
6
+ from freeplay.support import CallSupport, CustomMetadata
9
7
 
10
8
 
11
9
  @dataclass
@@ -18,6 +16,8 @@ class TraceInfo:
18
16
  session_id: str
19
17
  trace_id: str
20
18
  input: Optional[str] = None
19
+ agent_name: Optional[str] = None
20
+ custom_metadata: CustomMetadata = None
21
21
  _call_support: CallSupport
22
22
 
23
23
  def __init__(
@@ -26,16 +26,34 @@ class TraceInfo:
26
26
  session_id: str,
27
27
  _call_support: CallSupport,
28
28
  input: Optional[str] = None,
29
+ agent_name: Optional[str] = None,
30
+ custom_metadata: CustomMetadata = None,
29
31
  ):
30
32
  self.trace_id = trace_id
31
33
  self.session_id = session_id
32
34
  self.input = input
35
+ self.agent_name = agent_name
36
+ self.custom_metadata = custom_metadata
33
37
  self._call_support = _call_support
34
38
 
35
- def record_output(self, project_id: str, output: str) -> None:
39
+ def record_output(
40
+ self,
41
+ project_id: str,
42
+ output: str,
43
+ eval_results: Optional[Dict[str, Union[bool, float]]] = None
44
+ ) -> None:
36
45
  if self.input is None:
37
46
  raise FreeplayClientError("Input must be set before recording output")
38
- self._call_support.record_trace(project_id, self.session_id, self.trace_id, self.input, output)
47
+ self._call_support.record_trace(
48
+ project_id,
49
+ self.session_id,
50
+ self.trace_id,
51
+ self.input,
52
+ output,
53
+ agent_name=self.agent_name,
54
+ custom_metadata=self.custom_metadata,
55
+ eval_results=eval_results
56
+ )
39
57
 
40
58
 
41
59
  @dataclass
@@ -53,19 +71,34 @@ class Session:
53
71
  def session_info(self) -> SessionInfo:
54
72
  return self._session_info
55
73
 
56
- def create_trace(self, input: str) -> TraceInfo:
74
+ def create_trace(
75
+ self,
76
+ input: str,
77
+ agent_name: Optional[str] = None,
78
+ custom_metadata: CustomMetadata = None
79
+ ) -> TraceInfo:
57
80
  return TraceInfo(
58
81
  trace_id=str(uuid.uuid4()),
59
82
  session_id=self.session_id,
60
83
  input=input,
84
+ agent_name=agent_name,
85
+ custom_metadata=custom_metadata,
61
86
  _call_support=self._call_support
62
87
  )
63
88
 
64
- def restore_trace(self, trace_id: uuid.UUID, input: Optional[str]) -> TraceInfo:
89
+ def restore_trace(
90
+ self,
91
+ trace_id: uuid.UUID,
92
+ input: Optional[str],
93
+ agent_name: Optional[str] = None,
94
+ custom_metadata: CustomMetadata = None
95
+ ) -> TraceInfo:
65
96
  return TraceInfo(
66
97
  trace_id=str(trace_id),
67
98
  session_id=self.session_id,
68
99
  input=input,
100
+ agent_name=agent_name,
101
+ custom_metadata=custom_metadata,
69
102
  _call_support=self._call_support
70
103
  )
71
104
 
freeplay/support.py CHANGED
@@ -1,4 +1,3 @@
1
- import json
2
1
  from dataclasses import dataclass
3
2
  from json import JSONEncoder
4
3
  from typing import Optional, Dict, Any, List, Union
@@ -8,6 +7,8 @@ from freeplay.api_support import try_decode
8
7
  from freeplay.errors import freeplay_response_error, FreeplayServerError
9
8
  from freeplay.model import InputVariables, FeedbackValue, NormalizedMessage
10
9
 
10
+ CustomMetadata = Optional[Dict[str, Union[str, int, float, bool]]]
11
+
11
12
 
12
13
  @dataclass
13
14
  class PromptTemplateMetadata:
@@ -17,12 +18,14 @@ class PromptTemplateMetadata:
17
18
  params: Optional[Dict[str, Any]] = None
18
19
  provider_info: Optional[Dict[str, Any]] = None
19
20
 
21
+
20
22
  @dataclass
21
23
  class ToolSchema:
22
24
  name: str
23
25
  description: str
24
26
  parameters: Dict[str, Any]
25
27
 
28
+
26
29
  @dataclass
27
30
  class PromptTemplate:
28
31
  prompt_template_id: str
@@ -40,6 +43,7 @@ class PromptTemplate:
40
43
  class PromptTemplates:
41
44
  prompt_templates: List[PromptTemplate]
42
45
 
46
+
43
47
  @dataclass
44
48
  class SummaryStatistics:
45
49
  auto_evaluation: Dict[str, Any]
@@ -88,8 +92,10 @@ class TestRunRetrievalResponse:
88
92
  human_evaluation=summary_statistics['human_evaluation']
89
93
  )
90
94
 
95
+
91
96
  class DatasetTestCaseRequest:
92
- def __init__(self, history: Optional[List[NormalizedMessage]], inputs: InputVariables, metadata: Optional[Dict[str, str]], output: Optional[str]) -> None:
97
+ def __init__(self, history: Optional[List[NormalizedMessage]], inputs: InputVariables,
98
+ metadata: Optional[Dict[str, str]], output: Optional[str]) -> None:
93
99
  self.history: Optional[List[NormalizedMessage]] = history
94
100
  self.inputs: InputVariables = inputs
95
101
  self.metadata: Optional[Dict[str, str]] = metadata
@@ -104,6 +110,7 @@ class DatasetTestCaseResponse:
104
110
  self.history: Optional[List[NormalizedMessage]] = test_case.get('history')
105
111
  self.metadata: Optional[Dict[str, str]] = test_case.get('metadata')
106
112
 
113
+
107
114
  class DatasetTestCasesRetrievalResponse:
108
115
  def __init__(self, test_cases: List[Dict[str, Any]]) -> None:
109
116
  self.test_cases = [
@@ -111,6 +118,7 @@ class DatasetTestCasesRetrievalResponse:
111
118
  for test_case in test_cases
112
119
  ]
113
120
 
121
+
114
122
  class CallSupport:
115
123
  def __init__(
116
124
  self,
@@ -256,13 +264,26 @@ class CallSupport:
256
264
  summary_statistics=json_dom['summary_statistics']
257
265
  )
258
266
 
259
- def record_trace(self, project_id: str, session_id: str, trace_id: str, input: str, output: str) -> None:
267
+ def record_trace(
268
+ self,
269
+ project_id: str,
270
+ session_id: str,
271
+ trace_id: str,
272
+ input: str,
273
+ output: str,
274
+ agent_name: Optional[str] = None,
275
+ custom_metadata: CustomMetadata = None,
276
+ eval_results: Optional[Dict[str, Union[bool, float]]] = None
277
+ ) -> None:
260
278
  response = api_support.post_raw(
261
279
  self.freeplay_api_key,
262
280
  f'{self.api_base}/v2/projects/{project_id}/sessions/{session_id}/traces/id/{trace_id}',
263
281
  {
282
+ 'agent_name': agent_name,
264
283
  'input': input,
265
- 'output': output
284
+ 'output': output,
285
+ 'custom_metadata': custom_metadata,
286
+ 'eval_results': eval_results,
266
287
  }
267
288
  )
268
289
  if response.status_code != 201:
@@ -277,7 +298,13 @@ class CallSupport:
277
298
  raise freeplay_response_error('Error while deleting session.', response)
278
299
 
279
300
  def create_test_cases(self, project_id: str, dataset_id: str, test_cases: List[DatasetTestCaseRequest]) -> None:
280
- examples = [{"history": test_case.history, "output": test_case.output, "metadata": test_case.metadata, "inputs": test_case.inputs} for test_case in test_cases]
301
+ examples = [
302
+ {
303
+ "history": test_case.history,
304
+ "output": test_case.output,
305
+ "metadata": test_case.metadata,
306
+ "inputs": test_case.inputs
307
+ } for test_case in test_cases]
281
308
  payload: Dict[str, Any] = {"examples": examples}
282
309
  url = f'{self.api_base}/v2/projects/{project_id}/datasets/id/{dataset_id}/test-cases'
283
310
 
@@ -295,5 +322,13 @@ class CallSupport:
295
322
  json_dom = response.json()
296
323
 
297
324
  return DatasetTestCasesRetrievalResponse(
298
- test_cases=[{"history": jsn["history"], "id": jsn["id"], "output": jsn["output"], "values": jsn["values"], "metadata": jsn["metadata"] if 'metadata' in jsn.keys() else None} for jsn in json_dom]
325
+ test_cases=[
326
+ {
327
+ "history": jsn["history"],
328
+ "id": jsn["id"],
329
+ "output": jsn["output"],
330
+ "values": jsn["values"],
331
+ "metadata": jsn["metadata"] if 'metadata' in jsn.keys() else None
332
+ } for jsn in json_dom
333
+ ]
299
334
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: freeplay
3
- Version: 0.3.15
3
+ Version: 0.3.17
4
4
  Summary:
5
5
  License: MIT
6
6
  Author: FreePlay Engineering
@@ -1,4 +1,4 @@
1
- freeplay/__init__.py,sha256=fipXoYuxUtsy6mxshut2ujWJh4lVbQ5v_xQ-33TPC-g,406
1
+ freeplay/__init__.py,sha256=tzEFgRyriPBWPo04jTgR6MxzXUqIHBlUJZvuBj5kFiE,464
2
2
  freeplay/api_support.py,sha256=Kn2x3g6yloHQl3NwFRjbZE9BnIh7d1sgwGwC0mHuvw4,2483
3
3
  freeplay/errors.py,sha256=vwotUBldxDzREZOmLUeoiDoZjcvDwgH1AMwKBLhLooE,807
4
4
  freeplay/freeplay.py,sha256=J04-erDD6rI2SAje_Nsf3x5Qx-Z6p8gQvGrMRHFWoD4,1602
@@ -8,15 +8,15 @@ freeplay/model.py,sha256=o0de_RZ2WTJ4m5OJw1ZVfC2xG6zBq_XShBrRt1laEjc,1405
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=NZi4K6oGnbSgw_i0NFssSqRNonl6Ov8eGPFFbZ6O5aI,22185
12
- freeplay/resources/recordings.py,sha256=PhwdVJaEhc_-xrtu5CPXoKRVRSBD6uqwIhTJrDCOBro,8144
13
- freeplay/resources/sessions.py,sha256=Qz5v7VOf1DmQTd1wCOFXnrizlW5WFJT5V8-pq22Ifvg,2793
11
+ freeplay/resources/prompts.py,sha256=fkNY5mg2Iib36e0sUVr3g-btr7bYdgQL9Ug6_f-OGi0,22790
12
+ freeplay/resources/recordings.py,sha256=uslhoKWsXq51sAbF1lBs2uGL14qvBGc3dM8ZL6-H_04,8488
13
+ freeplay/resources/sessions.py,sha256=J5A3CjiV2MFqQyxN3TWTvJaa9jmMza58mRFRq2v9iAk,3746
14
14
  freeplay/resources/test_cases.py,sha256=nXL_976RwSJDT6OWDM4GEzbcOzcGkJ9ulvb0XOzCRDM,2240
15
15
  freeplay/resources/test_runs.py,sha256=Tp2N-odInT5XEEWrEsVhdgfnsclOE8n92_C8gTwO2MI,2623
16
- freeplay/support.py,sha256=we_FEtxcqc-8R0uOWy8p0nX0pHUbs-ulw7TC5NarlX4,11091
16
+ freeplay/support.py,sha256=royOK31X-TtluQQn_XG2xFoINV8GZAy3FRRO_P763NU,11800
17
17
  freeplay/utils.py,sha256=Xvt4mNLXLL7E6MI2hTuDLV5cl5Y83DgdjCZSyDGMjR0,3187
18
- freeplay-0.3.15.dist-info/LICENSE,sha256=_jzIw45hB1XHGxiQ8leZ0GH_X7bR_a8qgxaqnHbCUOo,1064
19
- freeplay-0.3.15.dist-info/METADATA,sha256=YVeTKAO32MXniQ9UVVigz4Yiyu09xFLHvEAXCISnjNs,1654
20
- freeplay-0.3.15.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
21
- freeplay-0.3.15.dist-info/entry_points.txt,sha256=32s3rf2UUCqiJT4jnClEXZhdXlvl30uwpcxz-Gsy4UU,54
22
- freeplay-0.3.15.dist-info/RECORD,,
18
+ freeplay-0.3.17.dist-info/LICENSE,sha256=_jzIw45hB1XHGxiQ8leZ0GH_X7bR_a8qgxaqnHbCUOo,1064
19
+ freeplay-0.3.17.dist-info/METADATA,sha256=lIB2GCeusLys0Dza7RxasOgbpxIRJiZAPpx5DeKMXKs,1654
20
+ freeplay-0.3.17.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
21
+ freeplay-0.3.17.dist-info/entry_points.txt,sha256=32s3rf2UUCqiJT4jnClEXZhdXlvl30uwpcxz-Gsy4UU,54
22
+ freeplay-0.3.17.dist-info/RECORD,,