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.
Files changed (34) hide show
  1. semantic_kernel/__init__.py +2 -0
  2. semantic_kernel/connectors/ai/chat_completion_client_base.py +26 -2
  3. semantic_kernel/connectors/ai/chat_request_settings.py +2 -0
  4. semantic_kernel/connectors/ai/complete_request_settings.py +1 -0
  5. semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py +43 -18
  6. semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py +48 -14
  7. semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py +16 -11
  8. semantic_kernel/connectors/ai/text_completion_client_base.py +24 -2
  9. semantic_kernel/connectors/memory/chroma/chroma_memory_store.py +8 -5
  10. semantic_kernel/connectors/memory/chroma/utils.py +45 -22
  11. semantic_kernel/connectors/memory/pinecone/__init__.py +7 -0
  12. semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py +401 -0
  13. semantic_kernel/connectors/memory/pinecone/utils.py +37 -0
  14. semantic_kernel/connectors/memory/{weaviate_memory_store.py → weaviate/weaviate_memory_store.py} +30 -15
  15. semantic_kernel/core_skills/file_io_skill.py +1 -1
  16. semantic_kernel/core_skills/math_skill.py +1 -1
  17. semantic_kernel/core_skills/text_skill.py +1 -1
  18. semantic_kernel/core_skills/time_skill.py +15 -3
  19. semantic_kernel/core_skills/wait_skill.py +23 -0
  20. semantic_kernel/kernel.py +2 -0
  21. semantic_kernel/memory/memory_query_result.py +4 -0
  22. semantic_kernel/memory/memory_record.py +15 -2
  23. semantic_kernel/memory/null_memory.py +7 -1
  24. semantic_kernel/memory/semantic_text_memory.py +17 -6
  25. semantic_kernel/memory/semantic_text_memory_base.py +2 -0
  26. semantic_kernel/orchestration/sk_function.py +5 -0
  27. semantic_kernel/planning/basic_planner.py +9 -1
  28. semantic_kernel/semantic_functions/prompt_template_config.py +6 -0
  29. semantic_kernel/skill_definition/functions_view.py +14 -1
  30. semantic_kernel/text/text_chunker.py +152 -69
  31. semantic_kernel/utils/settings.py +36 -31
  32. {semantic_kernel-0.3.0.dev0.dist-info → semantic_kernel-0.3.2.dev0.dist-info}/METADATA +3 -1
  33. {semantic_kernel-0.3.0.dev0.dist-info → semantic_kernel-0.3.2.dev0.dist-info}/RECORD +34 -30
  34. {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
- embedding: ndarray,
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, text: str, description: Optional[str], embedding: ndarray
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, collection: str, text: str, id: str, description: Optional[str] = None
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 = await self._embeddings_generator.generate_embeddings_async([text])
59
+ embedding = (
60
+ await self._embeddings_generator.generate_embeddings_async([text])
61
+ )[0]
59
62
  data = MemoryRecord.local_record(
60
- id=id, text=text, description=description, embedding=embedding
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 = await self._embeddings_generator.generate_embeddings_async([text])
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 = await self._embeddings_generator.generate_embeddings_async(
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
- generated_plan = json.loads(plan.generated_plan.result)
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
- return not self.is_semantic(skill_name, function_name)
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
- from typing import List
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 split_plaintext_lines(text: str, max_token_per_line: int) -> List[str]:
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(text, max_token_per_line, True)
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(text: str, max_token_per_line: int) -> List[str]:
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(text, max_token_per_line, True)
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(text: List[str], max_tokens: int) -> List[str]:
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(_split_text_lines(line, max_tokens, True))
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(split_lines, max_tokens)
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(text: List[str], max_tokens: int) -> List[str]:
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(_split_markdown_lines(line, max_tokens, False))
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(split_lines, max_tokens)
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(text: List[str], max_tokens: int) -> List[str]:
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 = _token_count(line)
90
- num_tokens_paragraph = _token_count("".join(current_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 _token_count(last_para) < max_tokens / 4:
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(text: str, max_token_per_line: int, trim: bool) -> List[str]:
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
- lines = _split_str_lines(text, max_token_per_line, MD_SPLIT_OPTIONS, trim)
134
- return lines
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(text: str, max_token_per_line: int, trim: bool) -> List[str]:
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
- lines = _split_str_lines(text, max_token_per_line, TEXT_SPLIT_OPTIONS, trim)
143
-
144
- return lines
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, max_tokens: int, separators: List[List[str]], trim: bool
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(text, max_tokens, split_option, trim)
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(lines, max_tokens, split_option, trim)
161
- if not was_split:
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, max_tokens: int, separators: List[str], trim: bool
169
- ) -> List[str]:
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
- input_was_split = False
177
- text = text.strip() if trim else text
260
+ if trim:
261
+ text = text.strip()
178
262
 
179
263
  text_as_is = [text]
180
264
 
181
- if _token_count(text) <= max_tokens:
265
+ if token_counter(text) <= max_tokens:
182
266
  return text_as_is, input_was_split
183
267
 
184
- input_was_split = True
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 index, text_char in enumerate(text):
195
- if text_char not in separators:
196
- continue
197
-
198
- if abs(half - index) < abs(half - cutpoint):
199
- cutpoint = index + 1
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
- first_split, has_split1 = _split_str(
207
- text[:cutpoint], max_tokens, separators, trim
208
- )
209
- second_split, has_split2 = _split_str(
210
- text[cutpoint:], max_tokens, separators, trim
211
- )
212
-
213
- lines.extend(first_split)
214
- lines.extend(second_split)
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], max_tokens: int, separators: List[str], trim: bool
225
- ) -> List[str]:
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(line, max_tokens, separators, trim)
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)