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
|
@@ -13,6 +13,7 @@ class MemoryRecord:
|
|
|
13
13
|
_id: str
|
|
14
14
|
_description: Optional[str]
|
|
15
15
|
_text: Optional[str]
|
|
16
|
+
_additional_metadata: Optional[str]
|
|
16
17
|
_embedding: ndarray
|
|
17
18
|
|
|
18
19
|
def __init__(
|
|
@@ -22,7 +23,8 @@ class MemoryRecord:
|
|
|
22
23
|
id: str,
|
|
23
24
|
description: Optional[str],
|
|
24
25
|
text: Optional[str],
|
|
25
|
-
|
|
26
|
+
additional_metadata: Optional[str],
|
|
27
|
+
embedding: Optional[ndarray],
|
|
26
28
|
key: Optional[str] = None,
|
|
27
29
|
timestamp: Optional[str] = None,
|
|
28
30
|
) -> None:
|
|
@@ -34,6 +36,7 @@ class MemoryRecord:
|
|
|
34
36
|
id {str} -- A unique for the record.
|
|
35
37
|
description {Optional[str]} -- The description of the record.
|
|
36
38
|
text {Optional[str]} -- The text of the record.
|
|
39
|
+
additional_metadata {Optional[str]} -- Custom metadata for the record.
|
|
37
40
|
embedding {ndarray} -- The embedding of the record.
|
|
38
41
|
|
|
39
42
|
Returns:
|
|
@@ -46,6 +49,7 @@ class MemoryRecord:
|
|
|
46
49
|
self._id = id
|
|
47
50
|
self._description = description
|
|
48
51
|
self._text = text
|
|
52
|
+
self._additional_metadata = additional_metadata
|
|
49
53
|
self._embedding = embedding
|
|
50
54
|
|
|
51
55
|
@property
|
|
@@ -57,6 +61,7 @@ class MemoryRecord:
|
|
|
57
61
|
external_id: str,
|
|
58
62
|
source_name: str,
|
|
59
63
|
description: Optional[str],
|
|
64
|
+
additional_metadata: Optional[str],
|
|
60
65
|
embedding: ndarray,
|
|
61
66
|
) -> "MemoryRecord":
|
|
62
67
|
"""Create a reference record.
|
|
@@ -65,6 +70,7 @@ class MemoryRecord:
|
|
|
65
70
|
external_id {str} -- The external id of the record.
|
|
66
71
|
source_name {str} -- The name of the external source.
|
|
67
72
|
description {Optional[str]} -- The description of the record.
|
|
73
|
+
additional_metadata {Optional[str]} -- Custom metadata for the record.
|
|
68
74
|
embedding {ndarray} -- The embedding of the record.
|
|
69
75
|
|
|
70
76
|
Returns:
|
|
@@ -76,12 +82,17 @@ class MemoryRecord:
|
|
|
76
82
|
id=external_id,
|
|
77
83
|
description=description,
|
|
78
84
|
text=None,
|
|
85
|
+
additional_metadata=additional_metadata,
|
|
79
86
|
embedding=embedding,
|
|
80
87
|
)
|
|
81
88
|
|
|
82
89
|
@staticmethod
|
|
83
90
|
def local_record(
|
|
84
|
-
id: str,
|
|
91
|
+
id: str,
|
|
92
|
+
text: str,
|
|
93
|
+
description: Optional[str],
|
|
94
|
+
additional_metadata: Optional[str],
|
|
95
|
+
embedding: ndarray,
|
|
85
96
|
) -> "MemoryRecord":
|
|
86
97
|
"""Create a local record.
|
|
87
98
|
|
|
@@ -89,6 +100,7 @@ class MemoryRecord:
|
|
|
89
100
|
id {str} -- A unique for the record.
|
|
90
101
|
text {str} -- The text of the record.
|
|
91
102
|
description {Optional[str]} -- The description of the record.
|
|
103
|
+
additional_metadata {Optional[str]} -- Custom metadata for the record.
|
|
92
104
|
embedding {ndarray} -- The embedding of the record.
|
|
93
105
|
|
|
94
106
|
Returns:
|
|
@@ -100,5 +112,6 @@ class MemoryRecord:
|
|
|
100
112
|
id=id,
|
|
101
113
|
description=description,
|
|
102
114
|
text=text,
|
|
115
|
+
additional_metadata=additional_metadata,
|
|
103
116
|
embedding=embedding,
|
|
104
117
|
)
|
|
@@ -8,7 +8,12 @@ from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryB
|
|
|
8
8
|
|
|
9
9
|
class NullMemory(SemanticTextMemoryBase):
|
|
10
10
|
async def save_information_async(
|
|
11
|
-
self,
|
|
11
|
+
self,
|
|
12
|
+
collection: str,
|
|
13
|
+
text: str,
|
|
14
|
+
id: str,
|
|
15
|
+
description: Optional[str] = None,
|
|
16
|
+
additional_metadata: Optional[str] = None,
|
|
12
17
|
) -> None:
|
|
13
18
|
return None
|
|
14
19
|
|
|
@@ -19,6 +24,7 @@ class NullMemory(SemanticTextMemoryBase):
|
|
|
19
24
|
external_id: str,
|
|
20
25
|
external_source_name: str,
|
|
21
26
|
description: Optional[str] = None,
|
|
27
|
+
additional_metadata: Optional[str] = None,
|
|
22
28
|
) -> None:
|
|
23
29
|
return None
|
|
24
30
|
|
|
@@ -37,6 +37,7 @@ class SemanticTextMemory(SemanticTextMemoryBase):
|
|
|
37
37
|
text: str,
|
|
38
38
|
id: str,
|
|
39
39
|
description: Optional[str] = None,
|
|
40
|
+
additional_metadata: Optional[str] = None,
|
|
40
41
|
) -> None:
|
|
41
42
|
"""Save information to the memory (calls the memory store's upsert method).
|
|
42
43
|
|
|
@@ -55,9 +56,15 @@ class SemanticTextMemory(SemanticTextMemoryBase):
|
|
|
55
56
|
):
|
|
56
57
|
await self._storage.create_collection_async(collection_name=collection)
|
|
57
58
|
|
|
58
|
-
embedding =
|
|
59
|
+
embedding = (
|
|
60
|
+
await self._embeddings_generator.generate_embeddings_async([text])
|
|
61
|
+
)[0]
|
|
59
62
|
data = MemoryRecord.local_record(
|
|
60
|
-
id=id,
|
|
63
|
+
id=id,
|
|
64
|
+
text=text,
|
|
65
|
+
description=description,
|
|
66
|
+
additional_metadata=additional_metadata,
|
|
67
|
+
embedding=embedding,
|
|
61
68
|
)
|
|
62
69
|
|
|
63
70
|
await self._storage.upsert_async(collection_name=collection, record=data)
|
|
@@ -69,6 +76,7 @@ class SemanticTextMemory(SemanticTextMemoryBase):
|
|
|
69
76
|
external_id: str,
|
|
70
77
|
external_source_name: str,
|
|
71
78
|
description: Optional[str] = None,
|
|
79
|
+
additional_metadata: Optional[str] = None,
|
|
72
80
|
) -> None:
|
|
73
81
|
"""Save a reference to the memory (calls the memory store's upsert method).
|
|
74
82
|
|
|
@@ -88,11 +96,14 @@ class SemanticTextMemory(SemanticTextMemoryBase):
|
|
|
88
96
|
):
|
|
89
97
|
await self._storage.create_collection_async(collection_name=collection)
|
|
90
98
|
|
|
91
|
-
embedding =
|
|
99
|
+
embedding = (
|
|
100
|
+
await self._embeddings_generator.generate_embeddings_async([text])
|
|
101
|
+
)[0]
|
|
92
102
|
data = MemoryRecord.reference_record(
|
|
93
103
|
external_id=external_id,
|
|
94
104
|
source_name=external_source_name,
|
|
95
105
|
description=description,
|
|
106
|
+
additional_metadata=additional_metadata,
|
|
96
107
|
embedding=embedding,
|
|
97
108
|
)
|
|
98
109
|
|
|
@@ -135,9 +146,9 @@ class SemanticTextMemory(SemanticTextMemoryBase):
|
|
|
135
146
|
Returns:
|
|
136
147
|
List[MemoryQueryResult] -- The list of MemoryQueryResult found.
|
|
137
148
|
"""
|
|
138
|
-
query_embedding =
|
|
139
|
-
[query]
|
|
140
|
-
)
|
|
149
|
+
query_embedding = (
|
|
150
|
+
await self._embeddings_generator.generate_embeddings_async([query])
|
|
151
|
+
)[0]
|
|
141
152
|
results = await self._storage.get_nearest_matches_async(
|
|
142
153
|
collection_name=collection,
|
|
143
154
|
embedding=query_embedding,
|
|
@@ -14,6 +14,7 @@ class SemanticTextMemoryBase(ABC):
|
|
|
14
14
|
text: str,
|
|
15
15
|
id: str,
|
|
16
16
|
description: Optional[str] = None,
|
|
17
|
+
additional_metadata: Optional[str] = None,
|
|
17
18
|
# TODO: ctoken?
|
|
18
19
|
) -> None:
|
|
19
20
|
pass
|
|
@@ -26,6 +27,7 @@ class SemanticTextMemoryBase(ABC):
|
|
|
26
27
|
external_id: str,
|
|
27
28
|
external_source_name: str,
|
|
28
29
|
description: Optional[str] = None,
|
|
30
|
+
additional_metadata: Optional[str] = None,
|
|
29
31
|
) -> None:
|
|
30
32
|
pass
|
|
31
33
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Copyright (c) Microsoft. All rights reserved.
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import platform
|
|
5
|
+
import sys
|
|
4
6
|
import threading
|
|
5
7
|
from enum import Enum
|
|
6
8
|
from logging import Logger
|
|
@@ -36,6 +38,9 @@ from semantic_kernel.skill_definition.read_only_skill_collection_base import (
|
|
|
36
38
|
)
|
|
37
39
|
from semantic_kernel.utils.null_logger import NullLogger
|
|
38
40
|
|
|
41
|
+
if platform.system() == "Windows" and sys.version_info >= (3, 8, 0):
|
|
42
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
43
|
+
|
|
39
44
|
|
|
40
45
|
class SKFunction(SKFunctionBase):
|
|
41
46
|
"""
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
"""A basic JSON-based planner for the Python Semantic Kernel"""
|
|
4
4
|
import json
|
|
5
5
|
|
|
6
|
+
import regex
|
|
7
|
+
|
|
6
8
|
from semantic_kernel.kernel import Kernel
|
|
7
9
|
from semantic_kernel.orchestration.context_variables import ContextVariables
|
|
8
10
|
from semantic_kernel.planning.plan import Plan
|
|
@@ -187,7 +189,13 @@ class BasicPlanner:
|
|
|
187
189
|
Given a plan, execute each of the functions within the plan
|
|
188
190
|
from start to finish and output the result.
|
|
189
191
|
"""
|
|
190
|
-
|
|
192
|
+
|
|
193
|
+
# Filter out good JSON from the result in case additional text is present
|
|
194
|
+
json_regex = r"\{(?:[^{}]|(?R))*\}"
|
|
195
|
+
generated_plan_string = regex.search(
|
|
196
|
+
json_regex, plan.generated_plan.result
|
|
197
|
+
).group()
|
|
198
|
+
generated_plan = json.loads(generated_plan_string)
|
|
191
199
|
|
|
192
200
|
context = ContextVariables()
|
|
193
201
|
context["input"] = generated_plan["input"]
|
|
@@ -13,6 +13,7 @@ class PromptTemplateConfig:
|
|
|
13
13
|
presence_penalty: float = 0.0
|
|
14
14
|
frequency_penalty: float = 0.0
|
|
15
15
|
max_tokens: int = 256
|
|
16
|
+
number_of_responses: int = 1
|
|
16
17
|
stop_sequences: List[str] = field(default_factory=list)
|
|
17
18
|
|
|
18
19
|
@dataclass
|
|
@@ -51,6 +52,9 @@ class PromptTemplateConfig:
|
|
|
51
52
|
config.completion.presence_penalty = completion_dict.get("presence_penalty")
|
|
52
53
|
config.completion.frequency_penalty = completion_dict.get("frequency_penalty")
|
|
53
54
|
config.completion.max_tokens = completion_dict.get("max_tokens")
|
|
55
|
+
config.completion.number_of_responses = completion_dict.get(
|
|
56
|
+
"number_of_responses"
|
|
57
|
+
)
|
|
54
58
|
config.completion.stop_sequences = completion_dict.get("stop_sequences", [])
|
|
55
59
|
config.default_services = data.get("default_services", [])
|
|
56
60
|
|
|
@@ -102,6 +106,7 @@ class PromptTemplateConfig:
|
|
|
102
106
|
presence_penalty: float = 0.0,
|
|
103
107
|
frequency_penalty: float = 0.0,
|
|
104
108
|
max_tokens: int = 256,
|
|
109
|
+
number_of_responses: int = 1,
|
|
105
110
|
stop_sequences: List[str] = [],
|
|
106
111
|
) -> "PromptTemplateConfig":
|
|
107
112
|
config = PromptTemplateConfig()
|
|
@@ -110,5 +115,6 @@ class PromptTemplateConfig:
|
|
|
110
115
|
config.completion.presence_penalty = presence_penalty
|
|
111
116
|
config.completion.frequency_penalty = frequency_penalty
|
|
112
117
|
config.completion.max_tokens = max_tokens
|
|
118
|
+
config.completion.number_of_responses = number_of_responses
|
|
113
119
|
config.completion.stop_sequences = stop_sequences
|
|
114
120
|
return config
|
|
@@ -43,4 +43,17 @@ class FunctionsView:
|
|
|
43
43
|
return as_sf
|
|
44
44
|
|
|
45
45
|
def is_native(self, skill_name: str, function_name: str) -> bool:
|
|
46
|
-
|
|
46
|
+
as_sf = self._semantic_functions.get(skill_name, [])
|
|
47
|
+
as_sf = any(f.name == function_name for f in as_sf)
|
|
48
|
+
|
|
49
|
+
as_nf = self._native_functions.get(skill_name, [])
|
|
50
|
+
as_nf = any(f.name == function_name for f in as_nf)
|
|
51
|
+
|
|
52
|
+
if as_sf and as_nf:
|
|
53
|
+
raise KernelException(
|
|
54
|
+
KernelException.ErrorCodes.AmbiguousImplementation,
|
|
55
|
+
f"There are 2 functions with the same name: {function_name}."
|
|
56
|
+
f"One is native and the other semantic.",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return as_nf
|
|
@@ -5,7 +5,8 @@ For plain text, split looking at new lines first, then periods, and so on.
|
|
|
5
5
|
For markdown, split looking at punctuation first, and so on.
|
|
6
6
|
"""
|
|
7
7
|
import os
|
|
8
|
-
|
|
8
|
+
import re
|
|
9
|
+
from typing import Callable, List, Tuple
|
|
9
10
|
|
|
10
11
|
NEWLINE = os.linesep
|
|
11
12
|
|
|
@@ -36,46 +37,95 @@ MD_SPLIT_OPTIONS = [
|
|
|
36
37
|
]
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
def
|
|
40
|
+
def _token_counter(text: str) -> int:
|
|
41
|
+
"""
|
|
42
|
+
Count the number of tokens in a string.
|
|
43
|
+
|
|
44
|
+
TODO: chunking methods should be configurable to allow for different
|
|
45
|
+
tokenization strategies depending on the model to be called.
|
|
46
|
+
For now, we use an extremely rough estimate.
|
|
47
|
+
"""
|
|
48
|
+
return len(text) // 4
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def split_plaintext_lines(
|
|
52
|
+
text: str, max_token_per_line: int, token_counter: Callable = _token_counter
|
|
53
|
+
) -> List[str]:
|
|
40
54
|
"""
|
|
41
55
|
Split plain text into lines.
|
|
42
56
|
it will split on new lines first, and then on punctuation.
|
|
43
57
|
"""
|
|
44
|
-
return _split_text_lines(
|
|
58
|
+
return _split_text_lines(
|
|
59
|
+
text=text,
|
|
60
|
+
max_token_per_line=max_token_per_line,
|
|
61
|
+
trim=True,
|
|
62
|
+
token_counter=token_counter,
|
|
63
|
+
)
|
|
45
64
|
|
|
46
65
|
|
|
47
|
-
def split_markdown_lines(
|
|
66
|
+
def split_markdown_lines(
|
|
67
|
+
text: str, max_token_per_line: int, token_counter: Callable = _token_counter
|
|
68
|
+
) -> List[str]:
|
|
48
69
|
"""
|
|
49
70
|
Split markdown into lines.
|
|
50
71
|
It will split on punctuation first, and then on space and new lines.
|
|
51
72
|
"""
|
|
52
|
-
return _split_markdown_lines(
|
|
73
|
+
return _split_markdown_lines(
|
|
74
|
+
text=text,
|
|
75
|
+
max_token_per_line=max_token_per_line,
|
|
76
|
+
trim=True,
|
|
77
|
+
token_counter=token_counter,
|
|
78
|
+
)
|
|
53
79
|
|
|
54
80
|
|
|
55
|
-
def split_plaintext_paragraph(
|
|
81
|
+
def split_plaintext_paragraph(
|
|
82
|
+
text: List[str], max_tokens: int, token_counter: Callable = _token_counter
|
|
83
|
+
) -> List[str]:
|
|
56
84
|
"""
|
|
57
85
|
Split plain text into paragraphs.
|
|
58
86
|
"""
|
|
59
87
|
|
|
60
88
|
split_lines = []
|
|
61
89
|
for line in text:
|
|
62
|
-
split_lines.extend(
|
|
90
|
+
split_lines.extend(
|
|
91
|
+
_split_text_lines(
|
|
92
|
+
text=line,
|
|
93
|
+
max_token_per_line=max_tokens,
|
|
94
|
+
trim=True,
|
|
95
|
+
token_counter=token_counter,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
63
98
|
|
|
64
|
-
return _split_text_paragraph(
|
|
99
|
+
return _split_text_paragraph(
|
|
100
|
+
text=split_lines, max_tokens=max_tokens, token_counter=token_counter
|
|
101
|
+
)
|
|
65
102
|
|
|
66
103
|
|
|
67
|
-
def split_markdown_paragraph(
|
|
104
|
+
def split_markdown_paragraph(
|
|
105
|
+
text: List[str], max_tokens: int, token_counter: Callable = _token_counter
|
|
106
|
+
) -> List[str]:
|
|
68
107
|
"""
|
|
69
108
|
Split markdown into paragraphs.
|
|
70
109
|
"""
|
|
71
110
|
split_lines = []
|
|
72
111
|
for line in text:
|
|
73
|
-
split_lines.extend(
|
|
112
|
+
split_lines.extend(
|
|
113
|
+
_split_markdown_lines(
|
|
114
|
+
text=line,
|
|
115
|
+
max_token_per_line=max_tokens,
|
|
116
|
+
trim=False,
|
|
117
|
+
token_counter=token_counter,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
74
120
|
|
|
75
|
-
return _split_text_paragraph(
|
|
121
|
+
return _split_text_paragraph(
|
|
122
|
+
text=split_lines, max_tokens=max_tokens, token_counter=token_counter
|
|
123
|
+
)
|
|
76
124
|
|
|
77
125
|
|
|
78
|
-
def _split_text_paragraph(
|
|
126
|
+
def _split_text_paragraph(
|
|
127
|
+
text: List[str], max_tokens: int, token_counter: Callable
|
|
128
|
+
) -> List[str]:
|
|
79
129
|
"""
|
|
80
130
|
Split text into paragraphs.
|
|
81
131
|
"""
|
|
@@ -86,8 +136,8 @@ def _split_text_paragraph(text: List[str], max_tokens: int) -> List[str]:
|
|
|
86
136
|
current_paragraph = []
|
|
87
137
|
|
|
88
138
|
for line in text:
|
|
89
|
-
num_tokens_line =
|
|
90
|
-
num_tokens_paragraph =
|
|
139
|
+
num_tokens_line = token_counter(line)
|
|
140
|
+
num_tokens_paragraph = token_counter("".join(current_paragraph))
|
|
91
141
|
|
|
92
142
|
if (
|
|
93
143
|
num_tokens_paragraph + num_tokens_line + 1 >= max_tokens
|
|
@@ -109,7 +159,7 @@ def _split_text_paragraph(text: List[str], max_tokens: int) -> List[str]:
|
|
|
109
159
|
last_para = paragraphs[-1]
|
|
110
160
|
sec_last_para = paragraphs[-2]
|
|
111
161
|
|
|
112
|
-
if
|
|
162
|
+
if token_counter(last_para) < max_tokens / 4:
|
|
113
163
|
last_para_tokens = last_para.split(" ")
|
|
114
164
|
sec_last_para_tokens = sec_last_para.split(" ")
|
|
115
165
|
last_para_token_count = len(last_para_tokens)
|
|
@@ -125,27 +175,44 @@ def _split_text_paragraph(text: List[str], max_tokens: int) -> List[str]:
|
|
|
125
175
|
return paragraphs
|
|
126
176
|
|
|
127
177
|
|
|
128
|
-
def _split_markdown_lines(
|
|
178
|
+
def _split_markdown_lines(
|
|
179
|
+
text: str, max_token_per_line: int, trim: bool, token_counter: Callable
|
|
180
|
+
) -> List[str]:
|
|
129
181
|
"""
|
|
130
182
|
Split markdown into lines.
|
|
131
183
|
"""
|
|
132
184
|
|
|
133
|
-
|
|
134
|
-
|
|
185
|
+
return _split_str_lines(
|
|
186
|
+
text=text,
|
|
187
|
+
max_tokens=max_token_per_line,
|
|
188
|
+
separators=MD_SPLIT_OPTIONS,
|
|
189
|
+
trim=trim,
|
|
190
|
+
token_counter=token_counter,
|
|
191
|
+
)
|
|
135
192
|
|
|
136
193
|
|
|
137
|
-
def _split_text_lines(
|
|
194
|
+
def _split_text_lines(
|
|
195
|
+
text: str, max_token_per_line: int, trim: bool, token_counter: Callable
|
|
196
|
+
) -> List[str]:
|
|
138
197
|
"""
|
|
139
198
|
Split text into lines.
|
|
140
199
|
"""
|
|
141
200
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
201
|
+
return _split_str_lines(
|
|
202
|
+
text=text,
|
|
203
|
+
max_tokens=max_token_per_line,
|
|
204
|
+
separators=TEXT_SPLIT_OPTIONS,
|
|
205
|
+
trim=trim,
|
|
206
|
+
token_counter=token_counter,
|
|
207
|
+
)
|
|
145
208
|
|
|
146
209
|
|
|
147
210
|
def _split_str_lines(
|
|
148
|
-
text: str,
|
|
211
|
+
text: str,
|
|
212
|
+
max_tokens: int,
|
|
213
|
+
separators: List[List[str]],
|
|
214
|
+
trim: bool,
|
|
215
|
+
token_counter: Callable,
|
|
149
216
|
) -> List[str]:
|
|
150
217
|
if not text:
|
|
151
218
|
return []
|
|
@@ -155,65 +222,82 @@ def _split_str_lines(
|
|
|
155
222
|
was_split = False
|
|
156
223
|
for split_option in separators:
|
|
157
224
|
if not lines:
|
|
158
|
-
lines, was_split = _split_str(
|
|
225
|
+
lines, was_split = _split_str(
|
|
226
|
+
text=text,
|
|
227
|
+
max_tokens=max_tokens,
|
|
228
|
+
separators=split_option,
|
|
229
|
+
trim=trim,
|
|
230
|
+
token_counter=token_counter,
|
|
231
|
+
)
|
|
159
232
|
else:
|
|
160
|
-
lines, was_split = _split_list(
|
|
161
|
-
|
|
233
|
+
lines, was_split = _split_list(
|
|
234
|
+
text=lines,
|
|
235
|
+
max_tokens=max_tokens,
|
|
236
|
+
separators=split_option,
|
|
237
|
+
trim=trim,
|
|
238
|
+
token_counter=token_counter,
|
|
239
|
+
)
|
|
240
|
+
if was_split:
|
|
162
241
|
break
|
|
163
242
|
|
|
164
243
|
return lines
|
|
165
244
|
|
|
166
245
|
|
|
167
246
|
def _split_str(
|
|
168
|
-
text: str,
|
|
169
|
-
|
|
247
|
+
text: str,
|
|
248
|
+
max_tokens: int,
|
|
249
|
+
separators: List[str],
|
|
250
|
+
trim: bool,
|
|
251
|
+
token_counter: Callable,
|
|
252
|
+
) -> Tuple[List[str], bool]:
|
|
170
253
|
"""
|
|
171
254
|
Split text into lines.
|
|
172
255
|
"""
|
|
256
|
+
input_was_split = False
|
|
173
257
|
if not text:
|
|
174
|
-
return []
|
|
258
|
+
return [], input_was_split
|
|
175
259
|
|
|
176
|
-
|
|
177
|
-
|
|
260
|
+
if trim:
|
|
261
|
+
text = text.strip()
|
|
178
262
|
|
|
179
263
|
text_as_is = [text]
|
|
180
264
|
|
|
181
|
-
if
|
|
265
|
+
if token_counter(text) <= max_tokens:
|
|
182
266
|
return text_as_is, input_was_split
|
|
183
267
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
half = int(len(text) / 2)
|
|
268
|
+
half = len(text) // 2
|
|
187
269
|
|
|
188
270
|
cutpoint = -1
|
|
189
271
|
|
|
190
272
|
if not separators:
|
|
191
273
|
cutpoint = half
|
|
192
|
-
|
|
193
274
|
elif set(separators) & set(text) and len(text) > 2:
|
|
194
|
-
for
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
275
|
+
regex_separators = re.compile("|".join(re.escape(s) for s in separators))
|
|
276
|
+
min_dist = half
|
|
277
|
+
for match in re.finditer(regex_separators, text):
|
|
278
|
+
end = match.end()
|
|
279
|
+
dist = abs(half - end)
|
|
280
|
+
if dist < min_dist:
|
|
281
|
+
min_dist = dist
|
|
282
|
+
cutpoint = end
|
|
283
|
+
elif end > half:
|
|
284
|
+
# distance is increasing, so we can stop searching
|
|
285
|
+
break
|
|
201
286
|
else:
|
|
202
287
|
return text_as_is, input_was_split
|
|
203
288
|
|
|
204
289
|
if 0 < cutpoint < len(text):
|
|
205
290
|
lines = []
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
input_was_split = has_split1 or has_split2
|
|
291
|
+
for text_part in [text[:cutpoint], text[cutpoint:]]:
|
|
292
|
+
split, has_split = _split_str(
|
|
293
|
+
text=text_part,
|
|
294
|
+
max_tokens=max_tokens,
|
|
295
|
+
separators=separators,
|
|
296
|
+
trim=trim,
|
|
297
|
+
token_counter=token_counter,
|
|
298
|
+
)
|
|
299
|
+
lines.extend(split)
|
|
300
|
+
input_was_split = input_was_split or has_split
|
|
217
301
|
else:
|
|
218
302
|
return text_as_is, input_was_split
|
|
219
303
|
|
|
@@ -221,30 +305,29 @@ def _split_str(
|
|
|
221
305
|
|
|
222
306
|
|
|
223
307
|
def _split_list(
|
|
224
|
-
text: List[str],
|
|
225
|
-
|
|
308
|
+
text: List[str],
|
|
309
|
+
max_tokens: int,
|
|
310
|
+
separators: List[str],
|
|
311
|
+
trim: bool,
|
|
312
|
+
token_counter: Callable,
|
|
313
|
+
) -> Tuple[List[str], bool]:
|
|
226
314
|
"""
|
|
227
315
|
Split list of string into lines.
|
|
228
316
|
"""
|
|
229
317
|
if not text:
|
|
230
|
-
return []
|
|
318
|
+
return [], False
|
|
231
319
|
|
|
232
320
|
lines = []
|
|
233
321
|
input_was_split = False
|
|
234
322
|
for line in text:
|
|
235
|
-
split_str, was_split = _split_str(
|
|
323
|
+
split_str, was_split = _split_str(
|
|
324
|
+
text=line,
|
|
325
|
+
max_tokens=max_tokens,
|
|
326
|
+
separators=separators,
|
|
327
|
+
trim=trim,
|
|
328
|
+
token_counter=token_counter,
|
|
329
|
+
)
|
|
236
330
|
lines.extend(split_str)
|
|
237
331
|
input_was_split = input_was_split or was_split
|
|
238
332
|
|
|
239
333
|
return lines, input_was_split
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def _token_count(text: str) -> int:
|
|
243
|
-
"""
|
|
244
|
-
Count the number of tokens in a string.
|
|
245
|
-
|
|
246
|
-
TODO: chunking methods should be configurable to allow for different
|
|
247
|
-
tokenization strategies depending on the model to be called.
|
|
248
|
-
For now, we use an extremely rough estimate.
|
|
249
|
-
"""
|
|
250
|
-
return int(len(text) / 4)
|