agentscope-runtime 0.1.5b2__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. agentscope_runtime/engine/agents/agentscope_agent.py +447 -0
  2. agentscope_runtime/engine/agents/agno_agent.py +19 -18
  3. agentscope_runtime/engine/agents/autogen_agent.py +13 -8
  4. agentscope_runtime/engine/agents/utils.py +53 -0
  5. agentscope_runtime/engine/deployers/__init__.py +0 -13
  6. agentscope_runtime/engine/deployers/local_deployer.py +501 -356
  7. agentscope_runtime/engine/helpers/helper.py +60 -41
  8. agentscope_runtime/engine/runner.py +11 -36
  9. agentscope_runtime/engine/schemas/agent_schemas.py +2 -70
  10. agentscope_runtime/engine/services/sandbox_service.py +62 -70
  11. agentscope_runtime/engine/services/tablestore_memory_service.py +304 -0
  12. agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
  13. agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
  14. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
  15. agentscope_runtime/sandbox/__init__.py +2 -0
  16. agentscope_runtime/sandbox/box/base/__init__.py +4 -0
  17. agentscope_runtime/sandbox/box/base/base_sandbox.py +4 -3
  18. agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
  19. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +8 -13
  20. agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
  21. agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
  22. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +8 -6
  23. agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
  24. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +80 -0
  25. agentscope_runtime/sandbox/box/sandbox.py +5 -2
  26. agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
  27. agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
  28. agentscope_runtime/sandbox/box/training_box/training_box.py +10 -15
  29. agentscope_runtime/sandbox/build.py +143 -58
  30. agentscope_runtime/sandbox/client/http_client.py +43 -49
  31. agentscope_runtime/sandbox/client/training_client.py +0 -1
  32. agentscope_runtime/sandbox/constant.py +24 -1
  33. agentscope_runtime/sandbox/custom/custom_sandbox.py +5 -5
  34. agentscope_runtime/sandbox/custom/example.py +2 -2
  35. agentscope_runtime/sandbox/enums.py +1 -0
  36. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +11 -6
  37. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +25 -9
  38. agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
  39. agentscope_runtime/sandbox/manager/container_clients/agentrun_client.py +1098 -0
  40. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +33 -205
  41. agentscope_runtime/sandbox/manager/container_clients/kubernetes_client.py +8 -555
  42. agentscope_runtime/sandbox/manager/sandbox_manager.py +187 -88
  43. agentscope_runtime/sandbox/manager/server/app.py +82 -14
  44. agentscope_runtime/sandbox/manager/server/config.py +50 -3
  45. agentscope_runtime/sandbox/model/container.py +6 -23
  46. agentscope_runtime/sandbox/model/manager_config.py +93 -5
  47. agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
  48. agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
  49. agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
  50. agentscope_runtime/sandbox/utils.py +124 -0
  51. agentscope_runtime/version.py +1 -1
  52. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/METADATA +168 -77
  53. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/RECORD +59 -78
  54. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/entry_points.txt +0 -1
  55. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
  56. agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
  57. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
  58. agentscope_runtime/engine/agents/llm_agent.py +0 -51
  59. agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +0 -2886
  60. agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +0 -51
  61. agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +0 -314
  62. agentscope_runtime/engine/deployers/cli_fc_deploy.py +0 -184
  63. agentscope_runtime/engine/deployers/kubernetes_deployer.py +0 -265
  64. agentscope_runtime/engine/deployers/modelstudio_deployer.py +0 -677
  65. agentscope_runtime/engine/deployers/utils/deployment_modes.py +0 -14
  66. agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +0 -8
  67. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +0 -429
  68. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +0 -240
  69. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +0 -297
  70. agentscope_runtime/engine/deployers/utils/package_project_utils.py +0 -932
  71. agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +0 -9
  72. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +0 -504
  73. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +0 -157
  74. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +0 -268
  75. agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +0 -75
  76. agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +0 -220
  77. agentscope_runtime/engine/deployers/utils/wheel_packager.py +0 -389
  78. agentscope_runtime/engine/helpers/agent_api_builder.py +0 -651
  79. agentscope_runtime/engine/llms/__init__.py +0 -3
  80. agentscope_runtime/engine/llms/base_llm.py +0 -60
  81. agentscope_runtime/engine/llms/qwen_llm.py +0 -47
  82. agentscope_runtime/engine/schemas/embedding.py +0 -37
  83. agentscope_runtime/engine/schemas/modelstudio_llm.py +0 -310
  84. agentscope_runtime/engine/schemas/oai_llm.py +0 -538
  85. agentscope_runtime/engine/schemas/realtime.py +0 -254
  86. /agentscope_runtime/engine/{deployers/adapter/responses → services/utils}/__init__.py +0 -0
  87. /agentscope_runtime/{engine/deployers/utils → sandbox/box/gui/box}/__init__.py +0 -0
  88. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/WHEEL +0 -0
  89. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/licenses/LICENSE +0 -0
  90. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/top_level.txt +0 -0
