agentscope-runtime 1.0.1__py3-none-any.whl → 1.0.2__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.
- agentscope_runtime/adapters/agentscope/message.py +32 -7
- agentscope_runtime/adapters/agentscope/stream.py +121 -91
- agentscope_runtime/adapters/agno/__init__.py +0 -0
- agentscope_runtime/adapters/agno/message.py +30 -0
- agentscope_runtime/adapters/agno/stream.py +122 -0
- agentscope_runtime/adapters/langgraph/__init__.py +12 -0
- agentscope_runtime/adapters/langgraph/message.py +257 -0
- agentscope_runtime/adapters/langgraph/stream.py +205 -0
- agentscope_runtime/cli/__init__.py +7 -0
- agentscope_runtime/cli/cli.py +63 -0
- agentscope_runtime/cli/commands/__init__.py +2 -0
- agentscope_runtime/cli/commands/chat.py +815 -0
- agentscope_runtime/cli/commands/deploy.py +1062 -0
- agentscope_runtime/cli/commands/invoke.py +58 -0
- agentscope_runtime/cli/commands/list_cmd.py +103 -0
- agentscope_runtime/cli/commands/run.py +176 -0
- agentscope_runtime/cli/commands/sandbox.py +128 -0
- agentscope_runtime/cli/commands/status.py +60 -0
- agentscope_runtime/cli/commands/stop.py +185 -0
- agentscope_runtime/cli/commands/web.py +166 -0
- agentscope_runtime/cli/loaders/__init__.py +6 -0
- agentscope_runtime/cli/loaders/agent_loader.py +295 -0
- agentscope_runtime/cli/state/__init__.py +10 -0
- agentscope_runtime/cli/utils/__init__.py +18 -0
- agentscope_runtime/cli/utils/console.py +378 -0
- agentscope_runtime/cli/utils/validators.py +118 -0
- agentscope_runtime/engine/app/agent_app.py +7 -4
- agentscope_runtime/engine/deployers/__init__.py +1 -0
- agentscope_runtime/engine/deployers/agentrun_deployer.py +152 -22
- agentscope_runtime/engine/deployers/base.py +27 -2
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +158 -31
- agentscope_runtime/engine/deployers/local_deployer.py +188 -25
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +109 -18
- agentscope_runtime/engine/deployers/state/__init__.py +9 -0
- agentscope_runtime/engine/deployers/state/manager.py +388 -0
- agentscope_runtime/engine/deployers/state/schema.py +96 -0
- agentscope_runtime/engine/deployers/utils/build_cache.py +736 -0
- agentscope_runtime/engine/deployers/utils/detached_app.py +105 -30
- agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +31 -10
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +15 -8
- agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +30 -2
- agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
- agentscope_runtime/engine/deployers/utils/package.py +56 -6
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +16 -2
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +155 -5
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +107 -123
- agentscope_runtime/engine/runner.py +25 -6
- agentscope_runtime/engine/schemas/exception.py +580 -0
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
- agentscope_runtime/sandbox/utils.py +2 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/METADATA +24 -7
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/RECORD +58 -28
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/entry_points.txt +1 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""agentscope chat command - Interactive and single-shot agent execution."""
|
|
3
|
+
# pylint: disable=no-value-for-parameter, too-many-branches, protected-access
|
|
4
|
+
# pylint: disable=too-many-statements, too-many-nested-blocks
|
|
5
|
+
# pylint: disable=too-many-nested-blocks, unused-argument
|
|
6
|
+
# pylint: disable=too-many-boolean-expressions
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import signal
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Optional
|
|
16
|
+
from urllib.parse import urljoin
|
|
17
|
+
|
|
18
|
+
import click
|
|
19
|
+
import requests
|
|
20
|
+
import shortuuid
|
|
21
|
+
|
|
22
|
+
from agentscope_runtime.cli.loaders.agent_loader import (
|
|
23
|
+
UnifiedAgentLoader,
|
|
24
|
+
AgentLoadError,
|
|
25
|
+
)
|
|
26
|
+
from agentscope_runtime.cli.utils.validators import validate_agent_source
|
|
27
|
+
from agentscope_runtime.engine.deployers.state import DeploymentStateManager
|
|
28
|
+
from agentscope_runtime.cli.utils.console import (
|
|
29
|
+
echo_error,
|
|
30
|
+
echo_info,
|
|
31
|
+
echo_success,
|
|
32
|
+
echo_warning,
|
|
33
|
+
)
|
|
34
|
+
from agentscope_runtime.engine.schemas.agent_schemas import (
|
|
35
|
+
AgentRequest,
|
|
36
|
+
Message,
|
|
37
|
+
TextContent,
|
|
38
|
+
Role,
|
|
39
|
+
ContentType,
|
|
40
|
+
MessageType,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@click.command()
|
|
45
|
+
@click.argument("source", required=True)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--query",
|
|
48
|
+
"-q",
|
|
49
|
+
help="Single query to execute (non-interactive mode)",
|
|
50
|
+
default=None,
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--session-id",
|
|
54
|
+
help="Session ID for conversation continuity",
|
|
55
|
+
default=None,
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--user-id",
|
|
59
|
+
help="User ID for the session",
|
|
60
|
+
default="default_user",
|
|
61
|
+
)
|
|
62
|
+
@click.option(
|
|
63
|
+
"--verbose",
|
|
64
|
+
"-v",
|
|
65
|
+
is_flag=True,
|
|
66
|
+
help="Show verbose output including logs and reasoning",
|
|
67
|
+
default=False,
|
|
68
|
+
)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--entrypoint",
|
|
71
|
+
"-e",
|
|
72
|
+
help="Entrypoint file name for directory sources (e.g., 'app.py', "
|
|
73
|
+
"'main.py')",
|
|
74
|
+
default=None,
|
|
75
|
+
)
|
|
76
|
+
def chat(
|
|
77
|
+
source: str,
|
|
78
|
+
query: Optional[str],
|
|
79
|
+
session_id: Optional[str],
|
|
80
|
+
user_id: str,
|
|
81
|
+
verbose: bool,
|
|
82
|
+
entrypoint: Optional[str],
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
Run agent interactively or execute a single query.
|
|
86
|
+
|
|
87
|
+
SOURCE can be:
|
|
88
|
+
\b
|
|
89
|
+
- Path to Python file (e.g., agent.py)
|
|
90
|
+
- Path to project directory (e.g., ./my-agent)
|
|
91
|
+
- Deployment ID (e.g., local_20250101_120000_abc123)
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
\b
|
|
95
|
+
# Interactive mode
|
|
96
|
+
$ agentscope chat agent.py
|
|
97
|
+
|
|
98
|
+
# Single query
|
|
99
|
+
$ agentscope chat agent.py --query "Hello, how are you?"
|
|
100
|
+
|
|
101
|
+
# Use deployment
|
|
102
|
+
$ agentscope chat local_20250101_120000_abc123 --session-id my-session
|
|
103
|
+
|
|
104
|
+
# Verbose mode (show reasoning and logs)
|
|
105
|
+
$ agentscope chat agent.py --query "Hello" --verbose
|
|
106
|
+
|
|
107
|
+
# Use custom entrypoint for directory source
|
|
108
|
+
$ agentscope chat ./my-project --entrypoint custom_app.py
|
|
109
|
+
"""
|
|
110
|
+
# Configure logging and tracing based on verbose flag
|
|
111
|
+
if not verbose:
|
|
112
|
+
# Disable console tracing output (JSON logs)
|
|
113
|
+
os.environ["TRACE_ENABLE_LOG"] = "false"
|
|
114
|
+
# Set root logger to WARNING to suppress INFO logs
|
|
115
|
+
logging.getLogger().setLevel(logging.WARNING)
|
|
116
|
+
# Also suppress specific library loggers
|
|
117
|
+
logging.getLogger("agentscope").setLevel(logging.WARNING)
|
|
118
|
+
logging.getLogger("agentscope_runtime").setLevel(logging.WARNING)
|
|
119
|
+
else:
|
|
120
|
+
# Enable console tracing output for verbose mode
|
|
121
|
+
os.environ["TRACE_ENABLE_LOG"] = "true"
|
|
122
|
+
# Set root logger to DEBUG for verbose output
|
|
123
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
# Initialize state manager
|
|
127
|
+
state_manager = DeploymentStateManager()
|
|
128
|
+
# Check if source is a deployment ID
|
|
129
|
+
try:
|
|
130
|
+
source_type, normalized_source = validate_agent_source(source)
|
|
131
|
+
except Exception:
|
|
132
|
+
# If validation fails, treat as file/directory
|
|
133
|
+
source_type = None
|
|
134
|
+
|
|
135
|
+
if source_type == "deployment_id":
|
|
136
|
+
# Handle deployment ID - use HTTP request
|
|
137
|
+
deployment = state_manager.get(normalized_source)
|
|
138
|
+
if deployment is None:
|
|
139
|
+
echo_error(f"Deployment not found: {normalized_source}")
|
|
140
|
+
sys.exit(1)
|
|
141
|
+
|
|
142
|
+
# Check if platform is modelstudio
|
|
143
|
+
if deployment.platform == "modelstudio":
|
|
144
|
+
echo_warning(
|
|
145
|
+
"ModelStudio deployments do not support query the url. "
|
|
146
|
+
"Please goto the ModelStudio console URL to interact with "
|
|
147
|
+
"the deployment: {deployment.url}",
|
|
148
|
+
)
|
|
149
|
+
sys.exit(1)
|
|
150
|
+
|
|
151
|
+
# Get URL and token
|
|
152
|
+
base_url = deployment.url
|
|
153
|
+
token = deployment.token
|
|
154
|
+
|
|
155
|
+
if not base_url:
|
|
156
|
+
echo_error(
|
|
157
|
+
f"Deployment {normalized_source} does not have a URL "
|
|
158
|
+
f"configured",
|
|
159
|
+
)
|
|
160
|
+
sys.exit(1)
|
|
161
|
+
|
|
162
|
+
# Build process endpoint URL
|
|
163
|
+
# Ensure base_url ends with / for proper urljoin behavior
|
|
164
|
+
base_url_normalized = base_url.rstrip("/") + "/"
|
|
165
|
+
process_url = urljoin(base_url_normalized, "process")
|
|
166
|
+
|
|
167
|
+
# Generate session ID if not provided
|
|
168
|
+
if session_id is None:
|
|
169
|
+
session_id = (
|
|
170
|
+
f"session_{shortuuid.ShortUUID().random(length=8)}"
|
|
171
|
+
)
|
|
172
|
+
echo_info(f"Generated session ID: {session_id}")
|
|
173
|
+
|
|
174
|
+
echo_info(f"Using deployment: {normalized_source}")
|
|
175
|
+
echo_info(f"Endpoint: {process_url}")
|
|
176
|
+
|
|
177
|
+
# Run HTTP-based operations
|
|
178
|
+
if query:
|
|
179
|
+
# Single-shot mode
|
|
180
|
+
_execute_single_query_http(
|
|
181
|
+
process_url,
|
|
182
|
+
token,
|
|
183
|
+
query,
|
|
184
|
+
session_id,
|
|
185
|
+
user_id,
|
|
186
|
+
verbose,
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
# Interactive mode
|
|
190
|
+
_interactive_mode_http(
|
|
191
|
+
process_url,
|
|
192
|
+
token,
|
|
193
|
+
session_id,
|
|
194
|
+
user_id,
|
|
195
|
+
verbose,
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
# Handle file/directory source - use local agent loading
|
|
199
|
+
echo_info(f"Loading agent from: {source}")
|
|
200
|
+
loader = UnifiedAgentLoader(state_manager=state_manager)
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
agent_app = loader.load(source, entrypoint=entrypoint)
|
|
204
|
+
echo_success("Agent loaded successfully")
|
|
205
|
+
except AgentLoadError as e:
|
|
206
|
+
echo_error(f"Failed to load agent: {e}")
|
|
207
|
+
sys.exit(1)
|
|
208
|
+
|
|
209
|
+
# Generate session ID if not provided
|
|
210
|
+
if session_id is None:
|
|
211
|
+
session_id = (
|
|
212
|
+
f"session_{shortuuid.ShortUUID().random(length=8)}"
|
|
213
|
+
)
|
|
214
|
+
echo_info(f"Generated session ID: {session_id}")
|
|
215
|
+
|
|
216
|
+
# Build runner
|
|
217
|
+
agent_app._build_runner()
|
|
218
|
+
runner = agent_app._runner
|
|
219
|
+
|
|
220
|
+
# Run async operations
|
|
221
|
+
if query:
|
|
222
|
+
# Single-shot mode
|
|
223
|
+
asyncio.run(
|
|
224
|
+
_execute_single_query(
|
|
225
|
+
runner,
|
|
226
|
+
query,
|
|
227
|
+
session_id,
|
|
228
|
+
user_id,
|
|
229
|
+
verbose,
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
else:
|
|
233
|
+
# Interactive mode
|
|
234
|
+
asyncio.run(
|
|
235
|
+
_interactive_mode(runner, session_id, user_id, verbose),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
except KeyboardInterrupt:
|
|
239
|
+
echo_warning("\nInterrupted by user")
|
|
240
|
+
sys.exit(0)
|
|
241
|
+
except Exception as e:
|
|
242
|
+
echo_error(f"Unexpected error: {e}")
|
|
243
|
+
import traceback
|
|
244
|
+
|
|
245
|
+
traceback.print_exc()
|
|
246
|
+
sys.exit(1)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
async def _execute_single_query(
|
|
250
|
+
runner,
|
|
251
|
+
query: str,
|
|
252
|
+
session_id: str,
|
|
253
|
+
user_id: str,
|
|
254
|
+
verbose: bool,
|
|
255
|
+
):
|
|
256
|
+
"""Execute a single query and print response."""
|
|
257
|
+
echo_info(f"Query: {query}")
|
|
258
|
+
echo_info("Response:")
|
|
259
|
+
|
|
260
|
+
# Create Message object for AgentRequest
|
|
261
|
+
user_message = Message(
|
|
262
|
+
role=Role.USER,
|
|
263
|
+
content=[TextContent(text=query)],
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
request = AgentRequest(
|
|
267
|
+
input=[user_message],
|
|
268
|
+
session_id=session_id,
|
|
269
|
+
user_id=user_id,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
# Start runner and execute query
|
|
274
|
+
async with runner:
|
|
275
|
+
# Track reasoning message IDs to filter out their content
|
|
276
|
+
reasoning_msg_ids = set()
|
|
277
|
+
|
|
278
|
+
# Use stream_query which handles framework adaptation
|
|
279
|
+
async for event in runner.stream_query(request):
|
|
280
|
+
# Track reasoning messages
|
|
281
|
+
if (
|
|
282
|
+
hasattr(event, "object")
|
|
283
|
+
and event.object == "message"
|
|
284
|
+
and hasattr(event, "type")
|
|
285
|
+
and event.type == MessageType.REASONING
|
|
286
|
+
and hasattr(event, "id")
|
|
287
|
+
):
|
|
288
|
+
reasoning_msg_ids.add(event.id)
|
|
289
|
+
# Skip reasoning messages in non-verbose mode
|
|
290
|
+
if not verbose:
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
# Handle streaming content deltas (primary method for
|
|
294
|
+
# streaming)
|
|
295
|
+
if (
|
|
296
|
+
hasattr(event, "object")
|
|
297
|
+
and event.object == "content"
|
|
298
|
+
and hasattr(event, "delta")
|
|
299
|
+
and event.delta is True
|
|
300
|
+
and hasattr(event, "type")
|
|
301
|
+
and event.type == ContentType.TEXT
|
|
302
|
+
and hasattr(event, "text")
|
|
303
|
+
and event.text
|
|
304
|
+
):
|
|
305
|
+
# Skip content from reasoning messages in non-verbose mode
|
|
306
|
+
if (
|
|
307
|
+
not verbose
|
|
308
|
+
and hasattr(event, "msg_id")
|
|
309
|
+
and event.msg_id in reasoning_msg_ids
|
|
310
|
+
):
|
|
311
|
+
continue
|
|
312
|
+
print(event.text, end="", flush=True)
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
# Handle completed messages (fallback for non-streaming
|
|
316
|
+
# responses)
|
|
317
|
+
if hasattr(event, "output") and event.output:
|
|
318
|
+
# This is a response with messages
|
|
319
|
+
for message in event.output:
|
|
320
|
+
# Filter out reasoning messages in non-verbose mode
|
|
321
|
+
if (
|
|
322
|
+
not verbose
|
|
323
|
+
and hasattr(message, "type")
|
|
324
|
+
and message.type == "reasoning"
|
|
325
|
+
):
|
|
326
|
+
continue
|
|
327
|
+
|
|
328
|
+
if hasattr(message, "content") and message.content:
|
|
329
|
+
# Extract text from content
|
|
330
|
+
for content_item in message.content:
|
|
331
|
+
if (
|
|
332
|
+
hasattr(content_item, "text")
|
|
333
|
+
and content_item.text
|
|
334
|
+
# Only print if this is not a delta (
|
|
335
|
+
# already printed)
|
|
336
|
+
and not (
|
|
337
|
+
hasattr(content_item, "delta")
|
|
338
|
+
and content_item.delta
|
|
339
|
+
)
|
|
340
|
+
):
|
|
341
|
+
print(
|
|
342
|
+
content_item.text,
|
|
343
|
+
end="",
|
|
344
|
+
flush=True,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
print() # New line after response
|
|
348
|
+
echo_success("Query completed")
|
|
349
|
+
|
|
350
|
+
except Exception as e:
|
|
351
|
+
echo_error(f"Query failed: {e}")
|
|
352
|
+
raise
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
async def _interactive_mode(
|
|
356
|
+
runner,
|
|
357
|
+
session_id: str,
|
|
358
|
+
user_id: str,
|
|
359
|
+
verbose: bool,
|
|
360
|
+
):
|
|
361
|
+
"""Run interactive REPL mode."""
|
|
362
|
+
echo_success(
|
|
363
|
+
"Entering interactive mode. Type 'exit' or 'quit' to leave, Ctrl+C "
|
|
364
|
+
"to interrupt.",
|
|
365
|
+
)
|
|
366
|
+
echo_info(f"Session ID: {session_id}")
|
|
367
|
+
echo_info(f"User ID: {user_id}")
|
|
368
|
+
print()
|
|
369
|
+
|
|
370
|
+
# Set up signal handler for Ctrl+C during input
|
|
371
|
+
def handle_sigint(signum, frame):
|
|
372
|
+
"""Handle SIGINT (Ctrl+C) gracefully."""
|
|
373
|
+
print() # New line after ^C
|
|
374
|
+
echo_warning(
|
|
375
|
+
"Interrupted. Type 'exit' to quit or continue chatting.",
|
|
376
|
+
)
|
|
377
|
+
# Raise KeyboardInterrupt to be caught by the exception handler
|
|
378
|
+
raise KeyboardInterrupt
|
|
379
|
+
|
|
380
|
+
# Install signal handler
|
|
381
|
+
original_handler = signal.signal(signal.SIGINT, handle_sigint)
|
|
382
|
+
|
|
383
|
+
# Start runner once for the entire interactive session
|
|
384
|
+
async with runner:
|
|
385
|
+
try:
|
|
386
|
+
while True:
|
|
387
|
+
try:
|
|
388
|
+
# Read user input with error handling for encoding issues
|
|
389
|
+
try:
|
|
390
|
+
user_input = input("> ").strip()
|
|
391
|
+
except UnicodeDecodeError as e:
|
|
392
|
+
echo_error(f"Input encoding error: {e}")
|
|
393
|
+
echo_warning(
|
|
394
|
+
"Please ensure your terminal supports UTF-8 "
|
|
395
|
+
"encoding",
|
|
396
|
+
)
|
|
397
|
+
continue
|
|
398
|
+
|
|
399
|
+
if not user_input:
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
if user_input.lower() in ["exit", "quit", "q"]:
|
|
403
|
+
echo_info("Exiting interactive mode...")
|
|
404
|
+
break
|
|
405
|
+
|
|
406
|
+
# Create Message object
|
|
407
|
+
user_message = Message(
|
|
408
|
+
role=Role.USER,
|
|
409
|
+
content=[TextContent(text=user_input)],
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Create request
|
|
413
|
+
request = AgentRequest(
|
|
414
|
+
input=[user_message],
|
|
415
|
+
session_id=session_id,
|
|
416
|
+
user_id=user_id,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Execute query using stream_query
|
|
420
|
+
# Track reasoning message IDs to filter out their content
|
|
421
|
+
reasoning_msg_ids = set()
|
|
422
|
+
|
|
423
|
+
async for event in runner.stream_query(request):
|
|
424
|
+
# Track reasoning messages
|
|
425
|
+
if (
|
|
426
|
+
hasattr(event, "object")
|
|
427
|
+
and event.object == "message"
|
|
428
|
+
and hasattr(event, "type")
|
|
429
|
+
and event.type == MessageType.REASONING
|
|
430
|
+
and hasattr(event, "id")
|
|
431
|
+
):
|
|
432
|
+
reasoning_msg_ids.add(event.id)
|
|
433
|
+
# Skip reasoning messages in non-verbose mode
|
|
434
|
+
if not verbose:
|
|
435
|
+
continue
|
|
436
|
+
|
|
437
|
+
# Handle streaming content deltas (primary method
|
|
438
|
+
# for streaming)
|
|
439
|
+
if (
|
|
440
|
+
hasattr(event, "object")
|
|
441
|
+
and event.object == "content"
|
|
442
|
+
and hasattr(event, "delta")
|
|
443
|
+
and event.delta is True
|
|
444
|
+
and hasattr(event, "type")
|
|
445
|
+
and event.type == ContentType.TEXT
|
|
446
|
+
and hasattr(event, "text")
|
|
447
|
+
and event.text
|
|
448
|
+
):
|
|
449
|
+
# Skip content from reasoning messages in
|
|
450
|
+
# non-verbose mode
|
|
451
|
+
if (
|
|
452
|
+
not verbose
|
|
453
|
+
and hasattr(event, "msg_id")
|
|
454
|
+
and event.msg_id in reasoning_msg_ids
|
|
455
|
+
):
|
|
456
|
+
continue
|
|
457
|
+
print(event.text, end="", flush=True)
|
|
458
|
+
continue
|
|
459
|
+
|
|
460
|
+
# Handle completed messages (fallback for
|
|
461
|
+
# non-streaming responses)
|
|
462
|
+
if hasattr(event, "output") and event.output:
|
|
463
|
+
# This is a response with messages
|
|
464
|
+
for message in event.output:
|
|
465
|
+
# Filter out reasoning in non-verbose mode
|
|
466
|
+
if (
|
|
467
|
+
not verbose
|
|
468
|
+
and hasattr(message, "type")
|
|
469
|
+
and message.type == "reasoning"
|
|
470
|
+
):
|
|
471
|
+
continue
|
|
472
|
+
|
|
473
|
+
if (
|
|
474
|
+
hasattr(message, "content")
|
|
475
|
+
and message.content
|
|
476
|
+
):
|
|
477
|
+
# Extract text from content
|
|
478
|
+
for content_item in message.content:
|
|
479
|
+
if (
|
|
480
|
+
hasattr(content_item, "text")
|
|
481
|
+
and content_item.text
|
|
482
|
+
# Only print if this is not a
|
|
483
|
+
# delta (already printed)
|
|
484
|
+
and not (
|
|
485
|
+
hasattr(content_item, "delta")
|
|
486
|
+
and content_item.delta
|
|
487
|
+
)
|
|
488
|
+
):
|
|
489
|
+
print(
|
|
490
|
+
content_item.text,
|
|
491
|
+
end="",
|
|
492
|
+
flush=True,
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
print() # New line after response
|
|
496
|
+
|
|
497
|
+
except KeyboardInterrupt:
|
|
498
|
+
# Handled by signal handler, just continue
|
|
499
|
+
continue
|
|
500
|
+
except EOFError:
|
|
501
|
+
print()
|
|
502
|
+
echo_info("EOF received. Exiting...")
|
|
503
|
+
break
|
|
504
|
+
except Exception as e:
|
|
505
|
+
# Catch any other unexpected errors
|
|
506
|
+
echo_error(f"\nUnexpected error: {e}")
|
|
507
|
+
import traceback
|
|
508
|
+
|
|
509
|
+
if verbose:
|
|
510
|
+
traceback.print_exc()
|
|
511
|
+
continue
|
|
512
|
+
finally:
|
|
513
|
+
# Restore original signal handler
|
|
514
|
+
signal.signal(signal.SIGINT, original_handler)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _parse_sse_line(line: bytes) -> tuple[Optional[str], Optional[str]]:
|
|
518
|
+
"""Parse a single SSE line."""
|
|
519
|
+
line_str = line.decode("utf-8").strip()
|
|
520
|
+
if line_str.startswith("data: "):
|
|
521
|
+
return "data", line_str[6:]
|
|
522
|
+
elif line_str.startswith("event:"):
|
|
523
|
+
return "event", line_str[7:].strip()
|
|
524
|
+
elif line_str.startswith("id: "):
|
|
525
|
+
return "id", line_str[4:].strip()
|
|
526
|
+
elif line_str.startswith("retry:"):
|
|
527
|
+
return "retry", line_str[7:].strip()
|
|
528
|
+
return None, None
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _execute_single_query_http(
|
|
532
|
+
url: str,
|
|
533
|
+
token: Optional[str],
|
|
534
|
+
query: str,
|
|
535
|
+
session_id: str,
|
|
536
|
+
user_id: str,
|
|
537
|
+
verbose: bool,
|
|
538
|
+
):
|
|
539
|
+
"""Execute a single query via HTTP and print response."""
|
|
540
|
+
echo_info(f"Query: {query}")
|
|
541
|
+
echo_info("Response:")
|
|
542
|
+
|
|
543
|
+
# Prepare request payload
|
|
544
|
+
payload = {
|
|
545
|
+
"input": [
|
|
546
|
+
{
|
|
547
|
+
"role": "user",
|
|
548
|
+
"content": [
|
|
549
|
+
{
|
|
550
|
+
"type": "text",
|
|
551
|
+
"text": query,
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
"session_id": session_id,
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
# Prepare headers
|
|
560
|
+
headers = {
|
|
561
|
+
"Content-Type": "application/json",
|
|
562
|
+
"Accept": "text/event-stream",
|
|
563
|
+
"Cache-Control": "no-cache",
|
|
564
|
+
}
|
|
565
|
+
if token:
|
|
566
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
567
|
+
|
|
568
|
+
try:
|
|
569
|
+
# Send POST request with streaming
|
|
570
|
+
response = requests.post(
|
|
571
|
+
url,
|
|
572
|
+
json=payload,
|
|
573
|
+
headers=headers,
|
|
574
|
+
stream=True,
|
|
575
|
+
)
|
|
576
|
+
response.raise_for_status()
|
|
577
|
+
|
|
578
|
+
# Parse SSE stream
|
|
579
|
+
for line in response.iter_lines():
|
|
580
|
+
if line:
|
|
581
|
+
field, value = _parse_sse_line(line)
|
|
582
|
+
if field == "data" and value:
|
|
583
|
+
try:
|
|
584
|
+
data = json.loads(value)
|
|
585
|
+
# Handle different object types
|
|
586
|
+
obj_type = data.get("object")
|
|
587
|
+
status = data.get("status")
|
|
588
|
+
|
|
589
|
+
# Skip reasoning messages in non-verbose mode
|
|
590
|
+
if (
|
|
591
|
+
not verbose
|
|
592
|
+
and obj_type == "message"
|
|
593
|
+
and data.get("type") == "reasoning"
|
|
594
|
+
):
|
|
595
|
+
continue
|
|
596
|
+
|
|
597
|
+
# Handle content deltas (streaming text)
|
|
598
|
+
if (
|
|
599
|
+
obj_type == "content"
|
|
600
|
+
and data.get("delta") is True
|
|
601
|
+
and data.get("type") == "text"
|
|
602
|
+
and data.get("text")
|
|
603
|
+
):
|
|
604
|
+
print(data["text"], end="", flush=True)
|
|
605
|
+
|
|
606
|
+
# Handle completed messages (for non-streaming
|
|
607
|
+
# responses)
|
|
608
|
+
# Note: We mainly rely on delta content for streaming,
|
|
609
|
+
# but handle completed messages as fallback
|
|
610
|
+
if (
|
|
611
|
+
obj_type == "message"
|
|
612
|
+
and status == "completed"
|
|
613
|
+
and data.get("type") != "reasoning"
|
|
614
|
+
and data.get("content")
|
|
615
|
+
):
|
|
616
|
+
for content_item in data["content"]:
|
|
617
|
+
if (
|
|
618
|
+
isinstance(content_item, dict)
|
|
619
|
+
and content_item.get("type") == "text"
|
|
620
|
+
and content_item.get("text")
|
|
621
|
+
# Only print if this is not a delta (
|
|
622
|
+
# already printed)
|
|
623
|
+
and not content_item.get("delta")
|
|
624
|
+
):
|
|
625
|
+
print(
|
|
626
|
+
content_item["text"],
|
|
627
|
+
end="",
|
|
628
|
+
flush=True,
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
except json.JSONDecodeError:
|
|
632
|
+
# Skip invalid JSON lines
|
|
633
|
+
pass
|
|
634
|
+
|
|
635
|
+
print() # New line after response
|
|
636
|
+
echo_success("Query completed")
|
|
637
|
+
|
|
638
|
+
except requests.exceptions.RequestException as e:
|
|
639
|
+
echo_error(f"HTTP request failed: {e}")
|
|
640
|
+
raise
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def _interactive_mode_http(
|
|
644
|
+
url: str,
|
|
645
|
+
token: Optional[str],
|
|
646
|
+
session_id: str,
|
|
647
|
+
user_id: str,
|
|
648
|
+
verbose: bool,
|
|
649
|
+
):
|
|
650
|
+
"""Run interactive REPL mode via HTTP."""
|
|
651
|
+
echo_success(
|
|
652
|
+
"Entering interactive mode. Type 'exit' or 'quit' to leave, Ctrl+C "
|
|
653
|
+
"to interrupt.",
|
|
654
|
+
)
|
|
655
|
+
echo_info(f"Session ID: {session_id}")
|
|
656
|
+
echo_info(f"User ID: {user_id}")
|
|
657
|
+
print()
|
|
658
|
+
|
|
659
|
+
# Set up signal handler for Ctrl+C during input
|
|
660
|
+
def handle_sigint(signum, frame):
|
|
661
|
+
"""Handle SIGINT (Ctrl+C) gracefully."""
|
|
662
|
+
print() # New line after ^C
|
|
663
|
+
echo_warning(
|
|
664
|
+
"KeyBoardInterrupted. Type 'exit' to quit or continue chatting.",
|
|
665
|
+
)
|
|
666
|
+
# Raise KeyboardInterrupt to be caught by the exception handler
|
|
667
|
+
raise KeyboardInterrupt
|
|
668
|
+
|
|
669
|
+
# Install signal handler
|
|
670
|
+
original_handler = signal.signal(signal.SIGINT, handle_sigint)
|
|
671
|
+
|
|
672
|
+
try:
|
|
673
|
+
while True:
|
|
674
|
+
try:
|
|
675
|
+
# Read user input with error handling for encoding issues
|
|
676
|
+
try:
|
|
677
|
+
user_input = input("> ").strip()
|
|
678
|
+
except UnicodeDecodeError as e:
|
|
679
|
+
echo_error(f"Input encoding error: {e}")
|
|
680
|
+
echo_warning(
|
|
681
|
+
"Please ensure your terminal supports UTF-8 encoding",
|
|
682
|
+
)
|
|
683
|
+
continue
|
|
684
|
+
|
|
685
|
+
if not user_input:
|
|
686
|
+
continue
|
|
687
|
+
|
|
688
|
+
if user_input.lower() in ["exit", "quit", "q"]:
|
|
689
|
+
echo_info("Exiting interactive mode...")
|
|
690
|
+
break
|
|
691
|
+
|
|
692
|
+
# Prepare request payload
|
|
693
|
+
payload = {
|
|
694
|
+
"input": [
|
|
695
|
+
{
|
|
696
|
+
"role": "user",
|
|
697
|
+
"content": [
|
|
698
|
+
{
|
|
699
|
+
"type": "text",
|
|
700
|
+
"text": user_input,
|
|
701
|
+
},
|
|
702
|
+
],
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
"session_id": session_id,
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
# Prepare headers
|
|
709
|
+
headers = {
|
|
710
|
+
"Content-Type": "application/json",
|
|
711
|
+
"Accept": "text/event-stream",
|
|
712
|
+
"Cache-Control": "no-cache",
|
|
713
|
+
}
|
|
714
|
+
if token:
|
|
715
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
716
|
+
|
|
717
|
+
# Execute query via HTTP
|
|
718
|
+
try:
|
|
719
|
+
response = requests.post(
|
|
720
|
+
url,
|
|
721
|
+
json=payload,
|
|
722
|
+
headers=headers,
|
|
723
|
+
stream=True,
|
|
724
|
+
)
|
|
725
|
+
response.raise_for_status()
|
|
726
|
+
|
|
727
|
+
# Parse SSE stream
|
|
728
|
+
for line in response.iter_lines():
|
|
729
|
+
if line:
|
|
730
|
+
field, value = _parse_sse_line(line)
|
|
731
|
+
if field == "data" and value:
|
|
732
|
+
try:
|
|
733
|
+
data = json.loads(value)
|
|
734
|
+
# Handle different object types
|
|
735
|
+
obj_type = data.get("object")
|
|
736
|
+
status = data.get("status")
|
|
737
|
+
|
|
738
|
+
# Skip reasoning messages in non-verbose
|
|
739
|
+
# mode
|
|
740
|
+
if (
|
|
741
|
+
not verbose
|
|
742
|
+
and obj_type == "message"
|
|
743
|
+
and data.get("type") == "reasoning"
|
|
744
|
+
):
|
|
745
|
+
continue
|
|
746
|
+
|
|
747
|
+
# Handle content deltas (streaming text)
|
|
748
|
+
if (
|
|
749
|
+
obj_type == "content"
|
|
750
|
+
and data.get("delta") is True
|
|
751
|
+
and data.get("type") == "text"
|
|
752
|
+
and data.get("text")
|
|
753
|
+
):
|
|
754
|
+
print(data["text"], end="", flush=True)
|
|
755
|
+
|
|
756
|
+
# Handle completed messages (for
|
|
757
|
+
# non-streaming responses)
|
|
758
|
+
# Note: We mainly rely on delta content for
|
|
759
|
+
# streaming,
|
|
760
|
+
# but handle completed messages as fallback
|
|
761
|
+
if (
|
|
762
|
+
obj_type == "message"
|
|
763
|
+
and status == "completed"
|
|
764
|
+
and data.get("type") != "reasoning"
|
|
765
|
+
and data.get("content")
|
|
766
|
+
):
|
|
767
|
+
for content_item in data["content"]:
|
|
768
|
+
if (
|
|
769
|
+
isinstance(content_item, dict)
|
|
770
|
+
and content_item.get("type")
|
|
771
|
+
== "text"
|
|
772
|
+
and content_item.get("text")
|
|
773
|
+
# Only print if this is not a
|
|
774
|
+
# delta (already printed)
|
|
775
|
+
and not content_item.get(
|
|
776
|
+
"delta",
|
|
777
|
+
)
|
|
778
|
+
):
|
|
779
|
+
print(
|
|
780
|
+
content_item["text"],
|
|
781
|
+
end="",
|
|
782
|
+
flush=True,
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
except json.JSONDecodeError:
|
|
786
|
+
# Skip invalid JSON lines
|
|
787
|
+
pass
|
|
788
|
+
|
|
789
|
+
print() # New line after response
|
|
790
|
+
|
|
791
|
+
except requests.exceptions.RequestException as e:
|
|
792
|
+
echo_error(f"\nQuery failed: {e}")
|
|
793
|
+
|
|
794
|
+
except KeyboardInterrupt:
|
|
795
|
+
# Handled by signal handler, just continue
|
|
796
|
+
continue
|
|
797
|
+
except EOFError:
|
|
798
|
+
print()
|
|
799
|
+
echo_info("EOF received. Exiting...")
|
|
800
|
+
break
|
|
801
|
+
except Exception as e:
|
|
802
|
+
# Catch any other unexpected errors
|
|
803
|
+
echo_error(f"\nUnexpected error: {e}")
|
|
804
|
+
import traceback
|
|
805
|
+
|
|
806
|
+
if verbose:
|
|
807
|
+
traceback.print_exc()
|
|
808
|
+
continue
|
|
809
|
+
finally:
|
|
810
|
+
# Restore original signal handler
|
|
811
|
+
signal.signal(signal.SIGINT, original_handler)
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
if __name__ == "__main__":
|
|
815
|
+
chat()
|