freeplay 0.2.42__py3-none-any.whl → 0.3.0a2__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 +10 -1
- freeplay/freeplay.py +26 -412
- freeplay/freeplay_cli.py +14 -28
- freeplay/model.py +6 -9
- freeplay/{thin/resources → resources}/prompts.py +97 -41
- freeplay/{thin/resources → resources}/recordings.py +5 -15
- freeplay/{thin/resources → resources}/test_runs.py +1 -1
- freeplay/support.py +57 -296
- freeplay/utils.py +15 -7
- {freeplay-0.2.42.dist-info → freeplay-0.3.0a2.dist-info}/METADATA +1 -3
- freeplay-0.3.0a2.dist-info/RECORD +20 -0
- {freeplay-0.2.42.dist-info → freeplay-0.3.0a2.dist-info}/WHEEL +1 -1
- freeplay/completions.py +0 -56
- freeplay/flavors.py +0 -459
- freeplay/provider_config.py +0 -49
- freeplay/py.typed +0 -0
- freeplay/record.py +0 -113
- freeplay/thin/__init__.py +0 -14
- freeplay/thin/freeplay_thin.py +0 -42
- freeplay-0.2.42.dist-info/RECORD +0 -27
- /freeplay/{thin/resources → resources}/__init__.py +0 -0
- /freeplay/{thin/resources → resources}/customer_feedback.py +0 -0
- /freeplay/{thin/resources → resources}/sessions.py +0 -0
- {freeplay-0.2.42.dist-info → freeplay-0.3.0a2.dist-info}/LICENSE +0 -0
- {freeplay-0.2.42.dist-info → freeplay-0.3.0a2.dist-info}/entry_points.txt +0 -0
freeplay/utils.py
CHANGED
@@ -1,19 +1,27 @@
|
|
1
|
-
from typing import Dict, Union, Optional
|
1
|
+
from typing import Dict, Union, Optional, Any
|
2
2
|
import importlib.metadata
|
3
3
|
import platform
|
4
4
|
|
5
5
|
import pystache # type: ignore
|
6
|
-
from pydantic import ValidationError
|
7
6
|
|
8
7
|
from .errors import FreeplayError, FreeplayConfigurationError
|
9
|
-
from .model import
|
8
|
+
from .model import InputVariables
|
9
|
+
|
10
|
+
|
11
|
+
# Validate that the variables are of the correct type, and do not include functions, dates, classes or None values.
|
12
|
+
def all_valid(obj: Any) -> bool:
|
13
|
+
if isinstance(obj, (int, str, bool)):
|
14
|
+
return True
|
15
|
+
elif isinstance(obj, list):
|
16
|
+
return all(all_valid(item) for item in obj)
|
17
|
+
elif isinstance(obj, dict):
|
18
|
+
return all(isinstance(key, str) and all_valid(value) for key, value in obj.items())
|
19
|
+
else:
|
20
|
+
return False
|
10
21
|
|
11
22
|
|
12
23
|
def bind_template_variables(template: str, variables: InputVariables) -> str:
|
13
|
-
|
14
|
-
try:
|
15
|
-
PydanticInputVariables.model_validate(variables)
|
16
|
-
except ValidationError as err:
|
24
|
+
if not all_valid(variables):
|
17
25
|
raise FreeplayError(
|
18
26
|
'Variables must be a string, number, bool, or a possibly nested'
|
19
27
|
' list or dict of strings, numbers and booleans.'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: freeplay
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0a2
|
4
4
|
Summary:
|
5
5
|
License: MIT
|
6
6
|
Author: FreePlay Engineering
|
@@ -13,10 +13,8 @@ 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
|
-
Requires-Dist: anthropic (>=0.20.0,<0.21.0)
|
17
16
|
Requires-Dist: click (==8.1.7)
|
18
17
|
Requires-Dist: dacite (>=1.8.0,<2.0.0)
|
19
|
-
Requires-Dist: openai (>=1,<2)
|
20
18
|
Requires-Dist: pystache (>=0.6.5,<0.7.0)
|
21
19
|
Requires-Dist: requests (>=2.20.0,<3.0.0dev)
|
22
20
|
Description-Content-Type: text/markdown
|
@@ -0,0 +1,20 @@
|
|
1
|
+
freeplay/__init__.py,sha256=_Uk0dUfn59IuToFeFXlhmA1-ULoA3uBWkg8DGbVomhw,346
|
2
|
+
freeplay/api_support.py,sha256=Tmc6VRcmPcIgXrDNJXz0VbBD969t5QHAyV3yZlMsYRI,2331
|
3
|
+
freeplay/errors.py,sha256=bPqsw32YX-xSr7O-G49M0sSFF7mq-YF1WGq928UV47s,631
|
4
|
+
freeplay/freeplay.py,sha256=vwtgLNcHnXTAjyuXU1cOjvYuLBqcZ-RQDAjdFAKh7q0,1479
|
5
|
+
freeplay/freeplay_cli.py,sha256=lmdsYwzdpWmUKHz_ieCzB-e6j1EnDHlVw3XIEyP_NEk,3460
|
6
|
+
freeplay/llm_parameters.py,sha256=bQbfuC8EICF0XMZQa5pwI3FkQqxmCUVqHO3gYHy3Tg8,898
|
7
|
+
freeplay/model.py,sha256=pC24wUsedD4RTI4k1BcYuDjizroeEHINH6FtEa_RLCk,384
|
8
|
+
freeplay/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
freeplay/resources/customer_feedback.py,sha256=aTM7Eez7iYmjXSpRqkHxf4pi6xBrzVnMiQCEJVfPGvg,527
|
10
|
+
freeplay/resources/prompts.py,sha256=MvrB7rAuHMzRyz6kH9NqfD5Yt33g-pmK7x0tVlyM2Cs,12476
|
11
|
+
freeplay/resources/recordings.py,sha256=6hrunYZtWFgGBZx4HzZRKcEYCzHbmaBVH48lBcXrL3w,5181
|
12
|
+
freeplay/resources/sessions.py,sha256=ioWdeTM9BSrEWKrFH66ysQIw5kCTlCVopJDmWtFgHz0,868
|
13
|
+
freeplay/resources/test_runs.py,sha256=2NAwoW_Yx7K8YM7QyQ3sv9dN6p0gjNgUAR7wSzLYP6w,1452
|
14
|
+
freeplay/support.py,sha256=7BizelIEIaSRFj2IL53-RQkjncUNlUS-DPUln2VxoJg,4532
|
15
|
+
freeplay/utils.py,sha256=8ZncuwCnzsAhRsaoxOMGa0Py8kXqGHlB9Avr3n79fk0,2064
|
16
|
+
freeplay-0.3.0a2.dist-info/LICENSE,sha256=_jzIw45hB1XHGxiQ8leZ0GH_X7bR_a8qgxaqnHbCUOo,1064
|
17
|
+
freeplay-0.3.0a2.dist-info/METADATA,sha256=btKL7QhbCkbZKOo_EplzS33-fsoSH8q66iAnaYI_1iY,1604
|
18
|
+
freeplay-0.3.0a2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
19
|
+
freeplay-0.3.0a2.dist-info/entry_points.txt,sha256=32s3rf2UUCqiJT4jnClEXZhdXlvl30uwpcxz-Gsy4UU,54
|
20
|
+
freeplay-0.3.0a2.dist-info/RECORD,,
|
freeplay/completions.py
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
from dataclasses import dataclass
|
2
|
-
from typing import Any, Dict, List, Optional, TypedDict
|
3
|
-
|
4
|
-
from openai.types.chat.chat_completion_chunk import ChoiceDeltaFunctionCall
|
5
|
-
|
6
|
-
from .llm_parameters import LLMParameters
|
7
|
-
|
8
|
-
|
9
|
-
class ChatMessage(TypedDict):
|
10
|
-
role: str
|
11
|
-
content: str
|
12
|
-
|
13
|
-
|
14
|
-
class OpenAIFunctionCall(TypedDict):
|
15
|
-
name: str
|
16
|
-
arguments: str
|
17
|
-
|
18
|
-
|
19
|
-
@dataclass
|
20
|
-
class CompletionResponse:
|
21
|
-
content: str
|
22
|
-
is_complete: bool
|
23
|
-
openai_function_call: Optional[OpenAIFunctionCall] = None
|
24
|
-
|
25
|
-
|
26
|
-
@dataclass
|
27
|
-
class ChatCompletionResponse:
|
28
|
-
content: str
|
29
|
-
is_complete: bool
|
30
|
-
message_history: List[ChatMessage]
|
31
|
-
|
32
|
-
|
33
|
-
@dataclass
|
34
|
-
class PromptTemplateWithMetadata:
|
35
|
-
prompt_template_id: str
|
36
|
-
prompt_template_version_id: str
|
37
|
-
|
38
|
-
name: str
|
39
|
-
content: str
|
40
|
-
flavor_name: Optional[str]
|
41
|
-
params: Optional[Dict[str, Any]]
|
42
|
-
|
43
|
-
def get_params(self) -> LLMParameters:
|
44
|
-
return LLMParameters.empty() if self.params is None else LLMParameters(self.params)
|
45
|
-
|
46
|
-
|
47
|
-
@dataclass
|
48
|
-
class PromptTemplates:
|
49
|
-
templates: List[PromptTemplateWithMetadata]
|
50
|
-
|
51
|
-
|
52
|
-
@dataclass
|
53
|
-
class CompletionChunk:
|
54
|
-
text: str
|
55
|
-
is_complete: bool
|
56
|
-
openai_function_call: Optional[ChoiceDeltaFunctionCall] = None
|
freeplay/flavors.py
DELETED
@@ -1,459 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from abc import abstractmethod, ABC
|
3
|
-
from copy import copy
|
4
|
-
from typing import cast, Any, Dict, Generator, List, Optional, Union
|
5
|
-
|
6
|
-
import anthropic
|
7
|
-
import openai
|
8
|
-
from openai import AuthenticationError, BadRequestError, Stream
|
9
|
-
from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessageParam
|
10
|
-
|
11
|
-
from .completions import CompletionChunk, PromptTemplateWithMetadata, CompletionResponse, ChatCompletionResponse, \
|
12
|
-
ChatMessage, OpenAIFunctionCall
|
13
|
-
from .errors import FreeplayConfigurationError, LLMClientError, LLMServerError, FreeplayError
|
14
|
-
from .llm_parameters import LLMParameters
|
15
|
-
from .model import InputVariables
|
16
|
-
from .provider_config import AnthropicConfig, AzureConfig, OpenAIConfig, ProviderConfig
|
17
|
-
from .utils import bind_template_variables
|
18
|
-
|
19
|
-
|
20
|
-
class Flavor(ABC):
|
21
|
-
@classmethod
|
22
|
-
def get_by_name(cls, flavor_name: str) -> 'Flavor':
|
23
|
-
if flavor_name == OpenAIChat.record_format_type:
|
24
|
-
return OpenAIChat()
|
25
|
-
elif flavor_name == AzureOpenAIChat.record_format_type:
|
26
|
-
return AzureOpenAIChat()
|
27
|
-
elif flavor_name == AnthropicClaudeChat.record_format_type:
|
28
|
-
return AnthropicClaudeChat()
|
29
|
-
else:
|
30
|
-
raise FreeplayConfigurationError(
|
31
|
-
f'Configured flavor ({flavor_name}) not found in SDK. Please update your SDK version or configure '
|
32
|
-
'a different model in the Freeplay UI.')
|
33
|
-
|
34
|
-
@property
|
35
|
-
@abstractmethod
|
36
|
-
def provider(self) -> str:
|
37
|
-
raise NotImplementedError()
|
38
|
-
|
39
|
-
@property
|
40
|
-
@abstractmethod
|
41
|
-
def record_format_type(self) -> str:
|
42
|
-
raise NotImplementedError()
|
43
|
-
|
44
|
-
@property
|
45
|
-
def _model_params_with_defaults(self) -> LLMParameters:
|
46
|
-
return LLMParameters.empty()
|
47
|
-
|
48
|
-
@abstractmethod
|
49
|
-
def format(self, prompt_template: PromptTemplateWithMetadata, variables: InputVariables) -> str:
|
50
|
-
pass
|
51
|
-
|
52
|
-
@abstractmethod
|
53
|
-
def to_llm_syntax(self, messages: List[ChatMessage]) -> Union[str, List[ChatMessage]]:
|
54
|
-
raise NotImplementedError()
|
55
|
-
|
56
|
-
@abstractmethod
|
57
|
-
def call_service(
|
58
|
-
self,
|
59
|
-
formatted_prompt: str,
|
60
|
-
provider_config: ProviderConfig,
|
61
|
-
llm_parameters: LLMParameters
|
62
|
-
) -> CompletionResponse:
|
63
|
-
pass
|
64
|
-
|
65
|
-
@abstractmethod
|
66
|
-
def call_service_stream(
|
67
|
-
self,
|
68
|
-
formatted_prompt: str,
|
69
|
-
provider_config: ProviderConfig,
|
70
|
-
llm_parameters: LLMParameters
|
71
|
-
) -> Generator[CompletionChunk, None, None]:
|
72
|
-
pass
|
73
|
-
|
74
|
-
def get_model_params(self, llm_parameters: LLMParameters) -> LLMParameters:
|
75
|
-
return self._model_params_with_defaults.merge_and_override(llm_parameters)
|
76
|
-
|
77
|
-
|
78
|
-
class ChatFlavor(Flavor, ABC):
|
79
|
-
@abstractmethod
|
80
|
-
def continue_chat(
|
81
|
-
self,
|
82
|
-
messages: List[ChatMessage],
|
83
|
-
provider_config: ProviderConfig,
|
84
|
-
llm_parameters: LLMParameters
|
85
|
-
) -> ChatCompletionResponse:
|
86
|
-
pass
|
87
|
-
|
88
|
-
@abstractmethod
|
89
|
-
def continue_chat_stream(
|
90
|
-
self,
|
91
|
-
messages: List[ChatMessage],
|
92
|
-
provider_config: ProviderConfig,
|
93
|
-
llm_parameters: LLMParameters
|
94
|
-
) -> Generator[CompletionChunk, None, None]:
|
95
|
-
pass
|
96
|
-
|
97
|
-
|
98
|
-
class OpenAIChatFlavor(ChatFlavor, ABC):
|
99
|
-
|
100
|
-
@abstractmethod
|
101
|
-
def _call_openai(
|
102
|
-
self,
|
103
|
-
messages: List[ChatMessage],
|
104
|
-
provider_config: ProviderConfig,
|
105
|
-
llm_parameters: LLMParameters,
|
106
|
-
stream: bool
|
107
|
-
) -> Union[ChatCompletion, openai.Stream[ChatCompletionChunk]]:
|
108
|
-
pass
|
109
|
-
|
110
|
-
def format(self, prompt_template: PromptTemplateWithMetadata, variables: InputVariables) -> str:
|
111
|
-
# Extract messages JSON to enable formatting of individual content fields of each message. If we do not
|
112
|
-
# extract the JSON, current variable interpolation will fail on JSON curly braces.
|
113
|
-
messages_as_json: List[Dict[str, str]] = json.loads(prompt_template.content)
|
114
|
-
formatted_messages = [
|
115
|
-
{
|
116
|
-
"content": bind_template_variables(message['content'], variables), "role": message['role']
|
117
|
-
} for message in messages_as_json]
|
118
|
-
return json.dumps(formatted_messages)
|
119
|
-
|
120
|
-
def to_llm_syntax(self, messages: List[ChatMessage]) -> List[ChatMessage]:
|
121
|
-
return messages
|
122
|
-
|
123
|
-
def call_service(
|
124
|
-
self,
|
125
|
-
formatted_prompt: str,
|
126
|
-
provider_config: ProviderConfig,
|
127
|
-
llm_parameters: LLMParameters
|
128
|
-
) -> CompletionResponse:
|
129
|
-
messages = json.loads(formatted_prompt)
|
130
|
-
completion = cast(ChatCompletion, self._call_openai(messages, provider_config, llm_parameters, stream=False))
|
131
|
-
|
132
|
-
return CompletionResponse(
|
133
|
-
content=completion.choices[0].message.content or '',
|
134
|
-
is_complete=completion.choices[0].finish_reason == 'stop',
|
135
|
-
openai_function_call=self.__maybe_function_call(completion),
|
136
|
-
)
|
137
|
-
|
138
|
-
# noinspection PyMethodMayBeStatic
|
139
|
-
def __maybe_function_call(self, completion: ChatCompletion) -> Optional[OpenAIFunctionCall]:
|
140
|
-
maybe_function_call = completion.choices[0].message.function_call
|
141
|
-
if maybe_function_call:
|
142
|
-
return OpenAIFunctionCall(
|
143
|
-
name=maybe_function_call.name,
|
144
|
-
arguments=maybe_function_call.arguments
|
145
|
-
)
|
146
|
-
return None
|
147
|
-
|
148
|
-
def call_service_stream(
|
149
|
-
self,
|
150
|
-
formatted_prompt: str,
|
151
|
-
provider_config: ProviderConfig,
|
152
|
-
llm_parameters: LLMParameters
|
153
|
-
) -> Generator[CompletionChunk, None, None]:
|
154
|
-
messages = json.loads(formatted_prompt)
|
155
|
-
completion_stream = cast(Stream[ChatCompletionChunk],
|
156
|
-
self._call_openai(messages, provider_config, llm_parameters, stream=True))
|
157
|
-
for chunk in completion_stream:
|
158
|
-
yield CompletionChunk(
|
159
|
-
text=chunk.choices[0].delta.content or '',
|
160
|
-
is_complete=chunk.choices[0].finish_reason == 'stop',
|
161
|
-
openai_function_call=chunk.choices[0].delta.function_call
|
162
|
-
)
|
163
|
-
|
164
|
-
def continue_chat(
|
165
|
-
self,
|
166
|
-
messages: List[ChatMessage],
|
167
|
-
provider_config: ProviderConfig,
|
168
|
-
llm_parameters: LLMParameters
|
169
|
-
) -> ChatCompletionResponse:
|
170
|
-
completion = cast(ChatCompletion, self._call_openai(messages, provider_config, llm_parameters, stream=False))
|
171
|
-
|
172
|
-
message_history = copy(messages)
|
173
|
-
message = completion.choices[0].message
|
174
|
-
message_history.append(ChatMessage(
|
175
|
-
role=message.role or '',
|
176
|
-
content=message.content or ''
|
177
|
-
))
|
178
|
-
return ChatCompletionResponse(
|
179
|
-
content=message.content or '',
|
180
|
-
message_history=message_history,
|
181
|
-
is_complete=completion.choices[0].finish_reason == "stop"
|
182
|
-
)
|
183
|
-
|
184
|
-
def continue_chat_stream(
|
185
|
-
self,
|
186
|
-
messages: List[ChatMessage],
|
187
|
-
provider_config: ProviderConfig,
|
188
|
-
llm_parameters: LLMParameters
|
189
|
-
) -> Generator[CompletionChunk, None, None]:
|
190
|
-
completion_stream = cast(Stream[ChatCompletionChunk],
|
191
|
-
self._call_openai(messages, provider_config, llm_parameters, stream=True))
|
192
|
-
for chunk in completion_stream:
|
193
|
-
yield CompletionChunk(
|
194
|
-
text=chunk.choices[0].delta.content or '',
|
195
|
-
is_complete=chunk.choices[0].finish_reason == "stop"
|
196
|
-
)
|
197
|
-
|
198
|
-
|
199
|
-
class OpenAIChat(OpenAIChatFlavor):
|
200
|
-
record_format_type = "openai_chat"
|
201
|
-
_model_params_with_defaults = LLMParameters({
|
202
|
-
"model": "gpt-3.5-turbo"
|
203
|
-
})
|
204
|
-
|
205
|
-
def __init__(self) -> None:
|
206
|
-
self.client: Optional[openai.OpenAI] = None
|
207
|
-
|
208
|
-
@property
|
209
|
-
def provider(self) -> str:
|
210
|
-
return "openai"
|
211
|
-
|
212
|
-
def get_openai_client(self, openai_config: Optional[OpenAIConfig]) -> openai.OpenAI:
|
213
|
-
if self.client:
|
214
|
-
return self.client
|
215
|
-
|
216
|
-
if not openai_config:
|
217
|
-
raise FreeplayConfigurationError(
|
218
|
-
"Missing OpenAI key. Use a ProviderConfig to specify keys prior to getting completion.")
|
219
|
-
|
220
|
-
self.client = openai.OpenAI(api_key=openai_config.api_key, base_url=openai_config.base_url)
|
221
|
-
return self.client
|
222
|
-
|
223
|
-
def _call_openai(
|
224
|
-
self,
|
225
|
-
messages: List[ChatMessage],
|
226
|
-
provider_config: ProviderConfig,
|
227
|
-
llm_parameters: LLMParameters,
|
228
|
-
stream: bool
|
229
|
-
) -> Union[ChatCompletion, openai.Stream[ChatCompletionChunk]]:
|
230
|
-
client = self.get_openai_client(provider_config.openai)
|
231
|
-
try:
|
232
|
-
return client.chat.completions.create(
|
233
|
-
messages=cast(List[ChatCompletionMessageParam], messages),
|
234
|
-
**self.get_model_params(llm_parameters),
|
235
|
-
stream=stream,
|
236
|
-
)
|
237
|
-
except (BadRequestError, AuthenticationError) as e:
|
238
|
-
raise LLMClientError("Unable to call OpenAI") from e
|
239
|
-
except Exception as e:
|
240
|
-
raise LLMServerError("Unable to call OpenAI") from e
|
241
|
-
|
242
|
-
|
243
|
-
class AzureOpenAIChat(OpenAIChatFlavor):
|
244
|
-
record_format_type = "azure_openai_chat"
|
245
|
-
|
246
|
-
def __init__(self) -> None:
|
247
|
-
self.client: Optional[openai.AzureOpenAI] = None
|
248
|
-
|
249
|
-
@property
|
250
|
-
def provider(self) -> str:
|
251
|
-
return "azure"
|
252
|
-
|
253
|
-
def get_azure_client(
|
254
|
-
self,
|
255
|
-
azure_config: Optional[AzureConfig],
|
256
|
-
api_version: Optional[str] = None,
|
257
|
-
endpoint: Optional[str] = None,
|
258
|
-
deployment: Optional[str] = None,
|
259
|
-
) -> openai.AzureOpenAI:
|
260
|
-
if self.client:
|
261
|
-
return self.client
|
262
|
-
|
263
|
-
if not azure_config:
|
264
|
-
raise FreeplayConfigurationError(
|
265
|
-
"Missing Azure key. Use a ProviderConfig to specify keys prior to getting completion.")
|
266
|
-
|
267
|
-
self.client = openai.AzureOpenAI(
|
268
|
-
api_key=azure_config.api_key,
|
269
|
-
api_version=api_version,
|
270
|
-
azure_endpoint=endpoint or '',
|
271
|
-
azure_deployment=deployment,
|
272
|
-
)
|
273
|
-
return self.client
|
274
|
-
|
275
|
-
def _call_openai(
|
276
|
-
self,
|
277
|
-
messages: List[ChatMessage],
|
278
|
-
provider_config: ProviderConfig,
|
279
|
-
llm_parameters: LLMParameters,
|
280
|
-
stream: bool
|
281
|
-
) -> Any:
|
282
|
-
api_version = llm_parameters.get('api_version')
|
283
|
-
deployment_id = llm_parameters.get('deployment_id')
|
284
|
-
resource_name = llm_parameters.get('resource_name')
|
285
|
-
endpoint = f'https://{resource_name}.openai.azure.com'
|
286
|
-
llm_parameters.pop('resource_name')
|
287
|
-
|
288
|
-
client = self.get_azure_client(
|
289
|
-
azure_config=provider_config.azure,
|
290
|
-
api_version=api_version,
|
291
|
-
endpoint=endpoint,
|
292
|
-
deployment=deployment_id,
|
293
|
-
)
|
294
|
-
|
295
|
-
try:
|
296
|
-
return client.chat.completions.create(
|
297
|
-
messages=cast(List[ChatCompletionMessageParam], messages),
|
298
|
-
**self.get_model_params(llm_parameters),
|
299
|
-
stream=stream,
|
300
|
-
)
|
301
|
-
except (BadRequestError, AuthenticationError) as e:
|
302
|
-
raise LLMClientError("Unable to call Azure") from e
|
303
|
-
except Exception as e:
|
304
|
-
raise LLMServerError("Unable to call Azure") from e
|
305
|
-
|
306
|
-
|
307
|
-
class AnthropicClaudeChat(ChatFlavor):
|
308
|
-
record_format_type = "anthropic_chat"
|
309
|
-
_model_params_with_defaults = LLMParameters({
|
310
|
-
"model": "claude-2",
|
311
|
-
"max_tokens_to_sample": 100
|
312
|
-
})
|
313
|
-
|
314
|
-
def __init__(self) -> None:
|
315
|
-
self.client: Optional[anthropic.Anthropic] = None
|
316
|
-
|
317
|
-
@property
|
318
|
-
def provider(self) -> str:
|
319
|
-
return "anthropic"
|
320
|
-
|
321
|
-
def get_anthropic_client(self, anthropic_config: Optional[AnthropicConfig]) -> anthropic.Client:
|
322
|
-
if self.client:
|
323
|
-
return self.client
|
324
|
-
|
325
|
-
if not anthropic_config:
|
326
|
-
raise FreeplayConfigurationError(
|
327
|
-
"Missing Anthropic key. Use a ProviderConfig to specify keys prior to getting completion.")
|
328
|
-
|
329
|
-
self.client = anthropic.Client(api_key=anthropic_config.api_key)
|
330
|
-
return self.client
|
331
|
-
|
332
|
-
# This just formats the prompt for uploading to the record endpoint.
|
333
|
-
# TODO: Move this to a base class.
|
334
|
-
def format(self, prompt_template: PromptTemplateWithMetadata, variables: InputVariables) -> str:
|
335
|
-
# Extract messages JSON to enable formatting of individual content fields of each message. If we do not
|
336
|
-
# extract the JSON, current variable interpolation will fail on JSON curly braces.
|
337
|
-
messages_as_json: List[Dict[str, str]] = json.loads(prompt_template.content)
|
338
|
-
formatted_messages = [
|
339
|
-
{
|
340
|
-
"content": bind_template_variables(message['content'], variables),
|
341
|
-
"role": self.__to_anthropic_role(message['role'])
|
342
|
-
} for message in messages_as_json]
|
343
|
-
return json.dumps(formatted_messages)
|
344
|
-
|
345
|
-
def to_llm_syntax(self, messages: List[ChatMessage]) -> str:
|
346
|
-
formatted_messages = [
|
347
|
-
ChatMessage(
|
348
|
-
content=message['content'],
|
349
|
-
role=self.__to_anthropic_role(message['role'])
|
350
|
-
) for message in messages
|
351
|
-
]
|
352
|
-
return self.__to_anthropic_chat_format(formatted_messages)
|
353
|
-
|
354
|
-
@staticmethod
|
355
|
-
def __to_anthropic_role(role: str) -> str:
|
356
|
-
if role == 'Human':
|
357
|
-
return 'Human'
|
358
|
-
elif role == 'assistant' or role == 'Assistant':
|
359
|
-
return 'Assistant'
|
360
|
-
else:
|
361
|
-
# Anthropic does not support system role for now.
|
362
|
-
return 'Human'
|
363
|
-
|
364
|
-
@staticmethod
|
365
|
-
def __to_anthropic_chat_format(messages: List[ChatMessage]) -> str:
|
366
|
-
formatted_messages = []
|
367
|
-
for message in messages:
|
368
|
-
formatted_messages.append(f"{message['role']}: {message['content']}")
|
369
|
-
formatted_messages.append('Assistant:')
|
370
|
-
|
371
|
-
return "\n\n" + "\n\n".join(formatted_messages)
|
372
|
-
|
373
|
-
def continue_chat(
|
374
|
-
self,
|
375
|
-
messages: List[ChatMessage],
|
376
|
-
provider_config: ProviderConfig,
|
377
|
-
llm_parameters: LLMParameters
|
378
|
-
) -> ChatCompletionResponse:
|
379
|
-
formatted_prompt = self.__to_anthropic_chat_format(messages)
|
380
|
-
try:
|
381
|
-
client = self.get_anthropic_client(provider_config.anthropic)
|
382
|
-
completion = client.completions.create(
|
383
|
-
prompt=formatted_prompt,
|
384
|
-
**self.get_model_params(llm_parameters)
|
385
|
-
)
|
386
|
-
content = completion.completion
|
387
|
-
message_history = messages + [{"role": "assistant", "content": content}]
|
388
|
-
return ChatCompletionResponse(
|
389
|
-
content=content,
|
390
|
-
is_complete=completion.stop_reason == 'stop_sequence',
|
391
|
-
message_history=message_history,
|
392
|
-
)
|
393
|
-
except anthropic.APIError as e:
|
394
|
-
raise FreeplayError("Error calling Anthropic") from e
|
395
|
-
|
396
|
-
def continue_chat_stream(
|
397
|
-
self,
|
398
|
-
messages: List[ChatMessage],
|
399
|
-
provider_config: ProviderConfig,
|
400
|
-
llm_parameters: LLMParameters
|
401
|
-
) -> Generator[CompletionChunk, None, None]:
|
402
|
-
formatted_prompt = self.__to_anthropic_chat_format(messages)
|
403
|
-
try:
|
404
|
-
client = self.get_anthropic_client(provider_config.anthropic)
|
405
|
-
anthropic_response = client.completions.create(
|
406
|
-
prompt=formatted_prompt,
|
407
|
-
stream=True,
|
408
|
-
**self.get_model_params(llm_parameters)
|
409
|
-
)
|
410
|
-
|
411
|
-
for chunk in anthropic_response:
|
412
|
-
yield CompletionChunk(
|
413
|
-
text=chunk.completion,
|
414
|
-
is_complete=chunk.stop_reason == 'stop_sequence'
|
415
|
-
)
|
416
|
-
except anthropic.APIError as e:
|
417
|
-
raise FreeplayError("Error calling Anthropic") from e
|
418
|
-
|
419
|
-
def call_service(self, formatted_prompt: str, provider_config: ProviderConfig,
|
420
|
-
llm_parameters: LLMParameters) -> CompletionResponse:
|
421
|
-
messages = json.loads(formatted_prompt)
|
422
|
-
completion = self.continue_chat(messages, provider_config, llm_parameters)
|
423
|
-
return CompletionResponse(
|
424
|
-
content=completion.content,
|
425
|
-
is_complete=completion.is_complete,
|
426
|
-
)
|
427
|
-
|
428
|
-
def call_service_stream(
|
429
|
-
self,
|
430
|
-
formatted_prompt: str,
|
431
|
-
provider_config: ProviderConfig,
|
432
|
-
llm_parameters: LLMParameters
|
433
|
-
) -> Generator[CompletionChunk, None, None]:
|
434
|
-
messages = json.loads(formatted_prompt)
|
435
|
-
return self.continue_chat_stream(messages, provider_config, llm_parameters)
|
436
|
-
|
437
|
-
|
438
|
-
def pick_flavor_from_config(completion_flavor: Optional[Flavor], ui_flavor_name: Optional[str]) -> Flavor:
|
439
|
-
ui_flavor = Flavor.get_by_name(ui_flavor_name) if ui_flavor_name else None
|
440
|
-
flavor = completion_flavor or ui_flavor
|
441
|
-
|
442
|
-
if flavor is None:
|
443
|
-
raise FreeplayConfigurationError(
|
444
|
-
"Flavor must be configured on either the Freeplay client, completion call, "
|
445
|
-
"or in the Freeplay UI. Unable to fulfill request.")
|
446
|
-
|
447
|
-
return flavor
|
448
|
-
|
449
|
-
|
450
|
-
def get_chat_flavor_from_config(completion_flavor: Optional[Flavor], ui_flavor_name: Optional[str]) -> ChatFlavor:
|
451
|
-
flavor = pick_flavor_from_config(completion_flavor, ui_flavor_name)
|
452
|
-
return require_chat_flavor(flavor)
|
453
|
-
|
454
|
-
|
455
|
-
def require_chat_flavor(flavor: Flavor) -> ChatFlavor:
|
456
|
-
if not isinstance(flavor, ChatFlavor):
|
457
|
-
raise FreeplayConfigurationError('A Chat flavor is required to start a chat session.')
|
458
|
-
|
459
|
-
return flavor
|
freeplay/provider_config.py
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
from dataclasses import dataclass
|
2
|
-
from typing import Optional
|
3
|
-
|
4
|
-
from .errors import FreeplayConfigurationError
|
5
|
-
|
6
|
-
|
7
|
-
@dataclass
|
8
|
-
class OpenAIConfig:
|
9
|
-
api_key: str
|
10
|
-
base_url: Optional[str] = None
|
11
|
-
|
12
|
-
def validate(self) -> None:
|
13
|
-
if not self.api_key or not self.api_key.strip():
|
14
|
-
raise FreeplayConfigurationError("OpenAI API key not set. It must be set to make calls to the service.")
|
15
|
-
|
16
|
-
|
17
|
-
@dataclass
|
18
|
-
class AzureConfig(OpenAIConfig):
|
19
|
-
engine: Optional[str] = None
|
20
|
-
api_version: Optional[str] = None
|
21
|
-
|
22
|
-
def validate(self) -> None:
|
23
|
-
super().validate()
|
24
|
-
|
25
|
-
if self.api_version is None:
|
26
|
-
raise FreeplayConfigurationError(
|
27
|
-
"OpenAI API version not set. It must be set to make calls to the service.")
|
28
|
-
|
29
|
-
if self.engine is None:
|
30
|
-
raise FreeplayConfigurationError("Azure engine is not set. It must be set to make calls to the service.")
|
31
|
-
|
32
|
-
|
33
|
-
@dataclass
|
34
|
-
class AnthropicConfig:
|
35
|
-
api_key: str
|
36
|
-
|
37
|
-
|
38
|
-
@dataclass
|
39
|
-
class ProviderConfig:
|
40
|
-
anthropic: Optional[AnthropicConfig] = None
|
41
|
-
openai: Optional[OpenAIConfig] = None
|
42
|
-
azure: Optional[AzureConfig] = None
|
43
|
-
|
44
|
-
def validate(self) -> None:
|
45
|
-
if all(config is None for config in [self.anthropic, self.openai, self.azure]):
|
46
|
-
FreeplayConfigurationError("At least one provider key must be set in ProviderConfig.")
|
47
|
-
|
48
|
-
if self.openai is not None:
|
49
|
-
self.openai.validate()
|
freeplay/py.typed
DELETED
File without changes
|