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.
Files changed (63) hide show
  1. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/PKG-INFO +56 -4
  2. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/README.md +54 -3
  3. jupyter_agent-2025.7.100/jupyter_agent/bot_actions.py +270 -0
  4. jupyter_agent-2025.7.100/jupyter_agent/bot_agents/__init__.py +0 -0
  5. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/base.py +89 -45
  6. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/master_planner.py +1 -0
  7. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/output_task_result.py +6 -7
  8. jupyter_agent-2025.7.100/jupyter_agent/bot_agents/prepare_next_cell.py +52 -0
  9. jupyter_agent-2025.7.100/jupyter_agent/bot_agents/request_user_supply.py +186 -0
  10. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_code_executor.py +3 -2
  11. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_planner_v3.py +16 -13
  12. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_reasoner.py +3 -2
  13. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_structrue_reasoner.py +22 -12
  14. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_structrue_summarier.py +22 -18
  15. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_summarier.py +3 -2
  16. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_verifier.py +2 -1
  17. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_verify_summarier.py +6 -6
  18. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_chat.py +2 -2
  19. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_contexts.py +37 -29
  20. jupyter_agent-2025.7.100/jupyter_agent/bot_evaluation.py +325 -0
  21. jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/__init__.py +0 -0
  22. jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/base.py +42 -0
  23. jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/dummy_flow.py +20 -0
  24. jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/dummy_global.py +20 -0
  25. jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/dummy_task.py +20 -0
  26. jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/flow_global_planning.py +88 -0
  27. jupyter_agent-2025.7.100/jupyter_agent/bot_evaluators/flow_task_executor.py +152 -0
  28. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_flows/__init__.py +0 -4
  29. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_flows/base.py +120 -41
  30. jupyter_agent-2025.7.100/jupyter_agent/bot_flows/master_planner.py +28 -0
  31. jupyter_agent-2025.7.100/jupyter_agent/bot_flows/task_executor_v3.py +119 -0
  32. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_magics.py +119 -69
  33. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_outputs.py +37 -43
  34. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/utils.py +20 -31
  35. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/PKG-INFO +56 -4
  36. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/SOURCES.txt +14 -4
  37. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/requires.txt +1 -0
  38. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/pyproject.toml +2 -1
  39. jupyter_agent-2025.7.100/tests/test_bot_actions.py +102 -0
  40. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/tests/test_bot_contexts.py +12 -12
  41. jupyter_agent-2025.7.100/tests/test_bot_evaluation.py +156 -0
  42. jupyter_agent-2025.7.100/tests/test_bot_evaluators_base.py +87 -0
  43. jupyter_agent-2025.7.100/tests/test_bot_flows_base.py +203 -0
  44. jupyter_agent-2025.6.104/jupyter_agent/bot_agents/__init__.py +0 -42
  45. jupyter_agent-2025.6.104/jupyter_agent/bot_agents/task_planner_v1.py +0 -158
  46. jupyter_agent-2025.6.104/jupyter_agent/bot_agents/task_planner_v2.py +0 -172
  47. jupyter_agent-2025.6.104/jupyter_agent/bot_evaluation.py +0 -206
  48. jupyter_agent-2025.6.104/jupyter_agent/bot_flows/master_planner.py +0 -17
  49. jupyter_agent-2025.6.104/jupyter_agent/bot_flows/task_executor_v1.py +0 -86
  50. jupyter_agent-2025.6.104/jupyter_agent/bot_flows/task_executor_v2.py +0 -84
  51. jupyter_agent-2025.6.104/jupyter_agent/bot_flows/task_executor_v3.py +0 -100
  52. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/LICENSE +0 -0
  53. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/__init__.py +0 -0
  54. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_coder.py +0 -0
  55. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent/bot_agents/task_debuger.py +0 -0
  56. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/dependency_links.txt +0 -0
  57. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/entry_points.txt +0 -0
  58. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/jupyter_agent.egg-info/top_level.txt +0 -0
  59. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/setup.cfg +0 -0
  60. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/setup.py +0 -0
  61. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/tests/test_bot_agents_base.py +0 -0
  62. {jupyter_agent-2025.6.104 → jupyter_agent-2025.7.100}/tests/test_bot_chat.py +0 -0
  63. {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.6.104
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
- # 设置是否保存任务数据到Metadata,只有Vscode中安装了jupyter-agent-extension后才支持
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
- # Set whether to save task data to Metadata, only Vscode installed with jupyter-agent-extension supports
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
- # 设置是否保存任务数据到Metadata,只有Vscode中安装了jupyter-agent-extension后才支持
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
- # Set whether to save task data to Metadata, only Vscode installed with jupyter-agent-extension supports
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
+ )
@@ -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, base_url, api_key, model_name, **chat_kwargs):
173
+ def __init__(self, notebook_context, **chat_kwargs):
167
174
  """初始化基础任务代理"""
168
175
  BaseAgent.__init__(self, notebook_context)
169
- BotChat.__init__(self, base_url, api_key, model_name, **chat_kwargs)
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
- contexts["OUTPUT_JSON_SCHEMA"] = json.dumps(json_schema, indent=2, ensure_ascii=False)
189
- contexts["OUTPUT_JSON_EXAMPLE"] = json.dumps(json_example, indent=2, ensure_ascii=False)
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
- if self.COMBINE_REPLY == AgentCombineReply.FIRST:
224
- json_obj = json.loads(json_replies[0]["content"])
225
- if self.OUTPUT_JSON_SCHEMA:
226
- json_obj = self.OUTPUT_JSON_SCHEMA(**json_obj)
227
- return json_obj
228
- elif self.COMBINE_REPLY == AgentCombineReply.LAST:
229
- json_obj = json.loads(json_replies[-1]["content"])
230
- if self.OUTPUT_JSON_SCHEMA:
231
- json_obj = self.OUTPUT_JSON_SCHEMA(**json_obj)
232
- return json_obj
233
- elif self.COMBINE_REPLY == AgentCombineReply.LIST:
234
- json_objs = [json.loads(reply["content"]) for reply in json_replies]
235
- if self.OUTPUT_JSON_SCHEMA:
236
- json_objs = [self.OUTPUT_JSON_SCHEMA(**json_obj) for json_obj in json_objs]
237
- return json_objs
238
- elif self.COMBINE_REPLY == AgentCombineReply.MERGE:
239
- json_obj = {}
240
- for json_reply in json_replies:
241
- json_obj.update(json.loads(json_reply["content"]))
242
- if self.OUTPUT_JSON_SCHEMA:
243
- json_obj = self.OUTPUT_JSON_SCHEMA(**json_obj)
244
- return json_obj
245
- else:
246
- raise ValueError("Unsupported combine_reply: {} for json output".format(self.COMBINE_REPLY))
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
- replies = self.chat(messages.get(), display_reply=self.DISPLAY_REPLY)
278
- reply = self.combine_replies(replies)
279
- if not self.ACCEPT_EMPYT_REPLY and not reply:
280
- raise ValueError("Reply is empty")
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 __call__(self, agent_class):
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
- return agent_class(
312
- notebook_context=self.notebook_context,
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=self.models.get(agent_model, {}).get("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=self.models.get(agent_model, {}).get("model")
356
+ "model_name": self.models.get(agent_model, {}).get("model")
318
357
  or self.models[AgentModelType.DEFAULT]["model"],
319
- **self.chat_kwargs,
320
- )
321
- elif issubclass(agent_class, BaseAgent):
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
- raise ValueError("Unsupported agent class: {}".format(agent_class))
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)