jarvis-ai-assistant 0.1.209__py3-none-any.whl → 0.1.211__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.
@@ -52,12 +52,20 @@ class HumanPlatform(BasePlatform):
52
52
  session_info = f"(会话ID: {self.conversation_id})"
53
53
 
54
54
  if self.system_message and self.first_message:
55
- prompt = f"{self.system_message}\n\n{message} {session_info}\n\n请回复:"
55
+ prompt = f"{self.system_message}\n\n{message} {session_info}"
56
56
  self.first_message = False
57
57
  else:
58
- prompt = f"{message} {session_info}\n\n请回复:"
58
+ prompt = f"{message} {session_info}"
59
59
 
60
- response = get_multiline_input(prompt)
60
+ # prompt复制到剪贴板
61
+ import subprocess
62
+ try:
63
+ subprocess.run(['xsel', '-ib'], input=prompt.encode('utf-8'), check=True)
64
+ PrettyOutput.print("提示已复制到剪贴板", OutputType.INFO)
65
+ except subprocess.CalledProcessError as e:
66
+ PrettyOutput.print(f"无法复制到剪贴板: {e}", OutputType.WARNING)
67
+
68
+ response = get_multiline_input(prompt + "\n\n请回复:")
61
69
  yield response
62
70
  return None
63
71
 
@@ -159,88 +159,93 @@ class TongyiPlatform(BasePlatform):
159
159
  }
160
160
 
161
161
  try:
