hanlp-haystack 2.0.0__tar.gz → 2.1.1__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,51 @@
1
+ # Changelog
2
+
3
+ ## [integrations/hanlp-v2.1.0] - 2026-01-14
4
+
5
+ ### 🚀 Features
6
+
7
+ - Update hanlp components to auto call run `warm_up` (#2749)
8
+
9
+
10
+ ## [integrations/hanlp-v2.0.0] - 2026-01-12
11
+
12
+ ### 🐛 Bug Fixes
13
+
14
+ - Docs: fix hanlp markdown filenames (#2480)
15
+
16
+ ### 📚 Documentation
17
+
18
+ - Add pydoc configurations for Docusaurus (#2411)
19
+
20
+ ### ⚙️ CI
21
+
22
+ - Change pytest command (#2475)
23
+
24
+ ### 🧹 Chores
25
+
26
+ - Remove Readme API CI workflow and configs (#2573)
27
+ - Make fmt command more forgiving (#2671)
28
+ - [**breaking**] Hanlp - drop Python 3.9 and use X|Y typing (#2707)
29
+
30
+ ### 🌀 Miscellaneous
31
+
32
+ - Enhancement: Adopt PEP 585 type hinting (part 4) (#2527)
33
+
34
+ ## [integrations/hanlp-v1.0.1] - 2025-10-14
35
+
36
+ ### 🐛 Bug Fixes
37
+
38
+ - Missing decorator output for ChineseDocumentSplitter (#2377)
39
+
40
+
41
+ ### 🧹 Chores
42
+
43
+ - Standardize readmes - part 2 (#2205)
44
+
45
+ ## [integrations/hanlp-v1.0.0] - 2025-07-22
46
+
47
+ ### 🚀 Features
48
+
49
+ - Add new HanLP integration with ChineseDocumentSplitter (#1943)
50
+
51
+ <!-- generated by git-cliff -->
@@ -58,7 +58,7 @@ APPENDIX: How to apply the Apache License to your work.
58
58
 
59
59
  To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
60
60
 
61
- Copyright [yyyy] [name of copyright owner]
61
+ Copyright 2025 deepset GmbH
62
62
 
63
63
  Licensed under the Apache License, Version 2.0 (the "License");
64
64
  you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hanlp-haystack
3
- Version: 2.0.0
3
+ Version: 2.1.1
4
4
  Summary: An integration of Han Language Processing - HanLP as a ChineseDocumentSplitter component.
5
5
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/hanlp#readme
6
6
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
@@ -20,6 +20,8 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
20
20
  Requires-Python: >=3.10
21
21
  Requires-Dist: hanlp>=2.1.1
22
22
  Requires-Dist: haystack-ai>=2.22.0
23
+ Requires-Dist: tokenizers>=0.20.2
24
+ Requires-Dist: transformers<5,>=4.1.1
23
25
  Description-Content-Type: text/markdown
24
26
 
25
27
  # hanlp-haystack
@@ -24,7 +24,11 @@ classifiers = [
24
24
  ]
25
25
  dependencies = [
26
26
  "haystack-ai>=2.22.0",
27
- "hanlp>=2.1.1"
27
+ "hanlp>=2.1.1",
28
+ # the following pins can be removed once HanLP supports transformers 5
29
+ "transformers>=4.1.1,<5",
30
+ # tokenizers is a dependency of transformers. The following version is the first one which supports Python 3.13.
31
+ "tokenizers>=0.20.2",
28
32
  ]
29
33
 
30
34
  [project.urls]
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2025-present deepset GmbH <info@deepset.ai>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
+
4
5
  from .chinese_document_splitter import ChineseDocumentSplitter
5
6
 
6
7
  __all__ = [
@@ -12,6 +12,7 @@ from haystack.utils import deserialize_callable, serialize_callable
12
12
  from more_itertools import windowed
13
13
 
14
14
  import hanlp
15
+ from hanlp.common.component import Component as HanlpComponent
15
16
 
16
17
  logger = logging.getLogger(__name__)
17
18
 
@@ -47,7 +48,6 @@ class ChineseDocumentSplitter:
47
48
  splitter = ChineseDocumentSplitter(
48
49
  split_by="word", split_length=10, split_overlap=3, respect_sentence_boundary=True
49
50
  )
50
- splitter.warm_up()
51
51
  result = splitter.run(documents=[doc])
52
52
  print(result["documents"])
53
53
  ```
@@ -62,7 +62,7 @@ class ChineseDocumentSplitter:
62
62
  respect_sentence_boundary: bool = False,
63
63
  splitting_function: Callable | None = None,
64
64
  granularity: Literal["coarse", "fine"] = "coarse",
65
- ):
65
+ ) -> None:
66
66
  """
67
67
  Initialize the ChineseDocumentSplitter component.
68
68
 
@@ -95,6 +95,9 @@ class ChineseDocumentSplitter:
95
95
  self.respect_sentence_boundary = respect_sentence_boundary
96
96
  self.splitting_function = splitting_function
97
97
  self.granularity = granularity
98
+ self.chinese_tokenizer: HanlpComponent | None = None
99
+ self.split_sent: HanlpComponent | None = None
100
+ self._is_warmed_up = False
98
101
 
99
102
  @staticmethod
100
103
  def _validate_init_parameters(
@@ -159,9 +162,8 @@ class ChineseDocumentSplitter:
159
162
 
160
163
  :raises RuntimeError: If the Chinese word segmentation model is not loaded.
161
164
  """
162
- if self.split_sent is None:
163
- msg = "The Chinese word segmentation model is not loaded. Please run 'warm_up()' before calling 'run()'."
164
- raise RuntimeError(msg)
165
+ if not self._is_warmed_up:
166
+ self.warm_up()
165
167
 
166
168
  split_docs = []
167
169
  for doc in documents:
@@ -170,11 +172,15 @@ class ChineseDocumentSplitter:
170
172
 
171
173
  def warm_up(self) -> None:
172
174
  """Warm up the component by loading the necessary models."""
175
+ if self._is_warmed_up:
176
+ return
177
+
173
178
  if self.granularity == "coarse":
174
179
  self.chinese_tokenizer = hanlp.load(hanlp.pretrained.tok.COARSE_ELECTRA_SMALL_ZH)
175
180
  if self.granularity == "fine":
176
181
  self.chinese_tokenizer = hanlp.load(hanlp.pretrained.tok.FINE_ELECTRA_SMALL_ZH)
177
182
  self.split_sent = hanlp.load(hanlp.pretrained.eos.UD_CTB_EOS_MUL)
183
+ self._is_warmed_up = True
178
184
 
179
185
  def _split_by_character(self, doc: Document) -> list[Document]:
180
186
  """
@@ -188,7 +194,7 @@ class ChineseDocumentSplitter:
188
194
 
189
195
  split_at = _CHARACTER_SPLIT_BY_MAPPING[self.split_by]
190
196
 
191
- units = self.chinese_tokenizer(doc.content)
197
+ units = self.chinese_tokenizer(doc.content) # type: ignore[misc]
192
198
 
193
199
  for i in range(len(units) - 1):
194
200
  units[i] += split_at
@@ -211,7 +217,7 @@ class ChineseDocumentSplitter:
211
217
  :return: A list of split sentences.
212
218
  """
213
219
  # Split sentences
214
- sentences = self.split_sent(text)
220
+ sentences = self.split_sent(text) # type: ignore[misc]
215
221
 
216
222
  # Organize the format of segmented sentences
217
223
  results = []
@@ -261,9 +267,15 @@ class ChineseDocumentSplitter:
261
267
 
262
268
  for sentence_idx, sentence in enumerate(sentences):
263
269
  current_chunk.append(sentence)
264
- chunk_word_count += len(self.chinese_tokenizer(sentence))
270
+ chunk_word_count += len(
271
+ self.chinese_tokenizer(sentence) # type: ignore[misc]
272
+ )
265
273
  next_sentence_word_count = (
266
- len(self.chinese_tokenizer(sentences[sentence_idx + 1])) if sentence_idx < len(sentences) - 1 else 0
274
+ len(
275
+ self.chinese_tokenizer(sentences[sentence_idx + 1]) # type: ignore[misc]
276
+ )
277
+ if sentence_idx < len(sentences) - 1
278
+ else 0
267
279
  )
268
280
 
269
281
  # Number of words in the current chunk plus the next sentence is larger than the split_length,
@@ -478,7 +490,9 @@ class ChineseDocumentSplitter:
478
490
  num_words = 0
479
491
 
480
492
  for sent in reversed(sentences[1:]):
481
- num_words += len(self.chinese_tokenizer(sent))
493
+ num_words += len(
494
+ self.chinese_tokenizer(sent) # type: ignore[misc]
495
+ )
482
496
  # If the number of words is larger than the split_length then don't add any more sentences
483
497
  if num_words > split_length:
484
498
  break
@@ -93,7 +93,6 @@ class TestChineseDocumentSplitter:
93
93
  @pytest.mark.integration
94
94
  def test_empty_list(self):
95
95
  splitter = ChineseDocumentSplitter()
96
- splitter.warm_up()
97
96
  results = splitter.run(documents=[])
98
97
  assert results == {"documents": []}
99
98
 
@@ -101,7 +100,6 @@ class TestChineseDocumentSplitter:
101
100
  def test_empty_document(self):
102
101
  splitter = ChineseDocumentSplitter()
103
102
  documents = [Document(content="")]
104
- splitter.warm_up()
105
103
  results = splitter.run(documents=documents)
106
104
  assert results == {"documents": []}
107
105
 
@@ -109,7 +107,6 @@ class TestChineseDocumentSplitter:
109
107
  def test_whitespace_only_document(self):
110
108
  splitter = ChineseDocumentSplitter()
111
109
  documents = [Document(content=" ")]
112
- splitter.warm_up()
113
110
  results = splitter.run(documents=documents)
114
111
  assert len(results["documents"]) == 0
115
112
 
@@ -120,7 +117,6 @@ class TestChineseDocumentSplitter:
120
117
  Document(content="这是另一个测试文本。", meta={"name": "doc 1"}),
121
118
  ]
122
119
  splitter = ChineseDocumentSplitter(split_by="word", split_length=5, split_overlap=2)
123
- splitter.warm_up()
124
120
  result = splitter.run(documents=documents)
125
121
  assert len(result["documents"]) == 2
126
122
  for doc, split_doc in zip(documents, result["documents"], strict=True):
@@ -133,7 +129,6 @@ class TestChineseDocumentSplitter:
133
129
  Document(content="这是第二个测试文本。"),
134
130
  ]
135
131
  splitter = ChineseDocumentSplitter(split_by="word", split_length=5, split_overlap=2)
136
- splitter.warm_up()
137
132
  result = splitter.run(documents=documents)
138
133
  assert len(result["documents"]) == 2
139
134
  for doc, split_doc in zip(documents, result["documents"], strict=True):
@@ -142,7 +137,6 @@ class TestChineseDocumentSplitter:
142
137
  @pytest.mark.integration
143
138
  def test_split_by_word(self, sample_text):
144
139
  splitter = ChineseDocumentSplitter(split_by="word", granularity="coarse", split_length=5, split_overlap=0)
145
- splitter.warm_up()
146
140
  result = splitter.run(documents=[Document(content=sample_text)])
147
141
  docs = result["documents"]
148
142
  assert all(isinstance(doc, Document) for doc in docs)
@@ -153,7 +147,6 @@ class TestChineseDocumentSplitter:
153
147
  @pytest.mark.integration
154
148
  def test_split_by_sentence(self, sample_text):
155
149
  splitter = ChineseDocumentSplitter(split_by="sentence", granularity="coarse", split_length=10, split_overlap=0)
156
- splitter.warm_up()
157
150
  result = splitter.run(documents=[Document(content=sample_text)])
158
151
  docs = result["documents"]
159
152
  assert all(isinstance(doc, Document) for doc in docs), "All docs should be instances of Document"
@@ -170,7 +163,6 @@ class TestChineseDocumentSplitter:
170
163
  splitter = ChineseDocumentSplitter(
171
164
  split_by="word", split_length=10, split_overlap=3, respect_sentence_boundary=True
172
165
  )
173
- splitter.warm_up()
174
166
  result = splitter.run(documents=[doc])
175
167
  docs = result["documents"]
176
168
 
@@ -193,7 +185,6 @@ class TestChineseDocumentSplitter:
193
185
  )
194
186
 
195
187
  splitter = ChineseDocumentSplitter(split_by="word", split_length=30, split_overlap=10, granularity="coarse")
196
- splitter.warm_up()
197
188
  result = splitter.run(documents=[doc])
198
189
  docs = result["documents"]
199
190
  assert len(docs) == 6
@@ -1,20 +0,0 @@
1
- # Changelog
2
-
3
- ## [integrations/hanlp-v1.0.1] - 2025-10-14
4
-
5
- ### 🐛 Bug Fixes
6
-
7
- - Missing decorator output for ChineseDocumentSplitter (#2377)
8
-
9
-
10
- ### 🧹 Chores
11
-
12
- - Standardize readmes - part 2 (#2205)
13
-
14
- ## [integrations/hanlp-v1.0.0] - 2025-07-22
15
-
16
- ### 🚀 Features
17
-
18
- - Add new HanLP integration with ChineseDocumentSplitter (#1943)
19
-
20
- <!-- generated by git-cliff -->
File without changes