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.
- agentscope_runtime/common/container_clients/__init__.py +0 -0
- agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +546 -6
- agentscope_runtime/engine/__init__.py +12 -0
- agentscope_runtime/engine/agents/agentscope_agent.py +130 -10
- agentscope_runtime/engine/agents/agno_agent.py +8 -10
- agentscope_runtime/engine/agents/langgraph_agent.py +52 -9
- agentscope_runtime/engine/app/__init__.py +6 -0
- agentscope_runtime/engine/app/agent_app.py +239 -0
- agentscope_runtime/engine/app/base_app.py +181 -0
- agentscope_runtime/engine/app/celery_mixin.py +92 -0
- agentscope_runtime/engine/deployers/__init__.py +13 -0
- agentscope_runtime/engine/deployers/adapter/responses/__init__.py +0 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +2890 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +51 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +314 -0
- agentscope_runtime/engine/deployers/base.py +1 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +203 -0
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +272 -0
- agentscope_runtime/engine/deployers/local_deployer.py +414 -501
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +838 -0
- agentscope_runtime/engine/deployers/utils/__init__.py +0 -0
- agentscope_runtime/engine/deployers/utils/deployment_modes.py +14 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +8 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +429 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +240 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +306 -0
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +1163 -0
- agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +9 -0
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +1064 -0
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +157 -0
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +268 -0
- agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +75 -0
- agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +220 -0
- agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +389 -0
- agentscope_runtime/engine/helpers/agent_api_builder.py +651 -0
- agentscope_runtime/engine/runner.py +76 -35
- agentscope_runtime/engine/schemas/agent_schemas.py +112 -2
- agentscope_runtime/engine/schemas/embedding.py +37 -0
- agentscope_runtime/engine/schemas/modelstudio_llm.py +310 -0
- agentscope_runtime/engine/schemas/oai_llm.py +538 -0
- agentscope_runtime/engine/schemas/realtime.py +254 -0
- agentscope_runtime/engine/services/tablestore_memory_service.py +4 -1
- agentscope_runtime/engine/tracing/__init__.py +9 -3
- agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
- agentscope_runtime/engine/tracing/base.py +66 -34
- agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
- agentscope_runtime/engine/tracing/message_util.py +528 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
- agentscope_runtime/engine/tracing/tracing_util.py +130 -0
- agentscope_runtime/engine/tracing/wrapper.py +794 -169
- agentscope_runtime/sandbox/box/base/base_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/training_box/training_box.py +0 -42
- agentscope_runtime/sandbox/client/http_client.py +52 -18
- agentscope_runtime/sandbox/constant.py +3 -0
- agentscope_runtime/sandbox/custom/custom_sandbox.py +2 -1
- agentscope_runtime/sandbox/custom/example.py +2 -1
- agentscope_runtime/sandbox/enums.py +0 -1
- agentscope_runtime/sandbox/manager/sandbox_manager.py +29 -22
- agentscope_runtime/sandbox/model/container.py +6 -0
- agentscope_runtime/sandbox/registry.py +1 -1
- agentscope_runtime/sandbox/tools/tool.py +4 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/METADATA +103 -59
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/RECORD +87 -52
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/entry_points.txt +1 -0
- /agentscope_runtime/{sandbox/manager/container_clients → common}/__init__.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/agentrun_client.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/docker_client.py +0 -0
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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.
|
|
9
|
-
|
|
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
|
|
182
|
-
|
|
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)
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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,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
|