freeplay 0.3.6__tar.gz → 0.3.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: freeplay
3
- Version: 0.3.6
3
+ Version: 0.3.8
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)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "freeplay"
3
- version = "0.3.6"
3
+ version = "0.3.8"
4
4
  description = ""
5
5
  authors = ["FreePlay Engineering <engineering@freeplay.ai>"]
6
6
  license = "MIT"
@@ -16,7 +16,7 @@ pystache = "^0.6.5"
16
16
  [tool.poetry.group.dev.dependencies]
17
17
  mypy = "^1"
18
18
  types-requests = "^2.31"
19
- anthropic = { version="^0.20.0", extras = ["bedrock"] }
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"
@@ -1,18 +1,21 @@
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, cast, Any, Union, overload
8
+ from anthropic.types.message import Message as AnthropicMessage
9
+ from openai.types.chat import ChatCompletionMessage as OpenAIMessage
7
10
 
8
11
  from freeplay.errors import FreeplayConfigurationError, FreeplayClientError, log_freeplay_client_warning
9
12
  from freeplay.llm_parameters import LLMParameters
10
13
  from freeplay.model import InputVariables
11
- from freeplay.support import CallSupport
14
+ from freeplay.support import CallSupport, ToolSchema
12
15
  from freeplay.support import PromptTemplate, PromptTemplates, PromptTemplateMetadata
13
16
  from freeplay.utils import bind_template_variables
14
17
 
15
-
18
+ logger = logging.getLogger(__name__)
16
19
  class MissingFlavorError(FreeplayConfigurationError):
17
20
  def __init__(self, flavor_name: str):
18
21
  super().__init__(
@@ -20,6 +23,12 @@ class MissingFlavorError(FreeplayConfigurationError):
20
23
  'a different model in the Freeplay UI.'
21
24
  )
22
25
 
26
+ class UnsupportedToolSchemaError(FreeplayConfigurationError):
27
+ def __init__(self) -> None:
28
+ super().__init__(
29
+ f'Tool schema not supported for this model and provider.'
30
+ )
31
+
23
32
 
24
33
  # SDK-Exposed Classes
25
34
  @dataclass
@@ -42,10 +51,12 @@ class FormattedPrompt:
42
51
  prompt_info: PromptInfo,
43
52
  messages: List[Dict[str, str]],
44
53
  formatted_prompt: Optional[List[Dict[str, str]]] = None,
45
- formatted_prompt_text: Optional[str] = None
54
+ formatted_prompt_text: Optional[str] = None,
55
+ tool_schema: Optional[List[Dict[str, Any]]] = None
46
56
  ):
47
57
  self.prompt_info = prompt_info
48
58
  self.llm_prompt = formatted_prompt
59
+ self.tool_schema = tool_schema
49
60
  if formatted_prompt_text:
50
61
  self.llm_prompt_text = formatted_prompt_text
51
62
 
@@ -58,19 +69,23 @@ class FormattedPrompt:
58
69
 
59
70
  def all_messages(
60
71
  self,
61
- new_message: Dict[str, str]
62
- ) -> List[Dict[str, str]]:
63
- return self.messages + [new_message]
64
-
72
+ new_message: Union[Dict[str, str], AnthropicMessage, OpenAIMessage]
73
+ ) -> List[Dict[str, Any]]:
74
+ if isinstance(new_message, AnthropicMessage) or isinstance(new_message, OpenAIMessage):
75
+ return self.messages + [new_message.to_dict()]
76
+ elif isinstance(new_message, dict):
77
+ return self.messages + [new_message]
65
78
 
66
79
  class BoundPrompt:
67
80
  def __init__(
68
81
  self,
69
82
  prompt_info: PromptInfo,
70
- messages: List[Dict[str, str]]
83
+ messages: List[Dict[str, str]],
84
+ tool_schema: Optional[List[ToolSchema]] = None
71
85
  ):
72
86
  self.prompt_info = prompt_info
73
87
  self.messages = messages
88
+ self.tool_schema = tool_schema
74
89
 
75
90
  @staticmethod
