semantic-kernel 0.3.0.dev0__py3-none-any.whl → 0.3.2.dev0__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.
- semantic_kernel/__init__.py +2 -0
- semantic_kernel/connectors/ai/chat_completion_client_base.py +26 -2
- semantic_kernel/connectors/ai/chat_request_settings.py +2 -0
- semantic_kernel/connectors/ai/complete_request_settings.py +1 -0
- semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py +43 -18
- semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py +48 -14
- semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py +16 -11
- semantic_kernel/connectors/ai/text_completion_client_base.py +24 -2
- semantic_kernel/connectors/memory/chroma/chroma_memory_store.py +8 -5
- semantic_kernel/connectors/memory/chroma/utils.py +45 -22
- semantic_kernel/connectors/memory/pinecone/__init__.py +7 -0
- semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py +401 -0
- semantic_kernel/connectors/memory/pinecone/utils.py +37 -0
- semantic_kernel/connectors/memory/{weaviate_memory_store.py → weaviate/weaviate_memory_store.py} +30 -15
- semantic_kernel/core_skills/file_io_skill.py +1 -1
- semantic_kernel/core_skills/math_skill.py +1 -1
- semantic_kernel/core_skills/text_skill.py +1 -1
- semantic_kernel/core_skills/time_skill.py +15 -3
- semantic_kernel/core_skills/wait_skill.py +23 -0
- semantic_kernel/kernel.py +2 -0
- semantic_kernel/memory/memory_query_result.py +4 -0
- semantic_kernel/memory/memory_record.py +15 -2
- semantic_kernel/memory/null_memory.py +7 -1
- semantic_kernel/memory/semantic_text_memory.py +17 -6
- semantic_kernel/memory/semantic_text_memory_base.py +2 -0
- semantic_kernel/orchestration/sk_function.py +5 -0
- semantic_kernel/planning/basic_planner.py +9 -1
- semantic_kernel/semantic_functions/prompt_template_config.py +6 -0
- semantic_kernel/skill_definition/functions_view.py +14 -1
- semantic_kernel/text/text_chunker.py +152 -69
- semantic_kernel/utils/settings.py +36 -31
- {semantic_kernel-0.3.0.dev0.dist-info → semantic_kernel-0.3.2.dev0.dist-info}/METADATA +3 -1
- {semantic_kernel-0.3.0.dev0.dist-info → semantic_kernel-0.3.2.dev0.dist-info}/RECORD +34 -30
- {semantic_kernel-0.3.0.dev0.dist-info → semantic_kernel-0.3.2.dev0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
from logging import Logger
|
|
4
|
+
from typing import List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from numpy import ndarray
|
|
7
|
+
|
|
8
|
+
import pinecone
|
|
9
|
+
from pinecone import FetchResponse, IndexDescription
|
|
10
|
+
from semantic_kernel.connectors.memory.pinecone.utils import (
|
|
11
|
+
build_payload,
|
|
12
|
+
parse_payload,
|
|
13
|
+
)
|
|
14
|
+
from semantic_kernel.memory.memory_record import MemoryRecord
|
|
15
|
+
from semantic_kernel.memory.memory_store_base import MemoryStoreBase
|
|
16
|
+
from semantic_kernel.utils.null_logger import NullLogger
|
|
17
|
+
|
|
18
|
+
# Limitations set by Pinecone at https://docs.pinecone.io/docs/limits
|
|
19
|
+
MAX_DIMENSIONALITY = 20000
|
|
20
|
+
MAX_UPSERT_BATCH_SIZE = 100
|
|
21
|
+
MAX_QUERY_WITHOUT_METADATA_BATCH_SIZE = 10000
|
|
22
|
+
MAX_QUERY_WITH_METADATA_BATCH_SIZE = 1000
|
|
23
|
+
MAX_FETCH_BATCH_SIZE = 1000
|
|
24
|
+
MAX_DELETE_BATCH_SIZE = 1000
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PineconeMemoryStore(MemoryStoreBase):
|
|
28
|
+
_logger: Logger
|
|
29
|
+
_pinecone_api_key: str
|
|
30
|
+
_pinecone_environment: str
|
|
31
|
+
_default_dimensionality: int
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
api_key: str,
|
|
36
|
+
environment: str,
|
|
37
|
+
default_dimensionality: int,
|
|
38
|
+
logger: Optional[Logger] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Initializes a new instance of the PineconeMemoryStore class.
|
|
41
|
+
|
|
42
|
+
Arguments:
|
|
43
|
+
pinecone_api_key {str} -- The Pinecone API key.
|
|
44
|
+
pinecone_environment {str} -- The Pinecone environment.
|
|
45
|
+
logger {Optional[Logger]} -- The logger to use. (default: {None})
|
|
46
|
+
default_dimensionality {int} -- The default dimensionality to use for new collections.
|
|
47
|
+
"""
|
|
48
|
+
if default_dimensionality > MAX_DIMENSIONALITY:
|
|
49
|
+
raise ValueError(
|
|
50
|
+
f"Dimensionality of {default_dimensionality} exceeds "
|
|
51
|
+
+ f"the maximum allowed value of {MAX_DIMENSIONALITY}."
|
|
52
|
+
)
|
|
53
|
+
self._pinecone_api_key = api_key
|
|
54
|
+
self._pinecone_environment = environment
|
|
55
|
+
self._default_dimensionality = default_dimensionality
|
|
56
|
+
self._logger = logger or NullLogger()
|
|
57
|
+
|
|
58
|
+
pinecone.init(
|
|
59
|
+
api_key=self._pinecone_api_key, environment=self._pinecone_environment
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def get_collections(self) -> List[str]:
|
|
63
|
+
return pinecone.list_indexes()
|
|
64
|
+
|
|
65
|
+
async def create_collection_async(
|
|
66
|
+
self,
|
|
67
|
+
collection_name: str,
|
|
68
|
+
dimension_num: Optional[int] = None,
|
|
69
|
+
distance_type: Optional[str] = "cosine",
|
|
70
|
+
num_of_pods: Optional[int] = 1,
|
|
71
|
+
replica_num: Optional[int] = 0,
|
|
72
|
+
type_of_pod: Optional[str] = "p1.x1",
|
|
73
|
+
metadata_config: Optional[dict] = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Creates a new collection in Pinecone if it does not exist.
|
|
76
|
+
This function creates an index, by default the following index
|
|
77
|
+
settings are used: metric = cosine, pods = 1, replicas = 0,
|
|
78
|
+
pod_type = p1.x1, metadata_config = None.
|
|
79
|
+
|
|
80
|
+
Arguments:
|
|
81
|
+
collection_name {str} -- The name of the collection to create.
|
|
82
|
+
In Pinecone, a collection is represented as an index. The concept
|
|
83
|
+
of "collection" in Pinecone is just a static copy of an index.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
None
|
|
87
|
+
"""
|
|
88
|
+
if dimension_num is None:
|
|
89
|
+
dimension_num = self._default_dimensionality
|
|
90
|
+
if dimension_num > MAX_DIMENSIONALITY:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Dimensionality of {dimension_num} exceeds "
|
|
93
|
+
+ f"the maximum allowed value of {MAX_DIMENSIONALITY}."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if collection_name not in pinecone.list_indexes():
|
|
97
|
+
pinecone.create_index(
|
|
98
|
+
name=collection_name,
|
|
99
|
+
dimension=dimension_num,
|
|
100
|
+
metric=distance_type,
|
|
101
|
+
pods=num_of_pods,
|
|
102
|
+
replicas=replica_num,
|
|
103
|
+
pod_type=type_of_pod,
|
|
104
|
+
metadata_config=metadata_config,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
async def describe_collection_async(
|
|
108
|
+
self, collection_name: str
|
|
109
|
+
) -> Optional[IndexDescription]:
|
|
110
|
+
"""Gets the description of the index.
|
|
111
|
+
Arguments:
|
|
112
|
+
collection_name {str} -- The name of the index to get.
|
|
113
|
+
Returns:
|
|
114
|
+
Optional[dict] -- The index.
|
|
115
|
+
"""
|
|
116
|
+
if collection_name in pinecone.list_indexes():
|
|
117
|
+
return pinecone.describe_index(collection_name)
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
async def get_collections_async(
|
|
121
|
+
self,
|
|
122
|
+
) -> List[str]:
|
|
123
|
+
"""Gets the list of collections.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List[str] -- The list of collections.
|
|
127
|
+
"""
|
|
128
|
+
return list(pinecone.list_indexes())
|
|
129
|
+
|
|
130
|
+
async def delete_collection_async(self, collection_name: str) -> None:
|
|
131
|
+
"""Deletes a collection.
|
|
132
|
+
|
|
133
|
+
Arguments:
|
|
134
|
+
collection_name {str} -- The name of the collection to delete.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
None
|
|
138
|
+
"""
|
|
139
|
+
if collection_name in pinecone.list_indexes():
|
|
140
|
+
pinecone.delete_index(collection_name)
|
|
141
|
+
|
|
142
|
+
async def does_collection_exist_async(self, collection_name: str) -> bool:
|
|
143
|
+
"""Checks if a collection exists.
|
|
144
|
+
|
|
145
|
+
Arguments:
|
|
146
|
+
collection_name {str} -- The name of the collection to check.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
bool -- True if the collection exists; otherwise, False.
|
|
150
|
+
"""
|
|
151
|
+
return collection_name in pinecone.list_indexes()
|
|
152
|
+
|
|
153
|
+
async def upsert_async(self, collection_name: str, record: MemoryRecord) -> str:
|
|
154
|
+
"""Upserts a record.
|
|
155
|
+
|
|
156
|
+
Arguments:
|
|
157
|
+
collection_name {str} -- The name of the collection to upsert the record into.
|
|
158
|
+
record {MemoryRecord} -- The record to upsert.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
str -- The unique database key of the record. In Pinecone, this is the record ID.
|
|
162
|
+
"""
|
|
163
|
+
if collection_name not in pinecone.list_indexes():
|
|
164
|
+
raise Exception(f"Collection '{collection_name}' does not exist")
|
|
165
|
+
|
|
166
|
+
collection = pinecone.Index(collection_name)
|
|
167
|
+
|
|
168
|
+
upsert_response = collection.upsert(
|
|
169
|
+
vectors=[(record._id, record.embedding.tolist(), build_payload(record))],
|
|
170
|
+
namespace="",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if upsert_response.upserted_count is None:
|
|
174
|
+
raise Exception(f"Error upserting record: {upsert_response.message}")
|
|
175
|
+
|
|
176
|
+
return record._id
|
|
177
|
+
|
|
178
|
+
async def upsert_batch_async(
|
|
179
|
+
self, collection_name: str, records: List[MemoryRecord]
|
|
180
|
+
) -> List[str]:
|
|
181
|
+
"""Upserts a batch of records.
|
|
182
|
+
|
|
183
|
+
Arguments:
|
|
184
|
+
collection_name {str} -- The name of the collection to upsert the records into.
|
|
185
|
+
records {List[MemoryRecord]} -- The records to upsert.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List[str] -- The unique database keys of the records.
|
|
189
|
+
"""
|
|
190
|
+
if collection_name not in pinecone.list_indexes():
|
|
191
|
+
raise Exception(f"Collection '{collection_name}' does not exist")
|
|
192
|
+
|
|
193
|
+
collection = pinecone.Index(collection_name)
|
|
194
|
+
|
|
195
|
+
vectors = [
|
|
196
|
+
(
|
|
197
|
+
record._id,
|
|
198
|
+
record.embedding.tolist(),
|
|
199
|
+
build_payload(record),
|
|
200
|
+
)
|
|
201
|
+
for record in records
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
upsert_response = collection.upsert(
|
|
205
|
+
vectors, namespace="", batch_size=MAX_UPSERT_BATCH_SIZE
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if upsert_response.upserted_count is None:
|
|
209
|
+
raise Exception(f"Error upserting record: {upsert_response.message}")
|
|
210
|
+
else:
|
|
211
|
+
return [record._id for record in records]
|
|
212
|
+
|
|
213
|
+
async def get_async(
|
|
214
|
+
self, collection_name: str, key: str, with_embedding: bool = False
|
|
215
|
+
) -> MemoryRecord:
|
|
216
|
+
"""Gets a record.
|
|
217
|
+
|
|
218
|
+
Arguments:
|
|
219
|
+
collection_name {str} -- The name of the collection to get the record from.
|
|
220
|
+
key {str} -- The unique database key of the record.
|
|
221
|
+
with_embedding {bool} -- Whether to include the embedding in the result. (default: {False})
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
MemoryRecord -- The record.
|
|
225
|
+
"""
|
|
226
|
+
if collection_name not in pinecone.list_indexes():
|
|
227
|
+
raise Exception(f"Collection '{collection_name}' does not exist")
|
|
228
|
+
|
|
229
|
+
collection = pinecone.Index(collection_name)
|
|
230
|
+
fetch_response = collection.fetch([key])
|
|
231
|
+
|
|
232
|
+
if len(fetch_response.vectors) == 0:
|
|
233
|
+
raise KeyError(f"Record with key '{key}' does not exist")
|
|
234
|
+
|
|
235
|
+
return parse_payload(fetch_response.vectors[key], with_embedding)
|
|
236
|
+
|
|
237
|
+
async def get_batch_async(
|
|
238
|
+
self, collection_name: str, keys: List[str], with_embeddings: bool = False
|
|
239
|
+
) -> List[MemoryRecord]:
|
|
240
|
+
"""Gets a batch of records.
|
|
241
|
+
|
|
242
|
+
Arguments:
|
|
243
|
+
collection_name {str} -- The name of the collection to get the records from.
|
|
244
|
+
keys {List[str]} -- The unique database keys of the records.
|
|
245
|
+
with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False})
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
List[MemoryRecord] -- The records.
|
|
249
|
+
"""
|
|
250
|
+
if collection_name not in pinecone.list_indexes():
|
|
251
|
+
raise Exception(f"Collection '{collection_name}' does not exist")
|
|
252
|
+
|
|
253
|
+
fetch_response = await self.__get_batch_async(
|
|
254
|
+
collection_name, keys, with_embeddings
|
|
255
|
+
)
|
|
256
|
+
return [
|
|
257
|
+
parse_payload(fetch_response.vectors[key], with_embeddings)
|
|
258
|
+
for key in fetch_response.vectors.keys()
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
async def remove_async(self, collection_name: str, key: str) -> None:
|
|
262
|
+
"""Removes a record.
|
|
263
|
+
|
|
264
|
+
Arguments:
|
|
265
|
+
collection_name {str} -- The name of the collection to remove the record from.
|
|
266
|
+
key {str} -- The unique database key of the record to remove.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
None
|
|
270
|
+
"""
|
|
271
|
+
if collection_name not in pinecone.list_indexes():
|
|
272
|
+
raise Exception(f"Collection '{collection_name}' does not exist")
|
|
273
|
+
|
|
274
|
+
collection = pinecone.Index(collection_name)
|
|
275
|
+
collection.delete([key])
|
|
276
|
+
|
|
277
|
+
async def remove_batch_async(self, collection_name: str, keys: List[str]) -> None:
|
|
278
|
+
"""Removes a batch of records.
|
|
279
|
+
|
|
280
|
+
Arguments:
|
|
281
|
+
collection_name {str} -- The name of the collection to remove the records from.
|
|
282
|
+
keys {List[str]} -- The unique database keys of the records to remove.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
None
|
|
286
|
+
"""
|
|
287
|
+
if collection_name not in pinecone.list_indexes():
|
|
288
|
+
raise Exception(f"Collection '{collection_name}' does not exist")
|
|
289
|
+
|
|
290
|
+
collection = pinecone.Index(collection_name)
|
|
291
|
+
for i in range(0, len(keys), MAX_DELETE_BATCH_SIZE):
|
|
292
|
+
collection.delete(keys[i : i + MAX_DELETE_BATCH_SIZE])
|
|
293
|
+
collection.delete(keys)
|
|
294
|
+
|
|
295
|
+
async def get_nearest_match_async(
|
|
296
|
+
self,
|
|
297
|
+
collection_name: str,
|
|
298
|
+
embedding: ndarray,
|
|
299
|
+
min_relevance_score: float = 0.0,
|
|
300
|
+
with_embedding: bool = False,
|
|
301
|
+
) -> Tuple[MemoryRecord, float]:
|
|
302
|
+
"""Gets the nearest match to an embedding using cosine similarity.
|
|
303
|
+
|
|
304
|
+
Arguments:
|
|
305
|
+
collection_name {str} -- The name of the collection to get the nearest match from.
|
|
306
|
+
embedding {ndarray} -- The embedding to find the nearest match to.
|
|
307
|
+
min_relevance_score {float} -- The minimum relevance score of the match. (default: {0.0})
|
|
308
|
+
with_embedding {bool} -- Whether to include the embedding in the result. (default: {False})
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Tuple[MemoryRecord, float] -- The record and the relevance score.
|
|
312
|
+
"""
|
|
313
|
+
matches = await self.get_nearest_matches_async(
|
|
314
|
+
collection_name=collection_name,
|
|
315
|
+
embedding=embedding,
|
|
316
|
+
limit=1,
|
|
317
|
+
min_relevance_score=min_relevance_score,
|
|
318
|
+
with_embeddings=with_embedding,
|
|
319
|
+
)
|
|
320
|
+
return matches[0]
|
|
321
|
+
|
|
322
|
+
async def get_nearest_matches_async(
|
|
323
|
+
self,
|
|
324
|
+
collection_name: str,
|
|
325
|
+
embedding: ndarray,
|
|
326
|
+
limit: int,
|
|
327
|
+
min_relevance_score: float = 0.0,
|
|
328
|
+
with_embeddings: bool = False,
|
|
329
|
+
) -> List[Tuple[MemoryRecord, float]]:
|
|
330
|
+
"""Gets the nearest matches to an embedding using cosine similarity.
|
|
331
|
+
|
|
332
|
+
Arguments:
|
|
333
|
+
collection_name {str} -- The name of the collection to get the nearest matches from.
|
|
334
|
+
embedding {ndarray} -- The embedding to find the nearest matches to.
|
|
335
|
+
limit {int} -- The maximum number of matches to return.
|
|
336
|
+
min_relevance_score {float} -- The minimum relevance score of the matches. (default: {0.0})
|
|
337
|
+
with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False})
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
List[Tuple[MemoryRecord, float]] -- The records and their relevance scores.
|
|
341
|
+
"""
|
|
342
|
+
if collection_name not in pinecone.list_indexes():
|
|
343
|
+
raise Exception(f"Collection '{collection_name}' does not exist")
|
|
344
|
+
|
|
345
|
+
collection = pinecone.Index(collection_name)
|
|
346
|
+
|
|
347
|
+
if limit > MAX_QUERY_WITHOUT_METADATA_BATCH_SIZE:
|
|
348
|
+
raise Exception(
|
|
349
|
+
"Limit must be less than or equal to "
|
|
350
|
+
+ f"{MAX_QUERY_WITHOUT_METADATA_BATCH_SIZE}"
|
|
351
|
+
)
|
|
352
|
+
elif limit > MAX_QUERY_WITH_METADATA_BATCH_SIZE:
|
|
353
|
+
query_response = collection.query(
|
|
354
|
+
vector=embedding.tolist(),
|
|
355
|
+
top_k=limit,
|
|
356
|
+
include_values=False,
|
|
357
|
+
include_metadata=False,
|
|
358
|
+
)
|
|
359
|
+
keys = [match.id for match in query_response.matches]
|
|
360
|
+
fetch_response = await self.__get_batch_async(
|
|
361
|
+
collection_name, keys, with_embeddings
|
|
362
|
+
)
|
|
363
|
+
vectors = fetch_response.vectors
|
|
364
|
+
for match in query_response.matches:
|
|
365
|
+
vectors[match.id].update(match)
|
|
366
|
+
matches = [vectors[key] for key in vectors.keys()]
|
|
367
|
+
else:
|
|
368
|
+
query_response = collection.query(
|
|
369
|
+
vector=embedding.tolist(),
|
|
370
|
+
top_k=limit,
|
|
371
|
+
include_values=with_embeddings,
|
|
372
|
+
include_metadata=True,
|
|
373
|
+
)
|
|
374
|
+
matches = query_response.matches
|
|
375
|
+
if min_relevance_score is not None:
|
|
376
|
+
matches = [match for match in matches if match.score >= min_relevance_score]
|
|
377
|
+
return (
|
|
378
|
+
[
|
|
379
|
+
(
|
|
380
|
+
parse_payload(match, with_embeddings),
|
|
381
|
+
match["score"],
|
|
382
|
+
)
|
|
383
|
+
for match in matches
|
|
384
|
+
]
|
|
385
|
+
if len(matches) > 0
|
|
386
|
+
else []
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
async def __get_batch_async(
|
|
390
|
+
self, collection_name: str, keys: List[str], with_embeddings: bool = False
|
|
391
|
+
) -> "FetchResponse":
|
|
392
|
+
index = pinecone.Index(collection_name)
|
|
393
|
+
if len(keys) > MAX_FETCH_BATCH_SIZE:
|
|
394
|
+
fetch_response = index.fetch(keys[0:MAX_FETCH_BATCH_SIZE])
|
|
395
|
+
for i in range(MAX_FETCH_BATCH_SIZE, len(keys), MAX_FETCH_BATCH_SIZE):
|
|
396
|
+
fetch_response.vectors.update(
|
|
397
|
+
index.fetch(keys[i : i + MAX_FETCH_BATCH_SIZE]).vectors
|
|
398
|
+
)
|
|
399
|
+
else:
|
|
400
|
+
fetch_response = index.fetch(keys)
|
|
401
|
+
return fetch_response
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import numpy
|
|
4
|
+
|
|
5
|
+
from pinecone import Vector
|
|
6
|
+
from semantic_kernel.memory.memory_record import MemoryRecord
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_payload(record: MemoryRecord) -> dict:
|
|
10
|
+
"""
|
|
11
|
+
Builds a metadata payload to be sent to Pinecone from a MemoryRecord.
|
|
12
|
+
"""
|
|
13
|
+
payload: dict = {}
|
|
14
|
+
if record._text:
|
|
15
|
+
payload["text"] = record._text
|
|
16
|
+
if record._description:
|
|
17
|
+
payload["description"] = record._description
|
|
18
|
+
if record._additional_metadata:
|
|
19
|
+
payload["additional_metadata"] = record._additional_metadata
|
|
20
|
+
return payload
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_payload(record: Vector, with_embeddings: bool) -> MemoryRecord:
|
|
24
|
+
"""
|
|
25
|
+
Parses a record from Pinecone into a MemoryRecord.
|
|
26
|
+
"""
|
|
27
|
+
payload = record.metadata
|
|
28
|
+
description = payload.get("description", None)
|
|
29
|
+
text = payload.get("text", None)
|
|
30
|
+
additional_metadata = payload.get("additional_metadata", None)
|
|
31
|
+
return MemoryRecord.local_record(
|
|
32
|
+
id=record.id,
|
|
33
|
+
description=description,
|
|
34
|
+
text=text,
|
|
35
|
+
additional_metadata=additional_metadata,
|
|
36
|
+
embedding=record.values if with_embeddings else numpy.array([]),
|
|
37
|
+
)
|
semantic_kernel/connectors/memory/{weaviate_memory_store.py → weaviate/weaviate_memory_store.py}
RENAMED
|
@@ -52,6 +52,11 @@ SCHEMA = {
|
|
|
52
52
|
"description": "The text of the record.",
|
|
53
53
|
"dataType": ["text"],
|
|
54
54
|
},
|
|
55
|
+
{
|
|
56
|
+
"name": "additionalMetadata",
|
|
57
|
+
"description": "Optional custom metadata of the record.",
|
|
58
|
+
"dataType": ["string"],
|
|
59
|
+
},
|
|
55
60
|
],
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -81,6 +86,7 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
|
|
81
86
|
"_id": "skId",
|
|
82
87
|
"_description": "description",
|
|
83
88
|
"_text": "text",
|
|
89
|
+
"_additional_metadata": "additionalMetadata",
|
|
84
90
|
"_embedding": "vector",
|
|
85
91
|
}
|
|
86
92
|
|
|
@@ -133,14 +139,20 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
|
|
133
139
|
async def create_collection_async(self, collection_name: str) -> None:
|
|
134
140
|
schema = SCHEMA.copy()
|
|
135
141
|
schema["class"] = collection_name
|
|
136
|
-
await asyncio.
|
|
142
|
+
await asyncio.get_running_loop().run_in_executor(
|
|
143
|
+
None, self.client.schema.create_class, schema
|
|
144
|
+
)
|
|
137
145
|
|
|
138
146
|
async def get_collections_async(self) -> List[str]:
|
|
139
|
-
schemas = await asyncio.
|
|
147
|
+
schemas = await asyncio.get_running_loop().run_in_executor(
|
|
148
|
+
None, self.client.schema.get
|
|
149
|
+
)
|
|
140
150
|
return [schema["class"] for schema in schemas["classes"]]
|
|
141
151
|
|
|
142
152
|
async def delete_collection_async(self, collection_name: str) -> bool:
|
|
143
|
-
await asyncio.
|
|
153
|
+
await asyncio.get_running_loop().run_in_executor(
|
|
154
|
+
None, self.client.schema.delete_class, collection_name
|
|
155
|
+
)
|
|
144
156
|
|
|
145
157
|
async def does_collection_exist_async(self, collection_name: str) -> bool:
|
|
146
158
|
collections = await self.get_collections_async()
|
|
@@ -152,12 +164,13 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
|
|
152
164
|
vector = weaviate_record.pop("vector", None)
|
|
153
165
|
weaviate_id = weaviate.util.generate_uuid5(weaviate_record, collection_name)
|
|
154
166
|
|
|
155
|
-
return await asyncio.
|
|
167
|
+
return await asyncio.get_running_loop().run_in_executor(
|
|
168
|
+
None,
|
|
156
169
|
self.client.data_object.create,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
weaviate_record,
|
|
171
|
+
collection_name,
|
|
172
|
+
weaviate_id,
|
|
173
|
+
vector,
|
|
161
174
|
)
|
|
162
175
|
|
|
163
176
|
async def upsert_batch_async(
|
|
@@ -182,7 +195,9 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
|
|
182
195
|
|
|
183
196
|
return results
|
|
184
197
|
|
|
185
|
-
return await asyncio.
|
|
198
|
+
return await asyncio.get_running_loop().run_in_executor(
|
|
199
|
+
None, _upsert_batch_inner
|
|
200
|
+
)
|
|
186
201
|
|
|
187
202
|
async def get_async(
|
|
188
203
|
self, collection_name: str, key: str, with_embedding: bool
|
|
@@ -196,7 +211,9 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
|
|
196
211
|
) -> List[MemoryRecord]:
|
|
197
212
|
queries = self._build_multi_get_query(collection_name, keys, with_embedding)
|
|
198
213
|
|
|
199
|
-
results = await asyncio.
|
|
214
|
+
results = await asyncio.get_running_loop().run_in_executor(
|
|
215
|
+
None, self.client.query.multi_get(queries).do
|
|
216
|
+
)
|
|
200
217
|
|
|
201
218
|
get_dict = results.get("data", {}).get("Get", {})
|
|
202
219
|
|
|
@@ -254,10 +271,8 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
|
|
254
271
|
"valueString": key,
|
|
255
272
|
}
|
|
256
273
|
|
|
257
|
-
await asyncio.
|
|
258
|
-
self.client.batch.delete_objects,
|
|
259
|
-
class_name=collection_name,
|
|
260
|
-
where=where,
|
|
274
|
+
await asyncio.get_running_loop().run_in_executor(
|
|
275
|
+
None, self.client.batch.delete_objects, collection_name, where
|
|
261
276
|
)
|
|
262
277
|
|
|
263
278
|
async def get_nearest_matches_async(
|
|
@@ -284,7 +299,7 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
|
|
284
299
|
.with_limit(limit)
|
|
285
300
|
)
|
|
286
301
|
|
|
287
|
-
results = await asyncio.
|
|
302
|
+
results = await asyncio.get_running_loop().run_in_executor(None, query.do)
|
|
288
303
|
|
|
289
304
|
get_dict = results.get("data", {}).get("Get", {})
|
|
290
305
|
|
|
@@ -9,7 +9,7 @@ class MathSkill:
|
|
|
9
9
|
Description: MathSkill provides a set of functions to make Math calculations.
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
|
-
kernel.import_skill("math"
|
|
12
|
+
kernel.import_skill(MathSkill(), skill_name="math")
|
|
13
13
|
|
|
14
14
|
Examples:
|
|
15
15
|
{{math.Add}} => Returns the sum of initial_value_text and Amount (provided in the SKContext)
|
|
@@ -9,11 +9,12 @@ class TimeSkill:
|
|
|
9
9
|
to get the current time and date.
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
|
-
kernel.import_skill("time"
|
|
12
|
+
kernel.import_skill(TimeSkill(), skill_name="time")
|
|
13
13
|
|
|
14
14
|
Examples:
|
|
15
15
|
{{time.date}} => Sunday, 12 January, 2031
|
|
16
16
|
{{time.today}} => Sunday, 12 January, 2031
|
|
17
|
+
{{time.iso_date}} => 2031-01-12
|
|
17
18
|
{{time.now}} => Sunday, January 12, 2031 9:15 PM
|
|
18
19
|
{{time.utcNow}} => Sunday, January 13, 2031 5:15 AM
|
|
19
20
|
{{time.time}} => 09:15:07 PM
|
|
@@ -55,6 +56,17 @@ class TimeSkill:
|
|
|
55
56
|
"""
|
|
56
57
|
return self.date()
|
|
57
58
|
|
|
59
|
+
@sk_function(description="Get the current date in iso format.")
|
|
60
|
+
def iso_date(self) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Get the current date in iso format
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
{{time.iso_date}} => 2031-01-12
|
|
66
|
+
"""
|
|
67
|
+
today = datetime.date.today()
|
|
68
|
+
return today.isoformat()
|
|
69
|
+
|
|
58
70
|
@sk_function(description="Get the current date and time in the local time zone")
|
|
59
71
|
def now(self) -> str:
|
|
60
72
|
"""
|
|
@@ -196,8 +208,8 @@ class TimeSkill:
|
|
|
196
208
|
return d.strftime("%A, %d %B, %Y")
|
|
197
209
|
|
|
198
210
|
@sk_function(
|
|
199
|
-
description="""Get the date of the last day matching the supplied week day name in English.
|
|
200
|
-
Example: Che giorno era 'Martedi' scorso -> dateMatchingLastDayName 'Tuesday' => Tuesday,
|
|
211
|
+
description="""Get the date of the last day matching the supplied week day name in English.
|
|
212
|
+
Example: Che giorno era 'Martedi' scorso -> dateMatchingLastDayName 'Tuesday' => Tuesday,
|
|
201
213
|
16 May, 2023"""
|
|
202
214
|
)
|
|
203
215
|
def date_matching_last_day_name(self, day_name: str) -> str:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from semantic_kernel.skill_definition import sk_function
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WaitSkill:
|
|
7
|
+
"""
|
|
8
|
+
WaitSkill provides a set of functions to wait for a certain amount of time.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
kernel.import_skill(WaitSkill(), skill_name="wait")
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
{{wait.seconds 5}} => Wait for 5 seconds
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@sk_function(description="Wait for a certain number of seconds.")
|
|
18
|
+
async def wait(self, seconds_text: str):
|
|
19
|
+
try:
|
|
20
|
+
seconds = max(float(seconds_text), 0)
|
|
21
|
+
except ValueError:
|
|
22
|
+
raise ValueError("seconds text must be a number")
|
|
23
|
+
await asyncio.sleep(seconds)
|
semantic_kernel/kernel.py
CHANGED
|
@@ -693,6 +693,7 @@ class Kernel:
|
|
|
693
693
|
top_p: float = 1.0,
|
|
694
694
|
presence_penalty: float = 0.0,
|
|
695
695
|
frequency_penalty: float = 0.0,
|
|
696
|
+
number_of_responses: int = 1,
|
|
696
697
|
stop_sequences: Optional[List[str]] = None,
|
|
697
698
|
) -> "SKFunctionBase":
|
|
698
699
|
function_name = (
|
|
@@ -714,6 +715,7 @@ class Kernel:
|
|
|
714
715
|
presence_penalty,
|
|
715
716
|
frequency_penalty,
|
|
716
717
|
max_tokens,
|
|
718
|
+
number_of_responses,
|
|
717
719
|
stop_sequences if stop_sequences is not None else [],
|
|
718
720
|
),
|
|
719
721
|
)
|
|
@@ -13,6 +13,7 @@ class MemoryQueryResult:
|
|
|
13
13
|
id: str
|
|
14
14
|
description: Optional[str]
|
|
15
15
|
text: Optional[str]
|
|
16
|
+
additional_metadata: Optional[str]
|
|
16
17
|
relevance: float
|
|
17
18
|
embedding: Optional[ndarray]
|
|
18
19
|
|
|
@@ -23,6 +24,7 @@ class MemoryQueryResult:
|
|
|
23
24
|
id: str,
|
|
24
25
|
description: Optional[str],
|
|
25
26
|
text: Optional[str],
|
|
27
|
+
additional_metadata: Optional[str],
|
|
26
28
|
embedding: Optional[ndarray],
|
|
27
29
|
relevance: float,
|
|
28
30
|
) -> None:
|
|
@@ -45,6 +47,7 @@ class MemoryQueryResult:
|
|
|
45
47
|
self.id = id
|
|
46
48
|
self.description = description
|
|
47
49
|
self.text = text
|
|
50
|
+
self.additional_metadata = additional_metadata
|
|
48
51
|
self.relevance = relevance
|
|
49
52
|
self.embedding = embedding
|
|
50
53
|
|
|
@@ -68,6 +71,7 @@ class MemoryQueryResult:
|
|
|
68
71
|
id=record._id,
|
|
69
72
|
description=record._description,
|
|
70
73
|
text=record._text,
|
|
74
|
+
additional_metadata=record._additional_metadata,
|
|
71
75
|
embedding=record._embedding,
|
|
72
76
|
relevance=relevance,
|
|
73
77
|
)
|