agentscope-runtime 0.1.6__py3-none-any.whl → 0.2.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 (87) hide show
  1. agentscope_runtime/common/container_clients/__init__.py +0 -0
  2. agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +546 -6
  3. agentscope_runtime/engine/__init__.py +12 -0
  4. agentscope_runtime/engine/agents/agentscope_agent.py +130 -10
  5. agentscope_runtime/engine/agents/agno_agent.py +8 -10
  6. agentscope_runtime/engine/agents/langgraph_agent.py +52 -9
  7. agentscope_runtime/engine/app/__init__.py +6 -0
  8. agentscope_runtime/engine/app/agent_app.py +239 -0
  9. agentscope_runtime/engine/app/base_app.py +181 -0
  10. agentscope_runtime/engine/app/celery_mixin.py +92 -0
  11. agentscope_runtime/engine/deployers/__init__.py +13 -0
  12. agentscope_runtime/engine/deployers/adapter/responses/__init__.py +0 -0
  13. agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +2890 -0
  14. agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +51 -0
  15. agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +314 -0
  16. agentscope_runtime/engine/deployers/base.py +1 -0
  17. agentscope_runtime/engine/deployers/cli_fc_deploy.py +203 -0
  18. agentscope_runtime/engine/deployers/kubernetes_deployer.py +272 -0
  19. agentscope_runtime/engine/deployers/local_deployer.py +414 -501
  20. agentscope_runtime/engine/deployers/modelstudio_deployer.py +838 -0
  21. agentscope_runtime/engine/deployers/utils/__init__.py +0 -0
  22. agentscope_runtime/engine/deployers/utils/deployment_modes.py +14 -0
  23. agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +8 -0
  24. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +429 -0
  25. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +240 -0
  26. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +306 -0
  27. agentscope_runtime/engine/deployers/utils/package_project_utils.py +1163 -0
  28. agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +9 -0
  29. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +1064 -0
  30. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +157 -0
  31. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +268 -0
  32. agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +75 -0
  33. agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +220 -0
  34. agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
  35. agentscope_runtime/engine/deployers/utils/wheel_packager.py +389 -0
  36. agentscope_runtime/engine/helpers/agent_api_builder.py +651 -0
  37. agentscope_runtime/engine/runner.py +76 -35
  38. agentscope_runtime/engine/schemas/agent_schemas.py +112 -2
  39. agentscope_runtime/engine/schemas/embedding.py +37 -0
  40. agentscope_runtime/engine/schemas/modelstudio_llm.py +310 -0
  41. agentscope_runtime/engine/schemas/oai_llm.py +538 -0
  42. agentscope_runtime/engine/schemas/realtime.py +254 -0
  43. agentscope_runtime/engine/services/tablestore_memory_service.py +4 -1
  44. agentscope_runtime/engine/tracing/__init__.py +9 -3
  45. agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
  46. agentscope_runtime/engine/tracing/base.py +66 -34
  47. agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
  48. agentscope_runtime/engine/tracing/message_util.py +528 -0
  49. agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
  50. agentscope_runtime/engine/tracing/tracing_util.py +130 -0
  51. agentscope_runtime/engine/tracing/wrapper.py +794 -169
  52. agentscope_runtime/sandbox/box/base/base_sandbox.py +2 -1
  53. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +2 -1
  54. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
  55. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +2 -1
  56. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +2 -1
  57. agentscope_runtime/sandbox/box/training_box/training_box.py +0 -42
  58. agentscope_runtime/sandbox/client/http_client.py +52 -18
  59. agentscope_runtime/sandbox/constant.py +3 -0
  60. agentscope_runtime/sandbox/custom/custom_sandbox.py +2 -1
  61. agentscope_runtime/sandbox/custom/example.py +2 -1
  62. agentscope_runtime/sandbox/enums.py +0 -1
  63. agentscope_runtime/sandbox/manager/sandbox_manager.py +29 -22
  64. agentscope_runtime/sandbox/model/container.py +6 -0
  65. agentscope_runtime/sandbox/registry.py +1 -1
  66. agentscope_runtime/sandbox/tools/tool.py +4 -0
  67. agentscope_runtime/version.py +1 -1
  68. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/METADATA +103 -59
  69. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/RECORD +87 -52
  70. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/entry_points.txt +1 -0
  71. /agentscope_runtime/{sandbox/manager/container_clients → common}/__init__.py +0 -0
  72. /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
  73. /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
  74. /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
  75. /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
  76. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_mapping.py +0 -0
  77. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
  78. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
  79. /agentscope_runtime/{sandbox/manager → common}/collections/redis_mapping.py +0 -0
  80. /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
  81. /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
  82. /agentscope_runtime/{sandbox/manager → common}/container_clients/agentrun_client.py +0 -0
  83. /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
  84. /agentscope_runtime/{sandbox/manager → common}/container_clients/docker_client.py +0 -0
  85. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/WHEEL +0 -0
  86. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/licenses/LICENSE +0 -0
  87. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@ import json