76
91
  def __format_messages_for_flavor(
@@ -113,6 +128,25 @@ class BoundPrompt:
113
128
  return formatted
114
129
 
115
130
  raise MissingFlavorError(flavor_name)
131
+
132
+ @staticmethod
133
+ def __format_tool_schema(flavor_name: str, tool_schema: List[ToolSchema]) -> List[Dict[str, Any]]:
134
+ if flavor_name == 'anthropic_chat':
135
+ return [{
136
+ 'name': tool_schema.name,
137
+ 'description': tool_schema.description,
138
+ 'input_schema': tool_schema.parameters
139
+ } for tool_schema in tool_schema]
140
+ elif flavor_name in ['openai_chat', 'azure_openai_chat']:
141
+ return [
142
+ {
143
+ 'function': asdict(tool_schema),
144
+ 'type': 'function'
145
+ } for tool_schema in tool_schema
146
+ ]
147
+
148
+ raise UnsupportedToolSchemaError()
149
+
116
150
 
117
151
  def format(
118
152
  self,
@@ -121,17 +155,24 @@ class BoundPrompt:
121
155
  final_flavor = flavor_name or self.prompt_info.flavor_name
122
156
  formatted_prompt = BoundPrompt.__format_messages_for_flavor(final_flavor, self.messages)
123
157
 
158
+ formatted_tool_schema = BoundPrompt.__format_tool_schema(
159
+ final_flavor,
160
+ self.tool_schema
161
+ ) if self.tool_schema else None
162
+
124
163
  if isinstance(formatted_prompt, str):
125
164
  return FormattedPrompt(
126
165
  prompt_info=self.prompt_info,
127
166
  messages=self.messages,
128
- formatted_prompt_text=formatted_prompt
167
+ formatted_prompt_text=formatted_prompt,
168
+ tool_schema=formatted_tool_schema
129
169
  )
130
170
  else:
131
171
  return FormattedPrompt(
132
172
  prompt_info=self.prompt_info,
133
173
  messages=self.messages,
134
- formatted_prompt=formatted_prompt
174
+ formatted_prompt=formatted_prompt,
175
+ tool_schema=formatted_tool_schema
135
176
  )
136
177
 
137
178
 
@@ -139,9 +180,11 @@ class TemplatePrompt:
139
180
  def __init__(
140
181
  self,
141
182
  prompt_info: PromptInfo,
142
- messages: List[Dict[str, str]]
183
+ messages: List[Dict[str, str]],
184
+ tool_schema: Optional[List[ToolSchema]] = None
143
185
  ):
144
186
  self.prompt_info = prompt_info
187
+ self.tool_schema = tool_schema
145
188
  self.messages = messages
146
189
 
147
190
  def bind(self, variables: InputVariables, history: Optional[List[Dict[str, str]]] = None) -> BoundPrompt:
@@ -172,7 +215,7 @@ class TemplatePrompt:
172
215
  'content': bind_template_variables(msg['content'], variables)},
173
216
  )
174
217
 
175
- return BoundPrompt(self.prompt_info, bound_messages)
218
+ return BoundPrompt(self.prompt_info, bound_messages, self.tool_schema)
176
219
 
177
220
 
178
221
  class TemplateResolver(ABC):
@@ -410,7 +453,7 @@ class Prompts:
410
453
  project_id=prompt.project_id
411
454
  )
412
455
 
413
- return TemplatePrompt(prompt_info, prompt.content)
456
+ return TemplatePrompt(prompt_info, prompt.content, prompt.tool_schema)
414
457
 
415
458
  def get_by_version_id(self, project_id: str, template_id: str, version_id: str) -> TemplatePrompt:
416
459
  prompt = self.template_resolver.get_prompt_version_id(project_id, template_id, version_id)
@@ -443,7 +486,7 @@ class Prompts:
443
486
  project_id=prompt.project_id
444
487
  )
445
488
 
446
- return TemplatePrompt(prompt_info, prompt.content)
489
+ return TemplatePrompt(prompt_info, prompt.content, prompt.tool_schema)
447
490
 
448
491
  def get_formatted(
449
492
  self,
@@ -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,
@@ -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,3 +1,4 @@
1
+ import json
1
2
  from typing import Dict, Union, Optional, Any
2
3
  import importlib.metadata
3
4
  import platform
@@ -20,6 +21,18 @@ def all_valid(obj: Any) -> bool:
20
21
  return False
21
22
 
22
23
 
24
+ class StandardPystache(pystache.Renderer): # type: ignore
25
+
26
+ def __init__(self) -> None:
27
+ super().__init__(escape=lambda s: s)
28
+
29
+ def str_coerce(self, val: Any) -> str:
30
+ if isinstance(val, dict) or isinstance(val, list):
31
+ # We hide spacing after punctuation so that the templating is the same across all SDKs.
32
+ return json.dumps(val, separators=(',', ':'))
33
+ return str(val)
34
+
35
+
23
36
  def bind_template_variables(template: str, variables: InputVariables) -> str:
24
37
  if not all_valid(variables):
25
38
  raise FreeplayError(
@@ -28,7 +41,7 @@ def bind_template_variables(template: str, variables: InputVariables) -> str:
28
41
  )
29
42
 
30
43
  # When rendering mustache, do not escape HTML special characters.
31
- rendered: str = pystache.Renderer(escape=lambda s: s).render(template, variables)
44
+ rendered: str = StandardPystache().render(template, variables)
32
45
  return rendered
33
46
 
34
47
 
File without changes
File without changes
File without changes
File without changes