python-codex 0.2.7__py3-none-any.whl → 0.3.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.
- pycodex/__init__.py +14 -14
- pycodex/agent.py +465 -499
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -510
- pycodex/compat.py +19 -5
- pycodex/context.py +222 -212
- pycodex/doctor.py +52 -48
- pycodex/events.py +857 -0
- pycodex/feishu_card.py +217 -163
- pycodex/feishu_link.py +43 -83
- pycodex/model.py +324 -253
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +76 -45
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +245 -983
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -176
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +1 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +58 -25
- pycodex/tools/close_agent_tool.py +2 -2
- pycodex/tools/code_mode_manager.py +77 -64
- pycodex/tools/exec_command_tool.py +26 -11
- pycodex/tools/exec_tool.py +4 -4
- pycodex/tools/grep_files_tool.py +12 -10
- pycodex/tools/ipython_tool.py +10 -13
- pycodex/tools/list_dir_tool.py +13 -9
- pycodex/tools/read_file_tool.py +29 -17
- pycodex/tools/request_permissions_tool.py +15 -5
- pycodex/tools/request_user_input_tool.py +13 -104
- pycodex/tools/resume_agent_tool.py +2 -2
- pycodex/tools/send_input_tool.py +11 -8
- pycodex/tools/shell_command_tool.py +7 -5
- pycodex/tools/shell_tool.py +7 -5
- pycodex/tools/spawn_agent_tool.py +7 -4
- pycodex/tools/unified_exec_manager.py +102 -69
- pycodex/tools/update_plan_tool.py +8 -5
- pycodex/tools/view_image_tool.py +7 -5
- pycodex/tools/wait_agent_tool.py +27 -4
- pycodex/tools/wait_tool.py +5 -4
- pycodex/tools/web_search_tool.py +4 -2
- pycodex/tools/write_stdin_tool.py +12 -11
- pycodex/utils/__init__.py +2 -17
- pycodex/utils/compactor.py +41 -72
- pycodex/utils/debug.py +2 -2
- pycodex/utils/dotenv.py +6 -7
- pycodex/utils/event_helpers.py +190 -0
- pycodex/utils/get_env.py +27 -70
- pycodex/{image_utils.py → utils/image_utils.py} +8 -11
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +217 -163
- pycodex/utils/truncation.py +21 -45
- python_codex-0.3.0.dist-info/METADATA +704 -0
- python_codex-0.3.0.dist-info/RECORD +90 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +36 -31
- responses_server/config.py +23 -23
- responses_server/messages_api.py +51 -53
- responses_server/payload_processors.py +25 -20
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +101 -98
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +36 -14
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +461 -375
- workspace_server/workspace.html +852 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +137 -79
- pycodex/collaboration.py +0 -20
- pycodex/interactive_session.py +0 -415
- pycodex/prompts/collaboration_default.md +0 -11
- pycodex/prompts/collaboration_plan.md +0 -128
- pycodex/utils/toolcall_visualize.py +0 -713
- pycodex/utils/visualize.py +0 -560
- python_codex-0.2.7.dist-info/METADATA +0 -455
- python_codex-0.2.7.dist-info/RECORD +0 -93
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
from copy import deepcopy
|
|
3
1
|
import json
|
|
4
2
|
import typing
|
|
3
|
+
from copy import deepcopy
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
class CustomToolAdapterError(ValueError):
|
|
@@ -82,8 +81,8 @@ It is important to remember:
|
|
|
82
81
|
"""
|
|
83
82
|
|
|
84
83
|
|
|
85
|
-
def collect_custom_tool_names(raw_tools:
|
|
86
|
-
names:
|
|
84
|
+
def collect_custom_tool_names(raw_tools: "object") -> "typing.Set[str]":
|
|
85
|
+
names: "typing.Set[str]" = set()
|
|
87
86
|
if not isinstance(raw_tools, list):
|
|
88
87
|
return names
|
|
89
88
|
for raw_tool in raw_tools:
|
|
@@ -95,7 +94,9 @@ def collect_custom_tool_names(raw_tools: 'object') -> 'typing.Set[str]':
|
|
|
95
94
|
return names
|
|
96
95
|
|
|
97
96
|
|
|
98
|
-
def build_tool_definition(
|
|
97
|
+
def build_tool_definition(
|
|
98
|
+
raw_tool: "typing.Dict[str, object]",
|
|
99
|
+
) -> "typing.Dict[str, object]":
|
|
99
100
|
name = _required_tool_name(raw_tool)
|
|
100
101
|
description = _build_description(raw_tool)
|
|
101
102
|
input_description = (
|
|
@@ -125,7 +126,7 @@ def build_tool_definition(raw_tool: 'typing.Dict[str, object]') -> 'typing.Dict[
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
|
|
128
|
-
def build_tool_call(raw_item:
|
|
129
|
+
def build_tool_call(raw_item: "typing.Dict[str, object]") -> "typing.Dict[str, object]":
|
|
129
130
|
name = _required_item_name(raw_item)
|
|
130
131
|
return {
|
|
131
132
|
"id": str(raw_item.get("call_id", "")).strip() or name,
|
|
@@ -141,7 +142,9 @@ def build_tool_call(raw_item: 'typing.Dict[str, object]') -> 'typing.Dict[str, o
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
|
|
144
|
-
def build_output_item(
|
|
145
|
+
def build_output_item(
|
|
146
|
+
tool_call: "typing.Dict[str, object]", index: "int"
|
|
147
|
+
) -> "typing.Dict[str, object]":
|
|
145
148
|
function = tool_call.get("function") or {}
|
|
146
149
|
if not isinstance(function, dict):
|
|
147
150
|
raise CustomToolAdapterError(
|
|
@@ -149,9 +152,7 @@ def build_output_item(tool_call: 'typing.Dict[str, object]', index: 'int') -> 't
|
|
|
149
152
|
)
|
|
150
153
|
name = str(function.get("name", "")).strip()
|
|
151
154
|
if not name:
|
|
152
|
-
raise CustomToolAdapterError(
|
|
153
|
-
"outcomming custom tool call is missing `name`"
|
|
154
|
-
)
|
|
155
|
+
raise CustomToolAdapterError("outcomming custom tool call is missing `name`")
|
|
155
156
|
return {
|
|
156
157
|
"type": "custom_tool_call",
|
|
157
158
|
"call_id": str(tool_call.get("id", "")).strip() or f"call_{index}",
|
|
@@ -160,7 +161,7 @@ def build_output_item(tool_call: 'typing.Dict[str, object]', index: 'int') -> 't
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
|
|
163
|
-
def extract_input_text(raw_arguments:
|
|
164
|
+
def extract_input_text(raw_arguments: "object") -> "str":
|
|
164
165
|
if isinstance(raw_arguments, dict):
|
|
165
166
|
parsed = deepcopy(raw_arguments)
|
|
166
167
|
else:
|
|
@@ -186,7 +187,7 @@ def extract_input_text(raw_arguments: 'object') -> 'str':
|
|
|
186
187
|
return str(raw_arguments or "")
|
|
187
188
|
|
|
188
189
|
|
|
189
|
-
def _build_description(raw_tool:
|
|
190
|
+
def _build_description(raw_tool: "typing.Dict[str, object]") -> "str":
|
|
190
191
|
name = _tool_name(raw_tool)
|
|
191
192
|
if name == APPLY_PATCH_NAME:
|
|
192
193
|
return APPLY_PATCH_CHAT_DESCRIPTION
|
|
@@ -200,7 +201,7 @@ def _build_description(raw_tool: 'typing.Dict[str, object]') -> 'str':
|
|
|
200
201
|
|
|
201
202
|
raw_format = raw_tool.get("format")
|
|
202
203
|
if isinstance(raw_format, dict):
|
|
203
|
-
format_lines:
|
|
204
|
+
format_lines: "typing.List[str]" = []
|
|
204
205
|
format_type = str(raw_format.get("type", "")).strip()
|
|
205
206
|
syntax = str(raw_format.get("syntax", "")).strip()
|
|
206
207
|
definition = str(raw_format.get("definition", "") or "").strip()
|
|
@@ -217,18 +218,18 @@ def _build_description(raw_tool: 'typing.Dict[str, object]') -> 'str':
|
|
|
217
218
|
return "\n\n".join(parts)
|
|
218
219
|
|
|
219
220
|
|
|
220
|
-
def _tool_name(raw_tool:
|
|
221
|
+
def _tool_name(raw_tool: "typing.Dict[str, object]") -> "str":
|
|
221
222
|
return str(raw_tool.get("name", "")).strip()
|
|
222
223
|
|
|
223
224
|
|
|
224
|
-
def _required_tool_name(raw_tool:
|
|
225
|
+
def _required_tool_name(raw_tool: "typing.Dict[str, object]") -> "str":
|
|
225
226
|
name = _tool_name(raw_tool)
|
|
226
227
|
if not name:
|
|
227
228
|
raise CustomToolAdapterError("custom tool definition is missing `name`")
|
|
228
229
|
return name
|
|
229
230
|
|
|
230
231
|
|
|
231
|
-
def _required_item_name(raw_item:
|
|
232
|
+
def _required_item_name(raw_item: "typing.Dict[str, object]") -> "str":
|
|
232
233
|
name = str(raw_item.get("name", "")).strip()
|
|
233
234
|
if not name:
|
|
234
235
|
raise CustomToolAdapterError("custom tool call is missing `name`")
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
from copy import deepcopy
|
|
3
1
|
import json
|
|
2
|
+
import typing
|
|
3
|
+
from copy import deepcopy
|
|
4
4
|
|
|
5
5
|
from pycodex.protocol import JSONValue
|
|
6
6
|
from pycodex.tools.base_tool import BaseTool, ToolContext
|
|
7
|
-
import typing
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class WebSearchTool(BaseTool):
|
|
@@ -29,10 +28,10 @@ class WebSearchTool(BaseTool):
|
|
|
29
28
|
}
|
|
30
29
|
supports_parallel = False
|
|
31
30
|
|
|
32
|
-
async def run(self, context:
|
|
31
|
+
async def run(self, context: "ToolContext", args: "JSONValue") -> "JSONValue":
|
|
33
32
|
del context
|
|
34
33
|
query, queries = extract_queries(args)
|
|
35
|
-
output_payload:
|
|
34
|
+
output_payload: "typing.Dict[str, object]" = {
|
|
36
35
|
"results": [],
|
|
37
36
|
"mock": True,
|
|
38
37
|
}
|
|
@@ -43,7 +42,7 @@ class WebSearchTool(BaseTool):
|
|
|
43
42
|
return output_payload
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
def build_tool_definition(tool:
|
|
45
|
+
def build_tool_definition(tool: "WebSearchTool") -> "typing.Dict[str, object]":
|
|
47
46
|
return {
|
|
48
47
|
"type": "function",
|
|
49
48
|
"name": tool.name,
|
|
@@ -57,13 +56,13 @@ def build_tool_definition(tool: 'WebSearchTool') -> 'typing.Dict[str, object]':
|
|
|
57
56
|
|
|
58
57
|
|
|
59
58
|
def partition_tool_calls(
|
|
60
|
-
tool:
|
|
61
|
-
tool_calls:
|
|
62
|
-
outcomming_request:
|
|
63
|
-
) ->
|
|
59
|
+
tool: "WebSearchTool",
|
|
60
|
+
tool_calls: "typing.Dict[int, typing.Dict[str, object]]",
|
|
61
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
62
|
+
) -> "typing.Tuple[typing.List[typing.Dict[str, object]], typing.Dict[int, typing.Dict[str, object]]]":
|
|
64
63
|
mock_tool_names = _collect_mock_tool_names(tool, outcomming_request)
|
|
65
|
-
mock_calls:
|
|
66
|
-
ordinary_tool_calls:
|
|
64
|
+
mock_calls: "typing.List[typing.Dict[str, object]]" = []
|
|
65
|
+
ordinary_tool_calls: "typing.Dict[int, typing.Dict[str, object]]" = {}
|
|
67
66
|
for index in sorted(tool_calls):
|
|
68
67
|
tool_call = tool_calls[index]
|
|
69
68
|
function = tool_call.get("function") or {}
|
|
@@ -78,9 +77,9 @@ def partition_tool_calls(
|
|
|
78
77
|
|
|
79
78
|
|
|
80
79
|
def hydrate_tool_call_names(
|
|
81
|
-
tool_calls:
|
|
82
|
-
outcomming_request:
|
|
83
|
-
) ->
|
|
80
|
+
tool_calls: "typing.Dict[int, typing.Dict[str, object]]",
|
|
81
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
82
|
+
) -> "None":
|
|
84
83
|
raw_tools = outcomming_request.get("tools") or []
|
|
85
84
|
if not isinstance(raw_tools, list):
|
|
86
85
|
return
|
|
@@ -104,15 +103,15 @@ def hydrate_tool_call_names(
|
|
|
104
103
|
|
|
105
104
|
|
|
106
105
|
def build_output_items(
|
|
107
|
-
mock_search_calls:
|
|
108
|
-
) ->
|
|
109
|
-
items:
|
|
106
|
+
mock_search_calls: "typing.List[typing.Dict[str, object]]",
|
|
107
|
+
) -> "typing.List[typing.Dict[str, object]]":
|
|
108
|
+
items: "typing.List[typing.Dict[str, object]]" = []
|
|
110
109
|
for tool_call in mock_search_calls:
|
|
111
110
|
function = tool_call.get("function") or {}
|
|
112
111
|
if not isinstance(function, dict):
|
|
113
112
|
continue
|
|
114
113
|
query, queries = extract_queries(function.get("arguments"))
|
|
115
|
-
action:
|
|
114
|
+
action: "typing.Dict[str, object]" = {"type": "search"}
|
|
116
115
|
if query:
|
|
117
116
|
action["query"] = query
|
|
118
117
|
if queries:
|
|
@@ -128,17 +127,17 @@ def build_output_items(
|
|
|
128
127
|
|
|
129
128
|
|
|
130
129
|
def build_followup_request(
|
|
131
|
-
tool:
|
|
132
|
-
outcomming_request:
|
|
133
|
-
mock_search_calls:
|
|
134
|
-
reasoning_text:
|
|
135
|
-
) ->
|
|
130
|
+
tool: "WebSearchTool",
|
|
131
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
132
|
+
mock_search_calls: "typing.List[typing.Dict[str, object]]",
|
|
133
|
+
reasoning_text: "typing.Union[str, None]" = None,
|
|
134
|
+
) -> "typing.Dict[str, object]":
|
|
136
135
|
followup_request = deepcopy(outcomming_request)
|
|
137
136
|
messages = followup_request.get("messages") or []
|
|
138
137
|
if not isinstance(messages, list):
|
|
139
138
|
raise ValueError("outcomming request messages must be a list")
|
|
140
139
|
|
|
141
|
-
assistant_tool_calls:
|
|
140
|
+
assistant_tool_calls: "typing.List[typing.Dict[str, object]]" = []
|
|
142
141
|
for tool_call in mock_search_calls:
|
|
143
142
|
function = tool_call.get("function") or {}
|
|
144
143
|
if not isinstance(function, dict):
|
|
@@ -154,7 +153,7 @@ def build_followup_request(
|
|
|
154
153
|
}
|
|
155
154
|
)
|
|
156
155
|
if assistant_tool_calls:
|
|
157
|
-
assistant_message:
|
|
156
|
+
assistant_message: "typing.Dict[str, object]" = {
|
|
158
157
|
"role": "assistant",
|
|
159
158
|
"tool_calls": assistant_tool_calls,
|
|
160
159
|
}
|
|
@@ -163,7 +162,9 @@ def build_followup_request(
|
|
|
163
162
|
messages.append(assistant_message)
|
|
164
163
|
|
|
165
164
|
for tool_call in mock_search_calls:
|
|
166
|
-
tool_output = _build_mock_output(
|
|
165
|
+
tool_output = _build_mock_output(
|
|
166
|
+
(tool_call.get("function") or {}).get("arguments")
|
|
167
|
+
)
|
|
167
168
|
messages.append(
|
|
168
169
|
{
|
|
169
170
|
"role": "tool",
|
|
@@ -187,7 +188,9 @@ def build_followup_request(
|
|
|
187
188
|
return followup_request
|
|
188
189
|
|
|
189
190
|
|
|
190
|
-
def extract_queries(
|
|
191
|
+
def extract_queries(
|
|
192
|
+
raw_arguments: "JSONValue",
|
|
193
|
+
) -> "typing.Tuple[str, typing.List[str]]":
|
|
191
194
|
if isinstance(raw_arguments, dict):
|
|
192
195
|
parsed = raw_arguments
|
|
193
196
|
else:
|
|
@@ -211,7 +214,7 @@ def extract_queries(raw_arguments: 'JSONValue') -> 'typing.Tuple[str, typing.Lis
|
|
|
211
214
|
|
|
212
215
|
query = str(parsed.get("query", "")).strip()
|
|
213
216
|
queries_value = parsed.get("queries") or []
|
|
214
|
-
queries:
|
|
217
|
+
queries: "typing.List[str]" = []
|
|
215
218
|
if isinstance(queries_value, list):
|
|
216
219
|
for value in queries_value:
|
|
217
220
|
normalized = str(value).strip()
|
|
@@ -224,7 +227,7 @@ def extract_queries(raw_arguments: 'JSONValue') -> 'typing.Tuple[str, typing.Lis
|
|
|
224
227
|
return query, queries
|
|
225
228
|
|
|
226
229
|
|
|
227
|
-
def is_mock_tool(tool:
|
|
230
|
+
def is_mock_tool(tool: "WebSearchTool", raw_tool: "object") -> "bool":
|
|
228
231
|
if not isinstance(raw_tool, dict) or raw_tool.get("type") != "function":
|
|
229
232
|
return False
|
|
230
233
|
function = raw_tool.get("function") or {}
|
|
@@ -237,10 +240,10 @@ def is_mock_tool(tool: 'WebSearchTool', raw_tool: 'object') -> 'bool':
|
|
|
237
240
|
|
|
238
241
|
|
|
239
242
|
def _collect_mock_tool_names(
|
|
240
|
-
tool:
|
|
241
|
-
outcomming_request:
|
|
242
|
-
) ->
|
|
243
|
-
names:
|
|
243
|
+
tool: "WebSearchTool",
|
|
244
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
245
|
+
) -> "typing.Set[str]":
|
|
246
|
+
names: "typing.Set[str]" = set()
|
|
244
247
|
raw_tools = outcomming_request.get("tools") or []
|
|
245
248
|
if not isinstance(raw_tools, list):
|
|
246
249
|
return names
|
|
@@ -250,9 +253,9 @@ def _collect_mock_tool_names(
|
|
|
250
253
|
return names
|
|
251
254
|
|
|
252
255
|
|
|
253
|
-
def _build_mock_output(raw_arguments:
|
|
256
|
+
def _build_mock_output(raw_arguments: "JSONValue") -> "typing.Dict[str, object]":
|
|
254
257
|
query, queries = extract_queries(raw_arguments)
|
|
255
|
-
output_payload:
|
|
258
|
+
output_payload: "typing.Dict[str, object]" = {
|
|
256
259
|
"results": [],
|
|
257
260
|
"mock": True,
|
|
258
261
|
}
|
|
@@ -4,19 +4,20 @@ import sys
|
|
|
4
4
|
import threading
|
|
5
5
|
import time
|
|
6
6
|
import typing
|
|
7
|
+
import urllib.error
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class TrajectoryDumpWriter:
|
|
10
11
|
ENV_VAR = "PYCODEX_DUMP"
|
|
11
12
|
|
|
12
|
-
def __init__(self, root_dir:
|
|
13
|
+
def __init__(self, root_dir: "str") -> "None":
|
|
13
14
|
self._root_dir = os.path.abspath(root_dir)
|
|
14
15
|
self._dump_path = os.path.join(self._root_dir, "dump.jsonl")
|
|
15
16
|
self._lock = threading.Lock()
|
|
16
17
|
os.makedirs(self._root_dir, exist_ok=True)
|
|
17
18
|
|
|
18
19
|
@classmethod
|
|
19
|
-
def from_env(cls) ->
|
|
20
|
+
def from_env(cls) -> "typing.Union[TrajectoryDumpWriter, None]":
|
|
20
21
|
root_dir = str(os.environ.get(cls.ENV_VAR, "") or "").strip()
|
|
21
22
|
if not root_dir:
|
|
22
23
|
return None
|
|
@@ -25,7 +26,7 @@ class TrajectoryDumpWriter:
|
|
|
25
26
|
def wrap_stream(
|
|
26
27
|
self,
|
|
27
28
|
outcomming_stream,
|
|
28
|
-
outcomming_request:
|
|
29
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
29
30
|
):
|
|
30
31
|
def iter_stream():
|
|
31
32
|
capture = _TrajectoryCapture(
|
|
@@ -37,12 +38,22 @@ class TrajectoryDumpWriter:
|
|
|
37
38
|
for chunk in outcomming_stream:
|
|
38
39
|
capture.observe_chunk(chunk)
|
|
39
40
|
yield chunk
|
|
41
|
+
except Exception as error:
|
|
42
|
+
capture.stream_error_type = type(error).__name__
|
|
43
|
+
cause = error.__cause__
|
|
44
|
+
if cause is not None:
|
|
45
|
+
capture.stream_error_cause_type = type(cause).__name__
|
|
46
|
+
if isinstance(cause, urllib.error.HTTPError):
|
|
47
|
+
capture.stream_error_http_status = cause.code
|
|
48
|
+
raise
|
|
49
|
+
else:
|
|
50
|
+
capture.stream_completed = True
|
|
40
51
|
finally:
|
|
41
52
|
capture.flush()
|
|
42
53
|
|
|
43
54
|
return iter_stream()
|
|
44
55
|
|
|
45
|
-
def _append_record(self, record:
|
|
56
|
+
def _append_record(self, record: "typing.Dict[str, object]") -> "None":
|
|
46
57
|
serialized = json.dumps(record, ensure_ascii=False)
|
|
47
58
|
with self._lock:
|
|
48
59
|
os.makedirs(self._root_dir, exist_ok=True)
|
|
@@ -54,19 +65,24 @@ class TrajectoryDumpWriter:
|
|
|
54
65
|
class _TrajectoryCapture:
|
|
55
66
|
def __init__(
|
|
56
67
|
self,
|
|
57
|
-
writer:
|
|
58
|
-
send_timestamp:
|
|
59
|
-
outcomming_request:
|
|
60
|
-
) ->
|
|
68
|
+
writer: "TrajectoryDumpWriter",
|
|
69
|
+
send_timestamp: "float",
|
|
70
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
71
|
+
) -> "None":
|
|
61
72
|
self._writer = writer
|
|
62
73
|
self._send_timestamp = float(send_timestamp)
|
|
63
74
|
self._outcomming_request = json.loads(json.dumps(outcomming_request))
|
|
64
75
|
self._prefill_token_ids = None
|
|
65
76
|
self._decode_token_ids = []
|
|
66
|
-
self._usage:
|
|
77
|
+
self._usage: "typing.Dict[str, object]" = {}
|
|
78
|
+
self._finish_reason = None
|
|
67
79
|
self._closed = False
|
|
80
|
+
self.stream_completed = False
|
|
81
|
+
self.stream_error_type = None
|
|
82
|
+
self.stream_error_cause_type = None
|
|
83
|
+
self.stream_error_http_status = None
|
|
68
84
|
|
|
69
|
-
def observe_chunk(self, payload:
|
|
85
|
+
def observe_chunk(self, payload: "object") -> "None":
|
|
70
86
|
if not isinstance(payload, dict):
|
|
71
87
|
return
|
|
72
88
|
usage = payload.get("usage")
|
|
@@ -83,34 +99,40 @@ class _TrajectoryCapture:
|
|
|
83
99
|
for raw_choice in choices:
|
|
84
100
|
if not isinstance(raw_choice, dict):
|
|
85
101
|
continue
|
|
102
|
+
if raw_choice.get("finish_reason") is not None:
|
|
103
|
+
self._finish_reason = raw_choice["finish_reason"]
|
|
86
104
|
normalized_decode = _normalize_token_ids(raw_choice.get("token_ids"))
|
|
87
105
|
if normalized_decode:
|
|
88
106
|
self._decode_token_ids.extend(normalized_decode)
|
|
89
107
|
|
|
90
|
-
def flush(self) ->
|
|
108
|
+
def flush(self) -> "None":
|
|
91
109
|
if self._closed:
|
|
92
110
|
return
|
|
93
111
|
self._closed = True
|
|
94
112
|
record = {
|
|
95
113
|
"request": self._outcomming_request,
|
|
96
114
|
"usage": self._usage,
|
|
115
|
+
"finish_reason": self._finish_reason,
|
|
97
116
|
"tokens": {
|
|
98
117
|
"prefill": list(self._prefill_token_ids or []),
|
|
99
118
|
"decode": list(self._decode_token_ids),
|
|
100
119
|
},
|
|
101
120
|
"send_timestamp": self._send_timestamp,
|
|
121
|
+
"stream_completed": self.stream_completed,
|
|
122
|
+
"stream_error_type": self.stream_error_type,
|
|
123
|
+
"stream_error_cause_type": self.stream_error_cause_type,
|
|
124
|
+
"stream_error_http_status": self.stream_error_http_status,
|
|
102
125
|
}
|
|
103
126
|
try:
|
|
104
127
|
self._writer._append_record(record)
|
|
105
128
|
except Exception as exc:
|
|
106
129
|
print(
|
|
107
|
-
"responses_server: failed to append PYCODEX_DUMP trajectory: %s"
|
|
108
|
-
% exc,
|
|
130
|
+
"responses_server: failed to append PYCODEX_DUMP trajectory: %s" % exc,
|
|
109
131
|
file=sys.stderr,
|
|
110
132
|
)
|
|
111
133
|
|
|
112
134
|
|
|
113
|
-
def _normalize_token_ids(raw_value:
|
|
135
|
+
def _normalize_token_ids(raw_value: "object") -> "typing.Union[typing.List[int], None]":
|
|
114
136
|
if not isinstance(raw_value, list):
|
|
115
137
|
return None
|
|
116
138
|
token_ids = []
|