7
7
  import traceback
8
8
  from functools import partial
9
9
  from typing import Optional, Type, List
10
+ from urllib.parse import urlparse
10
11
 
11
12
  from agentscope import setup_logger
12
13
  from agentscope.agent import AgentBase, ReActAgent
@@ -24,10 +25,12 @@ from agentscope.message import (
24
25
  ToolUseBlock,
25
26
  ToolResultBlock,
26
27
  TextBlock,
28
+ ThinkingBlock,
27
29
  ImageBlock,
28
- # AudioBlock, # TODO: support
30
+ AudioBlock,
29
31
  # VideoBlock, # TODO: support
30
32
  URLSource,
33
+ Base64Source,
31
34
  )
32
35
  from agentscope.model import (
33
36
  ChatModelBase,
@@ -54,6 +57,7 @@ from ..schemas.agent_schemas import (
54
57
  FunctionCall,
55
58
  FunctionCallOutput,
56
59
  MessageType,
60
+ RunStatus,
57
61
  )
58
62
  from ..schemas.context import Context
59
63
 
@@ -100,7 +104,7 @@ class AgentScopeContextAdapter:
100
104
  role_label = message.role
101
105
 
102
106
  result = {
103
- "name": message.role,
107
+ "name": message.role, # TODO: protocol support
104
108
  "role": role_label,
105
109
  "invocation_id": message.id,
106
110
  }
@@ -114,7 +118,7 @@ class AgentScopeContextAdapter:
114
118
  ToolUseBlock(
115
119
  type="tool_use",
116
120
  id=message.content[0].data["call_id"],
117
- name=message.role,
121
+ name=message.content[0].data["name"],
118
122
  input=json.loads(message.content[0].data["arguments"]),
119
123
  ),
120
124
  ]
@@ -128,15 +132,22 @@ class AgentScopeContextAdapter:
128
132
  ToolResultBlock(
129
133
  type="tool_result",
130
134
  id=message.content[0].data["call_id"],
131
- name=message.role,
132
- output=message.content[0].data["output"],
135
+ name=message.role, # TODO: match id of ToolUseBlock
136
+ output=json.loads(message.content[0].data["output"]),
137
+ ),
138
+ ]
139
+ elif message.type in (MessageType.REASONING,):
140
+ result["content"] = [
141
+ ThinkingBlock(
142
+ type="thinking",
143
+ thinking=message.content[0].text,
133
144
  ),
134
145
  ]
135
146
  else:
136
147
  type_mapping = {
137
148
  "text": (TextBlock, "text", None),
138
149
  "image": (ImageBlock, "image_url", True),
139
- # "audio": (AudioBlock, "audio_url", True), # TODO: support
150
+ "audio": (AudioBlock, "data", None),
140
151
  # "video": (VideoBlock, "video_url", True), # TODO: support
141
152
  }
142
153
 
@@ -149,14 +160,32 @@ class AgentScopeContextAdapter:
149
160
 
