tina-python 0.2.0__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.
@@ -0,0 +1,135 @@
1
+ """
2
+ 编写者:王出日
3
+ 日期:2024,12,1
4
+ 版本?
5
+ 对文件进行处理包括:
6
+ 1.文本类文件:docx,txt,pdf
7
+ 已经实现
8
+ 2.图片类文件:jpg,png,gif
9
+ 未实现
10
+
11
+ 内含:
12
+
13
+ 1.docxToTxt(docx_file,isClean = False):对docx文件进行处理,返回docx文件内容,每段内容用换行符分隔
14
+ 2.pdfToTxt(pdf_file,isClean = False):对pdf文件进行处理,返回pdf文件内容,每段内容用换行符分隔
15
+ 3.txtToTxt(txt_file,isClean = False):对txt文件进行处理,返回txt文件内容,每段内容用换行符分隔
16
+ """
17
+
18
+ import os
19
+ import docx
20
+ import PyPDF2
21
+ import urllib.request
22
+
23
+ from typing import Union,Generator
24
+ from .utils import cleaning, segment
25
+
26
+ class FileProcess:
27
+ """文件处理类"""
28
+ def __init__(self):
29
+ pass
30
+ def read_file(self,file_path:str=None,file_url:str=None)->str:
31
+ """通过文件路径自动判断文件类型并读取文件内容"""
32
+ if file_path is not None:
33
+ return fileToTxtByExten(file_path=file_path)
34
+ elif file_url is not None:
35
+ try:
36
+ response = urllib.request.urlopen(file_url)
37
+ content = response.read()
38
+ content = content.decode('utf-8')
39
+ return content
40
+ except Exception as e:
41
+ print(f"处理文件 {file_url} 时出错了:{e}")
42
+ raise
43
+ elif file_path is not None and file_url is not None:
44
+ raise ValueError("文件路径和文件url不能同时存在")
45
+ else:
46
+ raise ValueError("文件路径和文件url不能同时为空")
47
+
48
+
49
+ def process_document(content: str, isClean: bool, isSegments: bool, n: int,step:int = None, is_yield: bool = False) -> Union[Generator,list[str]]:
50
+ """处理文本内容,进行数据清洗和分段"""
51
+ if isClean:
52
+ content = cleaning(content)
53
+ return segment(content, n,step,is_yield) if isSegments else [content]
54
+
55
+
56
+ def docxToTxt(docx_file: str, isClean: bool = False, isSegments: bool = False, n: int = 100, step: int = None, is_yield: bool = False) -> Union[Generator,list[str]]:
57
+ """对docx文件进行处理"""
58
+ try:
59
+ doc = docx.Document(docx_file)
60
+ content = '\n'.join(para.text.strip() for para in doc.paragraphs if para.text.strip())
61
+ return process_document(content, isClean, isSegments, n,step, is_yield)
62
+ except Exception as e:
63
+ print(f"处理文件 {docx_file} 时出错了:{e}")
64
+ raise
65
+
66
+
67
+ def pdfToTxt(pdf_file: str, isClean: bool = False, isSegments: bool = False, n: int = 100, step: int = None, is_yield: bool = False) -> Union[Generator,list[str]]:
68
+ """对pdf文件进行处理"""
69
+ try:
70
+ with open(pdf_file, 'rb') as f:
71
+ pdf = PyPDF2.PdfReader(f)
72
+ content = ''.join(page.extract_text().strip() + '\n' for page in pdf.pages if page.extract_text())
73
+ return process_document(content, isClean, isSegments, n,step, is_yield)
74
+ except Exception as e:
75
+ print(f"处理文件 {pdf_file} 时出错了:{e}")
76
+ raise
77
+
78
+
79
+ def txtToTxt(txt_file: str, isClean: bool = False, isSegments: bool = False, n: int = 100, step: int = None, is_yield: bool = False) -> Union[Generator,list[str]]:
80
+ """对txt文件进行处理"""
81
+ try:
82
+ with open(txt_file, 'r', encoding='utf-8') as f:
83
+ content = f.read().strip().split('\n')
84
+ cleaned_content = '\n'.join(para for para in content if para.strip())
85
+ return process_document(cleaned_content, isClean, isSegments, n,step, is_yield)
86
+ except Exception as e:
87
+ print(f"处理文件 {txt_file} 时出错了:{e}")
88
+ raise
89
+
90
+ def mdToTxt(md_file: str, isClean: bool = False, isSegments: bool = False, n: int = 100, step: int = None, is_yield: bool = False) -> Union[Generator,list[str]]:
91
+ """对md文件进行处理"""
92
+ try:
93
+ with open(md_file, 'r', encoding='utf-8') as f:
94
+ content = f.read().strip().split('\n')
95
+ cleaned_content = '\n'.join(para for para in content if para.strip())
96
+ return process_document(cleaned_content, isClean, isSegments, n,step, is_yield)
97
+ except Exception as e:
98
+ print(f"处理文件 {md_file} 时出错了:{e}")
99
+ raise
100
+
101
+ def fileToTxt(file_path: str, isClean: bool = False, isSegments: bool = False, n: int = 100, step: int = None, is_yield: bool = False) -> list[str]:
102
+ """对文件夹内所有文件进行处理"""
103
+ if os.path.isfile(file_path):
104
+ return fileToTxtByExten(file_path, isClean, isSegments, n,step, is_yield)
105
+
106
+ content_list = []
107
+ file_list = os.listdir(file_path)
108
+ for file in file_list:
109
+ full_file_path = os.path.join(file_path, file)
110
+ content_list.extend(fileToTxtByExten(full_file_path, isClean, isSegments, n,step, is_yield))
111
+ return content_list
112
+
113
+
114
+ def fileToTxtByExten(file_path: str, isClean: bool = False, isSegments: bool = False, n: int = 100, step: int = None, is_yield: bool = False) -> Union[Generator,list[str]]:
115
+ """根据文件扩展名调用相应的转换函数"""
116
+ file_suffix = os.path.splitext(file_path)[1]
117
+ if file_suffix == '.docx':
118
+ return docxToTxt(file_path, isClean, isSegments, n,step, is_yield)
119
+ # elif file_suffix == '.doc':
120
+ # return docToTxt(file_path, isClean, isSegments, n)
121
+ elif file_suffix == '.pdf':
122
+ return pdfToTxt(file_path, isClean, isSegments, n,step, is_yield)
123
+ elif file_suffix == '.txt':
124
+ return txtToTxt(file_path, isClean, isSegments, n,step, is_yield)
125
+ elif file_suffix == '.md':
126
+ return mdToTxt(file_path, isClean, isSegments, n,step, is_yield)
127
+ #出现非法文件类型时,返回空列表
128
+ else:
129
+ return [f"该文件类型暂不支持,格式为{file_suffix},告诉用户使用docx,pdf,txt文件"]
130
+
131
+
132
+ class Image:
133
+ """图片类"""
134
+ def __init__(self):
135
+ pass
File without changes
@@ -0,0 +1,39 @@
1
+ """
2
+ 编写者:王出日
3
+ 日期:2024,12,13
4
+ 版本?
5
+ tina提供的查询文档工具,可以根据输入的文本进行向量检索,并返回最相似的文档。
6
+ """
7
+ import os
8
+ import numpy as np
9
+ import faiss
10
+ from tina.RAG.Embedding.embedding import Embedding
11
+ from tina.core.manage import TinaFolderManager
12
+ from tina.RAG.textSegments import TextSegments
13
+
14
+ def query(query_text, n=10)->list:
15
+ """
16
+ 根据输入的文本进行向量检索,并返回最相似的文档片段。
17
+ Args:
18
+ query_text: 输入的文本
19
+ n: 返回的文档片段数量
20
+ Returns:
21
+ 最相似的文档片段列表
22
+ """
23
+ text_segments = TextSegments()
24
+ text_embedding = Embedding()
25
+ faiss_index = faiss.read_index(os.path.join(TinaFolderManager.getFaissIndex()))
26
+ query_embedding = text_embedding.embedding(query_text)
27
+ distances, indices = faiss_index.search(np.array([query_embedding]).reshape(1, -1), n)
28
+ indices = indices.tolist()[0]
29
+ results = []
30
+ counter = 0
31
+ for i in indices:
32
+ if i == -1:
33
+ break
34
+ results.append(text_segments.find(i+1))
35
+ counter += 1
36
+
37
+
38
+ return results, counter
39
+
@@ -0,0 +1,117 @@
1
+ """
2
+ 编写者:王出日
3
+ 日期:2024,12,1
4
+ 版本?
5
+ 描述:
6
+ 文本分段类
7
+ 功能:
8
+ 1. 文本分段
9
+ 2. 获取指定编号的分段结果
10
+ 3. 找到第几段
11
+ """
12
+
13
+ import os
14
+ import pickle
15
+ import pathlib
16
+ import shutil
17
+
18
+ from ..core.manage import TinaFolderManager
19
+ from .processFiles import fileToTxtByExten
20
+
21
+ class TextSegments:
22
+ def __init__(self, folder_path:str=''):
23
+ self.folder_path = folder_path
24
+ self.segment_path = TinaFolderManager.getSegment()
25
+
26
+ def getMaxId(self):
27
+ #获取当前最大编号
28
+ if not os.path.exists(os.path.join(self.segment_path, 'id.txt')):
29
+ max_id = 0
30
+ for file in os.listdir(self.segment_path):
31
+ if file.startswith('seg_'):
32
+ id = int(file.split('_')[1])
33
+ if id > max_id:
34
+ max_id = id
35
+ return max_id
36
+ else:
37
+ with open(os.path.join(self.segment_path, 'id.txt'), 'r') as f:
38
+ max_id = int(f.read())
39
+ return max_id
40
+
41
+ def segments(self, n:int,isCopyFileToTinaFolder:bool=False):
42
+ """
43
+ 分段方法
44
+ Args:
45
+ n:每段的字数
46
+ isCopyFileToTinaFolder:是否将文件复制到Tina的文件夹中
47
+ """
48
+ for file in os.listdir(self.folder_path):
49
+ text_list = fileToTxtByExten(
50
+ os.path.join(self.folder_path, file),
51
+ isClean=True,
52
+ isSegments=True,
53
+ n=n
54
+ )
55
+ if isCopyFileToTinaFolder:
56
+ shutil.copy2(os.path.join(self.folder_path, file), TinaFolderManager.getDocumentFolder())
57
+
58
+ #构建文件名,数字+文件名+内分段数,数字从0开始
59
+ file_name = self.__getId() + '_' + file + '_' + str(len(text_list))+'.pkl'
60
+ #保存分段结果
61
+ with open(os.path.join(self.segment_path, file_name), 'wb') as f:
62
+ pickle.dump(text_list, f)
63
+
64
+
65
+ def get(self, id:int):
66
+ #获取指定文件的所有分段结果
67
+ for file in os.listdir(self.segment_path):
68
+ if file.startswith('seg_') and file.endswith('.pkl'):
69
+ #提取末尾分段数
70
+ num = int(pathlib.Path(file).stem.split('_')[1])
71
+ if num == id:
72
+ with open(os.path.join(self.segment_path, file), 'rb') as f:
73
+ result = pickle.load(f)
74
+ return result
75
+
76
+ def find(self, n:int):
77
+ #找到第几段
78
+ num = 0
79
+ for file in os.listdir(self.segment_path):
80
+ if file.startswith('seg_') and file.endswith('.pkl'):
81
+ #提取末尾分段数
82
+ num += int(pathlib.Path(file).stem.split('_')[3])
83
+ if num < n:
84
+ continue
85
+ elif num >= n:
86
+ with open(os.path.join(self.segment_path, file), 'rb') as f:
87
+ result = pickle.load(f)
88
+ return result[n-num-1]
89
+ else:
90
+ raise IndexError('查找值超出范围!')
91
+
92
+ def findFile(self,file_name:str):
93
+ #查找指定文件对应的分段文件地址
94
+ for file in os.listdir(self.segment_path):
95
+ if file.startswith('seg_') and file.endswith('.pkl'):
96
+ segment_onthistimesname = pathlib.Path(file).stem.split('_')[2]
97
+ if segment_onthistimesname == file_name:
98
+ return os.path.join(self.segment_path, file)
99
+
100
+ def __getId(self):
101
+ #获取当前最大编号
102
+ if not os.path.exists(os.path.join(self.segment_path, 'id.txt')):
103
+ max_id = 0
104
+ for file in os.listdir(self.segment_path):
105
+ if file.startswith('seg_'):
106
+ id = int(file.split('_')[1])
107
+ if id > max_id:
108
+ max_id = id
109
+ with open(os.path.join(self.segment_path, 'id.txt'), 'w') as f:
110
+ f.write(str(max_id + 1))
111
+ return'seg_' + str(max_id + 1)
112
+ else:
113
+ with open(os.path.join(self.segment_path, 'id.txt'), 'r') as f:
114
+ max_id = int(f.read())
115
+ with open(os.path.join(self.segment_path, 'id.txt'), 'w') as f:
116
+ f.write(str(max_id + 1))
117
+ return'seg_' + str(max_id + 1)
tina/RAG/utils.py ADDED
@@ -0,0 +1,55 @@
1
+ """
2
+ 编写者:王出日
3
+ 日期:2024,12,1
4
+ 版本?
5
+ 实用工具:
6
+ 1. cleaning(text):清理文本,去除乱码、空格、换行符、制表符等
7
+ 2. segment(text,n=100):将文本分段,每段不超过n个字符
8
+ """
9
+ __all__ = ['cleaning','segment']
10
+
11
+ import re
12
+ from typing import Union,Generator
13
+
14
+ def cleaning(text: str,word:list=['\u3000','\xa0','\u2003','\u2002','\u2004','\u2005','\u2006','\u2007','\u2008','\u2009','\u200a','\u202f','\u205f','\u3000','\u2028','\u2029']) -> str:
15
+ """
16
+ 清理文本,去除乱码、空格、换行符、制表符等,同时保留常用标点符号
17
+ Args:
18
+ text: 待清理文本
19
+ Returns:
20
+ str: 清理后的文本
21
+ """
22
+ # 定义需要保留的常用标点符号
23
+ punctuation = r',。!?;:“”‘’()、.?!\'\";:'
24
+
25
+ # 更新正则表达式以保留标点符号
26
+ pattern = re.compile(r'[^、\u4e00-\u9fa5\u0030-\u0039\u0041-\u005a\u0061-\u007a\u0020-\u007E\u00A0-\u00FF' + punctuation + r']+')
27
+ text = pattern.sub('', text)
28
+ text = text.replace('\n', '').replace('\t', '').replace('\r', '')
29
+
30
+ return text
31
+
32
+ def segment(text: str, n: int = 100, step: int = None, is_yield: bool = False) -> Union[Generator, list]:
33
+ """
34
+ 将文本分段,每段不超过n个字符,可以指定步长达到滚动窗口的效果,同时在大文本量时可以使用生成器节省内存
35
+ Args:
36
+ text: 待分段文本
37
+ n: 每段字符数
38
+ step: 分段步长,如果不指定则默认为n
39
+ is_yield: 是否使用生成器返回结果,默认为False
40
+ Returns:
41
+ Union[Generator, list]: 分段后的文本生成器或列表
42
+ """
43
+ if step is None:
44
+ step = n
45
+
46
+ if len(text) <= n:
47
+ return [text]
48
+ else:
49
+ if is_yield:
50
+ def gen():
51
+ for i in range(0, len(text) - n + 1, step):
52
+ yield text[i:i + n]
53
+ return gen()
54
+ else:
55
+ return [text[i:i + n] for i in range(0, len(text) - n + 1, step)]
tina/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from tina.tina import Tina
tina/core/__init__.py ADDED
File without changes
tina/core/executor.py ADDED
@@ -0,0 +1,117 @@
1
+ """
2
+ 编写者:王出日
3
+ 日期:2024,12,13
4
+ 版本 0.1.0
5
+ 功能:Agent的工具执行器
6
+ 通过导入AgentExecutor类,可以调用Agent的工具执行器,该类包含一个parser参数,该参数为解析工具调用的函数,默认为tina_parser函数。
7
+ 通过传入Tools对象来动态导入工具类,并调用该类的方法。
8
+ 使用方法:
9
+ 1. 导入AgentExecutor类
10
+ from executor import AgentExecutor
11
+ 2. 不需要创建实例,里面的方法为静态方法,可以直接调用
12
+ 例如使用execute方法执行工具调用:
13
+ result = AgentExecutor.execute(tool_call, tools, is_permissions)
14
+
15
+
16
+ """
17
+ import ast
18
+ import importlib.util
19
+ from .parser import tina_parser
20
+ from .tools import Tools
21
+
22
+ class AgentExecutor:
23
+ def __init__(self, parser: callable = tina_parser):
24
+ """
25
+ Agent的工具执行器
26
+ """
27
+ self.parser = parser
28
+ @staticmethod
29
+ def is_safe(node):
30
+ """
31
+ 验证AST节点是否安全
32
+ """
33
+ if isinstance(node, ast.Expression):
34
+ return all(AgentExecutor.is_safe(child) for child in ast.walk(node))
35
+ if isinstance(node, ast.BinOp):
36
+ return AgentExecutor.is_safe(node.left) and AgentExecutor.is_safe(node.right)
37
+ if isinstance(node, ast.UnaryOp):
38
+ return AgentExecutor.is_safe(node.operand)
39
+ if isinstance(node, ast.Num): # 用于Python 3.8及以下版本
40
+ return True
41
+ if isinstance(node, ast.Constant): # 用于Python 3.8及以上版本
42
+ return True
43
+ if isinstance(node, ast.Name):
44
+ return node.id.isidentifier() and not node.id.startswith('__')
45
+ if isinstance(node, ast.Call):
46
+ return AgentExecutor.is_safe(node.func) and all(AgentExecutor.is_safe(arg) for arg in node.args)
47
+ if isinstance(node, ast.Attribute):
48
+ return isinstance(node.value, ast.Name) and node.value.id.isidentifier()
49
+ raise ValueError(f"Unsupported AST node: {node}")
50
+
51
+ @staticmethod
52
+ def execute(tool_call: tuple[str, dict, bool], tools: type,is_permissions: bool = True,LLM:type = None) -> tuple[str, bool]:
53
+ """
54
+ 执行工具调用
55
+ 如何使用:
56
+ result = AgentExecutor.execute(tool_call, tools, is_permissions)
57
+ 其中,tool_call为工具调用的字符串,tools为Agent的工具类,is_permissions为是否需要验证权限,默认为True。
58
+ 返回值:
59
+ 第一个元素为执行结果,第二个元素为是否成功。
60
+ 可以使用变量拆包的方式获取执行结果:
61
+ result, success = AgentExecutor.execute(tool_call, tools, is_permissions)
62
+ 其中,success为是否使用了工具调用,True表示成功,False表示失败。
63
+ Args:
64
+ tool_call (str): 字符串,内含解析器会解析的工具调用
65
+ tools (type): 工具类,用于内部调用检测工具是否存在和参数验证
66
+ is_permissions (bool, optional): 对执行字符串进行安全验证,默认是True.
67
+ Returns:
68
+ tuple[str, bool]: 元组,执行结果和是否成功
69
+ """
70
+ if not tool_call[2]:
71
+ return result
72
+ module = AgentExecutor.import_module(tools.getToolsPath(name = tool_call[0]))
73
+
74
+ func = getattr(module, tool_call[0])
75
+ if tool_call[1]:
76
+ result = func(**tool_call[1])
77
+ else:
78
+ result = func()
79
+
80
+ #参数判断
81
+ if isinstance(result,str):
82
+ return result,True
83
+ elif isinstance(result, list):
84
+ result_str = ",".join(f"列表第{index+1}元素{value}" for index, value in enumerate(result))
85
+ elif isinstance(result,bool):
86
+ if result:
87
+ result_str = "True"
88
+ else:
89
+ result_str = "False"
90
+ elif isinstance(result, dict):
91
+ result_str = ",".join(f"字典的{key}键对应的值为{value}" for key, value in result.items())
92
+ else:
93
+ result_str = str(result)
94
+ return result_str,True
95
+ @staticmethod
96
+ def _extract_value(node):
97
+ if isinstance(node, ast.Constant):
98
+ return node.value
99
+ elif isinstance(node, ast.List):
100
+ return [AgentExecutor._extract_value(el) for el in node.elts]
101
+ else:
102
+ raise TypeError(f"不支持的节点类型: {type(node)}")
103
+
104
+
105
+ @staticmethod
106
+ def import_module(module_path:str):
107
+ """
108
+ 动态导入工具类
109
+ 给了路径,就可以导入
110
+ """
111
+ try:
112
+ spec = importlib.util.spec_from_file_location("tool", module_path)
113
+ module = importlib.util.module_from_spec(spec)
114
+ spec.loader.exec_module(module)
115
+ return module
116
+ except Exception as e:
117
+ raise Exception(f"导入工具失败,原因:{str(e)}")
tina/core/logging.py ADDED
@@ -0,0 +1,6 @@
1
+ """
2
+ 记录用户使用日志
3
+ 还没开始写呢!
4
+ """
5
+ import logging
6
+
tina/core/manage.py ADDED
@@ -0,0 +1,84 @@
1
+ """
2
+ 编写者:王出日
3
+ 日期:2024,12,1
4
+ 版本?
5
+ 管理tina的文件夹模块
6
+ """
7
+
8
+ import os
9
+
10
+ class TinaFolderManager:
11
+ """
12
+ 管理tina文件夹
13
+ """
14
+ @staticmethod
15
+ def init(base_dir: str = os.path.dirname(__file__)):
16
+ """
17
+ 初始化tina文件夹
18
+ """
19
+ TinaFolderManager.file_dir = os.path.join(base_dir, "Tina")
20
+ try:
21
+ os.makedirs(TinaFolderManager.file_dir, exist_ok=True)
22
+ os.makedirs(os.path.join(TinaFolderManager.file_dir, "memory"), exist_ok=True)
23
+ os.makedirs(os.path.join(TinaFolderManager.file_dir, "cache"), exist_ok=True)
24
+ os.makedirs(os.path.join(TinaFolderManager.file_dir, "segment"), exist_ok=True)
25
+ os.makedirs(os.path.join(TinaFolderManager.file_dir,"document"),exist_ok=True)
26
+ if not os.path.exists(os.path.join(TinaFolderManager.file_dir, "segment", "segment.index")):
27
+ with open(os.path.join(TinaFolderManager.file_dir, "segment", "segment.index"), "w") as f:
28
+ pass
29
+ if not os.path.exists(os.path.join(TinaFolderManager.file_dir, "memory", "memory.index")):
30
+ pass
31
+
32
+ except OSError as e:
33
+ print(f"初始化失败: {e}")
34
+
35
+ TinaFolderManager.embeding_model = ""
36
+
37
+ @staticmethod
38
+ def getCache() -> str:
39
+ """
40
+ 获取缓存文件夹路径
41
+ """
42
+ return os.path.join(TinaFolderManager.file_dir, "cache")
43
+
44
+ @staticmethod
45
+ def getMemory() -> str:
46
+ """
47
+ 获取记忆文件夹路径
48
+ """
49
+ return os.path.join(TinaFolderManager.file_dir, "memory")
50
+
51
+ @staticmethod
52
+ def getMemoryFile(filename: str) -> str:
53
+ """
54
+ 获取记忆文件路径
55
+ """
56
+ return os.path.join(TinaFolderManager.getMemory(), filename)
57
+
58
+ @staticmethod
59
+ def getFaissIndex() -> str:
60
+ """
61
+ 获取分段文件的索引文件路径
62
+ """
63
+ return os.path.join(TinaFolderManager.file_dir, "segment", "segment.index")
64
+
65
+ @staticmethod
66
+ def getSegment() -> str:
67
+ """
68
+ 获取分段文件夹路径
69
+ """
70
+ return os.path.join(TinaFolderManager.file_dir, "segment")
71
+
72
+ @staticmethod
73
+ def setEmbedingModel(model_path: str):
74
+ TinaFolderManager.embeding_model = model_path
75
+
76
+ @staticmethod
77
+ def getDocumentFolder()->str:
78
+ return os.path.join(TinaFolderManager.file_dir,"document")
79
+
80
+ @staticmethod
81
+ def getEmbedingModel() -> str:
82
+ if TinaFolderManager.embeding_model == "":
83
+ raise ValueError("未指定embedding模型路径,请使用TinaFolderManager.setEmbedingModel设置embedding模型路径")
84
+ return TinaFolderManager.embeding_model