agentscope-runtime 0.1.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.
Files changed (131) hide show
  1. agentscope_runtime/__init__.py +4 -0
  2. agentscope_runtime/engine/__init__.py +9 -0
  3. agentscope_runtime/engine/agents/__init__.py +2 -0
  4. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
  5. agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
  6. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
  7. agentscope_runtime/engine/agents/agno_agent.py +220 -0
  8. agentscope_runtime/engine/agents/base_agent.py +29 -0
  9. agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
  10. agentscope_runtime/engine/agents/llm_agent.py +51 -0
  11. agentscope_runtime/engine/deployers/__init__.py +3 -0
  12. agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
  13. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
  14. agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
  15. agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
  16. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
  17. agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
  18. agentscope_runtime/engine/deployers/base.py +17 -0
  19. agentscope_runtime/engine/deployers/local_deployer.py +586 -0
  20. agentscope_runtime/engine/helpers/helper.py +127 -0
  21. agentscope_runtime/engine/llms/__init__.py +3 -0
  22. agentscope_runtime/engine/llms/base_llm.py +60 -0
  23. agentscope_runtime/engine/llms/qwen_llm.py +47 -0
  24. agentscope_runtime/engine/misc/__init__.py +0 -0
  25. agentscope_runtime/engine/runner.py +186 -0
  26. agentscope_runtime/engine/schemas/__init__.py +0 -0
  27. agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
  28. agentscope_runtime/engine/schemas/context.py +54 -0
  29. agentscope_runtime/engine/services/__init__.py +9 -0
  30. agentscope_runtime/engine/services/base.py +77 -0
  31. agentscope_runtime/engine/services/context_manager.py +129 -0
  32. agentscope_runtime/engine/services/environment_manager.py +50 -0
  33. agentscope_runtime/engine/services/manager.py +174 -0
  34. agentscope_runtime/engine/services/memory_service.py +270 -0
  35. agentscope_runtime/engine/services/sandbox_service.py +198 -0
  36. agentscope_runtime/engine/services/session_history_service.py +256 -0
  37. agentscope_runtime/engine/tracing/__init__.py +40 -0
  38. agentscope_runtime/engine/tracing/base.py +309 -0
  39. agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
  40. agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
  41. agentscope_runtime/engine/tracing/wrapper.py +321 -0
  42. agentscope_runtime/sandbox/__init__.py +14 -0
  43. agentscope_runtime/sandbox/box/__init__.py +0 -0
  44. agentscope_runtime/sandbox/box/base/__init__.py +0 -0
  45. agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
  46. agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
  47. agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
  48. agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
  50. agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
  51. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
  52. agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
  53. agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
  54. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
  55. agentscope_runtime/sandbox/box/sandbox.py +115 -0
  56. agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
  57. agentscope_runtime/sandbox/box/shared/app.py +44 -0
  58. agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
  59. agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
  60. agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
  61. agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
  62. agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
  63. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
  64. agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
  65. agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
  66. agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
  67. agentscope_runtime/sandbox/box/training_box/base.py +120 -0
  68. agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
  69. agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
  70. agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
  71. agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
  72. agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
  73. agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
  74. agentscope_runtime/sandbox/build.py +213 -0
  75. agentscope_runtime/sandbox/client/__init__.py +5 -0
  76. agentscope_runtime/sandbox/client/http_client.py +527 -0
  77. agentscope_runtime/sandbox/client/training_client.py +265 -0
  78. agentscope_runtime/sandbox/constant.py +5 -0
  79. agentscope_runtime/sandbox/custom/__init__.py +16 -0
  80. agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
  81. agentscope_runtime/sandbox/custom/example.py +37 -0
  82. agentscope_runtime/sandbox/enums.py +68 -0
  83. agentscope_runtime/sandbox/manager/__init__.py +4 -0
  84. agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
  85. agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
  86. agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
  87. agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
  88. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
  89. agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
  90. agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
  91. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
  92. agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
  93. agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
  94. agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
  95. agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
  96. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
  97. agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
  98. agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
  99. agentscope_runtime/sandbox/manager/server/app.py +194 -0
  100. agentscope_runtime/sandbox/manager/server/config.py +68 -0
  101. agentscope_runtime/sandbox/manager/server/models.py +17 -0
  102. agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
  103. agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
  104. agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
  105. agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
  106. agentscope_runtime/sandbox/manager/utils.py +78 -0
  107. agentscope_runtime/sandbox/mcp_server.py +192 -0
  108. agentscope_runtime/sandbox/model/__init__.py +12 -0
  109. agentscope_runtime/sandbox/model/api.py +16 -0
  110. agentscope_runtime/sandbox/model/container.py +72 -0
  111. agentscope_runtime/sandbox/model/manager_config.py +158 -0
  112. agentscope_runtime/sandbox/registry.py +129 -0
  113. agentscope_runtime/sandbox/tools/__init__.py +12 -0
  114. agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
  115. agentscope_runtime/sandbox/tools/base/tool.py +52 -0
  116. agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
  117. agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
  118. agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
  119. agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
  120. agentscope_runtime/sandbox/tools/function_tool.py +321 -0
  121. agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
  122. agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
  123. agentscope_runtime/sandbox/tools/tool.py +123 -0
  124. agentscope_runtime/sandbox/tools/utils.py +68 -0
  125. agentscope_runtime/version.py +2 -0
  126. agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
  127. agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
  128. agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
  129. agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
  130. agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
  131. agentscope_runtime-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,586 @@