150
161
  block_cls, attr_name, is_url = type_mapping[cnt_type]
151
162
  value = getattr(cnt, attr_name)
152
-
163
+ if cnt_type == "audio":
164
+ result = urlparse(value)
165
+ is_url = all([result.scheme, result.netloc])
153
166
  if is_url:
154
167
  url_source = URLSource(type="url", url=value)
155
168
  msg_content.append(
156
169
  block_cls(type=cnt_type, source=url_source),
157
170
  )
158
171
  else:
159
- msg_content.append(block_cls(type=cnt_type, text=value))
172
+ if cnt_type == "audio":
173
+ audio_format = getattr(cnt, "format")
174
+ base64_source = Base64Source(
175
+ type="base64",
176
+ media_type=audio_format,
177
+ data=value,
178
+ )
179
+ msg_content.append(
180
+ block_cls(
181
+ type=cnt_type,
182
+ source=base64_source,
183
+ ),
184
+ )
185
+ else:
186
+ msg_content.append(
187
+ block_cls(type=cnt_type, text=value),
188
+ )
160
189
 
161
190
  result["content"] = msg_content
162
191
  return Msg(**result)
@@ -167,6 +196,10 @@ class AgentScopeContextAdapter:
167
196
 
168
197
  async def adapt_model(self):
169
198
  model = self.attr["model"]
199
+
200
+ if hasattr(model, "stream"):
201
+ model.stream = True
202
+
170
203
  formatter = self.attr["agent_config"].get("formatter")
171
204
  if formatter and isinstance(formatter, FormatterBase):
172
205
  return model, formatter
@@ -307,6 +340,7 @@ class AgentScopeAgent(Agent):
307
340
  as_context = AgentScopeContextAdapter(context=context, attr=self._attr)
308
341
  await as_context.initialize()
309
342
  local_truncate_memory = ""
343
+ local_truncate_reasoning_memory = ""
310
344
 
311
345
  # We should always build a new agent since the state is manage outside
312
346
  # the agent
@@ -316,7 +350,14 @@ class AgentScopeAgent(Agent):
316
350
  last_content = ""
317
351
 
318
352
  message = Message(type=MessageType.MESSAGE, role="assistant")
319
- yield message.in_progress()
353
+ reasoning_message = Message(
354
+ type=MessageType.REASONING,
355
+ role="assistant",
356
+ )
357
+
358
+ should_start_message = True
359
+ should_start_reasoning_message = True
360
+
320
361
  index = None
321
362
 
322
363
  # Run agent
@@ -347,6 +388,10 @@ class AgentScopeAgent(Agent):
347
388
  else:
348
389
  for element in content:
349
390
  if isinstance(element, str) and element:
391
+ if should_start_message:
392
+ index = None
393
+ yield message.in_progress()
394
+ should_start_message = False
350
395
  text_delta_content = TextContent(
351
396
  delta=True,
352
397
  index=index,
@@ -364,6 +409,11 @@ class AgentScopeAgent(Agent):
364
409
  "",
365
410
  )
366
411
  if text:
412
+ if should_start_message:
413
+ index = None
414
+ yield message.in_progress()
415
+ should_start_message = False
416
+
367
417
  text_delta_content = TextContent(
368
418
  delta=True,
369
419
  index=index,
@@ -391,8 +441,20 @@ class AgentScopeAgent(Agent):
391
441
  role="assistant",
392
442
  )
393
443
  index = None
444
+ should_start_message = True
394
445
 
395
446
  elif element.get("type") == "tool_use":
447
+ if (
448
+ reasoning_message.status
449
+ == RunStatus.InProgress
450
+ ):
451
+ yield reasoning_message.completed()
452
+ reasoning_message = Message(
453
+ type=MessageType.REASONING,
454
+ role="assistant",
455
+ )
456
+ index = None
457
+
396
458
  json_str = json.dumps(element.get("input"))
397
459
  data_delta_content = DataContent(
398
460
  index=index,
@@ -408,12 +470,15 @@ class AgentScopeAgent(Agent):
408
470
  content=[data_delta_content],
409
471
  )
