promptstorepy 0.1.0__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.
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.1
2
+ Name: promptstorepy
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: Mark Moloney
6
+ Author-email: markmo@me.com
7
+ Requires-Python: >=3.11,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Requires-Dist: asyncio (>=3.4.3,<4.0.0)
12
+ Requires-Dist: devtools (>=0.12.2,<0.13.0)
13
+ Requires-Dist: langchain-core (>=0.2.1,<0.3.0)
14
+ Requires-Dist: requests (>=2.32.2,<3.0.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+
File without changes
@@ -0,0 +1,153 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ import requests
5
+ from requests.exceptions import RequestException
6
+ from urllib.parse import urlencode
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class ApiService:
12
+ def __init__(self):
13
+ self.headers = {
14
+ "Accept": "application/json",
15
+ "Content-Type": "application/json",
16
+ }
17
+ self.is_token_refreshing = False
18
+ self.failed_queue = []
19
+ self.state = None
20
+ self.on_refresh_callback = None
21
+ self.on_expiry_callback = None
22
+
23
+ def set_token(self, token):
24
+ self.state = token
25
+ if callable(self.on_refresh_callback):
26
+ self.on_refresh_callback(token)
27
+
28
+ def on_refresh(self, callback):
29
+ self.on_refresh_callback = callback
30
+
31
+ def on_expiry(self, callback):
32
+ self.on_expiry_callback = callback
33
+
34
+ def get_access_token(self):
35
+ return self.state.get("access_token") if self.state else None
36
+
37
+ def get_refresh_token(self):
38
+ return self.state.get("refresh_token") if self.state else None
39
+
40
+ async def renew_token(self):
41
+ refresh_token = self.get_refresh_token()
42
+ token = await self.get_new_token(refresh_token)
43
+ self.set_token(token)
44
+
45
+ async def get_new_token(self, refresh_token):
46
+ host = os.getenv("KEYCLOAK_HOST")
47
+ realm = os.getenv("KEYCLOAK_REALM")
48
+ url = f"{host}/realms/{realm}/protocol/openid-connect/token"
49
+ client_id = os.getenv("KEYCLOAK_CLIENT_ID")
50
+ client_secret = os.getenv("KEYCLOAK_CLIENT_SECRET")
51
+ grant_type = "refresh_token"
52
+
53
+ data = {
54
+ "client_id": client_id,
55
+ "client_secret": client_secret,
56
+ "grant_type": grant_type,
57
+ "refresh_token": refresh_token,
58
+ }
59
+
60
+ try:
61
+ response = requests.post(
62
+ url,
63
+ data=urlencode(data),
64
+ headers={
65
+ "Content-Type": "application/x-www-form-urlencoded",
66
+ },
67
+ )
68
+ response.raise_for_status()
69
+ res_data = response.json()
70
+ return {
71
+ "accessToken": res_data["access_token"],
72
+ "refreshToken": res_data["refresh_token"],
73
+ }
74
+ except RequestException as err:
75
+ message = f"Error getting new token: {err}"
76
+ print(message)
77
+ raise Exception(message)
78
+
79
+ async def process_queue(self, error, access_token=None):
80
+ for prom in self.failed_queue:
81
+ if error:
82
+ prom["reject"](error)
83
+ else:
84
+ prom["resolve"](access_token)
85
+ self.failed_queue = []
86
+
87
+ def request(self, method, url, **kwargs):
88
+ access_token = self.get_access_token()
89
+ headers = kwargs.pop("headers", {})
90
+ if access_token:
91
+ headers["Authorization"] = f"Bearer {access_token}"
92
+ else:
93
+ headers["apikey"] = os.getenv("PROMPTSTORE_API_KEY")
94
+
95
+ headers.update(self.headers)
96
+ kwargs["headers"] = headers
97
+
98
+ try:
99
+ logger.debug(f"url: {url} {kwargs}")
100
+ response = requests.request(method, url, **kwargs, timeout=5)
101
+ logger.debug(f"response: {response}")
102
+ response.raise_for_status()
103
+ return response
104
+ except RequestException as err:
105
+ logger.error(err)
106
+ if err.response and err.response.status_code in [401, 403]:
107
+ return self.handle_token_refresh(err, method, url, **kwargs)
108
+ else:
109
+ raise
110
+
111
+ def handle_token_refresh(self, err, method, url, **kwargs):
112
+ if self.is_token_refreshing:
113
+ future = asyncio.Future()
114
+ self.failed_queue.append(
115
+ {"resolve": future.set_result, "reject": future.set_exception}
116
+ )
117
+ access_token = asyncio.run(future)
118
+ kwargs["headers"]["Authorization"] = f"Bearer {access_token}"
119
+ return self.request(method, url, **kwargs)
120
+
121
+ self.is_token_refreshing = True
122
+ original_request = err.request
123
+
124
+ try:
125
+ refresh_token = self.get_refresh_token()
126
+ token = asyncio.run(self.get_new_token(refresh_token))
127
+ self.set_token(token)
128
+ kwargs["headers"]["Authorization"] = f'Bearer {token["accessToken"]}'
129
+ response = self.request(method, url, **kwargs)
130
+ self.process_queue(None, token["accessToken"])
131
+ return response
132
+ except Exception as err:
133
+ self.process_queue(err)
134
+ if callable(self.on_expiry_callback):
135
+ self.on_expiry_callback(err)
136
+ raise
137
+ finally:
138
+ self.is_token_refreshing = False
139
+
140
+ def get(self, url, **kwargs):
141
+ return self.request("GET", url, **kwargs)
142
+
143
+ def post(self, url, **kwargs):
144
+ return self.request("POST", url, **kwargs)
145
+
146
+ def put(self, url, **kwargs):
147
+ return self.request("PUT", url, **kwargs)
148
+
149
+ def delete(self, url, **kwargs):
150
+ return self.request("DELETE", url, **kwargs)
151
+
152
+
153
+ service = ApiService()
@@ -0,0 +1,47 @@
1
+ import os
2
+ import logging
3
+ import requests
4
+ from urllib.parse import urlencode
5
+
6
+
7
+ class KeycloakAuth:
8
+
9
+ def __init__(self, constants, logger=None):
10
+ self.constants = constants
11
+ self.logger = logger or logging.getLogger(__name__)
12
+
13
+ async def get_access_token(self):
14
+ host = self.constants.get('KEYCLOAK_HOST', os.getenv('KEYCLOAK_HOST'))
15
+ realm = self.constants.get('PROMPTSTORE_KEYCLOAK_REALM', os.getenv('PROMPTSTORE_KEYCLOAK_REALM'))
16
+ client_id = self.constants.get('PROMPTSTORE_KEYCLOAK_CLIENT_ID', os.getenv('PROMPTSTORE_KEYCLOAK_CLIENT_ID'))
17
+ client_secret = self.constants.get('PROMPTSTORE_KEYCLOAK_CLIENT_SECRET', os.getenv('PROMPTSTORE_KEYCLOAK_CLIENT_SECRET'))
18
+ url = f'{host}/realms/{realm}/protocol/openid-connect/token'
19
+ grant_type = 'client_credentials'
20
+
21
+ # self.logger.debug(f"curl -vL -H 'Content-Type: application/x-www-form-urlencoded' {url} -d 'client_id={client_id}&client_secret={client_secret}&grant_type={grant_type}'")
22
+
23
+ data = {
24
+ 'client_id': client_id,
25
+ 'client_secret': client_secret,
26
+ 'grant_type': grant_type,
27
+ }
28
+ try:
29
+ response = requests.post(url, data=urlencode(data), headers={
30
+ 'Content-Type': 'application/x-www-form-urlencoded',
31
+ })
32
+ response.raise_for_status()
33
+ return response.json()
34
+ except requests.RequestException as err:
35
+ self.logger.error(err, exc_info=True)
36
+ raise
37
+
38
+ # Usage example:
39
+ # constants = {
40
+ # 'KEYCLOAK_HOST': 'https://keycloak.example.com',
41
+ # 'PROMPTSTORE_KEYCLOAK_REALM': 'myrealm',
42
+ # 'PROMPTSTORE_KEYCLOAK_CLIENT_ID': 'myclientid',
43
+ # 'PROMPTSTORE_KEYCLOAK_CLIENT_SECRET': 'myclientsecret',
44
+ # }
45
+ # logger = logging.getLogger('keycloak_auth')
46
+ # keycloak_auth = KeycloakAuth(constants, logger)
47
+ # access_token = asyncio.run(keycloak_auth.get_access_token())
@@ -0,0 +1,195 @@
1
+ from devtools import debug
2
+ from typing import Any, Dict, List, Optional
3
+ from uuid import UUID
4
+
5
+ from langchain_core.callbacks import BaseCallbackHandler
6
+ from langchain_core.messages.base import BaseMessage
7
+ from langchain_core.outputs.llm_result import LLMResult
8
+
9
+ from .PromptStore import PromptStore
10
+
11
+
12
+ class LangChainCallbackHandler(BaseCallbackHandler):
13
+ trace_id: str = None
14
+ runs = {}
15
+
16
+ def __init__(self):
17
+ super().__init__()
18
+ self.ps = PromptStore()
19
+
20
+ def get_langchain_run_name(self, serialized: Dict[str, Any], **kwargs: Any) -> str:
21
+ """Retrieves the 'run_name' for an entity based on Langchain convention, prioritizing the 'name'
22
+ key in 'kwargs' or falling back to the 'name' or 'id' in 'serialized'. Defaults to "<unknown>"
23
+ if none are available.
24
+
25
+ Args:
26
+ serialized (Dict[str, Any]): A dictionary containing the entity's serialized data.
27
+ **kwargs (Any): Additional keyword arguments, potentially including the 'name' override.
28
+
29
+ Returns:
30
+ str: The determined Langchain run name for the entity.
31
+ """
32
+ # Check if 'name' is in kwargs and not None, otherwise use default fallback logic
33
+ if "name" in kwargs and kwargs["name"] is not None:
34
+ return kwargs["name"]
35
+
36
+ # Fallback to serialized 'name', 'id', or "<unknown>"
37
+ return serialized.get("name", serialized.get("id", ["<unknown>"])[-1])
38
+
39
+ def on_chain_start(
40
+ self,
41
+ serialized: Dict[str, Any],
42
+ inputs: Dict[str, Any],
43
+ *,
44
+ run_id: UUID,
45
+ parent_run_id: Optional[UUID] = None,
46
+ tags: Optional[List[str]] = None,
47
+ metadata: Optional[Dict[str, Any]] = None,
48
+ **kwargs: Any,
49
+ ) -> Any:
50
+ # debug(inputs)
51
+ # debug(metadata)
52
+ # debug(kwargs)
53
+ # debug(serialized)
54
+ chain_name = kwargs.get("name")
55
+ if chain_name == "AgentExecutor":
56
+ debug(chain_name)
57
+ debug(run_id)
58
+ self.runs[str(run_id)] = chain_name # must come before trace, otherwise `on_chain_end` fires before trace completes and `self.runs` is set
59
+ self.trace_id = self.ps.trace(
60
+ {
61
+ "event": {"type": "trace"},
62
+ }
63
+ )
64
+ self.ps.trace(
65
+ {
66
+ "id": self.trace_id,
67
+ "event": {
68
+ "type": "agent-start",
69
+ "name": self.get_langchain_run_name(serialized, **kwargs),
70
+ "args": inputs,
71
+ },
72
+ }
73
+ )
74
+
75
+ elif chain_name == "ChatPromptTemplate": # kwargs["run_type"] == "prompt"
76
+ debug(chain_name)
77
+ debug(run_id)
78
+ self.runs[str(run_id)] = chain_name
79
+ self.ps.trace(
80
+ {
81
+ "id": self.trace_id,
82
+ "event": {
83
+ "type": "prompt-start",
84
+ "messageTemplates": {
85
+ "role": "system",
86
+ "content": serialized["kwargs"]["messages"]["kwargs"]["prompt"]["kwargs"]["template"],
87
+ "args": inputs,
88
+ },
89
+ },
90
+ }
91
+ )
92
+
93
+
94
+ def on_chain_end(
95
+ self,
96
+ outputs: Dict[str, Any],
97
+ *,
98
+ run_id: UUID,
99
+ parent_run_id: Optional[UUID] = None,
100
+ **kwargs: Any,
101
+ ) -> Any:
102
+ # debug(parent_run_id)
103
+ # debug(run_id)
104
+ chain_name = self.runs.get(str(run_id), None)
105
+ # debug(chain_name)
106
+ # debug(outputs)
107
+ # debug(kwargs)
108
+ if chain_name == "AgentExecutor":
109
+ debug(chain_name)
110
+ debug(outputs)
111
+ debug(kwargs)
112
+ self.ps.trace(
113
+ {
114
+ "id": self.trace_id,
115
+ "event": {
116
+ "type": "agent-end",
117
+ "response": outputs,
118
+ },
119
+ }
120
+ )
121
+
122
+ elif chain_name == "ChatPromptTemplate":
123
+ debug(chain_name)
124
+ debug(outputs)
125
+ debug(kwargs)
126
+ self.ps.trace(
127
+ {
128
+ "id": self.trace_id,
129
+ "event": {
130
+ "type": "prompt-end",
131
+ "response": outputs,
132
+ },
133
+ }
134
+ )
135
+
136
+ def on_chat_model_start(
137
+ self,
138
+ serialized: Dict[str, Any],
139
+ messages: List[List[BaseMessage]],
140
+ *,
141
+ run_id: UUID,
142
+ parent_run_id: Optional[UUID] = None,
143
+ tags: Optional[List[str]] = None,
144
+ metadata: Optional[Dict[str, Any]] = None,
145
+ **kwargs: Any,
146
+ ) -> Any:
147
+ self.ps.trace(
148
+ {
149
+ "id": self.trace_id,
150
+ "event": {
151
+ "type": "prompt-end",
152
+ "messages": messages,
153
+ },
154
+ }
155
+ )
156
+ self.ps.trace(
157
+ {
158
+ "id": self.trace_id,
159
+ "event": {
160
+ "type": "model-start",
161
+ "model": serialized["kwargs"]["model_name"],
162
+ "modelParams": {
163
+ "temperature": serialized["kwargs"]["temperature"],
164
+ "n": serialized["kwargs"]["n"],
165
+ },
166
+ },
167
+ }
168
+ )
169
+
170
+ def on_llm_end(
171
+ self,
172
+ response: LLMResult,
173
+ *,
174
+ run_id: UUID,
175
+ parent_run_id: Optional[UUID] = None,
176
+ **kwargs: Any,
177
+ ) -> Any:
178
+ self.ps.trace(
179
+ {
180
+ "id": self.trace_id,
181
+ "event": {
182
+ "type": "model-end",
183
+ "response": response,
184
+ },
185
+ }
186
+ )
187
+
188
+ def on_llm_error(
189
+ error: BaseException,
190
+ *,
191
+ run_id: UUID,
192
+ parent_run_id: Optional[UUID] = None,
193
+ **kwargs: Any,
194
+ ) -> Any:
195
+ pass
@@ -0,0 +1,539 @@
1
+ import os
2
+ import json
3
+ import logging
4
+ import requests
5
+ from devtools import debug
6
+ from uuid import uuid4
7
+
8
+ from .ApiService import ApiService
9
+ from .KeycloakAuth import KeycloakAuth
10
+ from .utils import get_search_type, strip_empty_values
11
+
12
+
13
+ class PromptStore:
14
+ def __init__(self, constants=None, logger=None, callback=None):
15
+ self.constants = constants or {}
16
+ self.logger = logger or logging.getLogger(__name__)
17
+ self.url = self.constants.get(
18
+ "PROMPTSTORE_BASE_URL", os.getenv("PROMPTSTORE_BASE_URL")
19
+ )
20
+ self.workspace_id = self.constants.get(
21
+ "WORKSPACE_ID", os.getenv("WORKSPACE_ID")
22
+ )
23
+ self.functions = {}
24
+ self.api_service = ApiService()
25
+
26
+ auth_method = self.constants.get("AUTH_METHOD", os.getenv("AUTH_METHOD"))
27
+ if auth_method and auth_method.lower() == "oauth":
28
+ self.auth = KeycloakAuth(self.constants, self.logger)
29
+ token = self.auth.get_access_token()
30
+ self.api_service.set_token(token)
31
+ if callable(callback):
32
+ callback()
33
+
34
+ def trace(self, event):
35
+ debug(f"trace [event={json.dumps(event)}]")
36
+ self.logger.debug(f"trace [event={json.dumps(event)}]")
37
+ try:
38
+ url = f"{self.url}/traces"
39
+ event = {**event, "workspaceId": self.workspace_id}
40
+ res = self.api_service.post(url, json=event)
41
+ # debug(res.json())
42
+ return res.json()
43
+ except requests.RequestException as err:
44
+ debug(err)
45
+ self.logger.error(str(err))
46
+ if err.response and err.response.json().get("errors"):
47
+ return err.response.json()
48
+ return {"errors": [{"message": str(err)}]}
49
+
50
+ def add_function(self, name, params, content_only=False):
51
+ self.logger.debug(
52
+ f"addFunction [name={name}; params={json.dumps(params)}; contentOnly={content_only}]"
53
+ )
54
+
55
+ async def func(args, param_overrides=None):
56
+ param_overrides = param_overrides or {}
57
+ try:
58
+ url = f"{self.url}/executions/{name}"
59
+ options = {
60
+ "args": args,
61
+ "params": {**params, **strip_empty_values(param_overrides)},
62
+ "workspaceId": self.workspace_id,
63
+ }
64
+ self.logger.debug(f"url: {url}, options: {options}")
65
+ res = self.api_service.post(url, json=options)
66
+ data = res.json()
67
+ if data.get("errors"):
68
+ return {"errors": data["errors"]}
69
+ if content_only:
70
+ message = data["response"]["choices"][0]["message"]
71
+ if message.get("function_call"):
72
+ try:
73
+ return json.loads(message["function_call"]["arguments"])
74
+ except json.JSONDecodeError as err:
75
+ self.logger.error(f"error parsing json response: {err}")
76
+ return {}
77
+ else:
78
+ return message["content"]
79
+ return data
80
+ except requests.RequestException as err:
81
+ self.logger.error(str(err))
82
+ if err.response and err.response.json().get("errors"):
83
+ return err.response.json()
84
+ return {"errors": [{"message": str(err)}]}
85
+
86
+ setattr(self, name, func)
87
+ return func
88
+
89
+ def execute(self, name, args, params, content_only=False):
90
+ self.logger.debug(
91
+ f"execute [name={name}; args={json.dumps(args)}; params={json.dumps(params)}; contentOnly={content_only}]"
92
+ )
93
+ try:
94
+ url = f"{self.url}/executions/{name}"
95
+ options = {
96
+ "args": args,
97
+ "params": params,
98
+ "workspaceId": self.workspace_id,
99
+ }
100
+ self.logger.debug(f"url: {url}, options: {options}")
101
+ res = self.api_service.post(url, json=options)
102
+ data = res.json()
103
+ if data.get("errors"):
104
+ return {"errors": data["errors"]}
105
+ if content_only:
106
+ message = data["response"]["choices"][0]["message"]
107
+ if message.get("function_call"):
108
+ try:
109
+ return json.loads(message["function_call"]["arguments"])
110
+ except json.JSONDecodeError as err:
111
+ self.logger.error(f"error parsing json response: {err}")
112
+ return {}
113
+ else:
114
+ return message["content"]
115
+ return data
116
+ except requests.RequestException as err:
117
+ self.logger.error(str(err))
118
+ if err.response and err.response.json().get("errors"):
119
+ return err.response.json()
120
+ return {"errors": [{"message": str(err)}]}
121
+
122
+ def execute_composition(self, name, args, params, functions):
123
+ self.logger.debug(
124
+ f"executeComposition [name={name}; args={json.dumps(args)}; params={json.dumps(params)}; functions={json.dumps(functions)}]"
125
+ )
126
+ try:
127
+ url = f"{self.url}/composition-executions/{name}"
128
+ options = {
129
+ "args": args,
130
+ "params": params,
131
+ "functions": functions,
132
+ "workspaceId": self.workspace_id,
133
+ }
134
+ self.logger.debug(f"url: {url}, options: {options}")
135
+ res = self.api_service.post(url, json=options)
136
+ return res.json()
137
+ except requests.RequestException as err:
138
+ self.logger.error(str(err))
139
+ if err.response and err.response.json().get("errors"):
140
+ return err.response.json()
141
+ return {"errors": [{"message": str(err)}]}
142
+
143
+ def call_tool(self, name, args, raw):
144
+ self.logger.debug(f"callTool [name={name}; args={json.dumps(args)}; raw={raw}]")
145
+ try:
146
+ url = f"{self.url}/tool-executions/{name}"
147
+ options = {"args": args, "raw": raw}
148
+ self.logger.debug(f"url: {url}, options: {options}")
149
+ res = self.api_service.post(url, json=options)
150
+ return res.json()
151
+ except requests.RequestException as err:
152
+ self.logger.error(str(err))
153
+ if err.response and err.response.json().get("errors"):
154
+ return err.response.json()
155
+ return {"errors": [{"message": str(err)}]}
156
+
157
+ def chat_completion(self, messages, model, max_tokens, index_name):
158
+ self.logger.debug(
159
+ f"chatCompletion [messages={json.dumps(messages)}; model={model}; maxTokens={max_tokens}; indexName={index_name}]"
160
+ )
161
+ try:
162
+ url = f"{self.url}/chat"
163
+ options = {
164
+ "messages": messages,
165
+ "model": model,
166
+ "maxTokens": max_tokens,
167
+ "indexName": index_name,
168
+ "workspaceId": self.workspace_id,
169
+ }
170
+ self.logger.debug(f"url: {url}, options: {options}")
171
+ res = self.api_service.post(url, json=options)
172
+ return res.json()
173
+ except requests.RequestException as err:
174
+ self.logger.error(str(err))
175
+ if err.response and err.response.json().get("errors"):
176
+ return err.response.json()
177
+ return {"errors": [{"message": str(err)}]}
178
+
179
+ def create_embeddings(self, embedding_provider, embedding_model, chunks):
180
+ self.logger.debug(
181
+ f"createEmbeddings [embeddingProvider={embedding_provider}; embeddingModel={embedding_model}]"
182
+ )
183
+ try:
184
+ url = f"{self.url}/embeddings/{embedding_provider}"
185
+ options = {
186
+ "chunks": chunks,
187
+ "embeddingModel": embedding_model,
188
+ "embeddingProvider": embedding_provider,
189
+ }
190
+ self.logger.debug(f"url: {url}, options: {options}")
191
+ res = self.api_service.post(url, json=options)
192
+ return res.json()
193
+ except requests.RequestException as err:
194
+ self.logger.error(str(err))
195
+ if err.response and err.response.json().get("errors"):
196
+ return err.response.json()
197
+ return {"errors": [{"message": str(err)}]}
198
+
199
+ def create_image(self, prompt, n=1, size="1024x1024"):
200
+ self.logger.debug(f"createImage [prompt={prompt}; n={n}; size={size}]")
201
+ try:
202
+ url = f"{self.url}/image-request"
203
+ options = {
204
+ "sourceId": self.workspace_id,
205
+ "prompt": prompt,
206
+ "size": size,
207
+ "n": n,
208
+ }
209
+ self.logger.debug(f"url: {url}, options: {options}")
210
+ res = self.api_service.post(url, json=options)
211
+ return res.json()
212
+ except requests.RequestException as err:
213
+ self.logger.error(str(err))
214
+ if err.response and err.response.json().get("errors"):
215
+ return err.response.json()
216
+ return {"errors": [{"message": str(err)}]}
217
+
218
+ def generate_image_variant(self, image_url, n=1):
219
+ self.logger.debug(f"generateImageVariant [imageUrl={image_url}; n={n}]")
220
+ try:
221
+ url = f"{self.url}/gen-image-variant"
222
+ options = {"imageUrl": image_url, "n": n, "workspaceId": self.workspace_id}
223
+ self.logger.debug(f"url: {url}, options: {options}")
224
+ res = self.api_service.post(url, json=options)
225
+ return res.json()
226
+ except requests.RequestException as err:
227
+ self.logger.error(str(err))
228
+ if err.response and err.response.json().get("errors"):
229
+ return err.response.json()
230
+ return {"errors": [{"message": str(err)}]}
231
+
232
+ def get_functions_by_tag(self, tag):
233
+ self.logger.debug(
234
+ f"getFunctionsByTag [workspaceId={self.workspace_id}; tag={tag}]"
235
+ )
236
+ try:
237
+ url = f"{self.url}/workspaces/{self.workspace_id}/functions/tags/{tag}"
238
+ self.logger.debug(f"url: {url}")
239
+ res = self.api_service.get(url)
240
+ return res.json()
241
+ except requests.RequestException as err:
242
+ self.logger.error(str(err))
243
+ if err.response and err.response.json().get("errors"):
244
+ return err.response.json()
245
+ return {"errors": [{"message": str(err)}]}
246
+
247
+ def get_compositions(self):
248
+ self.logger.debug(f"getCompositions [workspaceId={self.workspace_id}]")
249
+ try:
250
+ url = f"{self.url}/workspaces/{self.workspace_id}/compositions"
251
+ self.logger.debug(f"url: {url}")
252
+ res = self.api_service.get(url)
253
+ return res.json()
254
+ except requests.RequestException as err:
255
+ self.logger.error(str(err))
256
+ if err.response and err.response.json().get("errors"):
257
+ return err.response.json()
258
+ return {"errors": [{"message": str(err)}]}
259
+
260
+ def get_composition(self, composition_id):
261
+ self.logger.debug(f"getComposition [id={composition_id}]")
262
+ try:
263
+ url = f"{self.url}/compositions/{composition_id}"
264
+ self.logger.debug(f"url: {url}")
265
+ res = self.api_service.get(url)
266
+ return res.json()
267
+ except requests.RequestException as err:
268
+ self.logger.error(str(err))
269
+ if err.response and err.response.json().get("errors"):
270
+ return err.response.json()
271
+ return {"errors": [{"message": str(err)}]}
272
+
273
+ def get_prompt_set(self, skill: str):
274
+ self.logger.debug(f"getPromptSet [skill={skill}]")
275
+ try:
276
+ url = f"{self.url}/workspaces/{self.workspace_id}/prompt-sets?skill={skill}"
277
+ self.logger.debug(f"url: {url}")
278
+ res = self.api_service.get(url)
279
+ pss = res.json()
280
+ self.logger.debug(f"pss: {pss}")
281
+ self.logger.debug(f"len: {len(pss)}")
282
+ if len(pss) > 0:
283
+ return pss[0]
284
+ return None
285
+ except requests.RequestException as err:
286
+ self.logger.error(str(err))
287
+ if err.response and err.response.json().get("errors"):
288
+ return err.response.json()
289
+ return {"errors": [{"message": str(err)}]}
290
+
291
+ def get_prompt_sets(self):
292
+ self.logger.debug("getPromptSets")
293
+ try:
294
+ url = f"{self.url}/prompt-sets"
295
+ self.logger.debug(f"url: {url}")
296
+ res = self.api_service.get(url)
297
+ return res.json()
298
+ except requests.RequestException as err:
299
+ self.logger.error(str(err))
300
+ if err.response and err.response.json().get("errors"):
301
+ return err.response.json()
302
+ return {"errors": [{"message": str(err)}]}
303
+
304
+ def get_workspace_prompt_sets(self):
305
+ self.logger.debug(f"getWorkspacePromptSets [workspaceId={self.workspace_id}]")
306
+ try:
307
+ url = f"{self.url}/workspaces/{self.workspace_id}/prompt-sets"
308
+ self.logger.debug(f"url: {url}")
309
+ res = self.api_service.get(url)
310
+ return res.json()
311
+ except requests.RequestException as err:
312
+ self.logger.error(str(err))
313
+ if err.response and err.response.json().get("errors"):
314
+ return err.response.json()
315
+ return {"errors": [{"message": str(err)}]}
316
+
317
+ def get_workspace_prompt_sets_by_skill(self, skill):
318
+ self.logger.debug(
319
+ f"getWorkspacePromptSetsBySkill [workspaceId={self.workspace_id}; skill={skill}]"
320
+ )
321
+ try:
322
+ url = f"{self.url}/workspaces/{self.workspace_id}/prompt-sets?skill={skill}"
323
+ self.logger.debug(f"url: {url}")
324
+ res = self.api_service.get(url)
325
+ return res.json()
326
+ except requests.RequestException as err:
327
+ self.logger.error(str(err))
328
+ if err.response and err.response.json().get("errors"):
329
+ return err.response.json()
330
+ return {"errors": [{"message": str(err)}]}
331
+
332
+ def get_indexes(self):
333
+ self.logger.debug(f"getIndexes [workspaceId={self.workspace_id}]")
334
+ try:
335
+ url = f"{self.url}/workspaces/{self.workspace_id}/indexes"
336
+ self.logger.debug(f"url: {url}")
337
+ res = self.api_service.get(url)
338
+ return res.json()
339
+ except requests.RequestException as err:
340
+ self.logger.error(str(err))
341
+ if err.response and err.response.json().get("errors"):
342
+ return err.response.json()
343
+ return {"errors": [{"message": str(err)}]}
344
+
345
+ def get_index(self, vector_store_provider, index_name):
346
+ self.logger.debug(
347
+ f"getIndex [vectorStoreProvider={vector_store_provider}; indexName={index_name}]"
348
+ )
349
+ try:
350
+ url = f"{self.url}/index/{vector_store_provider}/{index_name}"
351
+ self.logger.debug(f"url: {url}")
352
+ res = self.api_service.get(url)
353
+ return res.json()
354
+ except requests.RequestException as err:
355
+ self.logger.error(str(err))
356
+ if err.response and err.response.json().get("errors"):
357
+ return err.response.json()
358
+ return {"errors": [{"message": str(err)}]}
359
+
360
+ def create_index(self, vector_store_provider, index_name, schema, params):
361
+ self.logger.debug(
362
+ f"createIndex [vectorStoreProvider={vector_store_provider}; indexName={index_name}; schema={json.dumps(schema)}; params={json.dumps(params)}]"
363
+ )
364
+ try:
365
+ url = f"{self.url}/index"
366
+ options = {
367
+ "indexName": index_name,
368
+ "schema": schema,
369
+ "params": params,
370
+ "vectorStoreProvider": vector_store_provider,
371
+ }
372
+ self.logger.debug(f"url: {url}, options: {options}")
373
+ res = self.api_service.post(url, json=options)
374
+ return res.json()
375
+ except requests.RequestException as err:
376
+ self.logger.error(str(err))
377
+ if err.response and err.response.json().get("errors"):
378
+ return err.response.json()
379
+ return {"errors": [{"message": str(err)}]}
380
+
381
+ def drop_index(self, vector_store_provider, index_name):
382
+ self.logger.debug(
383
+ f"dropIndex [vectorStoreProvider={vector_store_provider}; indexName={index_name}]"
384
+ )
385
+ try:
386
+ url = f"{self.url}/index/{vector_store_provider}/{index_name}"
387
+ self.logger.debug(f"url: {url}")
388
+ res = self.api_service.delete(url)
389
+ return res.json()
390
+ except requests.RequestException as err:
391
+ self.logger.error(str(err))
392
+ if err.response and err.response.json().get("errors"):
393
+ return err.response.json()
394
+ return {"errors": [{"message": str(err)}]}
395
+
396
+ def add_document(self, filepath):
397
+ self.logger.debug(f"addDocument [filepath={filepath}]")
398
+ try:
399
+ correlation_id = str(uuid4())
400
+ filename = os.path.basename(filepath)
401
+ mimetype = mimetypes.guess_type(filepath)[0]
402
+ with open(filepath, "rb") as file:
403
+ files = {
404
+ "file": (filename, file, mimetype),
405
+ }
406
+ data = {
407
+ "sourceId": self.workspace_id,
408
+ "correlationId": correlation_id,
409
+ }
410
+ url = f"{self.url}/upload/"
411
+ self.logger.debug(f"url: {url}, files: {files}, data: {data}")
412
+ res = self.api_service.post(url, files=files, data=data)
413
+ self.logger.debug("File uploaded to document service successfully.")
414
+ return res.json()
415
+ except requests.RequestException as err:
416
+ self.logger.error(str(err))
417
+ if err.response and err.response.json().get("errors"):
418
+ return err.response.json()
419
+ return {"errors": [{"message": str(err)}]}
420
+
421
+ def index_documents(self, index_name, documents):
422
+ self.logger.debug(f"indexDocuments [indexName={index_name}]")
423
+ try:
424
+ url = f"{self.url}/documents"
425
+ self.logger.debug(f"url: {url}")
426
+ res = self.api_service.post(
427
+ url, json={"indexName": index_name, "documents": documents}
428
+ )
429
+ return res.json()
430
+ except requests.RequestException as err:
431
+ self.logger.error(str(err))
432
+ if err.response and err.response.json().get("errors"):
433
+ return err.response.json()
434
+ return {"errors": [{"message": str(err)}]}
435
+
436
+ def drop_documents(self, index_name, query, attrs):
437
+ self.logger.debug(
438
+ f"dropDocuments [indexName={index_name}; query={query}; attrs={json.dumps(attrs)}]"
439
+ )
440
+ try:
441
+ query_params = "&".join([f"{k}={v}" for k, v in attrs.items()])
442
+ url = f"{self.url}/delete-matching?indexName={index_name}"
443
+ if query:
444
+ url += f"&q={query}"
445
+ if query_params:
446
+ url += f"&{query_params}"
447
+ self.logger.debug(f"url: {url}")
448
+ res = self.api_service.delete(url)
449
+ return res.json()
450
+ except requests.RequestException as err:
451
+ self.logger.error(str(err))
452
+ if err.response and err.response.json().get("errors"):
453
+ return err.response.json()
454
+ return {"errors": [{"message": str(err)}]}
455
+
456
+ def index_chunks(self, vector_store_provider, chunks, embeddings, params):
457
+ self.logger.debug(
458
+ f"indexChunks [vectorStoreProvider={vector_store_provider}; params={json.dumps(params)}]"
459
+ )
460
+ try:
461
+ url = f'{self.url}/chunks/{vector_store_provider}/{params["indexName"]}'
462
+ options = {"chunks": chunks, "embeddings": embeddings, "params": params}
463
+ self.logger.debug(f"url: {url}, options: {options}")
464
+ res = self.api_service.post(url, json=options)
465
+ return res.json()
466
+ except requests.RequestException as err:
467
+ self.logger.error(str(err))
468
+ if err.response and err.response.json().get("errors"):
469
+ return err.response.json()
470
+ return {"errors": [{"message": str(err)}]}
471
+
472
+ def delete_chunks(self, vector_store_provider, ids, params):
473
+ self.logger.debug(
474
+ f"deleteChunks [vectorStoreProvider={vector_store_provider}; ids={ids}; params={json.dumps(params)}]"
475
+ )
476
+ try:
477
+ url = f"{self.url}/bulk-delete"
478
+ options = {
479
+ "ids": ids,
480
+ "params": params,
481
+ "vectorStoreProvider": vector_store_provider,
482
+ }
483
+ self.logger.debug(f"url: {url}, options: {options}")
484
+ res = self.api_service.post(url, json=options)
485
+ return res.json()
486
+ except requests.RequestException as err:
487
+ self.logger.error(str(err))
488
+ if err.response and err.response.json().get("errors"):
489
+ return err.response.json()
490
+ return {"errors": [{"message": str(err)}]}
491
+
492
+ def delete_chunk(self, vector_store_provider, chunk_id, params):
493
+ self.logger.debug(
494
+ f"deleteChunk [vectorStoreProvider={vector_store_provider}; id={chunk_id}; params={json.dumps(params)}]"
495
+ )
496
+ try:
497
+ url = f"{self.url}/bulk-delete"
498
+ options = {
499
+ "ids": [chunk_id],
500
+ "params": params,
501
+ "vectorStoreProvider": vector_store_provider,
502
+ }
503
+ self.logger.debug(f"url: {url}, options: {options}")
504
+ res = self.api_service.post(url, json=options)
505
+ return res.json()
506
+ except requests.RequestException as err:
507
+ self.logger.error(str(err))
508
+ if err.response and err.response.json().get("errors"):
509
+ return err.response.json()
510
+ return {"errors": [{"message": str(err)}]}
511
+
512
+ def search(self, params):
513
+ self.logger.debug(f"search [params={json.dumps(params)}]")
514
+ try:
515
+ url = f"{self.url}/search"
516
+ options = {**params, "workspaceId": 1}
517
+ self.logger.debug(f"url: {url}, options: {options}")
518
+ res = self.api_service.post(url, json=options)
519
+ return res.json()
520
+ except requests.RequestException as err:
521
+ self.logger.error(str(err))
522
+ if err.response and err.response.json().get("errors"):
523
+ return err.response.json()
524
+ return {"errors": [{"message": str(err)}]}
525
+
526
+ def get_search_schema(self, schema):
527
+ self.logger.debug(f"getSearchSchema [schema={json.dumps(schema)}]")
528
+ try:
529
+ fields = {}
530
+ for k, v in schema.items():
531
+ for key, val in v.items():
532
+ fields[f"{k.lower()}_{key.lower()}"] = {
533
+ "type": get_search_type(val["dataType"])
534
+ }
535
+ fields[f"{k.lower()}__label"] = {"type": "TEXT"}
536
+ return fields
537
+ except Exception as err:
538
+ self.logger.error(str(err))
539
+ return {"errors": [{"message": str(err)}]}
@@ -0,0 +1,81 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from .PromptStore import PromptStore
6
+
7
+
8
+ class PromptTemplateOnStart(BaseModel):
9
+ prompt_set_name: str
10
+ message_templates: List[Any]
11
+ prompt_set_version: Optional[str] = None
12
+ args: Optional[Any] = None
13
+ is_batch: Optional[bool] = False
14
+
15
+
16
+ class PromptTemplateEnd(BaseModel):
17
+ messages: Optional[List[Any]] = None
18
+ errors: Optional[Any] = None
19
+
20
+
21
+ class ModelStart(BaseModel):
22
+ provider: str
23
+ request: Any
24
+ llm_params: Any
25
+
26
+
27
+ class ModelEnd(BaseModel):
28
+ errors: Optional[Any]
29
+ response: Optional[Any]
30
+
31
+
32
+ class AgentStart(BaseModel):
33
+ name: str
34
+ model: str
35
+ provider: str
36
+ args: Optional[Any] = None
37
+ tools: Optional[List[Any]] = None
38
+ extra_function_call_params: Optional[Any] = None
39
+ llm_params: Optional[Any] = None
40
+
41
+
42
+ class Trace:
43
+ ps: PromptStore
44
+ id: str
45
+ span_stack: List[Dict[str, Any]]
46
+
47
+ def __init__(self):
48
+ self.ps = PromptStore()
49
+ self.id = self.ps.trace({"event": {"type": "trace"}})
50
+ self.span_stack = []
51
+
52
+ def span(self, event: Dict[str, Any]):
53
+ self.ps.trace({"event": event, "id": self.id})
54
+ sp = Span(self, self.ps, event)
55
+ self.span_stack.append(sp)
56
+ return sp
57
+
58
+ def pop(self):
59
+ return self.span_stack.pop()
60
+
61
+
62
+ class Span:
63
+ ps: PromptStore
64
+ trace: Trace
65
+ event: Dict[str, Any]
66
+
67
+ def __init__(self, trace: Trace, ps: PromptStore, event: Dict[str, Any]):
68
+ self.trace = trace
69
+ self.ps = ps
70
+ self.event = event
71
+
72
+ def end(self, event: Dict[str, Any]):
73
+ self.ps.trace({"event": event, "id": self.trace.id})
74
+ self.event = {**self.event, **event}
75
+ return self.trace.pop()
76
+
77
+ def span(self, event: Dict[str, Any]):
78
+ return self.trace.span(event)
79
+
80
+ def generation(self, event: Dict[str, Any]):
81
+ return self.trace.span(event)
@@ -0,0 +1,3 @@
1
+ from .LangChainCallbackHandler import LangChainCallbackHandler
2
+ from .PromptStore import PromptStore
3
+ from .Tracing import Trace
@@ -0,0 +1,39 @@
1
+ def is_empty(value):
2
+ if isinstance(value, list):
3
+ return len(value) == 0
4
+ if is_object(value):
5
+ return len(value.keys()) == 0
6
+ if isinstance(value, str):
7
+ return value == ''
8
+ return value is None
9
+
10
+ def is_object(value):
11
+ return value is not None and isinstance(value, (dict, object))
12
+
13
+ def strip_empty_values(obj):
14
+ def inner(val):
15
+ if isinstance(val, list):
16
+ if len(val):
17
+ arr = [inner(x) for x in val if inner(x) is not None]
18
+ if len(arr):
19
+ return arr
20
+ elif is_object(val):
21
+ obj = {k: inner(v) for k, v in val.items() if inner(v) is not None}
22
+ if not is_empty(obj):
23
+ return obj
24
+ else:
25
+ if val is not None and val != '':
26
+ return val
27
+ return None
28
+
29
+ return inner(obj)
30
+
31
+ def get_search_type(data_type):
32
+ if data_type in ['String', 'DateTime']:
33
+ return 'TEXT'
34
+ elif data_type == 'Boolean':
35
+ return 'TAG'
36
+ elif data_type in ['Double', 'Long']:
37
+ return 'NUMERIC'
38
+ else:
39
+ return 'TEXT'
@@ -0,0 +1,18 @@
1
+ [tool.poetry]
2
+ name = "promptstorepy"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = ["Mark Moloney <markmo@me.com>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.11"
10
+ asyncio = "^3.4.3"
11
+ requests = "^2.32.2"
12
+ langchain-core = "^0.2.1"
13
+ devtools = "^0.12.2"
14
+
15
+
16
+ [build-system]
17
+ requires = ["poetry-core"]
18
+ build-backend = "poetry.core.masonry.api"