162
- response = while_success(
163
- lambda: http.post(url, headers=headers, json=payload, stream=True),
162
+ # 使用新的stream_post接口发送消息请求,获取流式响应
163
+ response_stream = while_success(
164
+ lambda: http.stream_post(url, headers=headers, json=payload),
164
165
  sleep_time=5,
165
166
  )
166
- if response.status_code != 200:
167
- raise Exception(f"HTTP {response.status_code}: {response.text}")
167
+
168
168
  msg_id = ""
169
169
  session_id = ""
170
170
  thinking_content = ""
171
171
  text_content = ""
172
172
  in_thinking = False
173
- for line in response.iter_lines():
174
- if not line:
175
- continue
173
+ response_data = b""
176
174
 
177
- # httpx 返回字符串,requests 返回字节,需要兼容处理
178
- if isinstance(line, bytes):
179
- line_str = line.decode("utf-8")
180
- else:
181
- line_str = str(line)
182
-
183
- if not line_str.startswith("data: "):
184
- continue
175
+ # 处理流式响应
176
+ for chunk in response_stream:
177
+ response_data += chunk
185
178
 
179
+ # 尝试解析SSE格式的数据
186
180
  try:
187
- data = json.loads(line_str[6:])
188
- # 记录消息ID和会话ID
189
- if "msgId" in data:
190
- msg_id = data["msgId"]
191
- if "sessionId" in data:
192
- session_id = data["sessionId"]
193
-
194
- if "contents" in data and len(data["contents"]) > 0:
195
- for content in data["contents"]:
196
- if content.get("contentType") == "think":
197
- if not in_thinking:
198
- yield "<think>\n\n"
199
- in_thinking = True
200
- if content.get("incremental"):
201
- tmp_content = json.loads(content.get("content"))[
202
- "content"
203
- ]
204
- thinking_content += tmp_content
205
- yield tmp_content
206
- else:
207
- tmp_content = json.loads(content.get("content"))[
208
- "content"
209
- ]
210
- if len(thinking_content) < len(tmp_content):
211
- yield tmp_content[len(thinking_content) :]
212
- thinking_content = tmp_content
213
- else:
214
- # thinking_content = "aaa</thi"
215
- # tmp_content = "aaa"
216
- # 应该yield nk>
217
- # print("\n")
218
- # print(len(thinking_content))
219
- # print(len(tmp_content))
220
- # print("--------------------------------")
221
- # print(thinking_content)
222
- # print("--------------------------------")
223
- # print(tmp_content)
224
- # print("--------------------------------")
225
- yield "\r\n</think>\n"[
226
- len(thinking_content) - len(tmp_content) :
227
- ]
228
- thinking_content = tmp_content
229
- in_thinking = False
230
- elif content.get("contentType") == "text":
231
- if in_thinking:
232
- continue
233
- if content.get("incremental"):
234
- tmp_content = content.get("content")
235
- text_content += tmp_content
236
- yield tmp_content
237
- else:
238
- tmp_content = content.get("content")
239
- if len(text_content) < len(tmp_content):
240
- yield tmp_content[len(text_content) :]
241
- text_content = tmp_content
242
-
243
- except json.JSONDecodeError:
181
+ # 查找完整的数据行
182
+ lines = response_data.decode("utf-8").split("\n")
183
+ response_data = b"" # 重置缓冲区
184
+
185
+ for line in lines:
186
+ if not line.strip():
187
+ continue
188
+
189
+ # SSE格式的行通常以"data: "开头
190
+ if line.startswith("data: "):
191
+ try:
192
+ data = json.loads(line[6:])
193
+ # 记录消息ID和会话ID
194
+ if "msgId" in data:
195
+ msg_id = data["msgId"]
196
+ if "sessionId" in data:
197
+ session_id = data["sessionId"]
198
+
199
+ if "contents" in data and len(data["contents"]) > 0:
200
+ for content in data["contents"]:
201
+ if content.get("contentType") == "think":
202
+ if not in_thinking:
203
+ yield "<think>\n\n"
204
+ in_thinking = True
205
+ if content.get("incremental"):
206
+ tmp_content = json.loads(
207
+ content.get("content")
208
+ )["content"]
209
+ thinking_content += tmp_content
210
+ yield tmp_content
211
+ else:
212
+ tmp_content = json.loads(
213
+ content.get("content")
214
+ )["content"]
215
+ if len(thinking_content) < len(
216
+ tmp_content
217
+ ):
218
+ yield tmp_content[
219
+ len(thinking_content) :
220
+ ]
221
+ thinking_content = tmp_content
222
+ else:
223
+ yield "\r\n</think>\n"[
224
+ len(thinking_content)
225
+ - len(tmp_content) :
226
+ ]
227
+ thinking_content = tmp_content
228
+ in_thinking = False
229
+ elif content.get("contentType") == "text":
230
+ if in_thinking:
231
+ continue
232
+ if content.get("incremental"):
233
+ tmp_content = content.get("content")
234
+ text_content += tmp_content
235
+ yield tmp_content
236
+ else:
237
+ tmp_content = content.get("content")
238
+ if len(text_content) < len(tmp_content):
239
+ yield tmp_content[
240
+ len(text_content) :
241
+ ]
242
+ text_content = tmp_content
243
+
244
+ except json.JSONDecodeError:
245
+ continue
246
+
247
+ except UnicodeDecodeError:
248
+ # 如果解码失败,继续累积数据
244
249
  continue
245
250
 
246
251
  self.msg_id = msg_id
@@ -468,63 +468,66 @@ class YuanbaoPlatform(BasePlatform):
468
468
  payload["displayPrompt"] = payload["prompt"]
469
469
 
470
470
  try:
471
- # 发送消息请求,获取流式响应
472
- response = while_success(
473
- lambda: http.post(
474
- url,
475
- headers=headers,
476
- json=payload,
477
- stream=True,
478
- ),
471
+ # 使用新的stream_post接口发送消息请求,获取流式响应
472
+ response_stream = while_success(
473
+ lambda: http.stream_post(url, headers=headers, json=payload),
479
474
  sleep_time=5,
480
475
  )
481
476
 
482
- # 检查响应状态
483
- if response.status_code != 200:
484
- error_msg = f"发送消息失败,状态码: {response.status_code}"
485
- if hasattr(response, "text"):
486
- error_msg += f", 响应: {response.text}"
487
- raise Exception(error_msg)
488
-
489
477
  in_thinking = False
490
-
491
- # 处理SSE流响应
492
- for line in response.iter_lines():
493
- if not line:
478
+ response_data = b""
479
+
480
+ # 处理流式响应
481
+ for chunk in response_stream:
482
+ response_data += chunk
483
+
484
+ # 尝试解析SSE格式的数据
485
+ try:
486
+ # 查找完整的数据行
487
+ lines = response_data.decode("utf-8").split("\n")
488
+ response_data = b"" # 重置缓冲区
489
+
490
+ for line in lines:
491
+ if not line.strip():
492
+ continue
493
+
494
+ # SSE格式的行通常以"data: "开头
495
+ if line.startswith("data: "):
496
+ try:
497
+ data_str = line[6:] # 移除"data: "前缀
498
+
499
+ # 检查结束标志
500
+ if data_str == "[DONE]":
501
+ self.first_chat = False
502
+ return None
503
+
504
+ data = json.loads(data_str)
505
+
506
+ # 处理文本类型的消息
507
+ if data.get("type") == "text":
508
+ if in_thinking:
509
+ yield "</think>\n"
510
+ in_thinking = False
511
+ msg = data.get("msg", "")
512
+ if msg:
513
+ yield msg
514
+
515
+ # 处理思考中的消息
516
+ elif data.get("type") == "think":
517
+ if not in_thinking:
518
+ yield "<think>\n"
519
+ in_thinking = True
520
+ think_content = data.get("content", "")
521
+ if think_content:
522
+ yield think_content
523
+
524
+ except json.JSONDecodeError:
525
+ pass
526
+
527
+ except UnicodeDecodeError:
528
+ # 如果解码失败,继续累积数据
494
529
  continue
495
530
 
496
- line_str = line.decode("utf-8")
497
-
498
- # SSE格式的行通常以"data: "开头
499
- if line_str.startswith("data: "):
500
- try:
501
- data_str = line_str[6:] # 移除"data: "前缀
502
- data = json.loads(data_str)
503
-
504
- # 处理文本类型的消息
505
- if data.get("type") == "text":
506
- if in_thinking:
507
- yield "</think>\n"
508
- in_thinking = False
509
- msg = data.get("msg", "")
510
- if msg:
511
- yield msg
512
-
513
- # 处理思考中的消息
514
- elif data.get("type") == "think":
515
- if not in_thinking:
516
- yield "<think>\n"
517
- in_thinking = True
518
- think_content = data.get("content", "")
519
- if think_content:
520
- yield think_content
521
-
522
- except json.JSONDecodeError:
523
- pass
524
-
525
- # 检测结束标志
526
- elif line_str == "data: [DONE]":
527
- return None
528
531
  self.first_chat = False
529
532
  return None
530
533
 
@@ -1,8 +1,16 @@
1
1
  # -*- coding: utf-8 -*-
2
+ import os
3
+ from pathlib import Path
2
4
  from typing import List
3
5
 
4
6
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
5
7
 
8
+ # 设置tiktoken缓存目录
9
+ script_dir = Path(__file__).parent
10
+ tiktoken_cache_dir = script_dir / "../jarvis_data/tiktoken"
11
+ os.makedirs(tiktoken_cache_dir, exist_ok=True)
12
+ os.environ["TIKTOKEN_CACHE_DIR"] = str(tiktoken_cache_dir.absolute())
13
+
6
14
 
7
15
  def get_context_token_count(text: str) -> int:
8
16
  """使用tiktoken获取文本的token数量。
@@ -9,6 +9,7 @@ Git工具模块
9
9
  - 获取最新提交的哈希值
10
10
  - 从Git差异中提取修改的行范围
11
11
  """
12
+ import datetime
12
13
  import os
13
14
  import re
14
15
  import subprocess
@@ -344,6 +345,19 @@ def check_and_update_git_repo(repo_path: str) -> bool:
344
345
  return False
345
346
 
346
347
  try:
348
+ # 检查最新提交时间是否为今天
349
+ commit_date_result = subprocess.run(
350
+ ["git", "log", "-1", "--format=%cd", "--date=short"],
351
+ cwd=git_root,
352
+ capture_output=True,
353
+ text=True,
354
+ )
355
+ if commit_date_result.returncode == 0:
356
+ commit_date = commit_date_result.stdout.strip()
357
+ today = datetime.date.today().strftime("%Y-%m-%d")
358
+ if commit_date == today:
359
+ return False
360
+
347
361
  # 检查是否有未提交的修改
348
362
  if has_uncommitted_changes():
349
363
  return False
@@ -499,9 +513,16 @@ def get_recent_commits_with_files() -> List[Dict[str, Any]]:
499
513
  失败时返回空列表
500
514
  """
501
515
  try:
502
- # 获取最近5次提交的基本信息
516
+ # 获取当前git用户名
517
+ current_author = subprocess.run(
518
+ ["git", "config", "user.name"],
519
+ capture_output=True,
520
+ text=True,
521
+ ).stdout.strip()
522
+
523
+ # 获取当前用户最近5次提交的基本信息
503
524
  result = subprocess.run(
504
- ["git", "log", "-5", "--pretty=format:%H%n%s%n%an%n%ad"],
525
+ ["git", "log", "-5", "--author=" + current_author, "--pretty=format:%H%n%s%n%an%n%ad"],
505
526
  capture_output=True,
506
527
  text=True,
507
528
  )
@@ -1,104 +1,169 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- import requests
4
- from typing import Any, Dict, Optional, Union
3
+ import httpx
4
+ from typing import Any, Dict, Optional, Union, AsyncGenerator, Generator
5
5
 
6
6
 
7
- def get_session() -> requests.Session:
7
+ def get_httpx_client() -> httpx.Client:
8
8
  """
9
- 获取一个永不超时的 requests.Session 对象
9
+ 获取一个配置好的 httpx.Client 对象
10
10
 
11
11
  返回:
12
- requests.Session 对象
12
+ httpx.Client 对象
13
13
  """
14
- session = requests.Session()
15
-
16
- # 设置默认请求头以优化连接
17
- session.headers.update(
18
- {"Connection": "keep-alive"}
14
+ client = httpx.Client(
15
+ timeout=httpx.Timeout(None), # 永不超时
16
+ headers={
17
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
18
+ },
19
19
  )
20
+ return client
21
+
22
+
23
+ def get_async_httpx_client() -> httpx.AsyncClient:
24
+ """
25
+ 获取一个配置好的 httpx.AsyncClient 对象
20
26
 
21
- return session
27
+ 返回:
28
+ httpx.AsyncClient 对象
29
+ """
30
+ client = httpx.AsyncClient(
31
+ timeout=httpx.Timeout(None), # 永不超时
32
+ headers={
33
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
34
+ },
35
+ )
36
+ return client
22
37
 
23
38
 
24
- # 增强版本的 HTTP 请求方法(带重试机制,解决连接中断问题)
39
+ # 增强版本的 HTTP 请求方法(使用 httpx 实现,带重试机制,解决连接中断问题)
25
40
  def post(
26
41
  url: str,
27
- data: Optional[Union[Dict[str, Any], str, bytes]] = None,
42
+ data: Optional[Any] = None,
28
43
  json: Optional[Dict[str, Any]] = None,
29
44
  **kwargs,
30
- ) -> requests.Response:
45
+ ) -> httpx.Response:
31
46
  """
32
- 发送增强版永不超时的 POST 请求,包含重试机制
47
+ 发送增强版永不超时的 POST 请求,使用 httpx 实现,包含重试机制
33
48
 
34
49
  参数:
35
50
  url: 请求的 URL
36
51
  data: (可选) 请求体数据 (表单数据或原始数据)
37
52
  json: (可选) JSON 数据,会自动设置 Content-Type
38
- **kwargs: 其他传递给 requests.post 的参数
53
+ **kwargs: 其他传递给 httpx.post 的参数
39
54
 
40
55
  返回:
41
- requests.Response 对象
56
+ httpx.Response 对象
42
57
 
43
58
  注意:
44
- 此方法使用增强的永不超时设置,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
59
+ 此方法使用 httpx 实现,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
45
60
  """
46
- session = get_session()
47
- return session.post(url=url, data=data, json=json, **kwargs)
61
+ client = get_httpx_client()
62
+ try:
63
+ response = client.post(url=url, data=data, json=json, **kwargs)
64
+ response.raise_for_status()
65
+ return response
66
+ finally:
67
+ client.close()
48
68
 
49
69
 
50
- def get(url: str, **kwargs) -> requests.Response:
70
+ def get(url: str, **kwargs) -> httpx.Response:
51
71
  """
52
- 发送增强版永不超时的 GET 请求,包含重试机制
72
+ 发送增强版永不超时的 GET 请求,使用 httpx 实现,包含重试机制
53
73
 
54
74
  参数:
55
75
  url: 请求的 URL
56
- **kwargs: 其他传递给 requests.get 的参数
76
+ **kwargs: 其他传递给 httpx.get 的参数
57
77
 
58
78
  返回:
59
- requests.Response 对象
79
+ httpx.Response 对象
60
80
 
61
81
  注意:
62
- 此方法使用增强的永不超时设置,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
82
+ 此方法使用 httpx 实现,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
63
83
  """
64
- session = get_session()
65
- return session.get(url=url, **kwargs)
84
+ client = get_httpx_client()
85
+ try:
86
+ response = client.get(url=url, **kwargs)
87
+ response.raise_for_status()
88
+ return response
89
+ finally:
90
+ client.close()
66
91
 
67
92
 
68
- def put(
69
- url: str, data: Optional[Union[Dict[str, Any], str, bytes]] = None, **kwargs
70
- ) -> requests.Response:
93
+ def put(url: str, data: Optional[Any] = None, **kwargs) -> httpx.Response:
71
94
  """
72
- 发送增强版永不超时的 PUT 请求,包含重试机制
95
+ 发送增强版永不超时的 PUT 请求,使用 httpx 实现,包含重试机制
73
96
 
74
97
  参数:
75
98
  url: 请求的 URL
76
99
  data: (可选) 请求体数据 (表单数据或原始数据)
77
- **kwargs: 其他传递给 requests.put 的参数
100
+ **kwargs: 其他传递给 httpx.put 的参数
78
101
 
79
102
  返回:
80
- requests.Response 对象
103
+ httpx.Response 对象
81
104
 
82
105
  注意:
83
- 此方法使用增强的永不超时设置,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
106
+ 此方法使用 httpx 实现,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
84
107
  """
85
- session = get_session()
86
- return session.put(url=url, data=data, **kwargs)
108
+ client = get_httpx_client()
109
+ try:
110
+ response = client.put(url=url, data=data, **kwargs)
111
+ response.raise_for_status()
112
+ return response
113
+ finally:
114
+ client.close()
87
115
 
88
116
 
89
- def delete(url: str, **kwargs) -> requests.Response:
117
+ def delete(url: str, **kwargs) -> httpx.Response:
90
118
  """
91
- 发送增强版永不超时的 DELETE 请求,包含重试机制
119
+ 发送增强版永不超时的 DELETE 请求,使用 httpx 实现,包含重试机制
92
120
 
93
121
  参数:
94
122
  url: 请求的 URL
95
- **kwargs: 其他传递给 requests.delete 的参数
123
+ **kwargs: 其他传递给 httpx.delete 的参数
124
+
125
+ 返回:
126
+ httpx.Response 对象
127
+
128
+ 注意:
129
+ 此方法使用 httpx 实现,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
130
+ """
131
+ client = get_httpx_client()
132
+ try:
133
+ response = client.delete(url=url, **kwargs)
134
+ response.raise_for_status()
135
+ return response
136
+ finally:
137
+ client.close()
138
+
139
+
140
+ # 同步流式POST请求方法
141
+ def stream_post(
142
+ url: str,
143
+ data: Optional[Any] = None,
144
+ json: Optional[Dict[str, Any]] = None,
145
+ **kwargs,
146
+ ) -> Generator[bytes, None, None]:
147
+ """
148
+ 发送流式 POST 请求,使用 httpx 实现,返回标准 Generator
149
+
150
+ 参数:
151
+ url: 请求的 URL
152
+ data: (可选) 请求体数据 (表单数据或原始数据)
153
+ json: (可选) JSON 数据,会自动设置 Content-Type
154
+ **kwargs: 其他传递给 httpx.post 的参数
96
155
 
97
156
  返回:
98
- requests.Response 对象
157
+ Generator[bytes, None, None]: 字节流生成器
99
158
 
100
159
  注意:
101
- 此方法使用增强的永不超时设置,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
160
+ 此方法使用 httpx 实现流式请求,适用于处理大文件下载或流式响应
102
161
  """
103
- session = get_session()
104
- return session.delete(url=url, **kwargs)
162
+ client = get_httpx_client()
163
+ try:
164
+ with client.stream("POST", url, data=data, json=json, **kwargs) as response:
165
+ response.raise_for_status()
166
+ for chunk in response.iter_bytes():
167
+ yield chunk
168
+ finally:
169
+ client.close()
@@ -66,23 +66,6 @@ def _show_welcome_message(welcome_str: str) -> None:
66
66
  PrettyOutput.print_gradient_text(jarvis_ascii_art, (0, 120, 255), (0, 255, 200))
67
67
 
68
68
 
69
- def _extract_huggingface_models() -> None:
70
- """解压HuggingFace模型"""
71
- data_dir = Path(get_data_dir())
72
- script_dir = Path(os.path.dirname(os.path.dirname(__file__)))
73
- hf_archive = script_dir / "jarvis_data" / "huggingface.tar.gz"
74
- hf_dir = data_dir / "huggingface" / "hub"
75
-
76
- if not hf_dir.exists() and hf_archive.exists():
77
- try:
78
- PrettyOutput.print("正在解压HuggingFace模型...", OutputType.INFO)
79
- with tarfile.open(hf_archive, "r:gz") as tar:
80
- tar.extractall(path=data_dir)
81
- PrettyOutput.print("HuggingFace模型解压完成", OutputType.SUCCESS)
82
- except Exception as e:
83
- PrettyOutput.print(f"解压HuggingFace模型失败: {e}", OutputType.ERROR)
84
-
85
-
86
69
  def _check_git_updates() -> bool:
87
70
  """检查并更新git仓库
88
71
 
@@ -117,10 +100,7 @@ def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
117
100
  g_config_file = config_file
118
101
  load_config()
119
102
 
120
- # 5. 解压模型
121
- _extract_huggingface_models()
122
-
123
- # 6. 检查git更新
103
+ # 5. 检查git更新
124
104
  if _check_git_updates():
125
105
  os.execv(sys.executable, [sys.executable] + sys.argv)
126
106
  sys.exit(0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.1.209
3
+ Version: 0.1.211
4
4
  Summary: Jarvis: An AI assistant that uses tools to interact with the system
5
5
  Home-page: https://github.com/skyfireitdiy/Jarvis
6
6
  Author: skyfire
@@ -55,6 +55,7 @@ Requires-Dist: openai==1.78.1
55
55
  Requires-Dist: tabulate==0.9.0
56
56
  Requires-Dist: pyte==0.8.2
57
57
  Requires-Dist: pyyaml>=6.0.2
58
+ Requires-Dist: httpx>=0.28.1
58
59
  Provides-Extra: dev
59
60
  Requires-Dist: pytest; extra == "dev"
60
61
  Requires-Dist: black; extra == "dev"