410
472
  yield plugin_call_message.completed()
473
+ index = None
474
+
411
475
  elif element.get("type") == "tool_result":
476
+ json_str = json.dumps(element.get("output"))
412
477
  data_delta_content = DataContent(
413
478
  index=index,
414
479
  data=FunctionCallOutput(
415
480
  call_id=element.get("id"),
416
- output=str(element.get("output")),
481
+ output=json_str,
417
482
  ).model_dump(),
418
483
  )
419
484
  plugin_output_message = Message(
@@ -422,7 +487,59 @@ class AgentScopeAgent(Agent):
422
487
  content=[data_delta_content],
423
488
  )
424
489
  yield plugin_output_message.completed()
490
+ message = Message(
491
+ type=MessageType.MESSAGE,
492
+ role="assistant",
493
+ )
494
+ should_start_message = True
495
+ index = None
496
+
497
+ elif element.get("type") == "thinking":
498
+ reasoning = element.get(
499
+ "thinking",
500
+ "",
501
+ )
502
+ if reasoning:
503
+ if should_start_reasoning_message:
504
+ index = None
505
+ yield reasoning_message.in_progress()
506
+ should_start_reasoning_message = False
507
+ text_delta_content = TextContent(
508
+ delta=True,
509
+ index=index,
510
+ text=reasoning.removeprefix(
511
+ local_truncate_reasoning_memory,
512
+ ),
513
+ )
514
+ local_truncate_reasoning_memory = element.get(
515
+ "thinking",
516
+ "",
517
+ )
518
+ text_delta_content = (
519
+ reasoning_message.add_delta_content(
520
+ new_content=text_delta_content,
521
+ )
522
+ )
523
+ index = text_delta_content.index
524
+
525
+ # Only yield valid text
526
+ if text_delta_content.text:
527
+ yield text_delta_content
528
+
529
+ # The last won't happen in the thinking message
530
+ if last:
531
+ yield reasoning_message.completed()
532
+ reasoning_message = Message(
533
+ type=MessageType.REASONING,
534
+ role="assistant",
535
+ )
536
+ index = None
425
537
  else:
538
+ if should_start_message:
539
+ index = None
540
+ yield message.in_progress()
541
+ should_start_message = False
542
+
426
543
  text_delta_content = TextContent(
427
544
  delta=True,
428
545
  index=index,
@@ -435,6 +552,9 @@ class AgentScopeAgent(Agent):
435
552
  yield text_delta_content
436
553
 
437
554
  if last_content:
555
+ if should_start_message:
556
+ index = None
557
+ yield message.in_progress()
438
558
  text_delta_content = TextContent(
439
559
  delta=True,
440
560
  index=index,
@@ -5,8 +5,8 @@ from typing import Optional, Type
5
5
 
6
6
  from agno.agent import Agent as AgAgent
7
7
  from agno.models.base import Model
8
- from agno.run.response import (
9
- RunResponseContentEvent,
8
+ from agno.run.agent import (
9
+ RunContentEvent,
10
10
  ToolCallStartedEvent,
11
11
  ToolCallCompletedEvent,
12
12
  )
@@ -163,12 +163,6 @@ class AgnoAgent(Agent):
163
163
  # the agent
164
164
  _agent = self.build(ag_context)
165
165
 
166
- resp = await _agent.arun(
167
- ag_context.new_message,
168
- messages=ag_context.memory,
169
- stream=True,
170
- )
171
-
172
166
  text_message = Message(
173
167
  type=MessageType.MESSAGE,
174
168
  role="assistant",
@@ -178,8 +172,12 @@ class AgnoAgent(Agent):
178
172
 
179
173
  text_delta_content = TextContent(delta=True)
180
174
  is_text_delta = False
181
- async for event in resp:
182
- if isinstance(event, RunResponseContentEvent):
175
+ async for event in _agent.arun(
176
+ ag_context.new_message,
177
+ session_state=ag_context.memory,
178
+ stream=True,
179
+ ):
180
+ if isinstance(event, RunContentEvent):
183
181
  is_text_delta = True
184
182
  text_delta_content.text = event.content
185
183
  text_delta_content = text_message.add_delta_content(
@@ -3,22 +3,65 @@ import json
3
3
 
4
4
  from langgraph.graph.state import CompiledStateGraph
5
5
 
6
+ from ..schemas.agent_schemas import Message, TextContent
6
7
  from .base_agent import Agent
7
- from ..schemas.agent_schemas import (
8
- Message,
9
- TextContent,
10
- )
11
8
 
12
9
 
13
10
  def _state_folder(messages):
14
- if len(messages) > 0:
15
- return json.loads(messages[0]["content"])
16
- else:
11
+ if not messages or len(messages) == 0:
12
+ # Return empty list if no messages
17
13
  return []
18
14
 
15
+ content = messages[0]["content"]
16
+ role = messages[0]["role"]
17
+
18
+ # If content is a list, extract the text content
19
+ if isinstance(content, list) and len(content) > 0:
20
+ if isinstance(content[0], dict) and content[0].get("type") == "text":
21
+ text_content = content[0].get("text", "")
22
+ else:
23
+ # If not text type, convert to string
24
+ text_content = str(content)
25
+ return {"messages": [{"role": role, "content": text_content}]}
26
+
27
+ # If content is string, parse it as JSON, if failed, return directly
28
+ if isinstance(content, str):
29
+ try:
30
+ return json.loads(content)
31
+ except json.JSONDecodeError:
32
+ # If not valid JSON, return the original string
33
+ return {"messages": [{"role": role, "content": content}]}
34
+
35
+ # If content is already a dictionary, return directly
36
+ if isinstance(content, dict):
37
+ return content
38
+
39
+ # For other cases, wrap in messages and return
40
+ return {"messages": [{"role": role, "content": str(content)}]}
41
+
19
42
 
20
43
  def _state_unfolder(state):
21
- state_jsons = json.dumps(state)
44
+ # Process state that may contain non-serializable objects
45
+ def default_serializer(obj):
46
+ # If object has __dict__ method, use it
47
+ if hasattr(obj, "__dict__"):
48
+ return obj.__dict__
49
+ # If object has model_dump method, use it
50
+ elif hasattr(obj, "model_dump"):
51
+ return obj.model_dump()
52
+ # If object is a message type, extract its content
53
+ elif hasattr(obj, "content"):
54
+ return str(obj.content)
55
+ # For other cases, convert to string
56
+ else:
57
+ return str(obj)
58
+
59
+ # Serialize state to JSON string with custom serializer
60
+ state_jsons = json.dumps(
61
+ state,
62
+ default=default_serializer,
63
+ ensure_ascii=False,
64
+ )
22
65
  return state_jsons
23
66
 
24
67
 
@@ -40,7 +83,7 @@ class LangGraphAgent(Agent):
40
83
  context,
41
84
  **kwargs,
42
85
  ):
43
- # fold the last m
86
+ # Convert messages to list format
44
87
  list_messages = []
45
88
  for m in context.session.messages:
46
89
  dumped = m.model_dump()
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .agent_app import AgentApp
3
+
4
+ __all__ = [
5
+ "AgentApp",
6
+ ]
@@ -0,0 +1,239 @@
1
+ # -*- coding: utf-8 -*-
2
+ import asyncio
3
+ import logging
4
+ from contextlib import asynccontextmanager
5
+ from typing import Optional, Any, Callable, List
6
+
7
+ import uvicorn
8
+ from fastapi import FastAPI
9
+ from pydantic import BaseModel
10
+
11
+ from .base_app import BaseApp
12
+ from ..agents.base_agent import Agent
13
+ from ..deployers.adapter.a2a import A2AFastAPIDefaultAdapter
14
+ from ..deployers.adapter.responses.response_api_protocol_adapter import (
15
+ ResponseAPIDefaultAdapter,
16
+ )
17
+ from ..deployers.utils.deployment_modes import DeploymentMode
18
+ from ..deployers.utils.service_utils.fastapi_factory import FastAPIAppFactory
19
+ from ..deployers.utils.service_utils.service_config import (
20
+ DEFAULT_SERVICES_CONFIG,
21
+ )
22
+ from ..runner import Runner
23
+ from ..schemas.agent_schemas import AgentRequest
24
+ from ..services.context_manager import ContextManager
25
+ from ..services.environment_manager import EnvironmentManager
26
+ from ...version import __version__
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class AgentApp(BaseApp):
32
+ """
33
+ The AgentApp class represents an application that runs as an agent.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ *,
39
+ agent: Optional[Agent] = None,
40
+ environment_manager: Optional[EnvironmentManager] = None,
41
+ context_manager: Optional[ContextManager] = None,
42
+ endpoint_path: str = "/process",
43
+ response_type: str = "sse",
44
+ stream: bool = True,
45
+ request_model: Optional[type[BaseModel]] = AgentRequest,
46
+ before_start: Optional[Callable] = None,
47
+ after_finish: Optional[Callable] = None,
48
+ broker_url: Optional[str] = None,
49
+ backend_url: Optional[str] = None,
50
+ **kwargs,
51
+ ):
52
+ """
53
+ Initialize the AgentApp.
54
+
55
+ Args:
56
+ *args: Variable length argument list.
57
+ **kwargs: Arbitrary keyword arguments.
58
+ """
59
+
60
+ self.endpoint_path = endpoint_path
61
+ self.response_type = response_type
62
+ self.stream = stream
63
+ self.request_model = request_model
64
+ self.before_start = before_start
65
+ self.after_finish = after_finish
66
+ self.broker_url = broker_url
67
+ self.backend_url = backend_url
68
+
69
+ self._agent = agent
70
+ self._runner = None
71
+ self.custom_endpoints = [] # Store custom endpoints
72
+
73
+ a2a_protocol = A2AFastAPIDefaultAdapter(agent=self._agent)
74
+ response_protocol = ResponseAPIDefaultAdapter()
75
+ self.protocol_adapters = [a2a_protocol, response_protocol]
76
+
77
+ if self._agent:
78
+ self._runner = Runner(
79
+ agent=self._agent,
80
+ environment_manager=environment_manager,
81
+ context_manager=context_manager,
82
+ )
83
+
84
+ @asynccontextmanager
85
+ async def lifespan(app: FastAPI) -> Any:
86
+ """Manage the application lifespan."""
87
+ if hasattr(self, "before_start") and self.before_start:
88
+ if asyncio.iscoroutinefunction(self.before_start):
89
+ await self.before_start(app, **getattr(self, "kwargs", {}))
90
+ else:
91
+ self.before_start(app, **getattr(self, "kwargs", {}))
92
+ yield
93
+ if hasattr(self, "after_finish") and self.after_finish:
94
+ if asyncio.iscoroutinefunction(self.after_finish):
95
+ await self.after_finish(app, **getattr(self, "kwargs", {}))
96
+ else:
97
+ self.after_finish(app, **getattr(self, "kwargs", {}))
98
+
99
+ kwargs = {
100
+ "title": "Agent Service",
101
+ "version": __version__,
102
+ "description": "Production-ready Agent Service API",
103
+ "lifespan": lifespan,
104
+ **kwargs,
105
+ }
106
+
107
+ if self._runner:
108
+ if self.stream:
109
+ self.func = self._runner.stream_query
110
+ else:
111
+ self.func = self._runner.query
112
+
113
+ super().__init__(
114
+ broker_url=broker_url,
115
+ backend_url=backend_url,
116
+ **kwargs,
117
+ )
118
+
119
+ # Store custom endpoints and tasks for deployment
120
+ # but don't add them to FastAPI here - let FastAPIAppFactory handle it
121
+
122
+ def run(
123
+ self,
124
+ host="0.0.0.0",
125
+ port=8090,
126
+ embed_task_processor=False,
127
+ services_config=None,
128
+ **kwargs,
129
+ ):
130
+ """
131
+ Run the AgentApp using FastAPIAppFactory directly.
132
+
133
+ Args:
134
+ host: Host to bind to
135
+ port: Port to bind to
136
+ embed_task_processor: Whether to embed task processor
137
+ services_config: Optional services configuration
138
+ **kwargs: Additional keyword arguments
139
+ """
140
+
141
+ try:
142
+ logger.info(
143
+ "[AgentApp] Starting AgentApp with FastAPIAppFactory...",
144
+ )
145
+
146
+ # Use default services config if not provided
147
+ if services_config is None:
148
+ services_config = DEFAULT_SERVICES_CONFIG
149
+
150
+ # Create FastAPI application using the factory
151
+ fastapi_app = FastAPIAppFactory.create_app(
152
+ runner=self._runner,
153
+ endpoint_path=self.endpoint_path,
154
+ request_model=self.request_model,
155
+ response_type=self.response_type,
156
+ stream=self.stream,
157
+ before_start=self.before_start,
158
+ after_finish=self.after_finish,
159
+ mode=DeploymentMode.DAEMON_THREAD,
160
+ services_config=services_config,
161
+ protocol_adapters=self.protocol_adapters,
162
+ custom_endpoints=self.custom_endpoints,
163
+ broker_url=self.broker_url,
164
+ backend_url=self.backend_url,
165
+ enable_embedded_worker=embed_task_processor,
166
+ **kwargs,
167
+ )
168
+
169
+ logger.info(f"[AgentApp] Starting server on {host}:{port}")
170
+
171
+ # Start the FastAPI application with uvicorn
172
+ uvicorn.run(
173
+ fastapi_app,
174
+ host=host,
175
+ port=port,
176
+ log_level="info",
177
+ access_log=True,
178
+ )
179
+
180
+ except Exception as e:
181
+ logger.error(f"[AgentApp] Error while running: {e}")
182
+ raise
183
+
184
+ async def deploy(self, deployer, **kwargs):
185
+ """Deploy the agent app with custom endpoints support"""
186
+ # Pass custom endpoints and tasks to the deployer
187
+
188
+ deploy_kwargs = {
189
+ **kwargs,
190
+ "custom_endpoints": self.custom_endpoints,
191
+ "agent": self._agent,
192
+ "runner": self._runner,
193
+ "endpoint_path": self.endpoint_path,
194
+ "stream": self.stream,
195
+ "protocol_adapters": self.protocol_adapters,
196
+ }
197
+ return await deployer.deploy(**deploy_kwargs)
198
+
199
+ def endpoint(self, path: str, methods: Optional[List[str]] = None):
200
+ """Decorator to register custom endpoints"""
201
+
202
+ if methods is None:
203
+ methods = ["POST"]
204
+
205
+ def decorator(func: Callable):
206
+ endpoint_info = {
207
+ "path": path,
208
+ "handler": func,
209
+ "methods": methods,
210
+ "module": getattr(func, "__module__", None),
211
+ "function_name": getattr(func, "__name__", None),
212
+ }
213
+ self.custom_endpoints.append(endpoint_info)
214
+ return func
215
+
216
+ return decorator
217
+
218
+ def task(self, path: str, queue: str = "default"):
219
+ """Decorator to register custom task endpoints"""
220
+
221
+ def decorator(func: Callable):
222
+ # Store task configuration for FastAPIAppFactory to handle
223
+ task_info = {
224
+ "path": path,
225
+ "handler": func, # Store original function
226
+ "methods": ["POST"],
227
+ "module": getattr(func, "__module__", None),
228
+ "function_name": getattr(func, "__name__", None),
229
+ "queue": queue,
230
+ "task_type": True, # Mark as task endpoint
231
+ "original_func": func,
232
+ }
233
+ self.custom_endpoints.append(
234
+ task_info,
235
+ ) # Add to endpoints for deployment
236
+
237
+ return func
238
+
239
+ return decorator