prompty 0.1.17__tar.gz → 0.1.19__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {prompty-0.1.17 → prompty-0.1.19}/PKG-INFO +1 -1
- prompty-0.1.19/prompty/azure/executor.py +120 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/core.py +3 -1
- prompty-0.1.19/prompty/openai/executor.py +98 -0
- prompty-0.1.19/prompty/serverless/executor.py +131 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/tracer.py +67 -12
- {prompty-0.1.17 → prompty-0.1.19}/pyproject.toml +1 -1
- {prompty-0.1.17 → prompty-0.1.19}/tests/test_execute.py +2 -1
- prompty-0.1.17/prompty/azure/executor.py +0 -95
- prompty-0.1.17/prompty/openai/executor.py +0 -74
- prompty-0.1.17/prompty/serverless/executor.py +0 -84
- {prompty-0.1.17 → prompty-0.1.19}/LICENSE +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/README.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/__init__.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/azure/__init__.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/azure/processor.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/cli.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/openai/__init__.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/openai/processor.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/parsers.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/renderers.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/serverless/__init__.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/prompty/serverless/processor.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/fake_azure_executor.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/fake_serverless_executor.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/1contoso.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/2contoso.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/3contoso.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/4contoso.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/basic.prompty.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/camping.jpg +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/context.prompty.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/contoso_multi.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/faithfulness.prompty.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/generated/groundedness.prompty.md +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/hello_world-goodbye_world-hello_again.embedding.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/hello_world.embedding.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/__init__.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/basic.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/basic.prompty.execution.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/basic_json_output.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/camping.jpg +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/chat.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/context.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/context.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/context.prompty.execution.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/embedding.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/embedding.prompty.execution.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/evaluation.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/faithfulness.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/faithfulness.prompty.execution.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/fake.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/funcfile.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/funcfile.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/functions.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/functions.prompty.execution.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/groundedness.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/groundedness.prompty.execution.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/prompty.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/serverless.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/serverless.prompty.execution.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/serverless_stream.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/serverless_stream.prompty.execution.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/streaming.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/streaming.prompty.execution.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/sub/__init__.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/sub/basic.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/sub/sub/__init__.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/sub/sub/basic.prompty +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/sub/sub/prompty.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/sub/sub/test.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompts/test.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/prompty.json +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/test_common.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/test_factory_invoker.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/test_path_exec.py +0 -0
- {prompty-0.1.17 → prompty-0.1.19}/tests/test_tracing.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: prompty
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.19
|
4
4
|
Summary: Prompty is a new asset class and format for LLM prompts that aims to provide observability, understandability, and portability for developers. It includes spec, tooling, and a runtime. This Prompty runtime supports Python
|
5
5
|
Author-Email: Seth Juarez <seth.juarez@microsoft.com>
|
6
6
|
Requires-Dist: pyyaml>=6.0.1
|
@@ -0,0 +1,120 @@
|
|
1
|
+
import azure.identity
|
2
|
+
import importlib.metadata
|
3
|
+
from typing import Iterator
|
4
|
+
from openai import AzureOpenAI
|
5
|
+
|
6
|
+
from prompty.tracer import Tracer
|
7
|
+
from ..core import Invoker, InvokerFactory, Prompty, PromptyStream
|
8
|
+
|
9
|
+
VERSION = importlib.metadata.version("prompty")
|
10
|
+
|
11
|
+
|
12
|
+
@InvokerFactory.register_executor("azure")
|
13
|
+
@InvokerFactory.register_executor("azure_openai")
|
14
|
+
class AzureOpenAIExecutor(Invoker):
|
15
|
+
"""Azure OpenAI Executor"""
|
16
|
+
|
17
|
+
def __init__(self, prompty: Prompty) -> None:
|
18
|
+
super().__init__(prompty)
|
19
|
+
self.kwargs = {
|
20
|
+
key: value
|
21
|
+
for key, value in self.prompty.model.configuration.items()
|
22
|
+
if key != "type"
|
23
|
+
}
|
24
|
+
|
25
|
+
# no key, use default credentials
|
26
|
+
if "api_key" not in self.kwargs:
|
27
|
+
# managed identity if client id
|
28
|
+
if "client_id" in self.kwargs:
|
29
|
+
default_credential = azure.identity.ManagedIdentityCredential(
|
30
|
+
client_id=self.kwargs.pop("client_id"),
|
31
|
+
)
|
32
|
+
# default credential
|
33
|
+
else:
|
34
|
+
default_credential = azure.identity.DefaultAzureCredential(
|
35
|
+
exclude_shared_token_cache_credential=True
|
36
|
+
)
|
37
|
+
|
38
|
+
self.kwargs["azure_ad_token_provider"] = (
|
39
|
+
azure.identity.get_bearer_token_provider(
|
40
|
+
default_credential, "https://cognitiveservices.azure.com/.default"
|
41
|
+
)
|
42
|
+
)
|
43
|
+
|
44
|
+
self.api = self.prompty.model.api
|
45
|
+
self.deployment = self.prompty.model.configuration["azure_deployment"]
|
46
|
+
self.parameters = self.prompty.model.parameters
|
47
|
+
|
48
|
+
def invoke(self, data: any) -> any:
|
49
|
+
"""Invoke the Azure OpenAI API
|
50
|
+
|
51
|
+
Parameters
|
52
|
+
----------
|
53
|
+
data : any
|
54
|
+
The data to send to the Azure OpenAI API
|
55
|
+
|
56
|
+
Returns
|
57
|
+
-------
|
58
|
+
any
|
59
|
+
The response from the Azure OpenAI API
|
60
|
+
"""
|
61
|
+
|
62
|
+
with Tracer.start("AzureOpenAI") as trace:
|
63
|
+
trace("type", "LLM")
|
64
|
+
trace("signature", "AzureOpenAI.ctor")
|
65
|
+
trace("description", "Azure OpenAI Constructor")
|
66
|
+
trace("inputs", self.kwargs)
|
67
|
+
client = AzureOpenAI(
|
68
|
+
default_headers={
|
69
|
+
"User-Agent": f"prompty/{VERSION}",
|
70
|
+
"x-ms-useragent": f"prompty/{VERSION}",
|
71
|
+
},
|
72
|
+
**self.kwargs,
|
73
|
+
)
|
74
|
+
trace("result", client)
|
75
|
+
|
76
|
+
with Tracer.start("create") as trace:
|
77
|
+
trace("type", "LLM")
|
78
|
+
trace("description", "Azure OpenAI Client")
|
79
|
+
|
80
|
+
if self.api == "chat":
|
81
|
+
trace("signature", "AzureOpenAI.chat.completions.create")
|
82
|
+
args = {
|
83
|
+
"model": self.deployment,
|
84
|
+
"messages": data if isinstance(data, list) else [data],
|
85
|
+
**self.parameters,
|
86
|
+
}
|
87
|
+
trace("inputs", args)
|
88
|
+
response = client.chat.completions.create(**args)
|
89
|
+
trace("result", response)
|
90
|
+
|
91
|
+
elif self.api == "completion":
|
92
|
+
trace("signature", "AzureOpenAI.completions.create")
|
93
|
+
args = {
|
94
|
+
"prompt": data.item,
|
95
|
+
"model": self.deployment,
|
96
|
+
**self.parameters,
|
97
|
+
}
|
98
|
+
trace("inputs", args)
|
99
|
+
response = client.completions.create(**args)
|
100
|
+
trace("result", response)
|
101
|
+
|
102
|
+
elif self.api == "embedding":
|
103
|
+
trace("signature", "AzureOpenAI.embeddings.create")
|
104
|
+
args = {
|
105
|
+
"input": data if isinstance(data, list) else [data],
|
106
|
+
"model": self.deployment,
|
107
|
+
**self.parameters,
|
108
|
+
}
|
109
|
+
trace("inputs", args)
|
110
|
+
response = client.embeddings.create(**args)
|
111
|
+
trace("result", response)
|
112
|
+
|
113
|
+
elif self.api == "image":
|
114
|
+
raise NotImplementedError("Azure OpenAI Image API is not implemented yet")
|
115
|
+
|
116
|
+
# stream response
|
117
|
+
if isinstance(response, Iterator):
|
118
|
+
return PromptyStream("AzureOpenAIExecutor", response)
|
119
|
+
else:
|
120
|
+
return response
|
@@ -529,7 +529,9 @@ class PromptyStream(Iterator):
|
|
529
529
|
# StopIteration is raised
|
530
530
|
# contents are exhausted
|
531
531
|
if len(self.items) > 0:
|
532
|
-
with Tracer.start(
|
532
|
+
with Tracer.start("PromptyStream") as trace:
|
533
|
+
trace("signature", f"{self.name}.PromptyStream")
|
534
|
+
trace("inputs", "None")
|
533
535
|
trace("result", [to_dict(s) for s in self.items])
|
534
536
|
|
535
537
|
raise StopIteration
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import importlib.metadata
|
2
|
+
from openai import OpenAI
|
3
|
+
from typing import Iterator
|
4
|
+
|
5
|
+
from prompty.tracer import Tracer
|
6
|
+
from ..core import Invoker, InvokerFactory, Prompty, PromptyStream
|
7
|
+
|
8
|
+
VERSION = importlib.metadata.version("prompty")
|
9
|
+
|
10
|
+
|
11
|
+
@InvokerFactory.register_executor("openai")
|
12
|
+
class OpenAIExecutor(Invoker):
|
13
|
+
"""OpenAI Executor"""
|
14
|
+
|
15
|
+
def __init__(self, prompty: Prompty) -> None:
|
16
|
+
super().__init__(prompty)
|
17
|
+
self.kwargs = {
|
18
|
+
key: value
|
19
|
+
for key, value in self.prompty.model.configuration.items()
|
20
|
+
if key != "type"
|
21
|
+
}
|
22
|
+
|
23
|
+
self.api = self.prompty.model.api
|
24
|
+
self.deployment = self.prompty.model.configuration["azure_deployment"]
|
25
|
+
self.parameters = self.prompty.model.parameters
|
26
|
+
|
27
|
+
def invoke(self, data: any) -> any:
|
28
|
+
"""Invoke the OpenAI API
|
29
|
+
|
30
|
+
Parameters
|
31
|
+
----------
|
32
|
+
data : any
|
33
|
+
The data to send to the OpenAI API
|
34
|
+
|
35
|
+
Returns
|
36
|
+
-------
|
37
|
+
any
|
38
|
+
The response from the OpenAI API
|
39
|
+
"""
|
40
|
+
with Tracer.start("OpenAI") as trace:
|
41
|
+
trace("type", "LLM")
|
42
|
+
trace("signature", "OpenAI.ctor")
|
43
|
+
trace("description", "OpenAI Constructor")
|
44
|
+
trace("inputs", self.kwargs)
|
45
|
+
client = OpenAI(
|
46
|
+
default_headers={
|
47
|
+
"User-Agent": f"prompty/{VERSION}",
|
48
|
+
"x-ms-useragent": f"prompty/{VERSION}",
|
49
|
+
},
|
50
|
+
**self.kwargs,
|
51
|
+
)
|
52
|
+
trace("result", client)
|
53
|
+
|
54
|
+
with Tracer.start("create") as trace:
|
55
|
+
trace("type", "LLM")
|
56
|
+
trace("description", "OpenAI Prompty Execution Invoker")
|
57
|
+
|
58
|
+
if self.api == "chat":
|
59
|
+
trace("signature", "OpenAI.chat.completions.create")
|
60
|
+
args = {
|
61
|
+
"model": self.deployment,
|
62
|
+
"messages": data if isinstance(data, list) else [data],
|
63
|
+
**self.parameters,
|
64
|
+
}
|
65
|
+
trace("inputs", args)
|
66
|
+
response = client.chat.completions.create(**args)
|
67
|
+
|
68
|
+
elif self.api == "completion":
|
69
|
+
trace("signature", "OpenAI.completions.create")
|
70
|
+
args = {
|
71
|
+
"prompt": data.item,
|
72
|
+
"model": self.deployment,
|
73
|
+
**self.parameters,
|
74
|
+
}
|
75
|
+
trace("inputs", args)
|
76
|
+
response = client.completions.create(**args)
|
77
|
+
|
78
|
+
elif self.api == "embedding":
|
79
|
+
trace("signature", "OpenAI.embeddings.create")
|
80
|
+
args = {
|
81
|
+
"input": data if isinstance(data, list) else [data],
|
82
|
+
"model": self.deployment,
|
83
|
+
**self.parameters,
|
84
|
+
}
|
85
|
+
trace("inputs", args)
|
86
|
+
response = client.embeddings.create(**args)
|
87
|
+
|
88
|
+
elif self.api == "image":
|
89
|
+
raise NotImplementedError("OpenAI Image API is not implemented yet")
|
90
|
+
|
91
|
+
# stream response
|
92
|
+
if isinstance(response, Iterator):
|
93
|
+
stream = PromptyStream("AzureOpenAIExecutor", response)
|
94
|
+
trace("result", stream)
|
95
|
+
return stream
|
96
|
+
else:
|
97
|
+
trace("result", response)
|
98
|
+
return response
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import importlib.metadata
|
2
|
+
from typing import Iterator
|
3
|
+
from azure.core.credentials import AzureKeyCredential
|
4
|
+
from azure.ai.inference import (
|
5
|
+
ChatCompletionsClient,
|
6
|
+
EmbeddingsClient,
|
7
|
+
)
|
8
|
+
from azure.ai.inference.models import (
|
9
|
+
StreamingChatCompletions,
|
10
|
+
AsyncStreamingChatCompletions,
|
11
|
+
)
|
12
|
+
|
13
|
+
from prompty.tracer import Tracer
|
14
|
+
from ..core import Invoker, InvokerFactory, Prompty, PromptyStream, AsyncPromptyStream
|
15
|
+
|
16
|
+
VERSION = importlib.metadata.version("prompty")
|
17
|
+
|
18
|
+
|
19
|
+
@InvokerFactory.register_executor("serverless")
|
20
|
+
class ServerlessExecutor(Invoker):
|
21
|
+
"""Azure OpenAI Executor"""
|
22
|
+
|
23
|
+
def __init__(self, prompty: Prompty) -> None:
|
24
|
+
super().__init__(prompty)
|
25
|
+
|
26
|
+
# serverless configuration
|
27
|
+
self.endpoint = self.prompty.model.configuration["endpoint"]
|
28
|
+
self.model = self.prompty.model.configuration["model"]
|
29
|
+
self.key = self.prompty.model.configuration["key"]
|
30
|
+
|
31
|
+
# api type
|
32
|
+
self.api = self.prompty.model.api
|
33
|
+
|
34
|
+
def _response(self, response: any) -> any:
|
35
|
+
# stream response
|
36
|
+
if isinstance(response, Iterator):
|
37
|
+
if isinstance(response, StreamingChatCompletions):
|
38
|
+
stream = PromptyStream("ServerlessExecutor", response)
|
39
|
+
return stream
|
40
|
+
elif isinstance(response, AsyncStreamingChatCompletions):
|
41
|
+
stream = AsyncPromptyStream("ServerlessExecutor", response)
|
42
|
+
return stream
|
43
|
+
else:
|
44
|
+
stream = PromptyStream("ServerlessExecutor", response)
|
45
|
+
|
46
|
+
return stream
|
47
|
+
else:
|
48
|
+
return response
|
49
|
+
|
50
|
+
def invoke(self, data: any) -> any:
|
51
|
+
"""Invoke the Serverless SDK
|
52
|
+
|
53
|
+
Parameters
|
54
|
+
----------
|
55
|
+
data : any
|
56
|
+
The data to send to the Serverless SDK
|
57
|
+
|
58
|
+
Returns
|
59
|
+
-------
|
60
|
+
any
|
61
|
+
The response from the Serverless SDK
|
62
|
+
"""
|
63
|
+
|
64
|
+
cargs = {
|
65
|
+
"endpoint": self.endpoint,
|
66
|
+
"credential": AzureKeyCredential(self.key),
|
67
|
+
}
|
68
|
+
|
69
|
+
if self.api == "chat":
|
70
|
+
with Tracer.start("ChatCompletionsClient") as trace:
|
71
|
+
trace("type", "LLM")
|
72
|
+
trace("signature", "azure.ai.inference.ChatCompletionsClient.ctor")
|
73
|
+
trace("description", "Azure Unified Inference SDK Chat Completions Client")
|
74
|
+
trace("inputs", cargs)
|
75
|
+
client = ChatCompletionsClient(
|
76
|
+
user_agent=f"prompty/{VERSION}",
|
77
|
+
**cargs,
|
78
|
+
)
|
79
|
+
trace("result", client)
|
80
|
+
|
81
|
+
with Tracer.start("complete") as trace:
|
82
|
+
trace("type", "LLM")
|
83
|
+
trace("signature", "azure.ai.inference.ChatCompletionsClient.complete")
|
84
|
+
trace("description", "Azure Unified Inference SDK Chat Completions Client")
|
85
|
+
eargs = {
|
86
|
+
"model": self.model,
|
87
|
+
"messages": data if isinstance(data, list) else [data],
|
88
|
+
**self.prompty.model.parameters,
|
89
|
+
}
|
90
|
+
trace("inputs", eargs)
|
91
|
+
r = client.complete(**eargs)
|
92
|
+
trace("result", r)
|
93
|
+
|
94
|
+
response = self._response(r)
|
95
|
+
|
96
|
+
elif self.api == "completion":
|
97
|
+
raise NotImplementedError(
|
98
|
+
"Serverless Completions API is not implemented yet"
|
99
|
+
)
|
100
|
+
|
101
|
+
elif self.api == "embedding":
|
102
|
+
with Tracer.start("EmbeddingsClient") as trace:
|
103
|
+
trace("type", "LLM")
|
104
|
+
trace("signature", "azure.ai.inference.EmbeddingsClient.ctor")
|
105
|
+
trace("description", "Azure Unified Inference SDK Embeddings Client")
|
106
|
+
trace("inputs", cargs)
|
107
|
+
client = EmbeddingsClient(
|
108
|
+
user_agent=f"prompty/{VERSION}",
|
109
|
+
**cargs,
|
110
|
+
)
|
111
|
+
trace("result", client)
|
112
|
+
|
113
|
+
with Tracer.start("complete") as trace:
|
114
|
+
trace("type", "LLM")
|
115
|
+
trace("signature", "azure.ai.inference.ChatCompletionsClient.complete")
|
116
|
+
trace("description", "Azure Unified Inference SDK Chat Completions Client")
|
117
|
+
eargs = {
|
118
|
+
"model": self.model,
|
119
|
+
"input": data if isinstance(data, list) else [data],
|
120
|
+
**self.prompty.model.parameters,
|
121
|
+
}
|
122
|
+
trace("inputs", eargs)
|
123
|
+
r = client.complete(**eargs)
|
124
|
+
trace("result", r)
|
125
|
+
|
126
|
+
response = self._response(r)
|
127
|
+
|
128
|
+
elif self.api == "image":
|
129
|
+
raise NotImplementedError("Azure OpenAI Image API is not implemented yet")
|
130
|
+
|
131
|
+
return response
|
@@ -11,6 +11,18 @@ from functools import wraps, partial
|
|
11
11
|
from typing import Any, Callable, Dict, Iterator, List
|
12
12
|
|
13
13
|
|
14
|
+
# clean up key value pairs for sensitive values
|
15
|
+
def sanitize(key: str, value: Any) -> Any:
|
16
|
+
if isinstance(value, str) and any(
|
17
|
+
[s in key.lower() for s in ["key", "token", "secret", "password", "credential"]]
|
18
|
+
):
|
19
|
+
return len(str(value)) * "*"
|
20
|
+
elif isinstance(value, dict):
|
21
|
+
return {k: sanitize(k, v) for k, v in value.items()}
|
22
|
+
else:
|
23
|
+
return value
|
24
|
+
|
25
|
+
|
14
26
|
class Tracer:
|
15
27
|
_tracers: Dict[str, Callable[[str], Iterator[Callable[[str, Any], None]]]] = {}
|
16
28
|
|
@@ -31,7 +43,11 @@ class Tracer:
|
|
31
43
|
traces = [
|
32
44
|
stack.enter_context(tracer(name)) for tracer in cls._tracers.values()
|
33
45
|
]
|
34
|
-
yield lambda key, value: [
|
46
|
+
yield lambda key, value: [
|
47
|
+
# normalize and sanitize any trace values
|
48
|
+
trace(key, sanitize(key, to_dict(value)))
|
49
|
+
for trace in traces
|
50
|
+
]
|
35
51
|
|
36
52
|
|
37
53
|
def to_dict(obj: Any) -> Dict[str, Any]:
|
@@ -94,7 +110,9 @@ def _results(result: Any) -> dict:
|
|
94
110
|
return to_dict(result) if result is not None else "None"
|
95
111
|
|
96
112
|
|
97
|
-
def _trace_sync(
|
113
|
+
def _trace_sync(
|
114
|
+
func: Callable = None, *, description: str = None, type: str = None
|
115
|
+
) -> Callable:
|
98
116
|
description = description or ""
|
99
117
|
|
100
118
|
@wraps(func)
|
@@ -105,18 +123,36 @@ def _trace_sync(func: Callable = None, *, description: str = None) -> Callable:
|
|
105
123
|
if description and description != "":
|
106
124
|
trace("description", description)
|
107
125
|
|
126
|
+
if type and type != "":
|
127
|
+
trace("type", type)
|
128
|
+
|
108
129
|
inputs = _inputs(func, args, kwargs)
|
109
130
|
trace("inputs", inputs)
|
110
131
|
|
111
|
-
|
112
|
-
|
132
|
+
try:
|
133
|
+
result = func(*args, **kwargs)
|
134
|
+
trace("result", _results(result))
|
135
|
+
except Exception as e:
|
136
|
+
trace(
|
137
|
+
"result",
|
138
|
+
{
|
139
|
+
"exception": {
|
140
|
+
"type": type(e).__name__,
|
141
|
+
"message": str(e),
|
142
|
+
"args": to_dict(e.args),
|
143
|
+
}
|
144
|
+
},
|
145
|
+
)
|
146
|
+
raise e
|
113
147
|
|
114
148
|
return result
|
115
149
|
|
116
150
|
return wrapper
|
117
151
|
|
118
152
|
|
119
|
-
def _trace_async(
|
153
|
+
def _trace_async(
|
154
|
+
func: Callable = None, *, description: str = None, type: str = None
|
155
|
+
) -> Callable:
|
120
156
|
description = description or ""
|
121
157
|
|
122
158
|
@wraps(func)
|
@@ -127,24 +163,41 @@ def _trace_async(func: Callable = None, *, description: str = None) -> Callable:
|
|
127
163
|
if description and description != "":
|
128
164
|
trace("description", description)
|
129
165
|
|
166
|
+
if type and type != "":
|
167
|
+
trace("type", type)
|
168
|
+
|
130
169
|
inputs = _inputs(func, args, kwargs)
|
131
170
|
trace("inputs", inputs)
|
132
|
-
|
133
|
-
|
134
|
-
|
171
|
+
try:
|
172
|
+
result = await func(*args, **kwargs)
|
173
|
+
trace("result", _results(result))
|
174
|
+
except Exception as e:
|
175
|
+
trace(
|
176
|
+
"result",
|
177
|
+
{
|
178
|
+
"exception": {
|
179
|
+
"type": type(e).__name__,
|
180
|
+
"message": str(e),
|
181
|
+
"args": to_dict(e.args),
|
182
|
+
}
|
183
|
+
},
|
184
|
+
)
|
185
|
+
raise e
|
135
186
|
|
136
187
|
return result
|
137
188
|
|
138
189
|
return wrapper
|
139
190
|
|
140
191
|
|
141
|
-
def trace(
|
192
|
+
def trace(
|
193
|
+
func: Callable = None, *, description: str = None, type: str = None
|
194
|
+
) -> Callable:
|
142
195
|
if func is None:
|
143
|
-
return partial(trace, description=description)
|
196
|
+
return partial(trace, description=description, type=type)
|
144
197
|
|
145
198
|
wrapped_method = _trace_async if inspect.iscoroutinefunction(func) else _trace_sync
|
146
199
|
|
147
|
-
return wrapped_method(func, description=description)
|
200
|
+
return wrapped_method(func, description=description, type=type)
|
148
201
|
|
149
202
|
|
150
203
|
class PromptyTracer:
|
@@ -255,6 +308,8 @@ class PromptyTracer:
|
|
255
308
|
def console_tracer(name: str) -> Iterator[Callable[[str, Any], None]]:
|
256
309
|
try:
|
257
310
|
print(f"Starting {name}")
|
258
|
-
yield lambda key, value: print(
|
311
|
+
yield lambda key, value: print(
|
312
|
+
f"{key}:\n{json.dumps(to_dict(value), indent=4)}"
|
313
|
+
)
|
259
314
|
finally:
|
260
315
|
print(f"Ending {name}")
|
@@ -3,7 +3,6 @@ import pytest
|
|
3
3
|
import prompty
|
4
4
|
from prompty.core import InvokerFactory
|
5
5
|
|
6
|
-
|
7
6
|
from tests.fake_azure_executor import FakeAzureExecutor
|
8
7
|
from tests.fake_serverless_executor import FakeServerlessExecutor
|
9
8
|
from prompty.azure import AzureOpenAIProcessor
|
@@ -24,6 +23,7 @@ def fake_azure_executor():
|
|
24
23
|
InvokerFactory.add_processor("serverless", ServerlessProcessor)
|
25
24
|
|
26
25
|
|
26
|
+
|
27
27
|
@pytest.mark.parametrize(
|
28
28
|
"prompt",
|
29
29
|
[
|
@@ -151,6 +151,7 @@ def test_streaming():
|
|
151
151
|
|
152
152
|
|
153
153
|
def test_serverless():
|
154
|
+
|
154
155
|
result = prompty.execute(
|
155
156
|
"prompts/serverless.prompty",
|
156
157
|
configuration={"key": os.environ.get("SERVERLESS_KEY", "key")},
|
@@ -1,95 +0,0 @@
|
|
1
|
-
import azure.identity
|
2
|
-
import importlib.metadata
|
3
|
-
from typing import Iterator
|
4
|
-
from openai import AzureOpenAI
|
5
|
-
from ..core import Invoker, InvokerFactory, Prompty, PromptyStream
|
6
|
-
|
7
|
-
VERSION = importlib.metadata.version("prompty")
|
8
|
-
|
9
|
-
|
10
|
-
@InvokerFactory.register_executor("azure")
|
11
|
-
@InvokerFactory.register_executor("azure_openai")
|
12
|
-
class AzureOpenAIExecutor(Invoker):
|
13
|
-
"""Azure OpenAI Executor"""
|
14
|
-
|
15
|
-
def __init__(self, prompty: Prompty) -> None:
|
16
|
-
super().__init__(prompty)
|
17
|
-
kwargs = {
|
18
|
-
key: value
|
19
|
-
for key, value in self.prompty.model.configuration.items()
|
20
|
-
if key != "type"
|
21
|
-
}
|
22
|
-
|
23
|
-
# no key, use default credentials
|
24
|
-
if "api_key" not in kwargs:
|
25
|
-
# managed identity if client id
|
26
|
-
if "client_id" in kwargs:
|
27
|
-
default_credential = azure.identity.ManagedIdentityCredential(
|
28
|
-
client_id=kwargs.pop("client_id"),
|
29
|
-
)
|
30
|
-
# default credential
|
31
|
-
else:
|
32
|
-
default_credential = azure.identity.DefaultAzureCredential(
|
33
|
-
exclude_shared_token_cache_credential=True
|
34
|
-
)
|
35
|
-
|
36
|
-
kwargs["azure_ad_token_provider"] = (
|
37
|
-
azure.identity.get_bearer_token_provider(
|
38
|
-
default_credential, "https://cognitiveservices.azure.com/.default"
|
39
|
-
)
|
40
|
-
)
|
41
|
-
|
42
|
-
self.client = AzureOpenAI(
|
43
|
-
default_headers={
|
44
|
-
"User-Agent": f"prompty/{VERSION}",
|
45
|
-
"x-ms-useragent": f"prompty/{VERSION}",
|
46
|
-
},
|
47
|
-
**kwargs,
|
48
|
-
)
|
49
|
-
|
50
|
-
self.api = self.prompty.model.api
|
51
|
-
self.deployment = self.prompty.model.configuration["azure_deployment"]
|
52
|
-
self.parameters = self.prompty.model.parameters
|
53
|
-
|
54
|
-
def invoke(self, data: any) -> any:
|
55
|
-
"""Invoke the Azure OpenAI API
|
56
|
-
|
57
|
-
Parameters
|
58
|
-
----------
|
59
|
-
data : any
|
60
|
-
The data to send to the Azure OpenAI API
|
61
|
-
|
62
|
-
Returns
|
63
|
-
-------
|
64
|
-
any
|
65
|
-
The response from the Azure OpenAI API
|
66
|
-
"""
|
67
|
-
if self.api == "chat":
|
68
|
-
response = self.client.chat.completions.create(
|
69
|
-
model=self.deployment,
|
70
|
-
messages=data if isinstance(data, list) else [data],
|
71
|
-
**self.parameters,
|
72
|
-
)
|
73
|
-
|
74
|
-
elif self.api == "completion":
|
75
|
-
response = self.client.completions.create(
|
76
|
-
prompt=data.item,
|
77
|
-
model=self.deployment,
|
78
|
-
**self.parameters,
|
79
|
-
)
|
80
|
-
|
81
|
-
elif self.api == "embedding":
|
82
|
-
response = self.client.embeddings.create(
|
83
|
-
input=data if isinstance(data, list) else [data],
|
84
|
-
model=self.deployment,
|
85
|
-
**self.parameters,
|
86
|
-
)
|
87
|
-
|
88
|
-
elif self.api == "image":
|
89
|
-
raise NotImplementedError("Azure OpenAI Image API is not implemented yet")
|
90
|
-
|
91
|
-
# stream response
|
92
|
-
if isinstance(response, Iterator):
|
93
|
-
return PromptyStream("AzureOpenAIExecutor", response)
|
94
|
-
else:
|
95
|
-
return response
|
@@ -1,74 +0,0 @@
|
|
1
|
-
import importlib.metadata
|
2
|
-
from openai import OpenAI
|
3
|
-
from typing import Iterator
|
4
|
-
from ..core import Invoker, InvokerFactory, Prompty, PromptyStream
|
5
|
-
|
6
|
-
VERSION = importlib.metadata.version("prompty")
|
7
|
-
|
8
|
-
|
9
|
-
@InvokerFactory.register_executor("openai")
|
10
|
-
class OpenAIExecutor(Invoker):
|
11
|
-
"""OpenAI Executor"""
|
12
|
-
|
13
|
-
def __init__(self, prompty: Prompty) -> None:
|
14
|
-
super().__init__(prompty)
|
15
|
-
kwargs = {
|
16
|
-
key: value
|
17
|
-
for key, value in self.prompty.model.configuration.items()
|
18
|
-
if key != "type"
|
19
|
-
}
|
20
|
-
|
21
|
-
self.client = OpenAI(
|
22
|
-
default_headers={
|
23
|
-
"User-Agent": f"prompty/{VERSION}",
|
24
|
-
"x-ms-useragent": f"prompty/{VERSION}",
|
25
|
-
},
|
26
|
-
**kwargs,
|
27
|
-
)
|
28
|
-
|
29
|
-
self.api = self.prompty.model.api
|
30
|
-
self.deployment = self.prompty.model.configuration["azure_deployment"]
|
31
|
-
self.parameters = self.prompty.model.parameters
|
32
|
-
|
33
|
-
def invoke(self, data: any) -> any:
|
34
|
-
"""Invoke the OpenAI API
|
35
|
-
|
36
|
-
Parameters
|
37
|
-
----------
|
38
|
-
data : any
|
39
|
-
The data to send to the OpenAI API
|
40
|
-
|
41
|
-
Returns
|
42
|
-
-------
|
43
|
-
any
|
44
|
-
The response from the OpenAI API
|
45
|
-
"""
|
46
|
-
if self.api == "chat":
|
47
|
-
response = self.client.chat.completions.create(
|
48
|
-
model=self.deployment,
|
49
|
-
messages=data if isinstance(data, list) else [data],
|
50
|
-
**self.parameters,
|
51
|
-
)
|
52
|
-
|
53
|
-
elif self.api == "completion":
|
54
|
-
response = self.client.completions.create(
|
55
|
-
prompt=data.item,
|
56
|
-
model=self.deployment,
|
57
|
-
**self.parameters,
|
58
|
-
)
|
59
|
-
|
60
|
-
elif self.api == "embedding":
|
61
|
-
response = self.client.embeddings.create(
|
62
|
-
input=data if isinstance(data, list) else [data],
|
63
|
-
model=self.deployment,
|
64
|
-
**self.parameters,
|
65
|
-
)
|
66
|
-
|
67
|
-
elif self.api == "image":
|
68
|
-
raise NotImplementedError("OpenAI Image API is not implemented yet")
|
69
|
-
|
70
|
-
# stream response
|
71
|
-
if isinstance(response, Iterator):
|
72
|
-
return PromptyStream("OpenAIExecutor", response)
|
73
|
-
else:
|
74
|
-
return response
|
@@ -1,84 +0,0 @@
|
|
1
|
-
import importlib.metadata
|
2
|
-
from typing import Iterator
|
3
|
-
from azure.core.credentials import AzureKeyCredential
|
4
|
-
from azure.ai.inference import (
|
5
|
-
ChatCompletionsClient,
|
6
|
-
EmbeddingsClient,
|
7
|
-
)
|
8
|
-
from azure.ai.inference.models import (
|
9
|
-
StreamingChatCompletions,
|
10
|
-
AsyncStreamingChatCompletions,
|
11
|
-
)
|
12
|
-
from ..core import Invoker, InvokerFactory, Prompty, PromptyStream, AsyncPromptyStream
|
13
|
-
|
14
|
-
VERSION = importlib.metadata.version("prompty")
|
15
|
-
|
16
|
-
|
17
|
-
@InvokerFactory.register_executor("serverless")
|
18
|
-
class ServerlessExecutor(Invoker):
|
19
|
-
"""Azure OpenAI Executor"""
|
20
|
-
|
21
|
-
def __init__(self, prompty: Prompty) -> None:
|
22
|
-
super().__init__(prompty)
|
23
|
-
|
24
|
-
# serverless configuration
|
25
|
-
self.endpoint = self.prompty.model.configuration["endpoint"]
|
26
|
-
self.model = self.prompty.model.configuration["model"]
|
27
|
-
self.key = self.prompty.model.configuration["key"]
|
28
|
-
|
29
|
-
# api type
|
30
|
-
self.api = self.prompty.model.api
|
31
|
-
|
32
|
-
def invoke(self, data: any) -> any:
|
33
|
-
"""Invoke the Serverless SDK
|
34
|
-
|
35
|
-
Parameters
|
36
|
-
----------
|
37
|
-
data : any
|
38
|
-
The data to send to the Serverless SDK
|
39
|
-
|
40
|
-
Returns
|
41
|
-
-------
|
42
|
-
any
|
43
|
-
The response from the Serverless SDK
|
44
|
-
"""
|
45
|
-
if self.api == "chat":
|
46
|
-
response = ChatCompletionsClient(
|
47
|
-
endpoint=self.endpoint,
|
48
|
-
credential=AzureKeyCredential(self.key),
|
49
|
-
user_agent=f"prompty/{VERSION}"
|
50
|
-
).complete(
|
51
|
-
model=self.model,
|
52
|
-
messages=data if isinstance(data, list) else [data],
|
53
|
-
**self.prompty.model.parameters,
|
54
|
-
)
|
55
|
-
|
56
|
-
elif self.api == "completion":
|
57
|
-
raise NotImplementedError(
|
58
|
-
"Serverless Completions API is not implemented yet"
|
59
|
-
)
|
60
|
-
|
61
|
-
elif self.api == "embedding":
|
62
|
-
response = EmbeddingsClient(
|
63
|
-
endpoint=self.endpoint,
|
64
|
-
credential=AzureKeyCredential(self.key),
|
65
|
-
user_agent=f"prompty/{VERSION}",
|
66
|
-
).complete(
|
67
|
-
model=self.model,
|
68
|
-
input=data if isinstance(data, list) else [data],
|
69
|
-
**self.prompty.model.parameters,
|
70
|
-
)
|
71
|
-
|
72
|
-
elif self.api == "image":
|
73
|
-
raise NotImplementedError("Azure OpenAI Image API is not implemented yet")
|
74
|
-
|
75
|
-
# stream response
|
76
|
-
if isinstance(response, Iterator):
|
77
|
-
if isinstance(response, StreamingChatCompletions):
|
78
|
-
return PromptyStream("ServerlessExecutor", response)
|
79
|
-
elif isinstance(response, AsyncStreamingChatCompletions):
|
80
|
-
return AsyncPromptyStream("ServerlessExecutor", response)
|
81
|
-
return PromptyStream("ServerlessExecutor", response)
|
82
|
-
else:
|
83
|
-
|
84
|
-
return response
|
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
|
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
|
{prompty-0.1.17 → prompty-0.1.19}/tests/hello_world-goodbye_world-hello_again.embedding.json
RENAMED
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
|
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
|
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
|
File without changes
|
File without changes
|
File without changes
|