From 71fe314955abb68bc29374c5bab31bfe61222fcf Mon Sep 17 00:00:00 2001
From: KevinHuSh
Date: Mon, 25 Mar 2024 13:11:57 +0800
Subject: [PATCH] refine page ranges (#147)
---
api/db/db_models.py | 4 +-
deepdoc/parser/pdf_parser.py | 6 +-
deepdoc/vision/layout_recognizer.py | 4 +-
rag/app/manual.py | 53 +------
rag/app/one.py | 5 +-
rag/app/paper.py | 2 +-
rag/app/presentation.py | 4 +-
rag/nlp/__init__.py | 15 +-
rag/svr/task_broker.py | 9 +-
rag/svr/task_executor.py | 2 +-
requirements.txt | 133 ++++++++++++++++++
.../knowledge-file/chunk-method-modal.tsx | 2 +-
.../components/knowledge-setting/utils.ts | 2 +-
13 files changed, 169 insertions(+), 72 deletions(-)
create mode 100644 requirements.txt
diff --git a/api/db/db_models.py b/api/db/db_models.py
index 5cec88a3d..caa756c3a 100644
--- a/api/db/db_models.py
+++ b/api/db/db_models.py
@@ -477,7 +477,7 @@ class Knowledgebase(DataBaseModel):
vector_similarity_weight = FloatField(default=0.3)
parser_id = CharField(max_length=32, null=False, help_text="default parser ID", default=ParserType.NAIVE.value)
- parser_config = JSONField(null=False, default={"pages":[[0,1000000]]})
+ parser_config = JSONField(null=False, default={"pages":[[1,1000000]]})
status = CharField(max_length=1, null=True, help_text="is it validate(0: wasted,1: validate)", default="1")
def __str__(self):
@@ -492,7 +492,7 @@ class Document(DataBaseModel):
thumbnail = TextField(null=True, help_text="thumbnail base64 string")
kb_id = CharField(max_length=256, null=False, index=True)
parser_id = CharField(max_length=32, null=False, help_text="default parser ID")
- parser_config = JSONField(null=False, default={"pages":[[0,1000000]]})
+ parser_config = JSONField(null=False, default={"pages":[[1,1000000]]})
source_type = CharField(max_length=128, null=False, default="local", help_text="where dose this document from")
type = CharField(max_length=32, null=False, help_text="file extension")
created_by = CharField(max_length=32, null=False, help_text="who created it")
diff --git a/deepdoc/parser/pdf_parser.py b/deepdoc/parser/pdf_parser.py
index 1a4afa138..079376df6 100644
--- a/deepdoc/parser/pdf_parser.py
+++ b/deepdoc/parser/pdf_parser.py
@@ -1074,15 +1074,15 @@ class HuParser:
class PlainParser(object):
- def __call__(self, filename, **kwargs):
+ def __call__(self, filename, from_page=0, to_page=100000, **kwargs):
self.outlines = []
lines = []
try:
self.pdf = pdf2_read(filename if isinstance(filename, str) else BytesIO(filename))
- outlines = self.pdf.outline
- for page in self.pdf.pages:
+ for page in self.pdf.pages[from_page:to_page]:
lines.extend([t for t in page.extract_text().split("\n")])
+ outlines = self.pdf.outline
def dfs(arr, depth):
for a in arr:
if isinstance(a, dict):
diff --git a/deepdoc/vision/layout_recognizer.py b/deepdoc/vision/layout_recognizer.py
index 47067068e..ba7ed85e0 100644
--- a/deepdoc/vision/layout_recognizer.py
+++ b/deepdoc/vision/layout_recognizer.py
@@ -15,6 +15,7 @@ import re
from collections import Counter
from copy import deepcopy
import numpy as np
+from huggingface_hub import snapshot_download
from api.db import ParserType
from api.utils.file_utils import get_project_base_directory
@@ -36,7 +37,8 @@ class LayoutRecognizer(Recognizer):
"Equation",
]
def __init__(self, domain):
- super().__init__(self.labels, domain, os.path.join(get_project_base_directory(), "rag/res/deepdoc/"))
+ model_dir = snapshot_download(repo_id="InfiniFlow/deepdoc")
+ super().__init__(self.labels, domain, model_dir)#os.path.join(get_project_base_directory(), "rag/res/deepdoc/"))
self.garbage_layouts = ["footer", "header", "reference"]
def __call__(self, image_list, ocr_res, scale_factor=3, thr=0.2, batch_size=16, drop=True):
diff --git a/rag/app/manual.py b/rag/app/manual.py
index 7ab1caa85..d829e3ad4 100644
--- a/rag/app/manual.py
+++ b/rag/app/manual.py
@@ -30,8 +30,6 @@ class Pdf(PdfParser):
# print(b)
print("OCR:", timer()-start)
-
-
self._layouts_rec(zoomin)
callback(0.65, "Layout analysis finished.")
print("paddle layouts:", timer() - start)
@@ -47,53 +45,8 @@ class Pdf(PdfParser):
for b in self.boxes:
b["text"] = re.sub(r"([\t ]|\u3000){2,}", " ", b["text"].strip())
- return [(b["text"], b.get("layout_no", ""), self.get_position(b, zoomin)) for i, b in enumerate(self.boxes)]
+ return [(b["text"], b.get("layout_no", ""), self.get_position(b, zoomin)) for i, b in enumerate(self.boxes)], tbls
- # set pivot using the most frequent type of title,
- # then merge between 2 pivot
- if len(self.boxes)>0 and len(self.outlines)/len(self.boxes) > 0.1:
- max_lvl = max([lvl for _, lvl in self.outlines])
- most_level = max(0, max_lvl-1)
- levels = []
- for b in self.boxes:
- for t,lvl in self.outlines:
- tks = set([t[i]+t[i+1] for i in range(len(t)-1)])
- tks_ = set([b["text"][i]+b["text"][i+1] for i in range(min(len(t), len(b["text"])-1))])
- if len(set(tks & tks_))/max([len(tks), len(tks_), 1]) > 0.8:
- levels.append(lvl)
- break
- else:
- levels.append(max_lvl + 1)
- else:
- bull = bullets_category([b["text"] for b in self.boxes])
- most_level, levels = title_frequency(bull, [(b["text"], b.get("layout_no","")) for b in self.boxes])
-
- assert len(self.boxes) == len(levels)
- sec_ids = []
- sid = 0
- for i, lvl in enumerate(levels):
- if lvl <= most_level and i > 0 and lvl != levels[i-1]: sid += 1
- sec_ids.append(sid)
- #print(lvl, self.boxes[i]["text"], most_level, sid)
-
- sections = [(b["text"], sec_ids[i], self.get_position(b, zoomin)) for i, b in enumerate(self.boxes)]
- for (img, rows), poss in tbls:
- sections.append((rows if isinstance(rows, str) else rows[0], -1, [(p[0]+1-from_page, p[1], p[2], p[3], p[4]) for p in poss]))
-
- chunks = []
- last_sid = -2
- tk_cnt = 0
- for txt, sec_id, poss in sorted(sections, key=lambda x: (x[-1][0][0], x[-1][0][3], x[-1][0][1])):
- poss = "\t".join([tag(*pos) for pos in poss])
- if tk_cnt < 2048 and (sec_id == last_sid or sec_id == -1):
- if chunks:
- chunks[-1] += "\n" + txt + poss
- tk_cnt += num_tokens_from_string(txt)
- continue
- chunks.append(txt + poss)
- tk_cnt = num_tokens_from_string(txt)
- if sec_id >-1: last_sid = sec_id
- return chunks, tbls
def chunk(filename, binary=None, from_page=0, to_page=100000, lang="Chinese", callback=None, **kwargs):
@@ -106,7 +59,8 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, lang="Chinese", ca
pdf_parser = Pdf() if kwargs.get("parser_config",{}).get("layout_recognize", True) else PlainParser()
sections, tbls = pdf_parser(filename if not binary else binary,
from_page=from_page, to_page=to_page, callback=callback)
- if sections and len(sections[0])<3: cks = [(t, l, [0]*5) for t, l in sections]
+ if sections and len(sections[0])<3: sections = [(t, l, [[0]*5]) for t, l in sections]
+
else: raise NotImplementedError("file type not supported yet(pdf supported)")
doc = {
"docnm_kwd": filename
@@ -131,6 +85,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, lang="Chinese", ca
break
else:
levels.append(max_lvl + 1)
+
else:
bull = bullets_category([txt for txt,_,_ in sections])
most_level, levels = title_frequency(bull, [(txt, l) for txt, l, poss in sections])
diff --git a/rag/app/one.py b/rag/app/one.py
index 2ad59bece..998cc5678 100644
--- a/rag/app/one.py
+++ b/rag/app/one.py
@@ -45,7 +45,7 @@ class Pdf(PdfParser):
for (img, rows), poss in tbls:
sections.append((rows if isinstance(rows, str) else rows[0],
[(p[0] + 1 - from_page, p[1], p[2], p[3], p[4]) for p in poss]))
- return [(txt, "") for txt, _ in sorted(sections, key=lambda x: (x[-1][0][0], x[-1][0][3], x[-1][0][1]))]
+ return [(txt, "") for txt, _ in sorted(sections, key=lambda x: (x[-1][0][0], x[-1][0][3], x[-1][0][1]))], None
def chunk(filename, binary=None, from_page=0, to_page=100000, lang="Chinese", callback=None, **kwargs):
@@ -56,7 +56,6 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, lang="Chinese", ca
eng = lang.lower() == "english"#is_english(cks)
- sections = []
if re.search(r"\.docx?$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
sections = [txt for txt in laws.Docx()(filename, binary) if txt]
@@ -64,7 +63,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, lang="Chinese", ca
elif re.search(r"\.pdf$", filename, re.IGNORECASE):
pdf_parser = Pdf() if kwargs.get("parser_config",{}).get("layout_recognize", True) else PlainParser()
- sections = pdf_parser(filename if not binary else binary, to_page=to_page, callback=callback)
+ sections, _ = pdf_parser(filename if not binary else binary, to_page=to_page, callback=callback)
sections = [s for s, _ in sections if s]
elif re.search(r"\.xlsx?$", filename, re.IGNORECASE):
diff --git a/rag/app/paper.py b/rag/app/paper.py
index 11045772b..37b4df990 100644
--- a/rag/app/paper.py
+++ b/rag/app/paper.py
@@ -136,7 +136,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, lang="Chinese", ca
"title": filename,
"authors": " ",
"abstract": "",
- "sections": pdf_parser(filename if not binary else binary),
+ "sections": pdf_parser(filename if not binary else binary, from_page=from_page, to_page=to_page),
"tables": []
}
else:
diff --git a/rag/app/presentation.py b/rag/app/presentation.py
index 597bc7ac4..356414542 100644
--- a/rag/app/presentation.py
+++ b/rag/app/presentation.py
@@ -65,10 +65,10 @@ class Pdf(PdfParser):
class PlainPdf(PlainParser):
- def __call__(self, filename, binary=None, callback=None, **kwargs):
+ def __call__(self, filename, binary=None, from_page=0, to_page=100000, callback=None, **kwargs):
self.pdf = pdf2_read(filename if not binary else BytesIO(filename))
page_txt = []
- for page in self.pdf.pages:
+ for page in self.pdf.pages[from_page: to_page]:
page_txt.append(page.extract_text())
callback(0.9, "Parsing finished")
return [(txt, None) for txt in page_txt]
diff --git a/rag/nlp/__init__.py b/rag/nlp/__init__.py
index e30a4fa87..d0a9cf9fc 100644
--- a/rag/nlp/__init__.py
+++ b/rag/nlp/__init__.py
@@ -16,8 +16,8 @@ BULLET_PATTERN = [[
], [
r"第[0-9]+章",
r"第[0-9]+节",
- r"[0-9]{,3}[\. 、]",
- r"[0-9]{,2}\.[0-9]{,2}",
+ r"[0-9]{,2}[\. 、]",
+ r"[0-9]{,2}\.[0-9]{,2}[^a-zA-Z/%~-]",
r"[0-9]{,2}\.[0-9]{,2}\.[0-9]{,2}",
r"[0-9]{,2}\.[0-9]{,2}\.[0-9]{,2}\.[0-9]{,2}",
], [
@@ -40,13 +40,20 @@ def random_choices(arr, k):
return random.choices(arr, k=k)
+def not_bullet(line):
+ patt = [
+ r"0", r"[0-9]+ +[0-9~个只-]", r"[0-9]+\.{2,}"
+ ]
+ return any([re.match(r, line) for r in patt])
+
+
def bullets_category(sections):
global BULLET_PATTERN
hits = [0] * len(BULLET_PATTERN)
for i, pro in enumerate(BULLET_PATTERN):
for sec in sections:
for p in pro:
- if re.match(p, sec):
+ if re.match(p, sec) and not not_bullet(sec):
hits[i] += 1
break
maxium = 0
@@ -194,7 +201,7 @@ def title_frequency(bull, sections):
for i, (txt, layout) in enumerate(sections):
for j, p in enumerate(BULLET_PATTERN[bull]):
- if re.match(p, txt.strip()):
+ if re.match(p, txt.strip()) and not not_bullet(txt):
levels[i] = j
break
else:
diff --git a/rag/svr/task_broker.py b/rag/svr/task_broker.py
index b88296689..2e7216f9e 100644
--- a/rag/svr/task_broker.py
+++ b/rag/svr/task_broker.py
@@ -81,21 +81,22 @@ def dispatch():
tsks = []
if r["type"] == FileType.PDF.value:
- if not r["parser_config"].get("layout_recognize", True):
- tsks.append(new_task())
- continue
+ do_layout = r["parser_config"].get("layout_recognize", True)
pages = PdfParser.total_page_number(r["name"], MINIO.get(r["kb_id"], r["location"]))
page_size = r["parser_config"].get("task_page_size", 12)
if r["parser_id"] == "paper": page_size = r["parser_config"].get("task_page_size", 22)
if r["parser_id"] == "one": page_size = 1000000000
+ if not do_layout: page_size = 1000000000
for s,e in r["parser_config"].get("pages", [(1, 100000)]):
s -= 1
- e = min(e, pages)
+ s = max(0, s)
+ e = min(e-1, pages)
for p in range(s, e, page_size):
task = new_task()
task["from_page"] = p
task["to_page"] = min(p + page_size, e)
tsks.append(task)
+
elif r["parser_id"] == "table":
rn = HuExcelParser.row_number(r["name"], MINIO.get(r["kb_id"], r["location"]))
for i in range(0, rn, 3000):
diff --git a/rag/svr/task_executor.py b/rag/svr/task_executor.py
index f88faf7fd..406565991 100644
--- a/rag/svr/task_executor.py
+++ b/rag/svr/task_executor.py
@@ -75,7 +75,7 @@ def set_progress(task_id, from_page=0, to_page=-1,
if to_page > 0:
if msg:
- msg = f"Page({from_page}~{to_page}): " + msg
+ msg = f"Page({from_page+1}~{to_page+1}): " + msg
d = {"progress_msg": msg}
if prog is not None:
d["progress"] = prog
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 000000000..ac885fefb
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,133 @@
+accelerate==0.27.2
+aiohttp==3.9.3
+aiosignal==1.3.1
+annotated-types==0.6.0
+anyio==4.3.0
+argon2-cffi==23.1.0
+argon2-cffi-bindings==21.2.0
+Aspose.Slides==24.2.0
+attrs==23.2.0
+blinker==1.7.0
+cachelib==0.12.0
+cachetools==5.3.3
+certifi==2024.2.2
+cffi==1.16.0
+charset-normalizer==3.3.2
+click==8.1.7
+coloredlogs==15.0.1
+cryptography==42.0.5
+dashscope==1.14.1
+datasets==2.17.1
+datrie==0.8.2
+demjson==2.2.4
+dill==0.3.8
+distro==1.9.0
+elastic-transport==8.12.0
+elasticsearch==8.12.1
+elasticsearch-dsl==8.12.0
+et-xmlfile==1.1.0
+filelock==3.13.1
+FlagEmbedding==1.2.5
+Flask==3.0.2
+Flask-Cors==4.0.0
+Flask-Login==0.6.3
+Flask-Session==0.6.0
+flatbuffers==23.5.26
+frozenlist==1.4.1
+fsspec==2023.10.0
+h11==0.14.0
+hanziconv==0.3.2
+httpcore==1.0.4
+httpx==0.27.0
+huggingface-hub==0.20.3
+humanfriendly==10.0
+idna==3.6
+install==1.3.5
+itsdangerous==2.1.2
+Jinja2==3.1.3
+joblib==1.3.2
+lxml==5.1.0
+MarkupSafe==2.1.5
+minio==7.2.4
+mpi4py==3.1.5
+mpmath==1.3.0
+multidict==6.0.5
+multiprocess==0.70.16
+networkx==3.2.1
+nltk==3.8.1
+numpy==1.26.4
+nvidia-cublas-cu12==12.1.3.1
+nvidia-cuda-cupti-cu12==12.1.105
+nvidia-cuda-nvrtc-cu12==12.1.105
+nvidia-cuda-runtime-cu12==12.1.105
+nvidia-cudnn-cu12==8.9.2.26
+nvidia-cufft-cu12==11.0.2.54
+nvidia-curand-cu12==10.3.2.106
+nvidia-cusolver-cu12==11.4.5.107
+nvidia-cusparse-cu12==12.1.0.106
+nvidia-nccl-cu12==2.19.3
+nvidia-nvjitlink-cu12==12.3.101
+nvidia-nvtx-cu12==12.1.105
+onnxruntime-gpu==1.17.1
+openai==1.12.0
+opencv-python==4.9.0.80
+openpyxl==3.1.2
+packaging==23.2
+pandas==2.2.1
+pdfminer.six==20221105
+pdfplumber==0.10.4
+peewee==3.17.1
+pillow==10.2.0
+protobuf==4.25.3
+psutil==5.9.8
+pyarrow==15.0.0
+pyarrow-hotfix==0.6
+pyclipper==1.3.0.post5
+pycparser==2.21
+pycryptodome==3.20.0
+pycryptodome-test-vectors==1.0.14
+pycryptodomex==3.20.0
+pydantic==2.6.2
+pydantic_core==2.16.3
+PyJWT==2.8.0
+PyMuPDF==1.23.25
+PyMuPDFb==1.23.22
+PyMySQL==1.1.0
+PyPDF2==3.0.1
+pypdfium2==4.27.0
+python-dateutil==2.8.2
+python-docx==1.1.0
+python-dotenv==1.0.1
+python-pptx==0.6.23
+pytz==2024.1
+PyYAML==6.0.1
+regex==2023.12.25
+requests==2.31.0
+ruamel.yaml==0.18.6
+ruamel.yaml.clib==0.2.8
+safetensors==0.4.2
+scikit-learn==1.4.1.post1
+scipy==1.12.0
+sentence-transformers==2.4.0
+shapely==2.0.3
+six==1.16.0
+sniffio==1.3.1
+StrEnum==0.4.15
+sympy==1.12
+threadpoolctl==3.3.0
+tiktoken==0.6.0
+tokenizers==0.15.2
+torch==2.2.1
+tqdm==4.66.2
+transformers==4.38.1
+triton==2.2.0
+typing_extensions==4.10.0
+tzdata==2024.1
+urllib3==2.2.1
+Werkzeug==3.0.1
+xgboost==2.0.3
+XlsxWriter==3.2.0
+xpinyin==0.7.6
+xxhash==3.4.1
+yarl==1.9.4
+zhipuai==2.0.1
diff --git a/web/src/pages/add-knowledge/components/knowledge-file/chunk-method-modal.tsx b/web/src/pages/add-knowledge/components/knowledge-file/chunk-method-modal.tsx
index 411ec0f24..10a309251 100644
--- a/web/src/pages/add-knowledge/components/knowledge-file/chunk-method-modal.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-file/chunk-method-modal.tsx
@@ -193,7 +193,7 @@ const ChunkMethodModal: React.FC = ({
rules={[
{
required: true,
- message: 'Missing end page number(excluding)',
+ message: 'Missing end page number(excluded)',
},
({ getFieldValue }) => ({
validator(_, value) {
diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/utils.ts b/web/src/pages/add-knowledge/components/knowledge-setting/utils.ts
index e92c0a9ba..48e141fe0 100644
--- a/web/src/pages/add-knowledge/components/knowledge-setting/utils.ts
+++ b/web/src/pages/add-knowledge/components/knowledge-setting/utils.ts
@@ -120,7 +120,7 @@ export const TextMap = {
For a document, it will be treated as an entire chunk, no split at all.
- If you don't trust any chunk method and the selected LLM's context length covers the document length, you can try this method.
+ If you want to summarize something that needs all the context of an article and the selected LLM's context length covers the document length, you can try this method.
`,
},
};