@@ -1,538 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # pylint:disable=not-an-iterable, redefined-builtin
3
-
4
- import json
5
- import time
6
- import uuid
7
- from typing import Dict, List, Optional, Union
8
-
9
- from openai.types.chat import ChatCompletion, ChatCompletionChunk
10
- from openai.types.chat.chat_completion_stream_options_param import (
11
- ChatCompletionStreamOptionsParam,
12
- )
13
- from pydantic import BaseModel, Field, model_validator
14
- from typing_extensions import Annotated, Literal
15
-
16
- from .agent_schemas import Role, Tool, FunctionCall
17
-
18
-
19
- def generate_tool_call_id(prefix: str = "call_") -> str:
20
- # generate a random uuid
21
- random_uuid = uuid.uuid4()
22
- # replace uuid to string and remove '-', then get latest 22 characters
23
- random_part = str(random_uuid).replace("-", "")[:22]
24
- # add prefix
25
- tool_call_id = f"{prefix}{random_part}"
26
- return tool_call_id
27
-
28
-
29
- class ImageMessageContent(BaseModel):
30
- class ImageUrl(BaseModel):
31
- """
32
- Model class for image prompt message content.
33
- """
34
-
35
- url: str
36
- """Either a URL of the image or the base64 encoded image data."""
37
-
38
- detail: Literal["auto", "low", "high"] = "low"
39
- """Specifies the detail level of the image."""
40
-
41
- type: Literal["image_url"] = "image_url"
42
- """The type of the content part."""
43
-
44
- image_url: ImageUrl
45
- """The image URL details."""
46
-
47
-
48
- class TextMessageContent(BaseModel):
49
- type: Literal["text"] = "text"
50
- """The type of the content part."""
51
-
52
- text: str
53
- """The text content."""
54
-
55
-
56
- class AudioMessageContent(BaseModel):
57
- class InputAudioDetail(BaseModel):
58
- """
59
- Model class for audio prompt message content.
60
- """
61
-
62
- base64_data: str = Field(
63
- default="",
64
- description="the base64 data of multi-modal file",
65
- )
66
- """The base64 encoded audio data."""
67
-
68
- format: str = Field(
69
- default="mp3",
70
- description="The format of the encoded audio data. supports "
71
- "'wav' and 'mp3'.",
72
- )
73
- """The format of the encoded audio data. Supports 'wav' and 'mp3'."""
74
-
75
- @property
76
- def data(self) -> str:
77
- return f"data:{self.format};base64,{self.base64_data}"
78
-
79
- type: Literal["input_audio"] = "input_audio"
80
- """The type of the content part."""
81
-
82
- input_audio: InputAudioDetail
83
- """The input audio details."""
84
-
85
-
86
- ChatCompletionMessage = Annotated[
87
- Union[TextMessageContent, ImageMessageContent, AudioMessageContent],
88
- Field(discriminator="type"),
89
- ]
90
-
91
-
92
- class ToolCall(BaseModel):
93
- """
94
- Model class for assistant prompt message tool call.
95
- """
96
-
97
- index: int = 0
98
- """The index of the tool call in the tool calls array."""
99
-
100
- id: str
101
- """The ID of the tool call."""
102
-
103
- type: Optional[str] = None
104
- """The type of the tool. Currently, only `function` is supported."""
105
-
106
- function: FunctionCall
107
- """The function that the model called."""
108
-
109
-
110
- class OpenAIMessage(BaseModel):
111
- """
112
- Model class for prompt message.
113
- """
114
-
115
- role: str
116
- """The role of the messages author, should be in `user`,`system`,
117
- 'assistant', 'tool'."""
118
-
119
- content: Optional[Union[str, List[ChatCompletionMessage]]] = None
120
- """The contents of the message.
121
-
122
- Can be a string, a list of content parts for multimodal messages.
123
- """
124
-
125
- name: Optional[str] = None
126
- """An optional name for the participant.
127
-
128
- Provides the model information to differentiate between participants of the
129
- same role.
130
- """
131
-
132
- tool_calls: Optional[List[ToolCall]] = None
133
- """The tool calls generated by the model, such as function calls."""
134
-
135
- def get_text_content(self) -> Optional[str]:
136
- """
137
- Extract the first text content from the message.
138
-
139
- :return: First text string found in the content, or None if no text
140
- content
141
- """
142
- if self.content is None:
143
- return None
144
-
145
- # Case 1: content is a simple string
146
- if isinstance(self.content, str):
147
- return self.content
148
- # Case 2: content is a list
149
- elif isinstance(self.content, list):
150
- for item in self.content:
151
- if hasattr(item, "type"):
152
- if item.type == "text" and hasattr(item, "text"):
153
- return item.text
154
- return None
155
-
156
- def get_image_content(self) -> List[str]:
157
- """
158
- Extract all image content (URLs or base64 data) from the message.
159
-
160
- :return: List of image URLs or base64 encoded strings found in the
161
- content
162
- """
163
- images = []
164
-
165
- if self.content is None:
166
- return images
167
-
168
- # Case 1: content is a simple string - no images
169
- if isinstance(self.content, str):
170
- return images
171
- # Case 2: content is a list
172
- elif isinstance(self.content, list):
173
- for item in self.content:
174
- if hasattr(item, "type"):
175
- if item.type == "image_url" and hasattr(item, "image_url"):
176
- if hasattr(item.image_url, "url"):
177
- images.append(item.image_url.url)
178
-
179
- return images
180
-
181
- def get_audio_content(self) -> List[str]:
182
- """
183
- Extract all audio content (URLs or base64 data) from the message.
184
-
185
- :return: List of audio URLs or base64 encoded strings found in the
186
- content
187
- """
188
- audios = []
189
-
190
- if self.content is None:
191
- return audios
192
-
193
- # Case 1: content is a simple string - no audios
194
- if isinstance(self.content, str):
195
- return audios
196
- # Case 2: content is a list
197
- elif isinstance(self.content, list):
198
- for item in self.content:
199
- if hasattr(item, "type"):
200
- if item.type == "input_audio" and hasattr(
201
- item,
202
- "input_audio",
203
- ):
204
- if hasattr(item.input_audio, "data"):
205
- audios.append(item.input_audio.data)
206
- elif hasattr(item.input_audio, "base64_data"):
207
- # Construct data URL for audio
208
- format_type = getattr(
209
- item.input_audio,
210
- "format",
211
- "mp3",
212
- )
213
- audios.append(
214
- f"data:{format_type};base64,"
215
- f"{item.input_audio.base64_data}",
216
- )
217
-
218
- return audios
219
-
220
- def has_multimodal_content(self) -> bool:
221
- """
222
- Check if the message contains multimodal content (images, audio,
223
- or video).
224
-
225
- :return: True if the message contains non-text content, False otherwise
226
- """
227
- return bool(
228
- self.get_image_content() or self.get_audio_content(),
229
- )
230
-
231
- def get_content_summary(self) -> Dict[str, int]:
232
- """
233
- Get a summary of different content types in the message.
234
-
235
- :return: Dictionary with counts of different content types
236
- """
237
- return {
238
- "text_count": 1 if self.get_text_content() is not None else 0,
239
- "image_count": len(self.get_image_content()),
240
- "audio_count": len(self.get_audio_content()),
241
- }
242
-
243
-
244
- class UserMessage(OpenAIMessage):
245
- """
246
- Model class for user prompt message.
247
- """
248
-
249
- role: str = Role.USER
250
- """The role of the messages author, in this case `user`."""
251
-
252
-
253
- class AssistantMessage(OpenAIMessage):
254
- """
255
- Model class for assistant prompt message.
256
- """
257
-
258
- role: str = Role.ASSISTANT
259
- """The role of the messages author, in this case `assistant`."""
260
-
261
-
262
- class SystemMessage(OpenAIMessage):
263
- """
264
- Model class for system prompt message.
265
- """
266
-
267
- role: str = Role.SYSTEM
268
- """The role of the messages author, in this case `system`."""
269
-
270
-
271
- class ToolMessage(OpenAIMessage):
272
- """
273
- Model class for tool prompt message.
274
- """
275
-
276
- role: str = Role.TOOL
277
- """The role of the messages author, in this case `tool`."""
278
-
279
- tool_call_id: str
280
- """Tool call that this message is responding to."""
281
-
282
-
283
- class ResponseFormat(BaseModel):
284
- class JsonSchema(BaseModel):
285
- name: str
286
- """The name of the response format. """
287
-
288
- description: Union[str, None] = None
289
- """A description of what the response format is for, used by the
290
- model to determine how to respond in the format.
291
- """
292
-
293
- schema_param: dict = Field(None, alias="schema")
294
- """The schema for the response format, described as a JSON Schema
295
- object."""
296
-
297
- strict: Union[bool, None] = False
298
- """Whether to enable strict schema adherence when generating the output
299
-
300
- If set to true, the model will follow the exact schema defined in the
301
- `schema` field. Only a subset of JSON Schema is supported when `strict`
302
- is `true`. Learn more about Structured Outputs in the
303
- [function calling guide](docs/guides/function-calling).
304
- """
305
-
306
- type: Literal["text", "json_object", "json_schema"] = "text"
307
- """The type of response format being defined.
308
-
309
- - `text`: The default response format, which can be either text or any
310
- value needed.
311
- - `json_object`: Enables JSON mode, which guarantees the message the model
312
- generates is valid JSON.
313
- - `json_schema`: Enables Structured Outputs which guarantees the model will
314
- match your supplied JSON schema.
315
- """
316
-
317
- json_schema: Optional[JsonSchema] = None
318
- """The JSON schema for the response format."""
319
-
320
- @model_validator(mode="before")
321
- def validate_schema(self, values: dict) -> dict:
322
- if not isinstance(values, dict) or "type" not in values:
323
- raise ValueError(f"Json schema not valid with type {type(values)}")
324
- format_type = values.get("type")
325
- json_schema = values.get("json_schema")
326
-
327
- if format_type in ["text", "json_object"] and json_schema is not None:
328
- raise ValueError(
329
- f"Json schema is not allowed for type {format_type}",
330
- )
331
-
332
- if format_type == "json_schema":
333
- if json_schema is None:
334
- raise ValueError(
335
- f"Json schema is required for type {format_type}",
336
- )
337
- return values
338
-
339
-
340
- class ToolChoiceInputFunction(BaseModel):
341
- name: str
342
- """The name of the function to call."""
343
-
344
-
345
- class ToolChoice(BaseModel):
346
- type: str
347
- """The type of the tool. Currently, only `function` is supported."""
348
-
349
- function: ToolChoiceInputFunction
350
- """The function that the model called."""
351
-
352
-
353
- class Parameters(BaseModel):
354
- """
355
- General Parameters for LLM
356
- """
357
-
358
- top_p: Optional[float] = None
359
- """Nucleus sampling, between (0, 1.0], where the model considers the
360
- results of the tokens with top_p probability mass.
361
-
362
- So 0.1 means only the tokens comprising the top 10% probability mass are
363
- considered.
364
-
365
- We generally recommend altering this or `temperature` but not both.
366
- """
367
-
368
- temperature: Optional[float] = None
369
- """What sampling temperature to use, between 0 and 2.
370
-
371
- Higher values like 0.8 will make the output more random, while lower values
372
- like 0.2 will make it more focused and deterministic.
373
-
374
- We generally recommend altering this or `top_p` but not both.
375
- """
376
-
377
- frequency_penalty: Optional[float] = None
378
- """Positive values penalize new tokens based on their existing frequency in
379
- the text so far, decreasing the model's likelihood to repeat the same line
380
- verbatim.
381
-
382
- """
383
-
384
- presence_penalty: Optional[float] = None
385
- """Number between -2.0 and 2.0.
386
-
387
- Positive values penalize new tokens based on whether they appear in the
388
- text so far, increasing the model's likelihood to talk about new topics.
389
-
390
- """
391
-
392
- max_tokens: Optional[int] = None
393
- """The maximum number of [tokens](/tokenizer) that can be generated in the
394
- chat completion.
395
-
396
- The total length of input tokens and generated tokens is limited by the
397
- model's context length.
398
- """
399
-
400
- stop: Optional[Union[Optional[str], List[str]]] = None
401
- """Up to 4 sequences where the API will stop generating further tokens."""
402
-
403
- stream: bool = True
404
- """If set, partial message deltas will be sent, like in ChatGPT. """
405
-
406
- stream_options: Optional[ChatCompletionStreamOptionsParam] = None
407
- """Options for streaming response. Only set this when you set
408
- `stream: true`."""
409
-
410
- tools: Optional[List[Union[Tool, Dict]]] = None
411
- """A list of tools the model may call.
412
-
413
- Currently, only functions are supported as a tool. Use this to provide a
414
- list of functions the model may generate JSON inputs for.
415
- """
416
-
417
- tool_choice: Optional[Union[str, ToolChoice]] = None
418
- """Controls which (if any) tool is called by the model.
419
-
420
- """
421
-
422
- parallel_tool_calls: bool = False
423
- """Whether to enable parallel function calling during tool use."""
424
-
425
- logit_bias: Optional[Dict[str, int]] = None
426
- """Modify the likelihood of specified tokens appearing in the completion.
427
-
428
- Accepts a JSON object that maps tokens (specified by their token ID in the
429
- tokenizer) to an associated bias value from -100 to 100. Mathematically,
430
- the bias is added to the logits generated by the model prior to
431
- sampling. The exact effect will vary per model, but values between -1
432
- and 1 should decrease or increase likelihood of selection; values like
433
- -100 or 100 should result in a ban or exclusive selection of the relevant
434
- token.
435
- """
436
-
437
- top_logprobs: Optional[int] = None
438
- """An integer between 0 and 20 specifying the number of most likely
439
- tokens to return at each token position, each with an associated log
440
- probability.
441
-
442
- `logprobs` must be set to `true` if this parameter is used.
443
- """
444
-
445
- logprobs: Optional[bool] = None
446
- """Whether to return log probabilities of the output tokens or not.
447
-
448
- If true, returns the log probabilities of each output token returned in the
449
- `content` of `message`.
450
- """
451
-
452
- n: Optional[int] = Field(default=1, ge=1, le=5)
453
- """How many chat completion choices to generate for each input message.
454
-
455
- Note that you will be charged based on the number of generated tokens
456
- across all of the choices. Keep `n` as `1` to minimize costs.
457
- """
458
-
459
- seed: Optional[int] = None
460
- """If specified, system will make a best effort to sample
461
- deterministically, such that repeated requests with the same `seed` and
462
- parameters should return the same result.
463
- """
464
-
465
- response_format: Optional[Union[ResponseFormat, str]] = ResponseFormat(
466
- type="text",
467
- )
468
- """An object specifying the format that the model must output.
469
-
470
- Setting to `{ "type": "json_object" }` enables JSON mode,
471
- which guarantees the message the model generates is valid JSON.
472
- """
473
-
474
-
475
- def create_chat_completion(
476
- message: OpenAIMessage,
477
- model_name: str,
478
- id: str = "",
479
- finish_reason: Optional[str] = None,
480
- ) -> ChatCompletion:
481
- # Create Choice object
482
- choice = {
483
- "finish_reason": finish_reason,
484
- "index": 0,
485
- "message": message.model_dump(),
486
- "logprobs": None,
487
- }
488
-
489
- # Construct ChatCompletion object
490
- return ChatCompletion(
491
- id=id, # Generate unique ID
492
- choices=[choice], # List containing at least one Choice
493
- created=int(time.time()), # Current timestamp
494
- model=model_name, # Adjust based on actual model used
495
- object="chat.completion", # Fixed literal value
496
- # Optional fields below
497
- service_tier=None,
498
- system_fingerprint=None,
499
- usage=None,
500
- )
501
-
502
-
503
- def create_chat_completion_chunk(
504
- message: OpenAIMessage,
505
- model_name: str,
506
- id: str = "",
507
- finish_reason: Optional[str] = None,
508
- ) -> ChatCompletionChunk:
509
- # Create Choice object for chunk
510
- choice = {
511
- "finish_reason": finish_reason,
512
- "index": 0,
513
- "logprobs": None,
514
- "delta": message.model_dump(),
515
- }
516
-
517
- # Construct ChatCompletionChunk object
518
- return ChatCompletionChunk(
519
- id=id, # Generate unique ID
520
- choices=[choice], # List containing at least one Choice
521
- created=int(time.time()), # Current timestamp
522
- model=model_name, # Adjust based on actual model used
523
- object="chat.completion.chunk", # Fixed literal value
524
- # Optional fields below
525
- service_tier=None,
526
- system_fingerprint=None,
527
- usage=None,
528
- )
529
-
530
-
531
- def is_json_string(s: Union[str, Dict, BaseModel, None]) -> bool:
532
- try:
533
- obj = json.loads(s) # type: ignore[arg-type]
534
- if isinstance(obj, (dict, list)):
535
- return True
536
- return False
537
- except Exception:
538
- return False