cloudbase-agent-server 0.1.9__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.
- cloudbase_agent/__init__.py +8 -0
- cloudbase_agent/server/__init__.py +91 -0
- cloudbase_agent/server/app.py +371 -0
- cloudbase_agent/server/healthz/__init__.py +14 -0
- cloudbase_agent/server/healthz/handler.py +203 -0
- cloudbase_agent/server/healthz/models.py +132 -0
- cloudbase_agent/server/openai/__init__.py +14 -0
- cloudbase_agent/server/openai/converter.py +69 -0
- cloudbase_agent/server/openai/models.py +57 -0
- cloudbase_agent/server/openai/server.py +46 -0
- cloudbase_agent/server/send_message/__init__.py +20 -0
- cloudbase_agent/server/send_message/handler.py +146 -0
- cloudbase_agent/server/send_message/models.py +62 -0
- cloudbase_agent/server/send_message/server.py +93 -0
- cloudbase_agent/server/utils/__init__.py +21 -0
- cloudbase_agent/server/utils/converters.py +25 -0
- cloudbase_agent/server/utils/sse.py +32 -0
- cloudbase_agent/server/utils/types.py +73 -0
- cloudbase_agent_server-0.1.9.dist-info/METADATA +45 -0
- cloudbase_agent_server-0.1.9.dist-info/RECORD +21 -0
- cloudbase_agent_server-0.1.9.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Health Check Data Models.
|
|
4
|
+
|
|
5
|
+
This module defines the data models for health check configuration and responses.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Callable, Optional
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HealthzConfig(BaseModel):
|
|
14
|
+
"""Configuration for health check endpoint.
|
|
15
|
+
|
|
16
|
+
This class allows customization of the health check endpoint behavior,
|
|
17
|
+
including service metadata and custom health checks.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
service_name: Name of the service (default: "Cloudbase Agent-python-server")
|
|
21
|
+
version: Service version (default: "1.0.0")
|
|
22
|
+
include_agent_info: Whether to include agent information in response
|
|
23
|
+
custom_checks: Optional async function that performs additional health checks
|
|
24
|
+
Should return a dict of check results or raise an exception if unhealthy
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
Basic configuration::
|
|
28
|
+
|
|
29
|
+
config = HealthzConfig(
|
|
30
|
+
service_name="my-agent-service",
|
|
31
|
+
version="2.0.0"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
With custom checks::
|
|
35
|
+
|
|
36
|
+
async def check_database():
|
|
37
|
+
# Check database connection
|
|
38
|
+
if not await db.is_connected():
|
|
39
|
+
raise Exception("Database not connected")
|
|
40
|
+
return {"database": "connected"}
|
|
41
|
+
|
|
42
|
+
config = HealthzConfig(
|
|
43
|
+
service_name="my-service",
|
|
44
|
+
custom_checks=check_database
|
|
45
|
+
)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
service_name: str = Field(default="Cloudbase Agent-python-server", description="Name of the service")
|
|
49
|
+
|
|
50
|
+
version: str = Field(default="1.0.0", description="Service version")
|
|
51
|
+
|
|
52
|
+
include_agent_info: bool = Field(default=False, description="Whether to include agent information in the response")
|
|
53
|
+
|
|
54
|
+
custom_checks: Optional[Callable[[], dict[str, Any]]] = Field(
|
|
55
|
+
default=None, description="Optional function that performs custom health checks"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
class Config:
|
|
59
|
+
arbitrary_types_allowed = True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class HealthzResponse(BaseModel):
|
|
63
|
+
"""Health check response model.
|
|
64
|
+
|
|
65
|
+
This model defines the structure of health check responses,
|
|
66
|
+
following common health check patterns used in production systems.
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
status: Health status ("healthy" or "unhealthy")
|
|
70
|
+
timestamp: ISO 8601 timestamp of the health check
|
|
71
|
+
service: Service name
|
|
72
|
+
version: Service version
|
|
73
|
+
python_version: Python runtime version
|
|
74
|
+
base_path: Base path of the service
|
|
75
|
+
agent_info: Optional agent information (if enabled)
|
|
76
|
+
custom: Optional custom check results
|
|
77
|
+
error: Optional error message (if unhealthy)
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
Healthy response::
|
|
81
|
+
|
|
82
|
+
{
|
|
83
|
+
"status": "healthy",
|
|
84
|
+
"timestamp": "2024-01-01T00:00:00Z",
|
|
85
|
+
"service": "my-agent-service",
|
|
86
|
+
"version": "1.0.0",
|
|
87
|
+
"python_version": "3.11.0",
|
|
88
|
+
"base_path": "/api"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Unhealthy response::
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
"status": "unhealthy",
|
|
95
|
+
"timestamp": "2024-01-01T00:00:00Z",
|
|
96
|
+
"service": "my-agent-service",
|
|
97
|
+
"version": "1.0.0",
|
|
98
|
+
"python_version": "3.11.0",
|
|
99
|
+
"base_path": "/api",
|
|
100
|
+
"error": "Database connection failed"
|
|
101
|
+
}
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
status: str = Field(description="Health status: 'healthy' or 'unhealthy'")
|
|
105
|
+
|
|
106
|
+
timestamp: str = Field(description="ISO 8601 timestamp of the health check")
|
|
107
|
+
|
|
108
|
+
service: str = Field(description="Service name")
|
|
109
|
+
|
|
110
|
+
version: str = Field(description="Service version")
|
|
111
|
+
|
|
112
|
+
python_version: str = Field(description="Python runtime version")
|
|
113
|
+
|
|
114
|
+
base_path: str = Field(description="Base path of the service")
|
|
115
|
+
|
|
116
|
+
agent_info: Optional[dict[str, Any]] = Field(default=None, description="Optional agent information")
|
|
117
|
+
|
|
118
|
+
custom: Optional[dict[str, Any]] = Field(default=None, description="Optional custom check results")
|
|
119
|
+
|
|
120
|
+
error: Optional[str] = Field(default=None, description="Error message if unhealthy")
|
|
121
|
+
|
|
122
|
+
class Config:
|
|
123
|
+
json_schema_extra = {
|
|
124
|
+
"example": {
|
|
125
|
+
"status": "healthy",
|
|
126
|
+
"timestamp": "2024-01-01T00:00:00.000Z",
|
|
127
|
+
"service": "Cloudbase Agent-python-server",
|
|
128
|
+
"version": "1.0.0",
|
|
129
|
+
"python_version": "3.11.0",
|
|
130
|
+
"base_path": "/",
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""OpenAI Compatibility Module.
|
|
2
|
+
|
|
3
|
+
This module provides OpenAI-compatible chat completions endpoint implementation,
|
|
4
|
+
allowing Cloudbase Agent agents to be used as drop-in replacements for OpenAI models.
|
|
5
|
+
|
|
6
|
+
Available Functions:
|
|
7
|
+
- create_adapter: Create a FastAPI endpoint adapter for chat/completions
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .server import create_adapter
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"create_adapter",
|
|
14
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""OpenAI Format Converter.
|
|
4
|
+
|
|
5
|
+
This module provides conversion utilities between OpenAI API format
|
|
6
|
+
and Cloudbase Agent native format.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
|
|
11
|
+
from ..send_message.models import (
|
|
12
|
+
AssistantMessage,
|
|
13
|
+
RunAgentInput,
|
|
14
|
+
SystemMessage,
|
|
15
|
+
ToolMessage,
|
|
16
|
+
UserMessage,
|
|
17
|
+
)
|
|
18
|
+
from .models import OpenAIChatCompletionRequest
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def convert_openai_to_agkit(openai_request: OpenAIChatCompletionRequest) -> RunAgentInput:
|
|
22
|
+
"""Convert OpenAI chat completion request to Cloudbase Agent format.
|
|
23
|
+
|
|
24
|
+
This function transforms OpenAI-compatible request format into Cloudbase Agent's
|
|
25
|
+
native RunAgentInput format, enabling OpenAI API compatibility.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
openai_request: OpenAI-formatted chat completion request
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Cloudbase Agent formatted run agent input
|
|
32
|
+
"""
|
|
33
|
+
# Convert messages
|
|
34
|
+
agkit_messages = []
|
|
35
|
+
for msg in openai_request.messages:
|
|
36
|
+
msg_id = str(uuid4())
|
|
37
|
+
if msg.role == "system":
|
|
38
|
+
agkit_messages.append(SystemMessage(id=msg_id, role="system", content=msg.content or ""))
|
|
39
|
+
elif msg.role == "user":
|
|
40
|
+
agkit_messages.append(UserMessage(id=msg_id, role="user", content=msg.content or ""))
|
|
41
|
+
elif msg.role == "assistant":
|
|
42
|
+
agkit_messages.append(
|
|
43
|
+
AssistantMessage(
|
|
44
|
+
id=msg_id,
|
|
45
|
+
role="assistant",
|
|
46
|
+
content=msg.content,
|
|
47
|
+
tool_calls=msg.tool_calls,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
elif msg.role == "tool":
|
|
51
|
+
agkit_messages.append(
|
|
52
|
+
ToolMessage(
|
|
53
|
+
id=msg_id,
|
|
54
|
+
role="tool",
|
|
55
|
+
content=msg.content or "",
|
|
56
|
+
tool_call_id=msg.tool_call_id or "",
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Create Cloudbase Agent request
|
|
61
|
+
return RunAgentInput(
|
|
62
|
+
thread_id=str(uuid4()), # Generate new thread ID
|
|
63
|
+
run_id=str(uuid4()), # Generate new run ID
|
|
64
|
+
messages=agkit_messages,
|
|
65
|
+
tools=openai_request.tools or [],
|
|
66
|
+
context=[],
|
|
67
|
+
state={},
|
|
68
|
+
forwarded_props={},
|
|
69
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""OpenAI-Compatible Data Models.
|
|
4
|
+
|
|
5
|
+
This module defines data models for OpenAI-compatible API endpoints,
|
|
6
|
+
enabling Cloudbase Agent agents to work with OpenAI client libraries.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from ..send_message.models import Tool, ToolCall
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OpenAIMessage(BaseModel):
|
|
17
|
+
"""OpenAI-compatible message model.
|
|
18
|
+
|
|
19
|
+
:param role: Message role (system, user, assistant, tool)
|
|
20
|
+
:type role: str
|
|
21
|
+
:param content: Message content
|
|
22
|
+
:type content: Optional[str]
|
|
23
|
+
:param tool_calls: Tool calls made by assistant
|
|
24
|
+
:type tool_calls: Optional[List[ToolCall]]
|
|
25
|
+
:param tool_call_id: ID of tool call (for tool role messages)
|
|
26
|
+
:type tool_call_id: Optional[str]
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
role: str
|
|
30
|
+
content: Optional[str] = None
|
|
31
|
+
tool_calls: Optional[List[ToolCall]] = None
|
|
32
|
+
tool_call_id: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class OpenAIChatCompletionRequest(BaseModel):
|
|
36
|
+
"""OpenAI-compatible chat completion request model.
|
|
37
|
+
|
|
38
|
+
:param model: Model identifier (ignored, uses configured agent)
|
|
39
|
+
:type model: str
|
|
40
|
+
:param messages: List of conversation messages
|
|
41
|
+
:type messages: List[OpenAIMessage]
|
|
42
|
+
:param stream: Whether to stream the response
|
|
43
|
+
:type stream: bool
|
|
44
|
+
:param tools: Optional list of available tools
|
|
45
|
+
:type tools: Optional[List[Tool]]
|
|
46
|
+
:param temperature: Sampling temperature (ignored)
|
|
47
|
+
:type temperature: Optional[float]
|
|
48
|
+
:param max_tokens: Maximum tokens to generate (ignored)
|
|
49
|
+
:type max_tokens: Optional[int]
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
model: str
|
|
53
|
+
messages: List[OpenAIMessage]
|
|
54
|
+
stream: bool = True
|
|
55
|
+
tools: Optional[List[Tool]] = None
|
|
56
|
+
temperature: Optional[float] = None
|
|
57
|
+
max_tokens: Optional[int] = None
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""OpenAI-Compatible Server Adapter.
|
|
4
|
+
|
|
5
|
+
This module provides the server adapter for OpenAI-compatible chat/completions endpoint.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from fastapi.responses import StreamingResponse
|
|
9
|
+
|
|
10
|
+
from ..send_message.server import create_adapter as create_send_message_adapter
|
|
11
|
+
from ..utils.types import AgentCreator
|
|
12
|
+
from .converter import convert_openai_to_agkit
|
|
13
|
+
from .models import OpenAIChatCompletionRequest
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def create_adapter(create_agent: AgentCreator, request: OpenAIChatCompletionRequest) -> StreamingResponse:
|
|
17
|
+
"""Create a FastAPI adapter for OpenAI-compatible chat/completions requests.
|
|
18
|
+
|
|
19
|
+
This function provides OpenAI API compatibility by converting OpenAI-formatted
|
|
20
|
+
requests to Cloudbase Agent format and delegating to the send_message adapter.
|
|
21
|
+
|
|
22
|
+
:param create_agent: Function that creates and returns agent with optional cleanup
|
|
23
|
+
:type create_agent: AgentCreator
|
|
24
|
+
:param request: OpenAI-formatted chat completion request
|
|
25
|
+
:type request: OpenAIChatCompletionRequest
|
|
26
|
+
:return: Streaming response with SSE-formatted events
|
|
27
|
+
:rtype: StreamingResponse
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
Using the adapter in a FastAPI route::
|
|
31
|
+
|
|
32
|
+
from fastapi import FastAPI
|
|
33
|
+
from cloudbase_agent.server.openai import create_adapter
|
|
34
|
+
from cloudbase_agent.server.openai.models import OpenAIChatCompletionRequest
|
|
35
|
+
|
|
36
|
+
app = FastAPI()
|
|
37
|
+
|
|
38
|
+
@app.post("/chat/completions")
|
|
39
|
+
async def chat_completions(request: OpenAIChatCompletionRequest):
|
|
40
|
+
return await create_adapter(create_agent, request)
|
|
41
|
+
"""
|
|
42
|
+
# Convert OpenAI format to Cloudbase Agent format
|
|
43
|
+
agkit_request = convert_openai_to_agkit(request)
|
|
44
|
+
|
|
45
|
+
# Delegate to send_message adapter
|
|
46
|
+
return await create_send_message_adapter(create_agent, agkit_request)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Send Message Module.
|
|
2
|
+
|
|
3
|
+
This module provides the Cloudbase Agent native send_message endpoint implementation,
|
|
4
|
+
including request models, event handlers, and server adapters.
|
|
5
|
+
|
|
6
|
+
Available Functions:
|
|
7
|
+
- create_adapter: Create a FastAPI endpoint adapter for send_message
|
|
8
|
+
|
|
9
|
+
Available Models:
|
|
10
|
+
- RunAgentInput: Standard input format for agent requests
|
|
11
|
+
- Event types: TextMessageContentEvent, ToolCallStartEvent, etc.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .models import RunAgentInput
|
|
15
|
+
from .server import create_adapter
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"create_adapter",
|
|
19
|
+
"RunAgentInput",
|
|
20
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""HTTP Request Handler for Send Message.
|
|
4
|
+
|
|
5
|
+
This module provides HTTP protocol-level mapping and request handling
|
|
6
|
+
for the Cloudbase Agent send_message endpoint. It processes incoming requests,
|
|
7
|
+
converts between client and internal message formats, and manages agent
|
|
8
|
+
execution with real-time event streaming support.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any, AsyncGenerator
|
|
13
|
+
|
|
14
|
+
from ag_ui.core.events import EventType
|
|
15
|
+
|
|
16
|
+
from .models import (
|
|
17
|
+
Event,
|
|
18
|
+
TextMessageContentEvent,
|
|
19
|
+
ToolCallStartEvent,
|
|
20
|
+
ToolCallArgsEvent,
|
|
21
|
+
ToolCallEndEvent,
|
|
22
|
+
ToolCallResultEvent,
|
|
23
|
+
RunErrorEvent,
|
|
24
|
+
RunAgentInput,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def handler(input_data: RunAgentInput, agent: Any) -> AsyncGenerator[Event, None]:
|
|
31
|
+
"""Handle HTTP requests and process agent execution with streaming.
|
|
32
|
+
|
|
33
|
+
This function serves as the main request handler for the send_message endpoint.
|
|
34
|
+
It processes agent events and streams back properly formatted events conforming
|
|
35
|
+
to ag_ui_protocol standards.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
input_data: Standard run agent input containing messages and config
|
|
39
|
+
agent: The agent instance to execute (must have a 'run' method)
|
|
40
|
+
|
|
41
|
+
Yields:
|
|
42
|
+
Standard protocol events (TEXT_MESSAGE_CONTENT, TOOL_CALL_*, etc.)
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
RuntimeError: When agent execution or message processing fails
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
logger.info("Agent run started: run_id=%s thread_id=%s", input_data.run_id, input_data.thread_id)
|
|
49
|
+
|
|
50
|
+
event_count = 0
|
|
51
|
+
|
|
52
|
+
async for event in agent.run(input_data):
|
|
53
|
+
event_count += 1
|
|
54
|
+
|
|
55
|
+
# Handle different event types with raw_event tracking for delta extraction
|
|
56
|
+
if event.type == EventType.TEXT_MESSAGE_CONTENT:
|
|
57
|
+
# Extract delta from framework-specific raw event structure
|
|
58
|
+
# Priority: raw_event (for framework compatibility) -> event.delta (standard fallback)
|
|
59
|
+
content = None
|
|
60
|
+
|
|
61
|
+
if event.raw_event:
|
|
62
|
+
raw_data = event.raw_event.get("data", {})
|
|
63
|
+
chunk = raw_data.get("chunk", {})
|
|
64
|
+
content = chunk.get("content")
|
|
65
|
+
|
|
66
|
+
# Fallback to event.delta if raw_event extraction failed
|
|
67
|
+
if content is None:
|
|
68
|
+
content = getattr(event, "delta", None)
|
|
69
|
+
|
|
70
|
+
if content:
|
|
71
|
+
yield TextMessageContentEvent(
|
|
72
|
+
message_id=event.message_id,
|
|
73
|
+
delta=content,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
elif event.type == EventType.TEXT_MESSAGE_CHUNK:
|
|
77
|
+
# Handle text chunk events with fallback to event.delta
|
|
78
|
+
# Priority: raw_event (for framework compatibility) -> event.delta (standard fallback)
|
|
79
|
+
content = None
|
|
80
|
+
|
|
81
|
+
if event.raw_event:
|
|
82
|
+
raw_data = event.raw_event.get("data", {})
|
|
83
|
+
chunk = raw_data.get("chunk", {})
|
|
84
|
+
content = chunk.get("content")
|
|
85
|
+
|
|
86
|
+
# Fallback to event.delta if raw_event extraction failed
|
|
87
|
+
if content is None:
|
|
88
|
+
content = getattr(event, "delta", None)
|
|
89
|
+
|
|
90
|
+
if content:
|
|
91
|
+
yield TextMessageContentEvent(
|
|
92
|
+
message_id=event.message_id,
|
|
93
|
+
delta=content,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
elif event.type == EventType.TOOL_CALL_START:
|
|
97
|
+
# Emit tool call start event
|
|
98
|
+
yield ToolCallStartEvent(
|
|
99
|
+
tool_call_id=event.tool_call_id,
|
|
100
|
+
tool_call_name=event.tool_call_name, # ✓ Correct field name
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Some frameworks provide initial args in start event
|
|
104
|
+
start_delta = None
|
|
105
|
+
if event.raw_event is not None:
|
|
106
|
+
start_delta = (
|
|
107
|
+
event.raw_event.get("data", {})
|
|
108
|
+
.get("chunk", {})
|
|
109
|
+
.get("tool_call_chunks", [{}])[0]
|
|
110
|
+
.get("args")
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if start_delta is not None:
|
|
114
|
+
yield ToolCallArgsEvent(
|
|
115
|
+
tool_call_id=event.tool_call_id,
|
|
116
|
+
delta=start_delta,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
elif event.type in (EventType.TOOL_CALL_ARGS, EventType.TOOL_CALL_CHUNK):
|
|
120
|
+
# Handle streaming tool arguments
|
|
121
|
+
yield ToolCallArgsEvent(
|
|
122
|
+
tool_call_id=event.tool_call_id,
|
|
123
|
+
delta=event.delta,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
elif event.type == EventType.TOOL_CALL_END:
|
|
127
|
+
# Tool call arguments complete
|
|
128
|
+
yield ToolCallEndEvent(
|
|
129
|
+
tool_call_id=event.tool_call_id,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
elif event.type == EventType.TOOL_CALL_RESULT:
|
|
133
|
+
# Tool execution result
|
|
134
|
+
yield ToolCallResultEvent(
|
|
135
|
+
tool_call_id=event.tool_call_id,
|
|
136
|
+
result=event.content,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Note: CUSTOM events for interrupts are handled by ag_ui_protocol internally
|
|
140
|
+
|
|
141
|
+
logger.info("Agent run completed: run_id=%s total_events=%d", input_data.run_id, event_count)
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error("Agent run failed: run_id=%s error=%s", input_data.run_id, str(e))
|
|
145
|
+
yield RunErrorEvent(run_id=input_data.run_id, message=str(e))
|
|
146
|
+
raise RuntimeError(f"Failed to process agent request: {str(e)}") from e
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Send Message Data Models.
|
|
4
|
+
|
|
5
|
+
This module re-exports standard types from ag_ui_protocol for HTTP communication.
|
|
6
|
+
All types conform to ag_ui_protocol standards for interoperability.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ag_ui.core import (
|
|
10
|
+
# Input types
|
|
11
|
+
RunAgentInput,
|
|
12
|
+
Message,
|
|
13
|
+
Tool,
|
|
14
|
+
Context,
|
|
15
|
+
# Event types
|
|
16
|
+
Event,
|
|
17
|
+
TextMessageContentEvent,
|
|
18
|
+
ToolCallStartEvent,
|
|
19
|
+
ToolCallArgsEvent,
|
|
20
|
+
ToolCallEndEvent,
|
|
21
|
+
ToolCallResultEvent,
|
|
22
|
+
RunErrorEvent,
|
|
23
|
+
RunStartedEvent,
|
|
24
|
+
RunFinishedEvent,
|
|
25
|
+
# Supporting types
|
|
26
|
+
ToolCall,
|
|
27
|
+
FunctionCall,
|
|
28
|
+
# Message types
|
|
29
|
+
SystemMessage,
|
|
30
|
+
UserMessage,
|
|
31
|
+
ToolMessage,
|
|
32
|
+
AssistantMessage,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Legacy compatibility types (will be removed in future versions)
|
|
36
|
+
from typing import Union
|
|
37
|
+
|
|
38
|
+
ClientMessage = Union[SystemMessage, UserMessage, ToolMessage, AssistantMessage]
|
|
39
|
+
|
|
40
|
+
# Re-export for convenience
|
|
41
|
+
__all__ = [
|
|
42
|
+
"RunAgentInput",
|
|
43
|
+
"Message",
|
|
44
|
+
"Tool",
|
|
45
|
+
"Context",
|
|
46
|
+
"Event",
|
|
47
|
+
"TextMessageContentEvent",
|
|
48
|
+
"ToolCallStartEvent",
|
|
49
|
+
"ToolCallArgsEvent",
|
|
50
|
+
"ToolCallEndEvent",
|
|
51
|
+
"ToolCallResultEvent",
|
|
52
|
+
"RunErrorEvent",
|
|
53
|
+
"RunStartedEvent",
|
|
54
|
+
"RunFinishedEvent",
|
|
55
|
+
"ToolCall",
|
|
56
|
+
"FunctionCall",
|
|
57
|
+
"SystemMessage",
|
|
58
|
+
"UserMessage",
|
|
59
|
+
"ToolMessage",
|
|
60
|
+
"AssistantMessage",
|
|
61
|
+
"ClientMessage",
|
|
62
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Send Message Server Adapter.
|
|
4
|
+
|
|
5
|
+
This module provides the server adapter for the send_message endpoint,
|
|
6
|
+
handling request processing, streaming responses, and resource cleanup.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from typing import AsyncGenerator
|
|
13
|
+
|
|
14
|
+
from fastapi.responses import StreamingResponse
|
|
15
|
+
from pydantic import ValidationError
|
|
16
|
+
|
|
17
|
+
from ..utils.sse import async_generator_from_string
|
|
18
|
+
from ..utils.types import AgentCreator
|
|
19
|
+
from .handler import handler
|
|
20
|
+
from .models import RunAgentInput
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def create_adapter(create_agent: AgentCreator, request: RunAgentInput) -> StreamingResponse:
|
|
26
|
+
"""Create a FastAPI adapter for send_message requests with streaming support.
|
|
27
|
+
|
|
28
|
+
This function creates a streaming HTTP response adapter that processes
|
|
29
|
+
send_message requests and returns Server-Sent Events (SSE) formatted responses.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
create_agent: Function that creates and returns agent with optional cleanup
|
|
33
|
+
request: The validated request input containing messages and tools
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Streaming response with SSE-formatted events and proper headers
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValidationError: When request validation fails
|
|
40
|
+
Exception: When agent processing fails or other errors occur
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
# Create agent and get optional cleanup function
|
|
44
|
+
result = create_agent()
|
|
45
|
+
if inspect.iscoroutine(result):
|
|
46
|
+
result = await result
|
|
47
|
+
|
|
48
|
+
agent = result["agent"]
|
|
49
|
+
cleanup = result.get("cleanup")
|
|
50
|
+
|
|
51
|
+
async def create_sse_stream() -> AsyncGenerator[str, None]:
|
|
52
|
+
"""Create Server-Sent Events stream with cleanup support.
|
|
53
|
+
|
|
54
|
+
Yields:
|
|
55
|
+
SSE-formatted event strings
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
async for event in handler(request, agent):
|
|
59
|
+
# Use by_alias=True for camelCase conversion
|
|
60
|
+
sse_chunk = f"data: {event.model_dump_json(by_alias=True, exclude_none=True)}\n\n"
|
|
61
|
+
yield sse_chunk
|
|
62
|
+
|
|
63
|
+
yield "data: [DONE]\n\n"
|
|
64
|
+
finally:
|
|
65
|
+
# Ensure cleanup is called even if errors occur
|
|
66
|
+
if cleanup:
|
|
67
|
+
if inspect.iscoroutinefunction(cleanup):
|
|
68
|
+
await cleanup()
|
|
69
|
+
else:
|
|
70
|
+
cleanup()
|
|
71
|
+
|
|
72
|
+
return StreamingResponse(
|
|
73
|
+
create_sse_stream(),
|
|
74
|
+
media_type="text/event-stream",
|
|
75
|
+
headers={
|
|
76
|
+
"Cache-Control": "no-cache, no-transform",
|
|
77
|
+
"Connection": "keep-alive",
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
except ValidationError as e:
|
|
82
|
+
logger.error("Request validation failed: %s", str(e))
|
|
83
|
+
error_stream = async_generator_from_string(
|
|
84
|
+
f"data: {json.dumps({'error': 'Validation failed', 'details': str(e)})}\n\n"
|
|
85
|
+
)
|
|
86
|
+
return StreamingResponse(error_stream, media_type="text/event-stream")
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.error("Server error occurred: %s", str(e))
|
|
90
|
+
error_stream = async_generator_from_string(
|
|
91
|
+
f"data: {json.dumps({'error': 'Server error', 'details': str(e)})}\n\n"
|
|
92
|
+
)
|
|
93
|
+
return StreamingResponse(error_stream, media_type="text/event-stream")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Utility Functions Package.
|
|
2
|
+
|
|
3
|
+
This package provides common utility functions and types used across
|
|
4
|
+
the Cloudbase Agent server implementation.
|
|
5
|
+
|
|
6
|
+
Available Modules:
|
|
7
|
+
- types: Type definitions for agent creators and results
|
|
8
|
+
- sse: Server-Sent Events utilities
|
|
9
|
+
- converters: Utility functions for JSON validation
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .converters import is_valid_json
|
|
13
|
+
from .sse import async_generator_from_string
|
|
14
|
+
from .types import AgentCreator, AgentCreatorResult
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AgentCreator",
|
|
18
|
+
"AgentCreatorResult",
|
|
19
|
+
"is_valid_json",
|
|
20
|
+
"async_generator_from_string",
|
|
21
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Message and Tool Format Converters.
|
|
4
|
+
|
|
5
|
+
This module provides utility functions for working with ag_ui_protocol types.
|
|
6
|
+
Most conversion functions have been removed as ag_ui_protocol provides native types.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_valid_json(json_str: str) -> bool:
|
|
13
|
+
"""Check if string is valid JSON.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
json_str: String to validate
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
True if valid JSON, False otherwise
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
json.loads(json_str)
|
|
23
|
+
return True
|
|
24
|
+
except json.JSONDecodeError:
|
|
25
|
+
return False
|