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.
- tina/Agent.py +353 -0
- tina/Environment.py +33 -0
- tina/LLM/DeepSeek.py +8 -0
- tina/LLM/Kimi.py +141 -0
- tina/LLM/Qwen.py +8 -0
- tina/LLM/__init__.py +0 -0
- tina/LLM/api.py +145 -0
- tina/LLM/llama.py +306 -0
- tina/RAG/Embedding/QwenEmbeddings.py +56 -0
- tina/RAG/Embedding/__init__.py +0 -0
- tina/RAG/Embedding/docToVec.py +30 -0
- tina/RAG/Embedding/embedding.py +27 -0
- tina/RAG/__init__.py +0 -0
- tina/RAG/processFiles.py +135 -0
- tina/RAG/query/__init__.py +0 -0
- tina/RAG/query/query.py +39 -0
- tina/RAG/textSegments.py +117 -0
- tina/RAG/utils.py +55 -0
- tina/__init__.py +1 -0
- tina/core/__init__.py +0 -0
- tina/core/executor.py +117 -0
- tina/core/logging.py +6 -0
- tina/core/manage.py +84 -0
- tina/core/memory.py +219 -0
- tina/core/parser.py +42 -0
- tina/core/prompt.py +34 -0
- tina/core/tools.py +310 -0
- tina/extend/__init__.py +3 -0
- tina/extend/multi-sourceInput.py +0 -0
- tina/tina.py +211 -0
- tina/tools/NULLTools.py +3 -0
- tina/tools/QwenDocToVec.py +41 -0
- tina/tools/__init__.py +1 -0
- tina/tools/codeMarker.py +63 -0
- tina/tools/readLoogText.py +20 -0
- tina/tools/search.py +0 -0
- tina/tools/systemTools.py +51 -0
- tina_python-0.2.0.dist-info/METADATA +358 -0
- tina_python-0.2.0.dist-info/RECORD +41 -0
- tina_python-0.2.0.dist-info/WHEEL +5 -0
- tina_python-0.2.0.dist-info/top_level.txt +1 -0
tina/Agent.py
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
from typing import Union, Generator, Iterator, Any
|
|
5
|
+
from .core.executor import AgentExecutor
|
|
6
|
+
from .RAG.processFiles import FileProcess
|
|
7
|
+
from .core.memory import Memory
|
|
8
|
+
from .tools.systemTools import *
|
|
9
|
+
from .core.parser import tina_parser
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Agent:
|
|
13
|
+
def __new__(cls, LLM: type, tools: type, prompt: type, is_tool_call_permission: bool = True):
|
|
14
|
+
if LLM._call == "API":
|
|
15
|
+
return object.__new__(Agent_API)
|
|
16
|
+
elif LLM._call == "LOCAL":
|
|
17
|
+
return object.__new__(Agent_LOCAL)
|
|
18
|
+
else:
|
|
19
|
+
raise ValueError("LLM 调用方式错误,如果是API调用,设置LLM._call = 'API',如果是本地调用,设置LLM._call = 'LOCAL'")
|
|
20
|
+
|
|
21
|
+
def __init__(self, LLM: type, tools: type, prompt: type, is_tool_call_permission: bool = True):
|
|
22
|
+
self.LLM = LLM
|
|
23
|
+
self.Tools = tools
|
|
24
|
+
self.Prompt = prompt
|
|
25
|
+
self.Memory = Memory()
|
|
26
|
+
self.fileProcess = FileProcess()
|
|
27
|
+
self.messages = [
|
|
28
|
+
{"role": "system", "content": self.Prompt.prompt["tina"]},
|
|
29
|
+
{"role": "system", "content": f"这次运行的开始数据有:你的最大上下文{self.LLM.context_length},时间为{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"}
|
|
30
|
+
]
|
|
31
|
+
# 加载记忆信息
|
|
32
|
+
self.messages.extend(
|
|
33
|
+
self.Memory.returnMessages(self.LLM.context_length, memory_percent=0.2, tag=["用户信息", "指令信息"], importance=3)
|
|
34
|
+
)
|
|
35
|
+
self.messages.append({"role": "system", "content": "这条消息之前的内容是你和用户的聊天记忆,他们发生在过去的对话中,用于你了解用户会做什么。"})
|
|
36
|
+
self.messages_conter = len(self.messages)
|
|
37
|
+
self.is_tool_call_permission = is_tool_call_permission
|
|
38
|
+
|
|
39
|
+
def predict(self, input_text: str = None,
|
|
40
|
+
temperature: float = 0.5,
|
|
41
|
+
top_p: float = 0.9,
|
|
42
|
+
top_k: int = 0,
|
|
43
|
+
min_p: float = 0.0,
|
|
44
|
+
stream: bool = True
|
|
45
|
+
) -> Union[str, Generator[str, None, None]]:
|
|
46
|
+
"""
|
|
47
|
+
调用agent进行生成文本回复,默认流式输出
|
|
48
|
+
"""
|
|
49
|
+
self.messages.append(
|
|
50
|
+
{"role": "user", "content": input_text}
|
|
51
|
+
)
|
|
52
|
+
if stream:
|
|
53
|
+
llm_result = self.LLM.predict(
|
|
54
|
+
messages=self.messages,
|
|
55
|
+
temperature=temperature,
|
|
56
|
+
tools=self.Tools.tools,
|
|
57
|
+
top_p=top_p,
|
|
58
|
+
top_k=top_k,
|
|
59
|
+
min_p=min_p,
|
|
60
|
+
stream=stream,
|
|
61
|
+
)
|
|
62
|
+
return self.tag_parser(text_generator=llm_result, tag="<tool_call>")
|
|
63
|
+
else:
|
|
64
|
+
tool_call = False
|
|
65
|
+
while tool_call == False:
|
|
66
|
+
llm_result = self.LLM.predict(
|
|
67
|
+
messages=self.messages,
|
|
68
|
+
temperature=temperature,
|
|
69
|
+
tools=self.Tools.tools,
|
|
70
|
+
top_p=top_p,
|
|
71
|
+
top_k=top_k,
|
|
72
|
+
min_p=min_p,
|
|
73
|
+
stream=stream
|
|
74
|
+
)
|
|
75
|
+
result = AgentExecutor.execute(llm_result["content"], self.Tools, is_permissions=self.is_tool_call_permission)
|
|
76
|
+
if not result[1]:
|
|
77
|
+
return result[0]
|
|
78
|
+
|
|
79
|
+
self.messages.append(
|
|
80
|
+
{"role": "assistant", "content": "工具的执行结果为:\n" + result[0]}
|
|
81
|
+
)
|
|
82
|
+
tool_call = result[1]
|
|
83
|
+
|
|
84
|
+
def readFile(self, path):
|
|
85
|
+
"""
|
|
86
|
+
读取文件
|
|
87
|
+
"""
|
|
88
|
+
file_content = self.fileProcess.read_file(file_path=path)
|
|
89
|
+
if len(file_content) >= int(self.LLM.context_length * 0.5):
|
|
90
|
+
self.messages.append(
|
|
91
|
+
{"role": "system", "content": f"文件内容为,文件过大所以只阅读了一半上下文长度的文字,建议用户使用RAG:\n{file_content[0:int(self.LLM.context_length * 0.5)]}"}
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
self.messages.append(
|
|
95
|
+
{"role": "system", "content": f"用户上传了文件,路径为:{path},文件内容为:\n{file_content}"}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def remember(self, message: str = None) -> None:
|
|
99
|
+
"""
|
|
100
|
+
记忆消息
|
|
101
|
+
"""
|
|
102
|
+
if message is None:
|
|
103
|
+
for message in self.messages[self.messages_conter:]:
|
|
104
|
+
self.Memory.remember(self.LLM, message)
|
|
105
|
+
self.messages_conter = len(self.messages)
|
|
106
|
+
else:
|
|
107
|
+
self.Memory.remember(self.LLM, message)
|
|
108
|
+
|
|
109
|
+
def forget(self, importance: int = None):
|
|
110
|
+
"""
|
|
111
|
+
忘记之前的对话信息,与记忆模块的遗忘有区别
|
|
112
|
+
"""
|
|
113
|
+
if importance is None:
|
|
114
|
+
self.messages = []
|
|
115
|
+
else:
|
|
116
|
+
self.Memory.forget(importance)
|
|
117
|
+
|
|
118
|
+
def tag_parser(self, text_generator: Iterator[Any], tag="") -> Generator[str, None, None]:
|
|
119
|
+
"""
|
|
120
|
+
解析流式消息
|
|
121
|
+
"""
|
|
122
|
+
tool_call = ""
|
|
123
|
+
whole_content = ""
|
|
124
|
+
in_tool_call = False
|
|
125
|
+
close_tag = tag[:1] + "/" + tag[1:]
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
for chunk in text_generator:
|
|
129
|
+
# 解析chunk结构
|
|
130
|
+
try:
|
|
131
|
+
delta = chunk["choices"][0]["delta"]
|
|
132
|
+
except (KeyError, IndexError, TypeError) as e:
|
|
133
|
+
yield f"错误: 消息格式不正确 - {str(e)}"
|
|
134
|
+
continue
|
|
135
|
+
# 跳过role字段更新
|
|
136
|
+
if "role" in delta:
|
|
137
|
+
continue
|
|
138
|
+
# 获取content内容
|
|
139
|
+
content = delta.get("content", "")
|
|
140
|
+
if not content:
|
|
141
|
+
continue
|
|
142
|
+
# 检测工具调用
|
|
143
|
+
if content.startswith(tag):
|
|
144
|
+
in_tool_call = True
|
|
145
|
+
tool_call += content
|
|
146
|
+
# 收集完整工具调用内容
|
|
147
|
+
while not tool_call.endswith(close_tag):
|
|
148
|
+
try:
|
|
149
|
+
next_chunk = next(text_generator)
|
|
150
|
+
next_delta = next_chunk["choices"][0]["delta"]
|
|
151
|
+
next_content = next_delta.get("content", "")
|
|
152
|
+
tool_call += next_content
|
|
153
|
+
except Exception as e:
|
|
154
|
+
yield "错误: 工具调用不完整或消息格式不正确" + str(e)
|
|
155
|
+
in_tool_call = False
|
|
156
|
+
break
|
|
157
|
+
if not in_tool_call:
|
|
158
|
+
continue
|
|
159
|
+
# 执行工具调用
|
|
160
|
+
yield "正在发生工具调用...\n"
|
|
161
|
+
tool_call = tina_parser(tool_call, self.Tools, self.LLM)
|
|
162
|
+
result = AgentExecutor.execute(tool_call, self.Tools, is_permissions=self.is_tool_call_permission, LLM=self.LLM)
|
|
163
|
+
if result[1]:
|
|
164
|
+
self.messages.extend([{
|
|
165
|
+
"role": "assistant",
|
|
166
|
+
"content": f"{tool_call}"
|
|
167
|
+
}, {
|
|
168
|
+
"role": "system",
|
|
169
|
+
"content": f"工具调用结果:\n{result[0]}"
|
|
170
|
+
}])
|
|
171
|
+
# 生成新的大模型响应
|
|
172
|
+
yield from self.predict(input_text=whole_content, stream=True)
|
|
173
|
+
else:
|
|
174
|
+
yield "工具调用执行失败"
|
|
175
|
+
return # 结束当前生成器
|
|
176
|
+
else:
|
|
177
|
+
# 普通响应内容
|
|
178
|
+
whole_content += content
|
|
179
|
+
yield content
|
|
180
|
+
except Exception as e:
|
|
181
|
+
# yield f"错误: 处理过程中发生异常 - {str(e)}"
|
|
182
|
+
raise e
|
|
183
|
+
# 非工具调用时保存完整响应
|
|
184
|
+
if not in_tool_call:
|
|
185
|
+
self.messages.append({
|
|
186
|
+
"role": "assistant",
|
|
187
|
+
"content": whole_content
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class Agent_API(Agent):
|
|
192
|
+
def __init__(self, LLM: type, tools: type, prompt: type, is_tool_call_permission: bool = True):
|
|
193
|
+
super().__init__(LLM, tools, prompt, is_tool_call_permission)
|
|
194
|
+
self.tool_calls:list = []
|
|
195
|
+
def predict(self, input_text: str = None,
|
|
196
|
+
temperature: float = 0.5,
|
|
197
|
+
top_p: float = 0.9,
|
|
198
|
+
top_k: int = 0,
|
|
199
|
+
min_p: float = 0.0,
|
|
200
|
+
stream: bool = True
|
|
201
|
+
) -> Union[str, Generator[str, None, None]]:
|
|
202
|
+
"""
|
|
203
|
+
调用agent进行生成文本回复,默认流式输出
|
|
204
|
+
"""
|
|
205
|
+
if input_text is not None:
|
|
206
|
+
self.messages.append(
|
|
207
|
+
{"role": "user", "content": input_text}
|
|
208
|
+
)
|
|
209
|
+
if stream:
|
|
210
|
+
llm_result = self.LLM.predict(
|
|
211
|
+
messages=self.messages,
|
|
212
|
+
temperature=temperature,
|
|
213
|
+
tools=self.Tools.tools,
|
|
214
|
+
top_p=top_p,
|
|
215
|
+
stream=stream,
|
|
216
|
+
)
|
|
217
|
+
return self.parser(llm_result)
|
|
218
|
+
else:
|
|
219
|
+
tool_call = False
|
|
220
|
+
while tool_call == False:
|
|
221
|
+
llm_result = self.LLM.predict(
|
|
222
|
+
messages=self.messages,
|
|
223
|
+
temperature=temperature,
|
|
224
|
+
tools=self.Tools.tools,
|
|
225
|
+
top_p=top_p,
|
|
226
|
+
stream=stream
|
|
227
|
+
)
|
|
228
|
+
if "tool_calls" in llm_result.keys():
|
|
229
|
+
tool_call = (llm_result["tool_calls"][0]["function"]["name"],json.loads(llm_result["tool_calls"][0]["function"]["arguments"]),True)
|
|
230
|
+
result = AgentExecutor.execute(tool_call, self.Tools, is_permissions=self.is_tool_call_permission)
|
|
231
|
+
if not result[1]:
|
|
232
|
+
return result[0]
|
|
233
|
+
|
|
234
|
+
self.messages.append(
|
|
235
|
+
{"role": "assistant", "content": "工具的执行结果为:\n" + result[0]}
|
|
236
|
+
)
|
|
237
|
+
tool_call = result[1]
|
|
238
|
+
|
|
239
|
+
def parser(self, generator):
|
|
240
|
+
whole_content = ""
|
|
241
|
+
tool_result = ('',False)
|
|
242
|
+
for chunk in generator:
|
|
243
|
+
if chunk["content"] is None:
|
|
244
|
+
chunk["content"] = ""
|
|
245
|
+
self.messages.append(chunk)
|
|
246
|
+
yield chunk["content"]
|
|
247
|
+
whole_content += chunk["content"]
|
|
248
|
+
elif "tool_calls" in chunk and chunk["id"] != '':
|
|
249
|
+
temp = chunk.copy() # 使用copy避免修改原始数据
|
|
250
|
+
temp["tool_calls"][0]["id"] = temp["id"]
|
|
251
|
+
temp.pop("id")
|
|
252
|
+
# 解析工具调用参数时要捕获异常
|
|
253
|
+
yield f"\n正在发生工具调用...\n工具名:{temp['tool_calls'][0]['function']['name']}\n"
|
|
254
|
+
try:
|
|
255
|
+
args = json.loads(chunk["tool_calls"][0]["function"]["arguments"])
|
|
256
|
+
if args is None:
|
|
257
|
+
yield "工具参数为空"
|
|
258
|
+
yield from self.predict(input_text="工具参数为空,请重新输入,你之前输入的内容为:\n"+whole_content,stream=True)
|
|
259
|
+
except json.JSONDecodeError:
|
|
260
|
+
yield "工具参数解析失败"
|
|
261
|
+
yield from self.predict(input_text="工具解析失败,请重新输入,你之前输入的内容为:\n"+whole_content,stream=True)
|
|
262
|
+
|
|
263
|
+
tool_call = (chunk["tool_calls"][0]["function"]["name"], args, True)
|
|
264
|
+
tool_result = AgentExecutor.execute(tool_call=tool_call,tools=self.Tools)
|
|
265
|
+
self.messages.append(temp)
|
|
266
|
+
|
|
267
|
+
if tool_result[1]: # 工具调用成功后
|
|
268
|
+
# 添加工具结果到消息历史
|
|
269
|
+
self.messages.append({"role":"tool","content":f"工具调用结果:\n{tool_result[0]}"})
|
|
270
|
+
# 递归调用并立即返回所有生成内容
|
|
271
|
+
yield from self.predict(input_text=whole_content,stream=True)
|
|
272
|
+
else:
|
|
273
|
+
yield chunk["content"]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class Agent_LOCAL(Agent):
|
|
280
|
+
def __init__(self, LLM: type, tools: type, prompt: type, is_tool_call_permission: bool = True):
|
|
281
|
+
super().__init__(LLM, tools, prompt, is_tool_call_permission)
|
|
282
|
+
|
|
283
|
+
def tag_parser(self, text_generator: Iterator[Any], tag="") -> Generator[str, None, None]:
|
|
284
|
+
"""
|
|
285
|
+
解析流式消息
|
|
286
|
+
"""
|
|
287
|
+
tool_call = ""
|
|
288
|
+
whole_content = ""
|
|
289
|
+
in_tool_call = False
|
|
290
|
+
close_tag = tag[:1] + "/" + tag[1:]
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
for chunk in text_generator:
|
|
294
|
+
# 解析chunk结构
|
|
295
|
+
try:
|
|
296
|
+
delta = chunk["choices"][0]["delta"]
|
|
297
|
+
except (KeyError, IndexError, TypeError) as e:
|
|
298
|
+
yield f"错误: 消息格式不正确 - {str(e)}"
|
|
299
|
+
continue
|
|
300
|
+
# 跳过role字段更新
|
|
301
|
+
if "role" in delta:
|
|
302
|
+
continue
|
|
303
|
+
# 获取content内容
|
|
304
|
+
content = delta.get("content", "")
|
|
305
|
+
if not content:
|
|
306
|
+
continue
|
|
307
|
+
# 检测工具调用
|
|
308
|
+
if content.startswith(tag):
|
|
309
|
+
in_tool_call = True
|
|
310
|
+
tool_call += content
|
|
311
|
+
# 收集完整工具调用内容
|
|
312
|
+
while not tool_call.endswith(close_tag):
|
|
313
|
+
try:
|
|
314
|
+
next_chunk = next(text_generator)
|
|
315
|
+
next_delta = next_chunk["choices"][0]["delta"]
|
|
316
|
+
next_content = next_delta.get("content", "")
|
|
317
|
+
tool_call += next_content
|
|
318
|
+
except Exception as e:
|
|
319
|
+
yield "错误: 工具调用不完整或消息格式不正确" + str(e)
|
|
320
|
+
in_tool_call = False
|
|
321
|
+
break
|
|
322
|
+
if not in_tool_call:
|
|
323
|
+
continue
|
|
324
|
+
# 执行工具调用
|
|
325
|
+
yield "正在发生工具调用...\n"
|
|
326
|
+
tool_call = tina_parser(tool_call, self.Tools, self.LLM)
|
|
327
|
+
result = AgentExecutor.execute(tool_call, self.Tools, is_permissions=self.is_tool_call_permission, LLM=self.LLM)
|
|
328
|
+
if result[1]:
|
|
329
|
+
self.messages.extend([{
|
|
330
|
+
"role": "assistant",
|
|
331
|
+
"content": f"{tool_call}"
|
|
332
|
+
}, {
|
|
333
|
+
"role": "system",
|
|
334
|
+
"content": f"工具调用结果:\n{result[0]}"
|
|
335
|
+
}])
|
|
336
|
+
# 生成新的大模型响应
|
|
337
|
+
yield from self.predict(input_text=whole_content, stream=True)
|
|
338
|
+
else:
|
|
339
|
+
yield "工具调用执行失败"
|
|
340
|
+
return # 结束当前生成器
|
|
341
|
+
else:
|
|
342
|
+
# 普通响应内容
|
|
343
|
+
whole_content += content
|
|
344
|
+
yield content
|
|
345
|
+
except Exception as e:
|
|
346
|
+
# yield f"错误: 处理过程中发生异常 - {str(e)}"
|
|
347
|
+
raise e
|
|
348
|
+
# 非工具调用时保存完整响应
|
|
349
|
+
if not in_tool_call:
|
|
350
|
+
self.messages.append({
|
|
351
|
+
"role": "assistant",
|
|
352
|
+
"content": whole_content
|
|
353
|
+
})
|
tina/Environment.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
设定agent的环境
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
from core.manage import TinaFolderManager
|
|
7
|
+
|
|
8
|
+
class Environment:
|
|
9
|
+
def __init__(self, work_path):
|
|
10
|
+
self.work_path = work_path
|
|
11
|
+
TinaFolderManager.init(work_path)
|
|
12
|
+
self.OperationSystemDict = {
|
|
13
|
+
"nt": "Windows",
|
|
14
|
+
"posix": "Linux"
|
|
15
|
+
}
|
|
16
|
+
self.OperationSystem = self.OperationSystemDict.get(os.name)
|
|
17
|
+
self.SystemVersion = platform.version()
|
|
18
|
+
self.SystemRelease = platform.release()
|
|
19
|
+
self.SystemPlatform = platform.platform()
|
|
20
|
+
self.SystemArchitecture = platform.architecture()
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def info(self):
|
|
24
|
+
return (f"系统:{self.OperationSystem},\n"
|
|
25
|
+
f"版本:{self.SystemVersion},\n "
|
|
26
|
+
f"发布:{self.SystemRelease}, \n"
|
|
27
|
+
f"平台:{self.SystemPlatform}, \n"
|
|
28
|
+
f"架构:{self.SystemArchitecture[0]},\n "
|
|
29
|
+
f"工作目录:{self.work_path}\n")
|
|
30
|
+
|
|
31
|
+
if __name__ == '__main__':
|
|
32
|
+
env = Environment(r'D:\development\project\TCG\test')
|
|
33
|
+
print(env.info)
|
tina/LLM/DeepSeek.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from .api import BaseAPI
|
|
2
|
+
|
|
3
|
+
class DeepSeek(BaseAPI):
|
|
4
|
+
API_ENV_VAR_NAME = "DEEPSEEK_API_KEY" # 重写API key环境变量名称
|
|
5
|
+
BASE_URL = "https://api.deepseek.com/v1" # 重写base_url
|
|
6
|
+
|
|
7
|
+
def __init__(self, api_key: str = None, model: str = "deepseek-chat", base_url: str = None):
|
|
8
|
+
super().__init__(api_key, model, base_url)
|
tina/LLM/Kimi.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from typing import Union, Generator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Kimi():
|
|
8
|
+
def __init__(self, api_key: str = None, model: str = "qwen-plus",base_url:str ="https://dashscope.aliyuncs.com/compatible-mode/v1" ):
|
|
9
|
+
self.base_url = base_url
|
|
10
|
+
if api_key is None:
|
|
11
|
+
try:
|
|
12
|
+
self.api_key = os.environ.get("DASHSCOPE_API_KEY")
|
|
13
|
+
except:
|
|
14
|
+
raise ValueError("API key并没有在环境变量‘DASHSCOPE_API_KEY’中找到,要么请你设置一下,要么输入api_key参数")
|
|
15
|
+
else:
|
|
16
|
+
print("我们建议你在环境变量中设置DASHSCOPE_API_KEY,不要输入api_key参数哦")
|
|
17
|
+
self.api_key = api_key
|
|
18
|
+
|
|
19
|
+
self.api_key = api_key
|
|
20
|
+
self.model = model
|
|
21
|
+
self.token = 0
|
|
22
|
+
self._call = "API"
|
|
23
|
+
self.context_length = 32000
|
|
24
|
+
|
|
25
|
+
def predict(self,
|
|
26
|
+
input_text: str = None,
|
|
27
|
+
sys_prompt: str = '你的工作非常的出色!',
|
|
28
|
+
messages: list = None,
|
|
29
|
+
temperature: float = 0.3,
|
|
30
|
+
top_p: float = 0.9,
|
|
31
|
+
stream: bool = False,
|
|
32
|
+
tools: list = None) -> Union[dict, Generator[dict, None, None]]:
|
|
33
|
+
if messages is None:
|
|
34
|
+
messages = []
|
|
35
|
+
messages.append({"role":"system","content":sys_prompt})
|
|
36
|
+
# 处理消息列表
|
|
37
|
+
if input_text:
|
|
38
|
+
messages.append({"role": "user", "content": input_text})
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# 请求参数
|
|
43
|
+
payload = {
|
|
44
|
+
"model": self.model,
|
|
45
|
+
"messages": messages,
|
|
46
|
+
"temperature": temperature,
|
|
47
|
+
"top_p": top_p,
|
|
48
|
+
"stream": stream,
|
|
49
|
+
"tools": tools
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
headers = {
|
|
53
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
54
|
+
"Content-Type": "application/json"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# **非流式请求**
|
|
58
|
+
if not stream:
|
|
59
|
+
response = httpx.post(f"{self.base_url}/chat/completions", json=payload, headers=headers, timeout=30)
|
|
60
|
+
response_data = response.json()
|
|
61
|
+
self.token += response_data.get("usage", {}).get("total_tokens", 0)
|
|
62
|
+
|
|
63
|
+
result = {"role": "assistant", "content": response_data["choices"][0]["message"]["content"]}
|
|
64
|
+
|
|
65
|
+
# 如果包含工具调用,添加 tool_calls
|
|
66
|
+
tool_calls = response_data["choices"][0]["message"].get("tool_calls")
|
|
67
|
+
if tool_calls:
|
|
68
|
+
result["tool_calls"] = tool_calls
|
|
69
|
+
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def stream_generator():
|
|
74
|
+
tool_calls_buffer = {}
|
|
75
|
+
final_tool_calls = None
|
|
76
|
+
received_ids = {} # 用于保存每个index首次收到的ID
|
|
77
|
+
|
|
78
|
+
with httpx.stream("POST", f"{self.base_url}/chat/completions", json=payload, headers=headers, timeout=60) as response:
|
|
79
|
+
for line in response.iter_lines():
|
|
80
|
+
line = line.strip()
|
|
81
|
+
if line.startswith("data: "):
|
|
82
|
+
try:
|
|
83
|
+
data = json.loads(line[6:])
|
|
84
|
+
for choice in data.get("choices", []):
|
|
85
|
+
delta = choice.get("delta", {})
|
|
86
|
+
result = {"role": "assistant"}
|
|
87
|
+
|
|
88
|
+
# 处理普通内容
|
|
89
|
+
if "content" in delta:
|
|
90
|
+
result["content"] = delta["content"]
|
|
91
|
+
yield result
|
|
92
|
+
|
|
93
|
+
# 处理工具调用
|
|
94
|
+
if "tool_calls" in delta:
|
|
95
|
+
for tool_call in delta["tool_calls"]:
|
|
96
|
+
index = tool_call["index"]
|
|
97
|
+
|
|
98
|
+
# 初始化缓冲区
|
|
99
|
+
if index not in tool_calls_buffer:
|
|
100
|
+
tool_calls_buffer[index] = {
|
|
101
|
+
"index": index,
|
|
102
|
+
"function": {"arguments": ""},
|
|
103
|
+
"type": "",
|
|
104
|
+
"id": ""
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# 保留首次收到的ID
|
|
108
|
+
if tool_call.get("id") and index not in received_ids:
|
|
109
|
+
received_ids[index] = tool_call["id"]
|
|
110
|
+
|
|
111
|
+
# 更新字段(保留首次ID)
|
|
112
|
+
current = tool_calls_buffer[index]
|
|
113
|
+
current["id"] = received_ids.get(index, "")
|
|
114
|
+
current["type"] = tool_call.get("type") or current["type"]
|
|
115
|
+
|
|
116
|
+
# 处理函数参数
|
|
117
|
+
if tool_call.get("function"):
|
|
118
|
+
func = tool_call["function"]
|
|
119
|
+
current["function"]["name"] = func.get("name") or current["function"].get("name", "")
|
|
120
|
+
if func.get("arguments") is None:
|
|
121
|
+
continue
|
|
122
|
+
current["function"]["arguments"] += func.get("arguments", "")
|
|
123
|
+
|
|
124
|
+
# 暂存当前状态
|
|
125
|
+
final_tool_calls = [v for k,v in sorted(tool_calls_buffer.items())]
|
|
126
|
+
|
|
127
|
+
except json.JSONDecodeError:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
# 流结束时处理最终工具调用
|
|
131
|
+
if final_tool_calls:
|
|
132
|
+
yield {
|
|
133
|
+
"role": "assistant",
|
|
134
|
+
"content": "",
|
|
135
|
+
"tool_calls": final_tool_calls,
|
|
136
|
+
"id": final_tool_calls[0]["id"] if final_tool_calls else ""
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
return stream_generator()
|
tina/LLM/Qwen.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from .api import BaseAPI
|
|
2
|
+
|
|
3
|
+
class Qwen(BaseAPI):
|
|
4
|
+
API_ENV_VAR_NAME = "DASHSCOPE_API_KEY" # 重写API key环境变量名称
|
|
5
|
+
BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1" # 重写base_url
|
|
6
|
+
|
|
7
|
+
def __init__(self, api_key: str = None, model: str = "qwen-plus", base_url: str = None):
|
|
8
|
+
super().__init__(api_key, model, base_url)
|
tina/LLM/__init__.py
ADDED
|
File without changes
|