jupyter-agent 2025.6.104__tar.gz → 2025.7.100__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.
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/PKG-INFO +56 -4
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/README.md +54 -3
- jupyter_agent-2025.7.100/jupyter_agent/bot_actions.py +270 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_agents/__init__.py +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/base.py +89 -45
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/master_planner.py +1 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/output_task_result.py +6 -7
- jupyter_agent-2025.7.100/jupyter_agent/bot_agents/prepare_next_cell.py +52 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_agents/request_user_supply.py +186 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_code_executor.py +3 -2
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_planner_v3.py +16 -13
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_reasoner.py +3 -2
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_structrue_reasoner.py +22 -12
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_structrue_summarier.py +22 -18
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_summarier.py +3 -2
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_verifier.py +2 -1
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_verify_summarier.py +6 -6
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_chat.py +2 -2
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_contexts.py +37 -29
- jupyter_agent-2025.7.100/jupyter_agent/bot_evaluation.py +325 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/__init__.py +0 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/base.py +42 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/dummy_flow.py +20 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/dummy_global.py +20 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/dummy_task.py +20 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/flow_global_planning.py +88 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/flow_task_executor.py +152 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_flows/__init__.py +0 -4
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_flows/base.py +120 -41
- jupyter_agent-2025.7.100/jupyter_agent/bot_flows/master_planner.py +28 -0
- jupyter_agent-2025.7.100/jupyter_agent/bot_flows/task_executor_v3.py +119 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_magics.py +119 -69
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_outputs.py +37 -43
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/utils.py +20 -31
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/PKG-INFO +56 -4
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/SOURCES.txt +14 -4
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/requires.txt +1 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/pyproject.toml +2 -1
- jupyter_agent-2025.7.100/tests/test_bot_actions.py +102 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/tests/test_bot_contexts.py +12 -12
- jupyter_agent-2025.7.100/tests/test_bot_evaluation.py +156 -0
- jupyter_agent-2025.7.100/tests/test_bot_evaluators_base.py +87 -0
- jupyter_agent-2025.7.100/tests/test_bot_flows_base.py +203 -0
- jupyter_agent-2025.6.104/jupyter_agent/bot_agents/__init__.py +0 -42
- jupyter_agent-2025.6.104/jupyter_agent/bot_agents/task_planner_v1.py +0 -158
- jupyter_agent-2025.6.104/jupyter_agent/bot_agents/task_planner_v2.py +0 -172
- jupyter_agent-2025.6.104/jupyter_agent/bot_evaluation.py +0 -206
- jupyter_agent-2025.6.104/jupyter_agent/bot_flows/master_planner.py +0 -17
- jupyter_agent-2025.6.104/jupyter_agent/bot_flows/task_executor_v1.py +0 -86
- jupyter_agent-2025.6.104/jupyter_agent/bot_flows/task_executor_v2.py +0 -84
- jupyter_agent-2025.6.104/jupyter_agent/bot_flows/task_executor_v3.py +0 -100
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/LICENSE +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/__init__.py +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_coder.py +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_debuger.py +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/dependency_links.txt +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/entry_points.txt +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/top_level.txt +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/setup.cfg +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/setup.py +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/tests/test_bot_agents_base.py +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/tests/test_bot_chat.py +0 -0
- {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/tests/test_bot_outputs.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: jupyter-agent
|
3
|
-
Version: 2025.
|
3
|
+
Version: 2025.7.100
|
4
4
|
Summary: 调用LLM实现Jupyter代码的自动生成、执行、调试等功能
|
5
5
|
Author: viewstar000
|
6
6
|
License: MIT
|
@@ -10,6 +10,7 @@ Classifier: Operating System :: OS Independent
|
|
10
10
|
Requires-Python: >=3.12
|
11
11
|
Description-Content-Type: text/markdown
|
12
12
|
License-File: LICENSE
|
13
|
+
Requires-Dist: bottle
|
13
14
|
Requires-Dist: ipynbname
|
14
15
|
Requires-Dist: ipython
|
15
16
|
Requires-Dist: jinja2
|
@@ -95,15 +96,26 @@ pip install /path/to/jupyter-agent/dist/jupyter_agent-xxxx-py3-none-any.whl
|
|
95
96
|
# 设置当前Notebook的路径,当无法自动获取时需要手工指定,以Vscode中的Notebook为例
|
96
97
|
%config BotMagics.notebook_path = globals()["__vsc_ipynb_file__"]
|
97
98
|
|
98
|
-
#
|
99
|
+
# 是否默认开启单步模式,每执行一个步骤都退出执行循环,需要用户手动执行下一个步骤,默认为False
|
100
|
+
%config BotMagics.default_step_mode = False
|
101
|
+
# 是否默认开启自动确认,若关闭自动确认,每执行一个步骤都需要用户手动确认,默认为True
|
102
|
+
%config BotMagics.default_auto_confirm = True
|
103
|
+
|
104
|
+
# 设置运行环境是否保存任务数据到Metadata,默认为False,仅在Vscode中安装jupyter-agent-extension后或在评估模式下支持
|
99
105
|
%config BotMagics.support_save_meta = True
|
106
|
+
# 设置运行环境是否设置单元格内容,默认为False,权在Vscode中安装jupyter-agent-extension后或在评估模式下支持
|
107
|
+
%config BotMagics.support_set_cell_content = True
|
100
108
|
|
101
109
|
# 设置日志级别,可选值为DEBUG、INFO、WARN、ERROR、FATAL,默认为INFO
|
102
110
|
%config BotMagics.logging_level = 'DEBUG'
|
103
111
|
|
112
|
+
# 开启自动评估功能,默认为False,调用LLM对当前结果进行打分,目前仅实现了对子任务的整体打分
|
113
|
+
%config BotMagics.enable_evaluating = True
|
114
|
+
# 开启模拟用户补充信息功能,默认为False,调用LLM模拟对Agent的提问进行补充,用于自动评估
|
115
|
+
%config BotMagics.enable_supply_mocking = True
|
116
|
+
|
104
117
|
# 设置是否显示思考过程,默认为True
|
105
118
|
%config BotMagics.display_think = True
|
106
|
-
|
107
119
|
# 设置是否显示发送给出LLM的消息和LLM的回答,默认为False
|
108
120
|
%config BotMagics.display_message = True
|
109
121
|
%config BotMagics.display_response = True
|
@@ -151,6 +163,20 @@ pip install /path/to/jupyter-agent/dist/jupyter_agent-xxxx-py3-none-any.whl
|
|
151
163
|
|
152
164
|
更详细用法可参考[示例Notebook](https://github.com/viewstar000/jupyter-agent/blob/main/examples/data_loader.ipynb)
|
153
165
|
|
166
|
+
### 评估模式
|
167
|
+
|
168
|
+
工具提供了`bot_eval`命令用于在评估模式下执行notebook。在评估模式下,工具会顺序执行所有有单元格,直到例全局目标完成。
|
169
|
+
|
170
|
+
```bash
|
171
|
+
bot_eval [-o output_eval.ipynb] [-e output_eval.jsonl] input.ipynb
|
172
|
+
```
|
173
|
+
|
174
|
+
例如
|
175
|
+
|
176
|
+
```bash
|
177
|
+
bot_eval examples/data_loader_eval.ipynb
|
178
|
+
```
|
179
|
+
|
154
180
|
## 贡献
|
155
181
|
|
156
182
|
欢迎提交 issue 或 pull request 参与贡献。
|
@@ -237,12 +263,24 @@ Advanced Configuration:
|
|
237
263
|
# Set the current notebook path, when it is not automatically obtained, it needs to be manually specified, for example, in Vscode Notebook
|
238
264
|
%config BotMagics.notebook_path = globals()["__vsc_ipynb_file__"]
|
239
265
|
|
240
|
-
#
|
266
|
+
# Whether to enable single step mode, each step will exit the execution loop, you need to manually execute the next step, the default is False
|
267
|
+
%config BotMagics.default_step_mode = False
|
268
|
+
# Whether to enable automatic confirmation, if automatic confirmation is closed, each step needs to be confirmed by the user, the default is True
|
269
|
+
%config BotMagics.default_auto_confirm = True
|
270
|
+
|
271
|
+
# Set whether to save task data to Metadata, only Vscode installed with jupyter-agent-extension or evaluation mode supports this.
|
241
272
|
%config BotMagics.support_save_meta = True
|
273
|
+
# Set whether to set cell content, only Vscode installed with jupyter-agent-extension or evaluation mode supports this.
|
274
|
+
%config BotMagics.support_set_cell_content = True
|
242
275
|
|
243
276
|
# Set the log level, available values are DEBUG、INFO、WARN、ERROR、FATAL, default is INFO
|
244
277
|
%config BotMagics.logging_level = 'DEBUG'
|
245
278
|
|
279
|
+
# Enable automatic evaluation, default is False, call LLM to evaluate the overall result of the subtask
|
280
|
+
%config BotMagics.enable_evaluating = True
|
281
|
+
# Enable the simulation of user filling in information, default is False, call LLM to simulate the question of the agent to fill in
|
282
|
+
%config BotMagics.enable_supply_mocking = True
|
283
|
+
|
246
284
|
# Set whether to display thinking process, default is True
|
247
285
|
%config BotMagics.display_think = True
|
248
286
|
|
@@ -290,6 +328,20 @@ After generating code for a subtask, the tool will call the corresponding agent
|
|
290
328
|
|
291
329
|
For more details, please refer to [example notebook](https://github.com/viewstar000/jupyter-agent/blob/main/examples/data_loader.ipynb)
|
292
330
|
|
331
|
+
### Evaluation mode
|
332
|
+
|
333
|
+
Use `bot_eval` command to evaluate the code generated by the agent in evaluation mode. The evaluation mode will execute all cells in order and stop when the global goal is completed.
|
334
|
+
|
335
|
+
```python
|
336
|
+
bot_eval [-o output_eval.ipynb] [-e output_eval.jsonl] input.ipynb
|
337
|
+
```
|
338
|
+
|
339
|
+
For example
|
340
|
+
|
341
|
+
```bash
|
342
|
+
bot_eval examples/data_loader_eval.ipynb
|
343
|
+
```
|
344
|
+
|
293
345
|
## Contributing
|
294
346
|
|
295
347
|
Welcome to submit issues or pull requests to participate in contributions.
|
@@ -72,15 +72,26 @@ pip install /path/to/jupyter-agent/dist/jupyter_agent-xxxx-py3-none-any.whl
|
|
72
72
|
# 设置当前Notebook的路径,当无法自动获取时需要手工指定,以Vscode中的Notebook为例
|
73
73
|
%config BotMagics.notebook_path = globals()["__vsc_ipynb_file__"]
|
74
74
|
|
75
|
-
#
|
75
|
+
# 是否默认开启单步模式,每执行一个步骤都退出执行循环,需要用户手动执行下一个步骤,默认为False
|
76
|
+
%config BotMagics.default_step_mode = False
|
77
|
+
# 是否默认开启自动确认,若关闭自动确认,每执行一个步骤都需要用户手动确认,默认为True
|
78
|
+
%config BotMagics.default_auto_confirm = True
|
79
|
+
|
80
|
+
# 设置运行环境是否保存任务数据到Metadata,默认为False,仅在Vscode中安装jupyter-agent-extension后或在评估模式下支持
|
76
81
|
%config BotMagics.support_save_meta = True
|
82
|
+
# 设置运行环境是否设置单元格内容,默认为False,权在Vscode中安装jupyter-agent-extension后或在评估模式下支持
|
83
|
+
%config BotMagics.support_set_cell_content = True
|
77
84
|
|
78
85
|
# 设置日志级别,可选值为DEBUG、INFO、WARN、ERROR、FATAL,默认为INFO
|
79
86
|
%config BotMagics.logging_level = 'DEBUG'
|
80
87
|
|
88
|
+
# 开启自动评估功能,默认为False,调用LLM对当前结果进行打分,目前仅实现了对子任务的整体打分
|
89
|
+
%config BotMagics.enable_evaluating = True
|
90
|
+
# 开启模拟用户补充信息功能,默认为False,调用LLM模拟对Agent的提问进行补充,用于自动评估
|
91
|
+
%config BotMagics.enable_supply_mocking = True
|
92
|
+
|
81
93
|
# 设置是否显示思考过程,默认为True
|
82
94
|
%config BotMagics.display_think = True
|
83
|
-
|
84
95
|
# 设置是否显示发送给出LLM的消息和LLM的回答,默认为False
|
85
96
|
%config BotMagics.display_message = True
|
86
97
|
%config BotMagics.display_response = True
|
@@ -128,6 +139,20 @@ pip install /path/to/jupyter-agent/dist/jupyter_agent-xxxx-py3-none-any.whl
|
|
128
139
|
|
129
140
|
更详细用法可参考[示例Notebook](https://github.com/viewstar000/jupyter-agent/blob/main/examples/data_loader.ipynb)
|
130
141
|
|
142
|
+
### 评估模式
|
143
|
+
|
144
|
+
工具提供了`bot_eval`命令用于在评估模式下执行notebook。在评估模式下,工具会顺序执行所有有单元格,直到例全局目标完成。
|
145
|
+
|
146
|
+
```bash
|
147
|
+
bot_eval [-o output_eval.ipynb] [-e output_eval.jsonl] input.ipynb
|
148
|
+
```
|
149
|
+
|
150
|
+
例如
|
151
|
+
|
152
|
+
```bash
|
153
|
+
bot_eval examples/data_loader_eval.ipynb
|
154
|
+
```
|
155
|
+
|
131
156
|
## 贡献
|
132
157
|
|
133
158
|
欢迎提交 issue 或 pull request 参与贡献。
|
@@ -214,12 +239,24 @@ Advanced Configuration:
|
|
214
239
|
# Set the current notebook path, when it is not automatically obtained, it needs to be manually specified, for example, in Vscode Notebook
|
215
240
|
%config BotMagics.notebook_path = globals()["__vsc_ipynb_file__"]
|
216
241
|
|
217
|
-
#
|
242
|
+
# Whether to enable single step mode, each step will exit the execution loop, you need to manually execute the next step, the default is False
|
243
|
+
%config BotMagics.default_step_mode = False
|
244
|
+
# Whether to enable automatic confirmation, if automatic confirmation is closed, each step needs to be confirmed by the user, the default is True
|
245
|
+
%config BotMagics.default_auto_confirm = True
|
246
|
+
|
247
|
+
# Set whether to save task data to Metadata, only Vscode installed with jupyter-agent-extension or evaluation mode supports this.
|
218
248
|
%config BotMagics.support_save_meta = True
|
249
|
+
# Set whether to set cell content, only Vscode installed with jupyter-agent-extension or evaluation mode supports this.
|
250
|
+
%config BotMagics.support_set_cell_content = True
|
219
251
|
|
220
252
|
# Set the log level, available values are DEBUG、INFO、WARN、ERROR、FATAL, default is INFO
|
221
253
|
%config BotMagics.logging_level = 'DEBUG'
|
222
254
|
|
255
|
+
# Enable automatic evaluation, default is False, call LLM to evaluate the overall result of the subtask
|
256
|
+
%config BotMagics.enable_evaluating = True
|
257
|
+
# Enable the simulation of user filling in information, default is False, call LLM to simulate the question of the agent to fill in
|
258
|
+
%config BotMagics.enable_supply_mocking = True
|
259
|
+
|
223
260
|
# Set whether to display thinking process, default is True
|
224
261
|
%config BotMagics.display_think = True
|
225
262
|
|
@@ -267,6 +304,20 @@ After generating code for a subtask, the tool will call the corresponding agent
|
|
267
304
|
|
268
305
|
For more details, please refer to [example notebook](https://github.com/viewstar000/jupyter-agent/blob/main/examples/data_loader.ipynb)
|
269
306
|
|
307
|
+
### Evaluation mode
|
308
|
+
|
309
|
+
Use `bot_eval` command to evaluate the code generated by the agent in evaluation mode. The evaluation mode will execute all cells in order and stop when the global goal is completed.
|
310
|
+
|
311
|
+
```python
|
312
|
+
bot_eval [-o output_eval.ipynb] [-e output_eval.jsonl] input.ipynb
|
313
|
+
```
|
314
|
+
|
315
|
+
For example
|
316
|
+
|
317
|
+
```bash
|
318
|
+
bot_eval examples/data_loader_eval.ipynb
|
319
|
+
```
|
320
|
+
|
270
321
|
## Contributing
|
271
322
|
|
272
323
|
Welcome to submit issues or pull requests to participate in contributions.
|
@@ -0,0 +1,270 @@
|
|
1
|
+
"""
|
2
|
+
Copyright (c) 2025 viewstar000
|
3
|
+
|
4
|
+
This software is released under the MIT License.
|
5
|
+
https://opensource.org/licenses/MIT
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
import time
|
10
|
+
import uuid
|
11
|
+
import threading
|
12
|
+
import queue
|
13
|
+
import traceback
|
14
|
+
import importlib
|
15
|
+
import socket
|
16
|
+
|
17
|
+
from enum import Enum
|
18
|
+
from typing import Optional, Dict, List, Any
|
19
|
+
from pydantic import BaseModel, Field
|
20
|
+
from wsgiref.simple_server import make_server
|
21
|
+
from bottle import default_app, get, post, request, response
|
22
|
+
from .utils import get_env_capbilities
|
23
|
+
|
24
|
+
|
25
|
+
class ActionBase(BaseModel):
|
26
|
+
timestamp: float = 0
|
27
|
+
uuid: str = ""
|
28
|
+
source: str = ""
|
29
|
+
action: str
|
30
|
+
params: Dict[str, Any] = {}
|
31
|
+
|
32
|
+
def __init__(self, **data):
|
33
|
+
super().__init__(**data)
|
34
|
+
self.timestamp = self.timestamp or time.time()
|
35
|
+
self.uuid = self.uuid or str(uuid.uuid4())
|
36
|
+
|
37
|
+
|
38
|
+
class ReplyActionBase(ActionBase):
|
39
|
+
reply_host: str = ""
|
40
|
+
reply_port: int = 0
|
41
|
+
|
42
|
+
|
43
|
+
class SetCellContentParams(BaseModel):
|
44
|
+
index: int = 1 # -1 previous, 0 current, 1 next
|
45
|
+
type: str = "code" # code/markdown
|
46
|
+
source: str = ""
|
47
|
+
tags: List[str] = []
|
48
|
+
metadata: Dict[str, Any] = {}
|
49
|
+
|
50
|
+
|
51
|
+
class ActionSetCellContent(ActionBase):
|
52
|
+
|
53
|
+
action: str = "set_cell_content"
|
54
|
+
params: SetCellContentParams = SetCellContentParams()
|
55
|
+
|
56
|
+
|
57
|
+
class ConfirmChoiceItem(BaseModel):
|
58
|
+
label: str = ""
|
59
|
+
value: str
|
60
|
+
|
61
|
+
|
62
|
+
class RequestUserConfirmParams(BaseModel):
|
63
|
+
prompt: str = ""
|
64
|
+
choices: List[ConfirmChoiceItem] = []
|
65
|
+
default: str = ""
|
66
|
+
|
67
|
+
|
68
|
+
class ActionRequestUserConfirm(ReplyActionBase):
|
69
|
+
|
70
|
+
action: str = "request_user_confirm"
|
71
|
+
params: RequestUserConfirmParams = RequestUserConfirmParams()
|
72
|
+
|
73
|
+
|
74
|
+
class ReceiveUserConfirmParams(BaseModel):
|
75
|
+
result: str = ""
|
76
|
+
|
77
|
+
|
78
|
+
class ActionReceiveUserConfirm(ActionBase):
|
79
|
+
|
80
|
+
action: str = "receive_user_confirm"
|
81
|
+
params: ReceiveUserConfirmParams = ReceiveUserConfirmParams()
|
82
|
+
|
83
|
+
|
84
|
+
class RequestUserSupplyInfo(BaseModel):
|
85
|
+
prompt: str = Field(
|
86
|
+
description="需要用户补充详细信息的Prompt",
|
87
|
+
examples=["请补充与...相关的详细的信息", "请确认...是否...", "请提供..."],
|
88
|
+
)
|
89
|
+
example: Optional[str] = Field(None, description="示例", examples=["..."])
|
90
|
+
|
91
|
+
|
92
|
+
class UserSupplyInfoReply(BaseModel):
|
93
|
+
prompt: str = Field(description="需要用户补充详细信息的Prompt", examples=["..."])
|
94
|
+
reply: str = Field(description="用户补充的详细信息", examples=["..."])
|
95
|
+
|
96
|
+
|
97
|
+
class RequestUserSupplyInfoParams(BaseModel):
|
98
|
+
title: str = ""
|
99
|
+
issues: List[RequestUserSupplyInfo] = []
|
100
|
+
|
101
|
+
|
102
|
+
class ActionRequestUserSupplyInfo(ReplyActionBase):
|
103
|
+
|
104
|
+
action: str = "request_user_supply_info"
|
105
|
+
params: RequestUserSupplyInfoParams = RequestUserSupplyInfoParams()
|
106
|
+
|
107
|
+
|
108
|
+
class ReceiveUserSupplyInfoParams(BaseModel):
|
109
|
+
replies: List[UserSupplyInfoReply] = Field(
|
110
|
+
description="完成补充确认的信息列表",
|
111
|
+
examples=[
|
112
|
+
UserSupplyInfoReply(prompt="请确认...是否...", reply="是"),
|
113
|
+
UserSupplyInfoReply(prompt="请补充...", reply="..."),
|
114
|
+
],
|
115
|
+
)
|
116
|
+
|
117
|
+
|
118
|
+
class ActionReceiveUserSupplyInfo(ActionBase):
|
119
|
+
action: str = "receive_user_supply_info"
|
120
|
+
params: ReceiveUserSupplyInfoParams = ReceiveUserSupplyInfoParams(replies=[])
|
121
|
+
|
122
|
+
|
123
|
+
def request_user_reply(prompts: list[RequestUserSupplyInfo]) -> list[UserSupplyInfoReply]:
|
124
|
+
responses = []
|
125
|
+
for prompt in prompts:
|
126
|
+
response = input(f"{prompt.prompt} (例如: {prompt.example})")
|
127
|
+
responses.append(UserSupplyInfoReply(prompt=prompt.prompt, reply=response))
|
128
|
+
return responses
|
129
|
+
|
130
|
+
|
131
|
+
def get_action_class(action_name: str) -> type[ActionBase]:
|
132
|
+
for obj in globals().values():
|
133
|
+
if isinstance(obj, type) and issubclass(obj, ActionBase):
|
134
|
+
if obj.__name__ == action_name or obj.model_fields["action"].default == action_name:
|
135
|
+
return obj
|
136
|
+
raise ValueError(f"Unknown action: {action_name}")
|
137
|
+
|
138
|
+
|
139
|
+
class ActionReply(BaseModel):
|
140
|
+
reply_timestamp: float
|
141
|
+
retrieved_timestamp: float = 0
|
142
|
+
uuid: str
|
143
|
+
source: str = ""
|
144
|
+
action: str = ""
|
145
|
+
retrieved: bool = False
|
146
|
+
reply: ActionBase
|
147
|
+
|
148
|
+
|
149
|
+
class ActionDispatcher(threading.Thread):
|
150
|
+
def __init__(self, host="127.0.0.1", port=0, app=None):
|
151
|
+
super().__init__(daemon=True)
|
152
|
+
self.action_queue = queue.Queue()
|
153
|
+
self.action_replies: dict[str, ActionReply] = {}
|
154
|
+
self.app = app or default_app()
|
155
|
+
self.host = host
|
156
|
+
self.port = port
|
157
|
+
self.server = None
|
158
|
+
if get_env_capbilities().user_confirm or get_env_capbilities().user_supply_info:
|
159
|
+
self.port = self.port or self.select_port(self.host)
|
160
|
+
self.server = make_server(self.host, self.port, self.app)
|
161
|
+
self.start()
|
162
|
+
|
163
|
+
def select_port(self, host):
|
164
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
165
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
166
|
+
sock.bind((host, 0))
|
167
|
+
port = sock.getsockname()[1]
|
168
|
+
sock.close()
|
169
|
+
return port
|
170
|
+
|
171
|
+
def run(self):
|
172
|
+
if self.server is not None:
|
173
|
+
self.server.serve_forever()
|
174
|
+
|
175
|
+
def close(self):
|
176
|
+
if self.server is not None:
|
177
|
+
self.server.shutdown()
|
178
|
+
self.server.server_close()
|
179
|
+
|
180
|
+
def __del__(self):
|
181
|
+
self.close()
|
182
|
+
|
183
|
+
def __enter__(self):
|
184
|
+
return self
|
185
|
+
|
186
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
187
|
+
self.close()
|
188
|
+
|
189
|
+
def send_action(self, action: ActionBase, need_reply: bool = False):
|
190
|
+
|
191
|
+
if need_reply:
|
192
|
+
assert isinstance(action, ReplyActionBase)
|
193
|
+
action.reply_host = self.host
|
194
|
+
action.reply_port = self.port
|
195
|
+
action.timestamp = action.timestamp or time.time()
|
196
|
+
action.uuid = action.uuid and str(uuid.uuid4())
|
197
|
+
self.action_queue.put(action.model_dump())
|
198
|
+
bot_outputs = importlib.import_module(".bot_outputs", __package__)
|
199
|
+
bot_outputs.output_action(action)
|
200
|
+
|
201
|
+
def get_action_reply(self, action: ReplyActionBase, wait: bool = True) -> Optional[ActionBase]:
|
202
|
+
|
203
|
+
while wait and action.uuid not in self.action_replies:
|
204
|
+
time.sleep(1)
|
205
|
+
if action.uuid in self.action_replies:
|
206
|
+
self.action_replies[action.uuid].retrieved = True
|
207
|
+
self.action_replies[action.uuid].retrieved_timestamp = time.time()
|
208
|
+
return self.action_replies.get(action.uuid) and self.action_replies[action.uuid].reply
|
209
|
+
|
210
|
+
|
211
|
+
_default_action_dispatcher = None
|
212
|
+
|
213
|
+
|
214
|
+
def get_action_dispatcher() -> ActionDispatcher:
|
215
|
+
global _default_action_dispatcher
|
216
|
+
|
217
|
+
if not _default_action_dispatcher:
|
218
|
+
_default_action_dispatcher = ActionDispatcher()
|
219
|
+
elif not _default_action_dispatcher.is_alive():
|
220
|
+
_default_action_dispatcher.close()
|
221
|
+
_default_action_dispatcher = ActionDispatcher()
|
222
|
+
return _default_action_dispatcher
|
223
|
+
|
224
|
+
|
225
|
+
def close_action_dispatcher():
|
226
|
+
global _default_action_dispatcher
|
227
|
+
|
228
|
+
if _default_action_dispatcher:
|
229
|
+
_default_action_dispatcher.close()
|
230
|
+
_default_action_dispatcher = None
|
231
|
+
|
232
|
+
|
233
|
+
@get("/echo")
|
234
|
+
def echo():
|
235
|
+
response.content_type = "application/json"
|
236
|
+
return json.dumps({"status": "OK"})
|
237
|
+
|
238
|
+
|
239
|
+
@post("/action_reply")
|
240
|
+
def action_reply():
|
241
|
+
try:
|
242
|
+
uuid = request.GET["uuid"] # type: ignore
|
243
|
+
action = request.GET.get("a") or request.json.get("action") # type: ignore
|
244
|
+
source = request.GET.get("s") or request.json.get("source") # type: ignore
|
245
|
+
reply = get_action_class(action)(**request.json) # type: ignore
|
246
|
+
action_reply = ActionReply(reply_timestamp=time.time(), uuid=uuid, source=source, action=action, reply=reply)
|
247
|
+
get_action_dispatcher().action_replies[action_reply.uuid] = action_reply
|
248
|
+
response.content_type = "application/json"
|
249
|
+
return json.dumps({"status": "OK"})
|
250
|
+
except Exception as e:
|
251
|
+
response.content_type = "application/json"
|
252
|
+
return json.dumps(
|
253
|
+
{"status": "ERROR", "error": f"{type(e).__name__}: {e}", "traceback": traceback.format_exc()}
|
254
|
+
)
|
255
|
+
|
256
|
+
|
257
|
+
@get("/action_fetch")
|
258
|
+
def action_fetch():
|
259
|
+
try:
|
260
|
+
action = get_action_dispatcher().action_queue.get(block=False)
|
261
|
+
response.content_type = "application/json"
|
262
|
+
return json.dumps({"status": "OK", "action": action})
|
263
|
+
except queue.Empty:
|
264
|
+
response.content_type = "application/json"
|
265
|
+
return json.dumps({"status": "EMPTY"})
|
266
|
+
except Exception as e:
|
267
|
+
response.content_type = "application/json"
|
268
|
+
return json.dumps(
|
269
|
+
{"status": "ERROR", "error": f"{type(e).__name__}: {e}", "traceback": traceback.format_exc()}
|
270
|
+
)
|
File without changes
|
@@ -7,11 +7,13 @@ https://opensource.org/licenses/MIT
|
|
7
7
|
|
8
8
|
import json
|
9
9
|
import importlib
|
10
|
+
import traceback
|
10
11
|
|
11
12
|
from typing import Tuple, Any
|
12
13
|
from enum import Enum, unique
|
14
|
+
from pydantic import BaseModel, Field
|
13
15
|
from IPython.display import Markdown
|
14
|
-
from ..bot_outputs import _C, flush_output
|
16
|
+
from ..bot_outputs import _C, _O, _W, _T, flush_output
|
15
17
|
from ..bot_chat import BotChat
|
16
18
|
from ..utils import no_indent
|
17
19
|
|
@@ -133,6 +135,7 @@ class AgentModelType(str, Enum):
|
|
133
135
|
DEFAULT = "default"
|
134
136
|
PLANNER = "planner"
|
135
137
|
CODING = "coding"
|
138
|
+
EVALUATING = "evaluating"
|
136
139
|
REASONING = "reasoning"
|
137
140
|
|
138
141
|
|
@@ -150,6 +153,9 @@ class BaseAgent:
|
|
150
153
|
def cells(self):
|
151
154
|
return self.notebook_context.cells
|
152
155
|
|
156
|
+
def __call__(self, **kwds: Any) -> Tuple[bool, Any]:
|
157
|
+
raise NotImplementedError
|
158
|
+
|
153
159
|
|
154
160
|
class BaseChatAgent(BotChat, BaseAgent):
|
155
161
|
"""基础聊天代理类"""
|
@@ -161,12 +167,13 @@ class BaseChatAgent(BotChat, BaseAgent):
|
|
161
167
|
DISPLAY_REPLY = True
|
162
168
|
COMBINE_REPLY = AgentCombineReply.MERGE
|
163
169
|
ACCEPT_EMPYT_REPLY = False
|
170
|
+
REPLY_ERROR_RETRIES = 1
|
164
171
|
MODEL_TYPE = AgentModelType.REASONING
|
165
172
|
|
166
|
-
def __init__(self, notebook_context,
|
173
|
+
def __init__(self, notebook_context, **chat_kwargs):
|
167
174
|
"""初始化基础任务代理"""
|
168
175
|
BaseAgent.__init__(self, notebook_context)
|
169
|
-
BotChat.__init__(self,
|
176
|
+
BotChat.__init__(self, **chat_kwargs)
|
170
177
|
|
171
178
|
def prepare_contexts(self, **kwargs):
|
172
179
|
contexts = {
|
@@ -185,8 +192,16 @@ class BaseChatAgent(BotChat, BaseAgent):
|
|
185
192
|
}
|
186
193
|
else:
|
187
194
|
json_example = {}
|
188
|
-
|
189
|
-
|
195
|
+
|
196
|
+
def _default(o):
|
197
|
+
if isinstance(o, BaseModel):
|
198
|
+
return o.model_dump()
|
199
|
+
if isinstance(o, Enum):
|
200
|
+
return o.value
|
201
|
+
return repr(o)
|
202
|
+
|
203
|
+
contexts["OUTPUT_JSON_SCHEMA"] = json.dumps(json_schema, indent=2, ensure_ascii=False, default=_default)
|
204
|
+
contexts["OUTPUT_JSON_EXAMPLE"] = json.dumps(json_example, indent=2, ensure_ascii=False, default=_default)
|
190
205
|
contexts.update(kwargs)
|
191
206
|
return contexts
|
192
207
|
|
@@ -220,30 +235,41 @@ class BaseChatAgent(BotChat, BaseAgent):
|
|
220
235
|
|
221
236
|
def combine_json_replies(self, replies):
|
222
237
|
json_replies = [reply for reply in replies if reply["type"] == "code" and reply["lang"] == "json"]
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
if self.
|
231
|
-
json_obj =
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
238
|
+
assert self.COMBINE_REPLY in [
|
239
|
+
AgentCombineReply.FIRST,
|
240
|
+
AgentCombineReply.LAST,
|
241
|
+
AgentCombineReply.LIST,
|
242
|
+
AgentCombineReply.MERGE,
|
243
|
+
]
|
244
|
+
try:
|
245
|
+
if self.COMBINE_REPLY == AgentCombineReply.FIRST:
|
246
|
+
json_obj = json.loads(json_replies[0]["content"])
|
247
|
+
if self.OUTPUT_JSON_SCHEMA:
|
248
|
+
json_obj = self.OUTPUT_JSON_SCHEMA(**json_obj)
|
249
|
+
return json_obj
|
250
|
+
elif self.COMBINE_REPLY == AgentCombineReply.LAST:
|
251
|
+
json_obj = json.loads(json_replies[-1]["content"])
|
252
|
+
if self.OUTPUT_JSON_SCHEMA:
|
253
|
+
json_obj = self.OUTPUT_JSON_SCHEMA(**json_obj)
|
254
|
+
return json_obj
|
255
|
+
elif self.COMBINE_REPLY == AgentCombineReply.LIST:
|
256
|
+
json_objs = [json.loads(reply["content"]) for reply in json_replies]
|
257
|
+
if self.OUTPUT_JSON_SCHEMA:
|
258
|
+
json_objs = [self.OUTPUT_JSON_SCHEMA(**json_obj) for json_obj in json_objs]
|
259
|
+
return json_objs
|
260
|
+
elif self.COMBINE_REPLY == AgentCombineReply.MERGE:
|
261
|
+
json_obj = {}
|
262
|
+
for json_reply in json_replies:
|
263
|
+
json_obj.update(json.loads(json_reply["content"]))
|
264
|
+
if self.OUTPUT_JSON_SCHEMA:
|
265
|
+
json_obj = self.OUTPUT_JSON_SCHEMA(**json_obj)
|
266
|
+
return json_obj
|
267
|
+
else:
|
268
|
+
return False
|
269
|
+
except Exception as e:
|
270
|
+
_T(f"提取JSON失败: {type(e).__name__}: {e}")
|
271
|
+
_W(traceback.format_exc())
|
272
|
+
return False
|
247
273
|
|
248
274
|
def combine_text_replies(self, replies):
|
249
275
|
text_replies = [reply for reply in replies if reply["type"] == "text"]
|
@@ -274,10 +300,22 @@ class BaseChatAgent(BotChat, BaseAgent):
|
|
274
300
|
def __call__(self, **kwargs) -> Tuple[bool, Any]:
|
275
301
|
contexts = self.prepare_contexts(**kwargs)
|
276
302
|
messages = self.create_messages(contexts)
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
303
|
+
reply_retries = 0
|
304
|
+
while reply_retries <= self.REPLY_ERROR_RETRIES:
|
305
|
+
replies = self.chat(messages.get(), display_reply=self.DISPLAY_REPLY)
|
306
|
+
reply = self.combine_replies(replies)
|
307
|
+
if reply is False:
|
308
|
+
reply_retries += 1
|
309
|
+
if reply_retries > self.REPLY_ERROR_RETRIES:
|
310
|
+
raise ValueError("Failed to get reply")
|
311
|
+
_W("Failed to get reply, retrying...")
|
312
|
+
elif not self.ACCEPT_EMPYT_REPLY and not reply:
|
313
|
+
reply_retries += 1
|
314
|
+
if reply_retries > self.REPLY_ERROR_RETRIES:
|
315
|
+
raise ValueError("Reply is empty")
|
316
|
+
_W("Reply is empty, retrying...")
|
317
|
+
else:
|
318
|
+
break
|
281
319
|
result = self.on_reply(reply)
|
282
320
|
flush_output()
|
283
321
|
if not isinstance(result, tuple):
|
@@ -300,25 +338,31 @@ class AgentFactory:
|
|
300
338
|
"model": model_name,
|
301
339
|
}
|
302
340
|
|
303
|
-
def
|
304
|
-
|
341
|
+
def get_agent_class(self, agent_class):
|
305
342
|
if isinstance(agent_class, str):
|
306
343
|
bot_agents = importlib.import_module("..bot_agents", __package__)
|
307
344
|
agent_class = getattr(bot_agents, agent_class)
|
345
|
+
assert issubclass(agent_class, BaseAgent), "Unsupported agent class: {}".format(agent_class)
|
346
|
+
return agent_class
|
308
347
|
|
348
|
+
def get_chat_kwargs(self, agent_class):
|
309
349
|
if issubclass(agent_class, BaseChatAgent):
|
310
350
|
agent_model = agent_class.MODEL_TYPE if hasattr(agent_class, "MODEL_TYPE") else AgentModelType.DEFAULT
|
311
|
-
|
312
|
-
|
313
|
-
base_url=self.models.get(agent_model, {}).get("api_url")
|
351
|
+
chat_kwargs = {
|
352
|
+
"base_url": self.models.get(agent_model, {}).get("api_url")
|
314
353
|
or self.models[AgentModelType.DEFAULT]["api_url"],
|
315
|
-
api_key
|
354
|
+
"api_key": self.models.get(agent_model, {}).get("api_key")
|
316
355
|
or self.models[AgentModelType.DEFAULT]["api_key"],
|
317
|
-
model_name
|
356
|
+
"model_name": self.models.get(agent_model, {}).get("model")
|
318
357
|
or self.models[AgentModelType.DEFAULT]["model"],
|
319
|
-
|
320
|
-
)
|
321
|
-
|
322
|
-
return agent_class(notebook_context=self.notebook_context)
|
358
|
+
}
|
359
|
+
chat_kwargs.update(self.chat_kwargs)
|
360
|
+
return chat_kwargs
|
323
361
|
else:
|
324
|
-
|
362
|
+
return {}
|
363
|
+
|
364
|
+
def __call__(self, agent_class):
|
365
|
+
|
366
|
+
agent_class = self.get_agent_class(agent_class)
|
367
|
+
chat_kwargs = self.get_chat_kwargs(agent_class)
|
368
|
+
return agent_class(self.notebook_context, **chat_kwargs)
|