prompty 0.1.18__py2.py3-none-any.whl → 0.1.19__py2.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.
prompty/azure/executor.py CHANGED
@@ -2,6 +2,8 @@ import azure.identity
2
2
  import importlib.metadata
3
3
  from typing import Iterator
4
4
  from openai import AzureOpenAI
5
+
6
+ from prompty.tracer import Tracer
5
7
  from ..core import Invoker, InvokerFactory, Prompty, PromptyStream
6
8
 
7
9
  VERSION = importlib.metadata.version("prompty")
@@ -14,18 +16,18 @@ class AzureOpenAIExecutor(Invoker):
14
16
 
15
17
  def __init__(self, prompty: Prompty) -> None:
16
18
  super().__init__(prompty)
17
- kwargs = {
19
+ self.kwargs = {
18
20
  key: value
19
21
  for key, value in self.prompty.model.configuration.items()
20
22
  if key != "type"
21
23
  }
22
24
 
23
25
  # no key, use default credentials
24
- if "api_key" not in kwargs:
26
+ if "api_key" not in self.kwargs:
25
27
  # managed identity if client id
26
- if "client_id" in kwargs:
28
+ if "client_id" in self.kwargs:
27
29
  default_credential = azure.identity.ManagedIdentityCredential(
28
- client_id=kwargs.pop("client_id"),
30
+ client_id=self.kwargs.pop("client_id"),
29
31
  )
30
32
  # default credential
31
33
  else:
@@ -33,20 +35,12 @@ class AzureOpenAIExecutor(Invoker):
33
35
  exclude_shared_token_cache_credential=True
34
36
  )
35
37
 
36
- kwargs["azure_ad_token_provider"] = (
38
+ self.kwargs["azure_ad_token_provider"] = (
37
39
  azure.identity.get_bearer_token_provider(
38
40
  default_credential, "https://cognitiveservices.azure.com/.default"
39
41
  )
40
42
  )
41
43
 
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
44
  self.api = self.prompty.model.api
51
45
  self.deployment = self.prompty.model.configuration["azure_deployment"]
52
46
  self.parameters = self.prompty.model.parameters
@@ -64,29 +58,60 @@ class AzureOpenAIExecutor(Invoker):
64
58
  any
65
59
  The response from the Azure OpenAI API
66
60
  """
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
61
 
74
- elif self.api == "completion":
75
- response = self.client.completions.create(
76
- prompt=data.item,
77
- model=self.deployment,
78
- **self.parameters,
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,
79
73
  )
74
+ trace("result", client)
80
75
 
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
- )
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)
87
112
 
88
- elif self.api == "image":
89
- raise NotImplementedError("Azure OpenAI Image API is not implemented yet")
113
+ elif self.api == "image":
114
+ raise NotImplementedError("Azure OpenAI Image API is not implemented yet")
90
115
 
91
116
  # stream response
92
117
  if isinstance(response, Iterator):
@@ -1,6 +1,8 @@
1
1
  import importlib.metadata
2
2
  from openai import OpenAI
3
3
  from typing import Iterator
4
+
5
+ from prompty.tracer import Tracer
4
6
  from ..core import Invoker, InvokerFactory, Prompty, PromptyStream
5
7
 
6
8
  VERSION = importlib.metadata.version("prompty")
@@ -12,20 +14,12 @@ class OpenAIExecutor(Invoker):
12
14
 
13
15
  def __init__(self, prompty: Prompty) -> None:
14
16
  super().__init__(prompty)
15
- kwargs = {
17
+ self.kwargs = {
16
18
  key: value
17
19
  for key, value in self.prompty.model.configuration.items()
18
20
  if key != "type"
19
21
  }
20
22
 
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
23
  self.api = self.prompty.model.api
30
24
  self.deployment = self.prompty.model.configuration["azure_deployment"]
31
25
  self.parameters = self.prompty.model.parameters
@@ -43,32 +37,62 @@ class OpenAIExecutor(Invoker):
43
37
  any
44
38
  The response from the OpenAI API
45
39
  """
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,
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
51
  )
52
+ trace("result", client)
52
53
 
53
- elif self.api == "completion":
54
- response = self.client.completions.create(
55
- prompt=data.item,
56
- model=self.deployment,
57
- **self.parameters,
58
- )
54
+ with Tracer.start("create") as trace:
55
+ trace("type", "LLM")
56
+ trace("description", "OpenAI Prompty Execution Invoker")
59
57
 
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
- )
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)
66
87
 
67
- elif self.api == "image":
68
- raise NotImplementedError("OpenAI Image API is not implemented yet")
88
+ elif self.api == "image":
89
+ raise NotImplementedError("OpenAI Image API is not implemented yet")
69
90
 
