agentscope-runtime 0.1.5b2__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/__init__.py +0 -0
- agentscope_runtime/common/collections/in_memory_mapping.py +27 -0
- agentscope_runtime/common/collections/redis_mapping.py +42 -0
- agentscope_runtime/common/container_clients/__init__.py +0 -0
- agentscope_runtime/common/container_clients/agentrun_client.py +1098 -0
- agentscope_runtime/common/container_clients/docker_client.py +250 -0
- agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +6 -13
- agentscope_runtime/engine/__init__.py +12 -0
- agentscope_runtime/engine/agents/agentscope_agent.py +567 -0
- agentscope_runtime/engine/agents/agno_agent.py +26 -27
- agentscope_runtime/engine/agents/autogen_agent.py +13 -8
- agentscope_runtime/engine/agents/langgraph_agent.py +52 -9
- agentscope_runtime/engine/agents/utils.py +53 -0
- 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/adapter/responses/response_api_adapter_utils.py +5 -1
- agentscope_runtime/engine/deployers/base.py +1 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +39 -20
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +12 -5
- agentscope_runtime/engine/deployers/local_deployer.py +61 -3
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +201 -40
- agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +9 -0
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +234 -3
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +567 -7
- agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +1 -1
- agentscope_runtime/engine/helpers/helper.py +60 -41
- agentscope_runtime/engine/runner.py +40 -24
- agentscope_runtime/engine/schemas/agent_schemas.py +42 -0
- agentscope_runtime/engine/schemas/modelstudio_llm.py +14 -14
- agentscope_runtime/engine/services/sandbox_service.py +62 -70
- agentscope_runtime/engine/services/tablestore_memory_service.py +307 -0
- agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
- agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
- agentscope_runtime/engine/services/utils/__init__.py +0 -0
- agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
- 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/__init__.py +2 -0
- agentscope_runtime/sandbox/box/base/__init__.py +4 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +6 -4
- agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +10 -14
- agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +10 -7
- agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
- agentscope_runtime/sandbox/box/gui/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +81 -0
- agentscope_runtime/sandbox/box/sandbox.py +5 -2
- agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
- agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +7 -54
- agentscope_runtime/sandbox/build.py +143 -58
- agentscope_runtime/sandbox/client/http_client.py +87 -59
- agentscope_runtime/sandbox/client/training_client.py +0 -1
- agentscope_runtime/sandbox/constant.py +27 -1
- agentscope_runtime/sandbox/custom/custom_sandbox.py +7 -6
- agentscope_runtime/sandbox/custom/example.py +4 -3
- agentscope_runtime/sandbox/enums.py +1 -1
- agentscope_runtime/sandbox/manager/sandbox_manager.py +212 -106
- agentscope_runtime/sandbox/manager/server/app.py +82 -14
- agentscope_runtime/sandbox/manager/server/config.py +50 -3
- agentscope_runtime/sandbox/model/container.py +12 -23
- agentscope_runtime/sandbox/model/manager_config.py +93 -5
- agentscope_runtime/sandbox/registry.py +1 -1
- agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
- agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
- agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
- agentscope_runtime/sandbox/tools/tool.py +4 -0
- agentscope_runtime/sandbox/utils.py +124 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/METADATA +246 -111
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/RECORD +96 -80
- agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
- agentscope_runtime/engine/agents/llm_agent.py +0 -51
- agentscope_runtime/engine/llms/__init__.py +0 -3
- agentscope_runtime/engine/llms/base_llm.py +0 -60
- agentscope_runtime/engine/llms/qwen_llm.py +0 -47
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +0 -22
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +0 -26
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +0 -422
- /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_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.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/base_client.py +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -11,6 +11,7 @@ from autogen_agentchat.messages import (
|
|
|
11
11
|
ModelClientStreamingChunkEvent,
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
+
from .utils import build_agent
|
|
14
15
|
from ..agents import Agent
|
|
15
16
|
from ..schemas.context import Context
|
|
16
17
|
from ..schemas.agent_schemas import (
|
|
@@ -136,20 +137,24 @@ class AutogenAgent(Agent):
|
|
|
136
137
|
"agent_config": self.agent_config,
|
|
137
138
|
"agent_builder": agent_builder,
|
|
138
139
|
}
|
|
139
|
-
self._agent = None
|
|
140
140
|
self.tools = tools
|
|
141
141
|
|
|
142
142
|
def copy(self) -> "AutogenAgent":
|
|
143
143
|
return AutogenAgent(**self._attr)
|
|
144
144
|
|
|
145
145
|
def build(self, as_context):
|
|
146
|
-
|
|
146
|
+
params = {
|
|
147
147
|
**self._attr["agent_config"],
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
**{
|
|
149
|
+
"model_client": as_context.model,
|
|
150
|
+
"tools": as_context.toolkit,
|
|
151
|
+
}, # Context will be added at `_agent.run_stream`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
builder_cls = self._attr["agent_builder"]
|
|
155
|
+
_agent = build_agent(builder_cls, params)
|
|
151
156
|
|
|
152
|
-
return
|
|
157
|
+
return _agent
|
|
153
158
|
|
|
154
159
|
async def run(self, context):
|
|
155
160
|
ag_context = AutogenContextAdapter(context=context, attr=self._attr)
|
|
@@ -157,9 +162,9 @@ class AutogenAgent(Agent):
|
|
|
157
162
|
|
|
158
163
|
# We should always build a new agent since the state is manage outside
|
|
159
164
|
# the agent
|
|
160
|
-
|
|
165
|
+
_agent = self.build(ag_context)
|
|
161
166
|
|
|
162
|
-
resp =
|
|
167
|
+
resp = _agent.run_stream(
|
|
163
168
|
task=ag_context.memory + [ag_context.new_message],
|
|
164
169
|
)
|
|
165
170
|
|
|
@@ -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,53 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import inspect
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_agent(builder_cls, params):
|
|
9
|
+
"""
|
|
10
|
+
Filters out unsupported parameters based on the __init__ signature of
|
|
11
|
+
builder_cls
|
|
12
|
+
and instantiates the class.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
builder_cls (type): The class to instantiate.
|
|
16
|
+
params (dict): Dictionary of parameters to pass to the constructor.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
object: An instance of builder_cls.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
# Get the signature of the __init__ method
|
|
23
|
+
sig = inspect.signature(builder_cls.__init__)
|
|
24
|
+
allowed_params = set(sig.parameters.keys())
|
|
25
|
+
# Remove 'self' from the list of allowed parameters
|
|
26
|
+
allowed_params.discard("self")
|
|
27
|
+
except (TypeError, ValueError):
|
|
28
|
+
# If signature cannot be inspected, allow all given params
|
|
29
|
+
allowed_params = set(params.keys())
|
|
30
|
+
|
|
31
|
+
filtered_params = {} # Parameters that are accepted by the constructor
|
|
32
|
+
unsupported = [] # Parameters that are not accepted
|
|
33
|
+
|
|
34
|
+
# Separate supported and unsupported parameters
|
|
35
|
+
for k, v in params.items():
|
|
36
|
+
if k in allowed_params:
|
|
37
|
+
filtered_params[k] = v
|
|
38
|
+
else:
|
|
39
|
+
unsupported.append(f"{k}={v!r}")
|
|
40
|
+
|
|
41
|
+
# Log a warning if there are unsupported parameters
|
|
42
|
+
if unsupported:
|
|
43
|
+
unsupported_str = ", ".join(unsupported)
|
|
44
|
+
logger.warning(
|
|
45
|
+
f"The following parameters are not supported by "
|
|
46
|
+
f"{builder_cls.__name__} and have been ignored: "
|
|
47
|
+
f"{unsupported_str}. If you require these parameters, "
|
|
48
|
+
f"please update the `__init__` method of "
|
|
49
|
+
f"{builder_cls.__name__} to accept and handle them.",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Instantiate the class with only supported parameters
|
|
53
|
+
return builder_cls(**filtered_params)
|
|
@@ -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
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import inspect
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Callable, Optional
|
|
6
|
+
|
|
7
|
+
import uvicorn
|
|
8
|
+
from fastapi import FastAPI, Request
|
|
9
|
+
from fastapi.responses import StreamingResponse
|
|
10
|
+
|
|
11
|
+
from .celery_mixin import CeleryMixin
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseApp(FastAPI, CeleryMixin):
|
|
17
|
+
"""
|
|
18
|
+
BaseApp extends FastAPI and integrates with Celery
|
|
19
|
+
for asynchronous background task execution.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
broker_url: Optional[str] = None,
|
|
25
|
+
backend_url: Optional[str] = None,
|
|
26
|
+
**kwargs,
|
|
27
|
+
):
|
|
28
|
+
# Initialize CeleryMixin
|
|
29
|
+
CeleryMixin.__init__(self, broker_url, backend_url)
|
|
30
|
+
|
|
31
|
+
self.server = None
|
|
32
|
+
|
|
33
|
+
# Initialize FastAPI
|
|
34
|
+
FastAPI.__init__(self, **kwargs)
|
|
35
|
+
|
|
36
|
+
def task(self, path: str, queue: str = "celery"):
|
|
37
|
+
"""
|
|
38
|
+
Register an asynchronous task endpoint.
|
|
39
|
+
POST <path> -> Create a task and return task ID
|
|
40
|
+
GET <path>/{task_id} -> Check the task status and result
|
|
41
|
+
Combines Celery and FastAPI routing functionality.
|
|
42
|
+
"""
|
|
43
|
+
if self.celery_app is None:
|
|
44
|
+
raise RuntimeError(
|
|
45
|
+
f"[AgentApp] Cannot register task endpoint '{path}'.\n"
|
|
46
|
+
f"Reason: The @task decorator requires a background task "
|
|
47
|
+
f"queue to run asynchronous jobs.\n\n"
|
|
48
|
+
"If you want to use async task queue, you must initialize "
|
|
49
|
+
"AgentApp with broker_url and backend_url, e.g.: \n\n"
|
|
50
|
+
" app = AgentApp(\n"
|
|
51
|
+
" broker_url='redis://localhost:6379/0',\n"
|
|
52
|
+
" backend_url='redis://localhost:6379/0'\n"
|
|
53
|
+
" )\n",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def decorator(func: Callable):
|
|
57
|
+
# Register Celery task using CeleryMixin
|
|
58
|
+
celery_task = self.register_celery_task(func, queue=queue)
|
|
59
|
+
|
|
60
|
+
# Add FastAPI HTTP routes
|
|
61
|
+
@self.post(path)
|
|
62
|
+
async def create_task(request: Request):
|
|
63
|
+
if len(inspect.signature(func).parameters) > 0:
|
|
64
|
+
body = await request.json()
|
|
65
|
+
task = celery_task.delay(body)
|
|
66
|
+
else:
|
|
67
|
+
task = celery_task.delay()
|
|
68
|
+
return {"task_id": task.id}
|
|
69
|
+
|
|
70
|
+
@self.get(path + "/{task_id}")
|
|
71
|
+
async def get_task(task_id: str):
|
|
72
|
+
return self.get_task_status(task_id)
|
|
73
|
+
|
|
74
|
+
return func
|
|
75
|
+
|
|
76
|
+
return decorator
|
|
77
|
+
|
|
78
|
+
def endpoint(self, path: str):
|
|
79
|
+
"""
|
|
80
|
+
Unified POST endpoint decorator.
|
|
81
|
+
Pure FastAPI functionality, independent of Celery.
|
|
82
|
+
Supports:
|
|
83
|
+
- Sync functions
|
|
84
|
+
- Async functions (coroutines)
|
|
85
|
+
- Sync/async generator functions (streaming responses)
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def decorator(func: Callable):
|
|
89
|
+
is_async_gen = inspect.isasyncgenfunction(func)
|
|
90
|
+
is_sync_gen = inspect.isgeneratorfunction(func)
|
|
91
|
+
|
|
92
|
+
if is_async_gen or is_sync_gen:
|
|
93
|
+
# Handle streaming responses
|
|
94
|
+
async def _stream_generator(request: Request):
|
|
95
|
+
if is_async_gen:
|
|
96
|
+
async for chunk in func(request):
|
|
97
|
+
yield chunk
|
|
98
|
+
else:
|
|
99
|
+
for chunk in func(request):
|
|
100
|
+
yield chunk
|
|
101
|
+
|
|
102
|
+
@self.post(path)
|
|
103
|
+
async def _wrapped(request: Request):
|
|
104
|
+
return StreamingResponse(
|
|
105
|
+
_stream_generator(request),
|
|
106
|
+
media_type="text/plain",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
# Handle regular responses
|
|
111
|
+
@self.post(path)
|
|
112
|
+
async def _wrapped(request: Request):
|
|
113
|
+
if inspect.iscoroutinefunction(func):
|
|
114
|
+
return await func(request)
|
|
115
|
+
else:
|
|
116
|
+
return func(request)
|
|
117
|
+
|
|
118
|
+
return func
|
|
119
|
+
|
|
120
|
+
return decorator
|
|
121
|
+
|
|
122
|
+
def run(
|
|
123
|
+
self,
|
|
124
|
+
host="0.0.0.0",
|
|
125
|
+
port=8090,
|
|
126
|
+
embed_task_processor=False,
|
|
127
|
+
**kwargs,
|
|
128
|
+
):
|
|
129
|
+
"""
|
|
130
|
+
Run FastAPI with uvicorn.
|
|
131
|
+
"""
|
|
132
|
+
if embed_task_processor:
|
|
133
|
+
if self.celery_app is None:
|
|
134
|
+
logger.warning(
|
|
135
|
+
"[AgentApp] Celery is not configured. "
|
|
136
|
+
"Cannot run embedded worker.",
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
logger.warning(
|
|
140
|
+
"[AgentApp] embed_task_processor=True: Running "
|
|
141
|
+
"task_processor in embedded thread mode. This is "
|
|
142
|
+
"intended for development/debug purposes only. In "
|
|
143
|
+
"production, run Celery worker in a separate process!",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
queues = self._registered_queues or {"celery"}
|
|
147
|
+
queue_list = ",".join(sorted(queues))
|
|
148
|
+
|
|
149
|
+
def start_celery_worker():
|
|
150
|
+
logger.info(
|
|
151
|
+
f"[AgentApp] Embedded worker listening "
|
|
152
|
+
f"queues: {queue_list}",
|
|
153
|
+
)
|
|
154
|
+
self.celery_app.worker_main(
|
|
155
|
+
[
|
|
156
|
+
"worker",
|
|
157
|
+
"--loglevel=INFO",
|
|
158
|
+
"-Q",
|
|
159
|
+
queue_list,
|
|
160
|
+
],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
threading.Thread(
|
|
164
|
+
target=start_celery_worker,
|
|
165
|
+
daemon=True,
|
|
166
|
+
).start()
|
|
167
|
+
logger.info(
|
|
168
|
+
"[AgentApp] Embedded task processor started in background "
|
|
169
|
+
"thread (DEV mode).",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# TODO: Add CLI to main entrypoint to control run/deploy
|
|
173
|
+
|
|
174
|
+
config = uvicorn.Config(
|
|
175
|
+
app=self,
|
|
176
|
+
host=host,
|
|
177
|
+
port=port,
|
|
178
|
+
**kwargs,
|
|
179
|
+
)
|
|
180
|
+
self.server = uvicorn.Server(config)
|
|
181
|
+
self.server.run()
|