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.
@@ -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