70
- # stream response
71
- if isinstance(response, Iterator):
72
- return PromptyStream("OpenAIExecutor", response)
73
- else:
74
- return response
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
@@ -9,6 +9,8 @@ from azure.ai.inference.models import (
9
9
  StreamingChatCompletions,
10
10
  AsyncStreamingChatCompletions,
11
11
  )
12
+
13
+ from prompty.tracer import Tracer
12
14
  from ..core import Invoker, InvokerFactory, Prompty, PromptyStream, AsyncPromptyStream
13
15
 
14
16
  VERSION = importlib.metadata.version("prompty")
@@ -29,6 +31,22 @@ class ServerlessExecutor(Invoker):
29
31
  # api type
30
32
  self.api = self.prompty.model.api
31
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
+
32
50
  def invoke(self, data: any) -> any:
33
51
  """Invoke the Serverless SDK
34
52
 
@@ -42,16 +60,38 @@ class ServerlessExecutor(Invoker):
42
60
  any
43
61
  The response from the Serverless SDK
44
62
  """
63
+
64
+ cargs = {
65
+ "endpoint": self.endpoint,
66
+ "credential": AzureKeyCredential(self.key),
67
+ }
68
+
45
69
  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
- )
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)
55
95
 
56
96
  elif self.api == "completion":
57
97
  raise NotImplementedError(
@@ -59,26 +99,33 @@ class ServerlessExecutor(Invoker):
59
99
  )
60
100
 
61
101
  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
- )
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)
71
127
 
72
128
  elif self.api == "image":
73
129
  raise NotImplementedError("Azure OpenAI Image API is not implemented yet")
74
130
 
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
131
+ return response
prompty/tracer.py CHANGED
@@ -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: [trace(key, value) for trace in traces]
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(func: Callable = None, *, description: str = None) -> Callable:
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,6 +123,9 @@ 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
 
@@ -118,7 +139,7 @@ def _trace_sync(func: Callable = None, *, description: str = None) -> Callable:
118
139
  "exception": {
119
140
  "type": type(e).__name__,
120
141
  "message": str(e),
121
- "args": e.args,
142
+ "args": to_dict(e.args),
122
143
  }
123
144
  },
124
145
  )
@@ -129,7 +150,9 @@ def _trace_sync(func: Callable = None, *, description: str = None) -> Callable:
129
150
  return wrapper
130
151
 
131
152
 
132
- def _trace_async(func: Callable = None, *, description: str = None) -> Callable:
153
+ def _trace_async(
154
+ func: Callable = None, *, description: str = None, type: str = None
155
+ ) -> Callable:
133
156
  description = description or ""
134
157
 
135
158
  @wraps(func)
@@ -140,6 +163,9 @@ def _trace_async(func: Callable = None, *, description: str = None) -> Callable:
140
163
  if description and description != "":
141
164
  trace("description", description)
142
165
 
166
+ if type and type != "":
167
+ trace("type", type)
168
+
143
169
  inputs = _inputs(func, args, kwargs)
144
170
  trace("inputs", inputs)
145
171
  try:
@@ -152,7 +178,7 @@ def _trace_async(func: Callable = None, *, description: str = None) -> Callable:
152
178
  "exception": {
153
179
  "type": type(e).__name__,
154
180
  "message": str(e),
155
- "args": e.args,
181
+ "args": to_dict(e.args),
156
182
  }
157
183
  },
158
184
  )
@@ -163,13 +189,15 @@ def _trace_async(func: Callable = None, *, description: str = None) -> Callable:
163
189
  return wrapper
164
190
 
165
191
 
166
- def trace(func: Callable = None, *, description: str = None) -> Callable:
192
+ def trace(
193
+ func: Callable = None, *, description: str = None, type: str = None
194
+ ) -> Callable:
167
195
  if func is None:
168
- return partial(trace, description=description)
196
+ return partial(trace, description=description, type=type)
169
197
 
170
198
  wrapped_method = _trace_async if inspect.iscoroutinefunction(func) else _trace_sync
171
199
 
172
- return wrapped_method(func, description=description)
200
+ return wrapped_method(func, description=description, type=type)
173
201
 
174
202
 
175
203
  class PromptyTracer:
@@ -280,6 +308,8 @@ class PromptyTracer:
280
308
  def console_tracer(name: str) -> Iterator[Callable[[str, Any], None]]:
281
309
  try:
282
310
  print(f"Starting {name}")
283
- yield lambda key, value: print(f"{key}:\n{json.dumps(value, indent=4)}")
311
+ yield lambda key, value: print(
312
+ f"{key}:\n{json.dumps(to_dict(value), indent=4)}"
313
+ )
284
314
  finally:
285
315
  print(f"Ending {name}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prompty
3
- Version: 0.1.18
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
@@ -1,20 +1,20 @@
1
- prompty-0.1.18.dist-info/METADATA,sha256=xQtpcI0HepyxgkqLN5BnUT9RrbcrTSrY68XeMnEe1MM,8916
2
- prompty-0.1.18.dist-info/WHEEL,sha256=CuZGaXTwoRLAOVv0AcE3bCTxO5ejVuBEJkUBe9C-kvk,94
3
- prompty-0.1.18.dist-info/entry_points.txt,sha256=9y1lKPWUpPWRJzUslcVH-gMwbNoa2PzjyoZsKYLQqyw,45
4
- prompty-0.1.18.dist-info/licenses/LICENSE,sha256=KWSC4z9cfML_t0xThoQYjzTdcZQj86Y_mhXdatzU-KM,1052
1
+ prompty-0.1.19.dist-info/METADATA,sha256=PnyGMOsBjZ6U9t76hJ3xQNFUULeE1nr7ef8MWz9SJgA,8916
2
+ prompty-0.1.19.dist-info/WHEEL,sha256=CuZGaXTwoRLAOVv0AcE3bCTxO5ejVuBEJkUBe9C-kvk,94
3
+ prompty-0.1.19.dist-info/entry_points.txt,sha256=9y1lKPWUpPWRJzUslcVH-gMwbNoa2PzjyoZsKYLQqyw,45
4
+ prompty-0.1.19.dist-info/licenses/LICENSE,sha256=KWSC4z9cfML_t0xThoQYjzTdcZQj86Y_mhXdatzU-KM,1052
5
5
  prompty/__init__.py,sha256=XTUgJ3xT7HYJieuWW5PBItey0BWneg3G7iBBjIeNJZU,11628
6
6
  prompty/azure/__init__.py,sha256=ptGajCh68s_tugPv45Y4GJCyBToNFCExUzUh9yIBIfo,292
7
- prompty/azure/executor.py,sha256=x2ng2EbYUxbingjy8w27TFGWezs4QC0LHh_S0F0-E1U,3082
7
+ prompty/azure/executor.py,sha256=M52n_hxxT57-_VdGYK-pXoELT0CDwnJaxN3xNNabgp8,4210
8
8
  prompty/azure/processor.py,sha256=e9CcKG665zvCLPeJfS91FM6c_W_6YY0mVENxinCo19A,2253
9
9
  prompty/cli.py,sha256=k8Rxm41fMFNvmnsX737UiN6v-7756tpoJPN4rPXMNcU,3726
10
10
  prompty/core.py,sha256=VzhOp2tulAKAF0I_5ULlII6QZ0thN1FRtPqPhwDpjPY,17226
11
11
  prompty/openai/__init__.py,sha256=16LxFrG_qGMg_Nx_BTMkCZupPEADsi8Gj234uFiXoZo,273
12
- prompty/openai/executor.py,sha256=5LXME0ACvbX3PSpSfh9ohDGWB50ZYBXLZG238wQiVGc,2212
12
+ prompty/openai/executor.py,sha256=tpX2vkPIJKM4XqEU0KzahgALcR_IBRrY_ca-woST-uc,3314
13
13
  prompty/openai/processor.py,sha256=Cw-_O_r9B5QqiCsfIglI5lcJgKCStkse2iIDbPWxfhg,2169
14
14
  prompty/parsers.py,sha256=4mmIn4SVNs8B0R1BufanqUJk8v4r0OEEo8yx6UOxQpA,4670
15
15
  prompty/renderers.py,sha256=RSHFQFx7AtKLUfsMLCXR0a56Mb7DL1NJNgjUqgg3IqU,776
16
16
  prompty/serverless/__init__.py,sha256=NPqoFATEMQ96G8OQkVcGxUWU4llIQCwxfJePPo8YFY8,279
17
- prompty/serverless/executor.py,sha256=fYCMV01iLBLpH2SQ9nxmrvY9ijAGpZezwkobZwUjSVQ,2781
17
+ prompty/serverless/executor.py,sha256=jbTnYE2aq8oS5PWcZ96NhhjL-gU1a_tlUoB449QqHaY,4648
18
18
  prompty/serverless/processor.py,sha256=pft1XGbPzo0MzQMbAt1VxsLsvRrjQO3B8MXEE2PfSA0,1982
19
- prompty/tracer.py,sha256=tROL2gdDyjMi1WvyVzHXOsojGbPL2wvI2Dlnzt1VBcA,9628
20
- prompty-0.1.18.dist-info/RECORD,,
19
+ prompty/tracer.py,sha256=dtF7rlJVNkl30m5ZE_9nROGW6Nmm8vGSSWS6p3M3MXI,10436
20
+ prompty-0.1.19.dist-info/RECORD,,