1
+ # -*- coding: utf-8 -*-
2
+ import asyncio
3
+ import json
4
+ import logging
5
+ import socket
6
+ import threading
7
+ import time
8
+ import uuid
9
+ from contextlib import asynccontextmanager
10
+ from typing import Optional, Dict, Any, Callable, Type, Tuple, Union
11
+
12
+ import uvicorn
13
+ from fastapi import FastAPI, HTTPException, Request, Response
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+ from fastapi.responses import StreamingResponse
16
+ from pydantic import BaseModel
17
+
18
+ from .base import DeployManager
19
+ from .adapter.protocol_adapter import ProtocolAdapter
20
+ from ..schemas.agent_schemas import AgentRequest, AgentResponse, Error
21
+
22
+
23
+ class LocalDeployManager(DeployManager):
24
+ def __init__(self, host: str = "localhost", port: int = 8090):
25
+ super().__init__()
26
+ self.host = host
27
+ self.port = port
28
+ self._server = None
29
+ self._server_task = None
30
+ self._server_thread = None # Add thread for server
31
+ self._is_running = False
32
+ self._logger = logging.getLogger(__name__)
33
+ self._app = None
34
+ self._startup_timeout = 30 # seconds
35
+ self._shutdown_timeout = 10 # seconds
36
+ self._setup_logging()
37
+
38
+ def _setup_logging(self):
39
+ formatter = logging.Formatter(
40
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
41
+ )
42
+
43
+ app_logger = logging.getLogger("app")
44
+ app_logger.setLevel(logging.INFO)
45
+
46
+ file_handler = logging.handlers.RotatingFileHandler(
47
+ "app.log",
48
+ maxBytes=10 * 1024 * 1024, # 10MB
49
+ backupCount=5,
50
+ )
51
+ file_handler.setFormatter(formatter)
52
+ app_logger.addHandler(file_handler)
53
+ console_handler = logging.StreamHandler()
54
+ console_handler.setFormatter(formatter)
55
+ app_logger.addHandler(console_handler)
56
+
57
+ access_logger = logging.getLogger("access")
58
+ access_logger.setLevel(logging.INFO)
59
+ access_file_handler = logging.handlers.RotatingFileHandler(
60
+ "access.log",
61
+ maxBytes=10 * 1024 * 1024,
62
+ backupCount=5,
63
+ )
64
+ access_file_handler.setFormatter(
65
+ logging.Formatter("%(asctime)s - %(message)s"),
66
+ )
67
+ access_logger.addHandler(access_file_handler)
68
+
69
+ self.app_logger = app_logger
70
+ self.access_logger = access_logger
71
+
72
+ def _create_fastapi_app(self) -> FastAPI:
73
+ """Create and configure FastAPI application with lifespan
74
+ management."""
75
+
76
+ @asynccontextmanager
77
+ async def lifespan(app: FastAPI) -> Any:
78
+ """Manage the application lifespan."""
79
+ if hasattr(self, "before_start") and self.before_start:
80
+ if asyncio.iscoroutinefunction(self.before_start):
81
+ await self.before_start(app, **getattr(self, "kwargs", {}))
82
+ else:
83
+ self.before_start(app, **getattr(self, "kwargs", {}))
84
+ yield
85
+ if hasattr(self, "after_finish") and self.after_finish:
86
+ if asyncio.iscoroutinefunction(self.after_finish):
87
+ await self.after_finish(app, **getattr(self, "kwargs", {}))
88
+ else:
89
+ self.after_finish(app, **getattr(self, "kwargs", {}))
90
+
91
+ app = FastAPI(
92
+ title="Agent Service",
93
+ version="1.0.0",
94
+ description="Production-ready Agent Service API",
95
+ lifespan=lifespan,
96
+ )
97
+
98
+ self._add_middleware(app)
99
+ self._add_health_endpoints(app)
100
+
101
+ if hasattr(self, "func") and self.func:
102
+ self._add_main_endpoint(app)
103
+
104
+ return app
105
+
106
+ def _add_middleware(self, app: FastAPI) -> None:
107
+ """Add middleware to the FastAPI application."""
108
+
109
+ @app.middleware("http")
110
+ async def log_requests(request: Request, call_next):
111
+ start_time = time.time()
112
+
113
+ self.app_logger.info(f"Request: {request.method} {request.url}")
114
+ response = await call_next(
115
+ request,
116
+ )
117
+ process_time = time.time() - start_time
118
+ self.access_logger.info(
119
+ f'{request.client.host} - "{request.method} {request.url}" '
120
+ f"{response.status_code} - {process_time:.3f}s",
121
+ )
122
+
123
+ return response
124
+
125
+ @app.middleware("http")
126
+ async def custom_middleware(
127
+ request: Request,
128
+ call_next: Callable,
129
+ ) -> Response:
130
+ """Custom middleware for request processing."""
131
+ response: Response = await call_next(request)
132
+ return response
133
+
134
+ app.add_middleware(
135
+ CORSMiddleware,
136
+ allow_origins=["*"],
137
+ allow_credentials=True,
138
+ allow_methods=["*"],
139
+ allow_headers=["*"],
140
+ )
141
+
142
+ def _add_health_endpoints(self, app: FastAPI) -> None:
143
+ """Add health check endpoints to the FastAPI application."""
144
+
145
+ @app.get("/health")
146
+ async def health_check():
147
+ return {
148
+ "status": "healthy",
149
+ "timestamp": time.time(),
150
+ "service": "agent-service",
151
+ }
152
+
153
+ @app.get("/readiness")
154
+ async def readiness() -> str:
155
+ """Check if the application is ready to serve requests."""
156
+ if getattr(app.state, "is_ready", True):
157
+ return "success"
158
+ raise HTTPException(
159
+ status_code=500,
160
+ detail="Application is not ready",
161
+ )
162
+
163
+ @app.get("/liveness")
164
+ async def liveness() -> str:
165
+ """Check if the application is alive and healthy."""
166
+ if getattr(app.state, "is_healthy", True):
167
+ return "success"
168
+ raise HTTPException(
169
+ status_code=500,
170
+ detail="Application is not healthy",
171
+ )
172
+
173
+ @app.get("/")
174
+ async def root():
175
+ return {"message": "Agent Service is running"}
176
+
177
+ def _add_main_endpoint(self, app: FastAPI) -> None:
178
+ """Add the main processing endpoint to the FastAPI application."""
179
+
180
+ async def _get_request_info(request: Request) -> Tuple[Dict, Any, str]:
181
+ """Extract request information from the HTTP request."""
182
+ body = await request.body()
183
+ request_body = json.loads(body.decode("utf-8")) if body else {}
184
+
185
+ user_id = request_body.get("user_id", "")
186
+
187
+ if hasattr(self, "request_model") and self.request_model:
188
+ try:
189
+ request_body_obj = self.request_model.model_validate(
190
+ request_body,
191
+ )
192
+ except Exception as e:
193
+ raise HTTPException(
194
+ status_code=400,
195
+ detail=f"Invalid request format: {e}",
196
+ ) from e
197
+ else:
198
+ request_body_obj = request_body
199
+
200
+ query_params = dict(request.query_params)
201
+ return query_params, request_body_obj, user_id
202
+
203
+ def _get_request_id(request_body_obj: Any) -> str:
204
+ """Extract or generate a request ID from the request body."""
205
+ if hasattr(request_body_obj, "header") and hasattr(
206
+ request_body_obj.header,
207
+ "request_id",
208
+ ):
209
+ request_id = request_body_obj.header.request_id
210
+ elif (
211
+ isinstance(
212
+ request_body_obj,
213
+ dict,
214
+ )
215
+ and "request_id" in request_body_obj
216
+ ):
217
+ request_id = request_body_obj["request_id"]
218
+ else:
219
+ request_id = str(uuid.uuid4())
220
+ return request_id
221
+
222
+ @app.post(self.endpoint_path)
223
+ async def main_endpoint(request: Request):
224
+ """Main endpoint handler for processing requests."""
225
+ try:
226
+ (
227
+ _, # query_params
228
+ request_body_obj,
229
+ user_id,
230
+ ) = await _get_request_info(
231
+ request=request,
232
+ )
233
+ request_id = _get_request_id(request_body_obj)
234
+ if (
235
+ hasattr(
236
+ self,
237
+ "response_type",
238
+ )
239
+ and self.response_type == "sse"
240
+ ):
241
+ return self._handle_sse_response(
242
+ user_id=user_id,
243
+ request_body_obj=request_body_obj,
244
+ request_id=request_id,
245
+ )
246
+ else:
247
+ return await self._handle_standard_response(
248
+ user_id=user_id,
249
+ request_body_obj=request_body_obj,
250
+ request_id=request_id,
251
+ )
252
+
253
+ except Exception as e:
254
+ self._logger.error(f"Request processing failed: {e}")
255
+ raise HTTPException(status_code=500, detail=str(e)) from e
256
+
257
+ def _handle_sse_response(
258
+ self,
259
+ user_id: str,
260
+ request_body_obj: Any,
261
+ request_id: str,
262
+ ) -> StreamingResponse:
263
+ """Handle Server-Sent Events response."""
264
+
265
+ async def stream_generator():
266
+ """Generate streaming response data."""
267
+ try:
268
+ if asyncio.iscoroutinefunction(self.func):
269
+ async for output in self.func(
270
+ user_id=user_id,
271
+ request=request_body_obj,
272
+ request_id=request_id,
273
+ ):
274
+ _data = self._create_success_result(
275
+ output=output,
276
+ )
277
+ yield f"data: {_data}\n\n"
278
+ else:
279
+ # For sync functions, we need to handle differently
280
+ result = self.func(
281
+ user_id=user_id,
282
+ request=request_body_obj,
283
+ request_id=request_id,
284
+ )
285
+ if hasattr(result, "__aiter__"):
286
+ async for output in result:
287
+ _data = self._create_success_result(
288
+ output=output,
289
+ )
290
+ yield f"data: {_data}\n\n"
291
+ else:
292
+ _data = self._create_success_result(
293
+ output=result,
294
+ )
295
+ yield f"data: {_data}\n\n"
296
+ except Exception as e:
297
+ _data = self._create_error_response(
298
+ request_id=request_id,
299
+ error=e,
300
+ )
301
+ yield f"data: {_data}\n\n"
302
+
303
+ return StreamingResponse(
304
+ stream_generator(),
305
+ media_type="text/event-stream",
306
+ headers={
307
+ "Cache-Control": "no-cache",
308
+ "Connection": "keep-alive",
309
+ },
310
+ )
311
+
312
+ async def _handle_standard_response(
313
+ self,
314
+ user_id: str,
315
+ request_body_obj: Any,
316
+ request_id: str,
317
+ ):
318
+ """Handle standard JSON response."""
319
+ try:
320
+ if asyncio.iscoroutinefunction(self.func):
321
+ result = await self.func(
322
+ user_id=user_id,
323
+ request=request_body_obj,
324
+ request_id=request_id,
325
+ )
326
+ else:
327
+ result = self.func(
328
+ user_id=user_id,
329
+ request=request_body_obj,
330
+ request_id=request_id,
331
+ )
332
+
333
+ return self._create_success_result(
334
+ output=result,
335
+ )
336
+ except Exception as e:
337
+ return self._create_error_response(request_id=request_id, error=e)
338
+
339
+ def _create_success_result(
340
+ self,
341
+ output: Union[BaseModel, Dict, str],
342
+ ) -> str:
343
+ """Create a success response."""
344
+ if isinstance(output, BaseModel):
345
+ return output.model_dump_json()
346
+ elif isinstance(output, dict):
347
+ return json.dumps(output)
348
+ else:
349
+ return output
350
+
351
+ def _create_error_response(
352
+ self,
353
+ request_id: str,
354
+ error: Exception,
355
+ ) -> str:
356
+ """Create an error response."""
357
+ response = AgentResponse(id=request_id)
358
+ response.failed(Error(code=str(error), message=str(error)))
359
+ return response.model_dump_json()
360
+
361
+ def deploy_sync(
362
+ self,
363
+ func: Callable,
364
+ endpoint_path: str = "/process",
365
+ request_model: Optional[Type] = AgentRequest,
366
+ response_type: str = "sse",
367
+ before_start: Optional[Callable] = None,
368
+ after_finish: Optional[Callable] = None,
369
+ **kwargs: Any,
370
+ ) -> Dict[str, str]:
371
+ """
372
+ Deploy the agent as a FastAPI service (synchronous version).
373
+
374
+ Args:
375
+ func: Custom processing function
376
+ endpoint_path: API endpoint path for the processing function
377
+ request_model: Pydantic model for request validation
378
+ response_type: Response type - "json", "sse", or "text"
379
+ before_start: Callback function called before server starts
380
+ after_finish: Callback function called after server finishes
381
+ **kwargs: Additional keyword arguments passed to callbacks
382
+
383
+ Returns:
384
+ Dict[str, str]: Dictionary containing deploy_id and url of the
385
+ deployed service
386
+
387
+ Raises:
388
+ RuntimeError: If deployment fails
389
+ """
390
+ return asyncio.run(
391
+ self._deploy_async(
392
+ func=func,
393
+ endpoint_path=endpoint_path,
394
+ request_model=request_model,
395
+ response_type=response_type,
396
+ before_start=before_start,
397
+ after_finish=after_finish,
398
+ **kwargs,
399
+ ),
400
+ )
401
+
402
+ async def deploy(
403
+ self,
404
+ func: Callable,
405
+ endpoint_path: str = "/process",
406
+ request_model: Optional[Type] = AgentRequest,
407
+ response_type: str = "sse",
408
+ before_start: Optional[Callable] = None,
409
+ after_finish: Optional[Callable] = None,
410
+ protocol_adapters: Optional[list[ProtocolAdapter]] = None,
411
+ **kwargs: Any,
412
+ ) -> Dict[str, str]:
413
+ """
414
+ Deploy the agent as a FastAPI service (asynchronous version).
415
+
416
+ Args:
417
+ func: Custom processing function
418
+ endpoint_path: API endpoint path for the processing function
419
+ request_model: Pydantic model for request validation
420
+ response_type: Response type - "json", "sse", or "text"
421
+ before_start: Callback function called before server starts
422
+ after_finish: Callback function called after server finishes
423
+ **kwargs: Additional keyword arguments passed to callbacks
424
+
425
+ Returns:
426
+ Dict[str, str]: Dictionary containing deploy_id and url of the
427
+ deployed service
428
+
429
+ Raises:
430
+ RuntimeError: If deployment fails
431
+ """
432
+ return await self._deploy_async(
433
+ func=func,
434
+ endpoint_path=endpoint_path,
435
+ request_model=request_model,
436
+ response_type=response_type,
437
+ before_start=before_start,
438
+ after_finish=after_finish,
439
+ protocol_adapters=protocol_adapters,
440
+ **kwargs,
441
+ )
442
+
443
+ async def _deploy_async(
444
+ self,
445
+ func: Callable,
446
+ endpoint_path: str = "/process",
447
+ request_model: Optional[Type] = None,
448
+ response_type: str = "sse",
449
+ before_start: Optional[Callable] = None,
450
+ after_finish: Optional[Callable] = None,
451
+ protocol_adapters: Optional[list[ProtocolAdapter]] = None,
452
+ **kwargs: Any,
453
+ ) -> Dict[str, str]:
454
+ if self._is_running:
455
+ raise RuntimeError("Service is already running")
456
+
457
+ try:
458
+ self._logger.info("Starting FastAPI service deployment...")
459
+
460
+ # Store callable configuration
461
+ self.func = func
462
+ self.endpoint_path = endpoint_path
463
+ self.request_model = request_model
464
+ self.response_type = response_type
465
+ self.before_start = before_start
466
+ self.after_finish = after_finish
467
+ self.kwargs = kwargs
468
+
469
+ # Create FastAPI app
470
+ self._app = self._create_fastapi_app()
471
+
472
+ # Support extension protocol
473
+ if protocol_adapters:
474
+ for protocol_adapter in protocol_adapters:
475
+ protocol_adapter.add_endpoint(app=self._app, func=func)
476
+
477
+ # Configure uvicorn server
478
+ config = uvicorn.Config(
479
+ self._app,
480
+ host=self.host,
481
+ port=self.port,
482
+ log_level="info",
483
+ access_log=False,
484
+ timeout_keep_alive=30,
485
+ )
486
+
487
+ self._server = uvicorn.Server(config)
488
+ # Run the server in a separate thread
489
+ self._server_thread = threading.Thread(target=self._server.run)
490
+ self._server_thread.daemon = (
491
+ True # Ensure thread doesn't block exit
492
+ )
493
+ self._server_thread.start()
494
+
495
+ # Wait for server to start with timeout
496
+ start_time = time.time()
497
+ while not self._is_server_ready():
498
+ if time.time() - start_time > self._startup_timeout:
499
+ # Clean up the thread if server fails to start
500
+ if self._server:
501
+ self._server.should_exit = True
502
+ self._server_thread.join(timeout=self._shutdown_timeout)
503
+ raise RuntimeError(
504
+ f"Server startup timeout after "
505
+ f"{self._startup_timeout} seconds",
506
+ )
507
+ await asyncio.sleep(0.1)
508
+
509
+ self._is_running = True
510
+ url = f"http://{self.host}:{self.port}"
511
+ self._logger.info(
512
+ f"FastAPI service deployed successfully at {url}",
513
+ )
514
+ return {
515
+ "deploy_id": self.deploy_id,
516
+ "url": url,
517
+ }
518
+
519
+ except Exception as e:
520
+ self._logger.error(f"Deployment failed: {e}")
521
+ await self._cleanup_server()
522
+ raise RuntimeError(f"Failed to deploy FastAPI service: {e}") from e
523
+
524
+ def _is_server_ready(self) -> bool:
525
+ """Check if the server is ready to accept connections."""
526
+ try:
527
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
528
+ s.settimeout(0.1)
529
+ result = s.connect_ex((self.host, self.port))
530
+ return result == 0
531
+ except Exception:
532
+ return False
533
+
534
+ async def stop(self) -> None:
535
+ """
536
+ Stop the FastAPI service.
537
+
538
+ Raises:
539
+ RuntimeError: If stopping fails
540
+ """
541
+ if not self._is_running:
542
+ self._logger.warning("Service is not running")
543
+ return
544
+
545
+ try:
546
+ self._logger.info("Stopping FastAPI service...")
547
+
548
+ # Stop the server gracefully
549
+ if self._server:
550
+ self._server.should_exit = True
551
+
552
+ # Wait for the server thread to finish
553
+ if self._server_thread and self._server_thread.is_alive():
554
+ self._server_thread.join(timeout=self._shutdown_timeout)
555
+ if self._server_thread.is_alive():
556
+ self._logger.warning(
557
+ "Server thread did not terminate, "
558
+ "potential resource leak",
559
+ )
560
+
561
+ await self._cleanup_server()
562
+ self._is_running = False
563
+ self._logger.info("FastAPI service stopped successfully")
564
+
565
+ except Exception as e:
566
+ self._logger.error(f"Failed to stop service: {e}")
567
+ raise RuntimeError(f"Failed to stop FastAPI service: {e}") from e
568
+
569
+ async def _cleanup_server(self):
570
+ """Clean up server resources."""
571
+ self._server = None
572
+ self._server_task = None
573
+ self._server_thread = None
574
+ self._app = None
575
+
576
+ @property
577
+ def is_running(self) -> bool:
578
+ """Check if the service is currently running."""
579
+ return self._is_running
580
+
581
+ @property
582
+ def service_url(self) -> Optional[str]:
583
+ """Get the current service URL if running."""
584
+ if self._is_running and self.port:
585
+ return f"http://{self.host}:{self.port}"
586
+ return None
@@ -0,0 +1,127 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from agentscope_runtime.engine import Runner
4
+ from agentscope_runtime.engine.schemas.agent_schemas import (
5
+ AgentRequest,
6
+ MessageType,
7
+ RunStatus,
8
+ )
9
+ from agentscope_runtime.engine.services.context_manager import (
10
+ create_context_manager,
11
+ )
12
+ from agentscope_runtime.engine.services.environment_manager import (
13
+ create_environment_manager,
14
+ )
15
+
16
+
17
+ async def simple_call_agent(query, runner, user_id=None, session_id=None):
18
+ request = AgentRequest(
19
+ input=[
20
+ {
21
+ "role": "user",
22
+ "content": [
23
+ {
24
+ "type": "text",
25
+ "text": query,
26
+ },
27
+ ],
28
+ },
29
+ ],
30
+ session_id=session_id,
31
+ )
32
+ all_result = ""
33
+ async for message in runner.stream_query(
34
+ user_id=user_id,
35
+ request=request,
36
+ ):
37
+ if (
38
+ message.object == "message"
39
+ and MessageType.MESSAGE == message.type
40
+ and RunStatus.Completed == message.status
41
+ ):
42
+ all_result = message.content[0].text
43
+
44
+ return all_result
45
+
46
+
47
+ async def simple_call_agent_direct(agent, query):
48
+ async with create_context_manager() as context_manager:
49
+ runner = Runner(
50
+ agent=agent,
51
+ context_manager=context_manager,
52
+ )
53
+ result = await simple_call_agent(
54
+ query,
55
+ runner,
56
+ )
57
+ return result
58
+
59
+
60
+ async def simple_call_agent_tool(agent, query):
61
+ all_result = ""
62
+ async with create_context_manager() as context_manager:
63
+ async with create_environment_manager() as environment_manager:
64
+ runner = Runner(
65
+ agent=agent,
66
+ context_manager=context_manager,
67
+ environment_manager=environment_manager,
68
+ )
69
+
70
+ request = AgentRequest(
71
+ input=[
72
+ {
73
+ "role": "user",
74
+ "content": [
75
+ {
76
+ "type": "text",
77
+ "text": query,
78
+ },
79
+ ],
80
+ },
81
+ ],
82
+ )
83
+
84
+ async for message in runner.stream_query(
85
+ request=request,
86
+ ):
87
+ if (
88
+ message.object == "message"
89
+ and MessageType.MESSAGE == message.type
90
+ and RunStatus.Completed == message.status
91
+ ):
92
+ all_result = message.content[0].text
93
+ return all_result
94
+
95
+
96
+ async def simple_call_agent_tool_wo_env(agent, query):
97
+ all_result = ""
98
+ async with create_context_manager() as context_manager:
99
+ runner = Runner(
100
+ agent=agent,
101
+ context_manager=context_manager,
102
+ )
103
+
104
+ request = AgentRequest(
105
+ input=[
106
+ {
107
+ "role": "user",
108
+ "content": [
109
+ {
110
+ "type": "text",
111
+ "text": query,
112
+ },
113
+ ],
114
+ },
115
+ ],
116
+ )
117
+
118
+ async for message in runner.stream_query(
119
+ request=request,
120
+ ):
121
+ if (
122
+ message.object == "message"
123
+ and MessageType.MESSAGE == message.type
124
+ and RunStatus.Completed == message.status
125
+ ):
126
+ all_result = message.content[0].text
127
+ return all_result