diff --git a/agentrun/integration/utils/tool.py b/agentrun/integration/utils/tool.py index 5b0e874..bde72a5 100644 --- a/agentrun/integration/utils/tool.py +++ b/agentrun/integration/utils/tool.py @@ -1562,8 +1562,8 @@ def _build_openapi_schema( if isinstance(schema, dict): properties[name] = { **schema, - "description": ( - param.get("description") or schema.get("description", "") + "description": param.get("description") or schema.get( + "description", "" ), } if param.get("required"): diff --git a/agentrun/sandbox/__sandbox_async_template.py b/agentrun/sandbox/__sandbox_async_template.py index 734214f..3f555c0 100644 --- a/agentrun/sandbox/__sandbox_async_template.py +++ b/agentrun/sandbox/__sandbox_async_template.py @@ -15,6 +15,8 @@ Union, ) +from pydantic import Field + from agentrun.sandbox.model import TemplateType from agentrun.utils.config import Config from agentrun.utils.model import BaseModel @@ -56,7 +58,9 @@ class Sandbox(BaseModel): """沙箱全局唯一资源名称 / Sandbox ARN""" sandbox_id: Optional[str] = None """沙箱 ID / Sandbox ID""" - sandbox_idle_ttlin_seconds: Optional[int] = None + sandbox_idle_ttlin_seconds: Optional[int] = Field( + None, alias="sandboxIdleTTLInSeconds" + ) """沙箱空闲 TTL(秒) / Sandbox Idle TTL (seconds)""" sandbox_idle_timeout_seconds: Optional[int] = None """沙箱空闲超时时间(秒) / Sandbox Idle Timeout (seconds)""" diff --git a/agentrun/sandbox/__template_async_template.py b/agentrun/sandbox/__template_async_template.py index bcebe07..2042515 100644 --- a/agentrun/sandbox/__template_async_template.py +++ b/agentrun/sandbox/__template_async_template.py @@ -6,6 +6,8 @@ from typing import Dict, List, Optional +from pydantic import Field + from agentrun.sandbox.model import ( PageableInput, TemplateContainerConfiguration, @@ -52,7 +54,9 @@ class Template(BaseModel): """执行角色 ARN / Execution Role ARN""" sandbox_idle_timeout_in_seconds: Optional[int] = None """沙箱空闲超时时间(秒) / Sandbox Idle Timeout (seconds)""" - sandbox_ttlin_seconds: Optional[int] = None + sandbox_ttlin_seconds: Optional[int] = Field( + None, alias="sandboxTTLInSeconds" + ) """沙箱存活时间(秒) / Sandbox TTL (seconds)""" share_concurrency_limit_per_sandbox: Optional[int] = None """每个沙箱的最大并发会话数 / Max Concurrency Limit Per Sandbox""" diff --git a/agentrun/sandbox/model.py b/agentrun/sandbox/model.py index f79e1b2..b392616 100644 --- a/agentrun/sandbox/model.py +++ b/agentrun/sandbox/model.py @@ -8,7 +8,7 @@ from typing import Any, Dict, List, Optional, TYPE_CHECKING import uuid -from pydantic import model_validator +from pydantic import Field, model_validator from agentrun.utils.model import BaseModel @@ -264,7 +264,9 @@ class TemplateInput(BaseModel): """执行角色 ARN / Execution Role ARN""" sandbox_idle_timeout_in_seconds: Optional[int] = 1800 """沙箱空闲超时时间(秒) / Sandbox Idle Timeout (seconds)""" - sandbox_ttlin_seconds: Optional[int] = 21600 + sandbox_ttlin_seconds: Optional[int] = Field( + 21600, alias="sandboxTTLInSeconds" + ) """沙箱存活时间(秒) / Sandbox TTL (seconds)""" share_concurrency_limit_per_sandbox: Optional[int] = 200 """每个沙箱的最大并发会话数 / Max Concurrency Limit Per Sandbox""" diff --git a/agentrun/sandbox/sandbox.py b/agentrun/sandbox/sandbox.py index 3bf229a..0b6dafd 100644 --- a/agentrun/sandbox/sandbox.py +++ b/agentrun/sandbox/sandbox.py @@ -25,6 +25,8 @@ Union, ) +from pydantic import Field + from agentrun.sandbox.model import TemplateType from agentrun.utils.config import Config from agentrun.utils.model import BaseModel @@ -66,7 +68,9 @@ class Sandbox(BaseModel): """沙箱全局唯一资源名称 / Sandbox ARN""" sandbox_id: Optional[str] = None """沙箱 ID / Sandbox ID""" - sandbox_idle_ttlin_seconds: Optional[int] = None + sandbox_idle_ttlin_seconds: Optional[int] = Field( + None, alias="sandboxIdleTTLInSeconds" + ) """沙箱空闲 TTL(秒) / Sandbox Idle TTL (seconds)""" sandbox_idle_timeout_seconds: Optional[int] = None """沙箱空闲超时时间(秒) / Sandbox Idle Timeout (seconds)""" diff --git a/agentrun/sandbox/template.py b/agentrun/sandbox/template.py index f5bbb4d..3203c14 100644 --- a/agentrun/sandbox/template.py +++ b/agentrun/sandbox/template.py @@ -16,6 +16,8 @@ from typing import Dict, List, Optional +from pydantic import Field + from agentrun.sandbox.model import ( PageableInput, TemplateContainerConfiguration, @@ -62,7 +64,9 @@ class Template(BaseModel): """执行角色 ARN / Execution Role ARN""" sandbox_idle_timeout_in_seconds: Optional[int] = None """沙箱空闲超时时间(秒) / Sandbox Idle Timeout (seconds)""" - sandbox_ttlin_seconds: Optional[int] = None + sandbox_ttlin_seconds: Optional[int] = Field( + None, alias="sandboxTTLInSeconds" + ) """沙箱存活时间(秒) / Sandbox TTL (seconds)""" share_concurrency_limit_per_sandbox: Optional[int] = None """每个沙箱的最大并发会话数 / Max Concurrency Limit Per Sandbox""" diff --git a/tests/unittests/integration/test_langchain_agui_integration.py b/tests/unittests/integration/test_langchain_agui_integration.py index b5dba63..6cfb32b 100644 --- a/tests/unittests/integration/test_langchain_agui_integration.py +++ b/tests/unittests/integration/test_langchain_agui_integration.py @@ -664,9 +664,7 @@ async def invoke_agent(request: AgentRequest): json={ "messages": [{ "role": "user", - "content": ( - "查询当前的时间,并获取天气信息,同时输出我的密钥信息" - ), + "content": "查询当前的时间,并获取天气信息,同时输出我的密钥信息", }], "stream": True, }, @@ -729,9 +727,7 @@ async def invoke_agent(request: AgentRequest): json={ "messages": [{ "role": "user", - "content": ( - "查询当前的时间,并获取天气信息,同时输出我的密钥信息" - ), + "content": "查询当前的时间,并获取天气信息,同时输出我的密钥信息", }], "stream": True, }, diff --git a/tests/unittests/toolset/api/test_openapi.py b/tests/unittests/toolset/api/test_openapi.py index ab9acc6..3a60866 100644 --- a/tests/unittests/toolset/api/test_openapi.py +++ b/tests/unittests/toolset/api/test_openapi.py @@ -545,9 +545,7 @@ def test_post_with_ref_schema(self): "content": { "application/json": { "schema": { - "$ref": ( - "#/components/schemas/CreateOrderRequest" - ) + "$ref": "#/components/schemas/CreateOrderRequest" } } }, @@ -758,9 +756,7 @@ def test_invalid_ref_gracefully_handled(self): "content": { "application/json": { "schema": { - "$ref": ( - "#/components/schemas/NonExistent" - ) + "$ref": "#/components/schemas/NonExistent" } } } @@ -793,9 +789,7 @@ def test_external_ref_not_resolved(self): "content": { "application/json": { "schema": { - "$ref": ( - "https://example.com/schemas/external.json" - ) + "$ref": "https://example.com/schemas/external.json" } } } @@ -915,9 +909,7 @@ def _get_coffee_shop_schema(): "content": { "application/json": { "schema": { - "$ref": ( - "#/components/schemas/CreateOrderRequest" - ) + "$ref": "#/components/schemas/CreateOrderRequest" } } }, @@ -953,9 +945,7 @@ def _get_coffee_shop_schema(): "content": { "application/json": { "schema": { - "$ref": ( - "#/components/schemas/UpdateOrderStatusRequest" - ) + "$ref": "#/components/schemas/UpdateOrderStatusRequest" } } }, diff --git a/tests/unittests/utils/test_model.py b/tests/unittests/utils/test_model.py index b74399c..ab02dcc 100644 --- a/tests/unittests/utils/test_model.py +++ b/tests/unittests/utils/test_model.py @@ -204,3 +204,81 @@ def test_is_final_instance_method(self): """测试实例方法 is_final""" assert Status.READY.is_final() is True assert Status.CREATING.is_final() is False + + +class TestTTLAliasFixIssue53: + """测试 TTL 字段的显式 alias 修复 (Issue #53) + + 验证含有连续大写缩写词 (TTL) 的字段能正确从 API 返回的 camelCase key 解析。 + """ + + def test_template_sandbox_ttlin_seconds_from_api_data(self): + """Template.sandbox_ttlin_seconds 应能通过 sandboxTTLInSeconds 正确解析""" + from agentrun.sandbox.template import Template + + api_data = { + "templateName": "code-interpreter-01", + "sandboxIdleTimeoutInSeconds": 900, + "sandboxTTLInSeconds": 3600, + } + t = Template.model_validate(api_data, by_alias=True) + assert t.sandbox_idle_timeout_in_seconds == 900 + assert t.sandbox_ttlin_seconds == 3600 + assert t.model_extra.get("sandboxTTLInSeconds") is None + + def test_template_sandbox_ttlin_seconds_by_field_name(self): + """Template.sandbox_ttlin_seconds 也应支持通过字段名直接赋值""" + from agentrun.sandbox.template import Template + + t = Template(sandbox_ttlin_seconds=1800) + assert t.sandbox_ttlin_seconds == 1800 + + def test_template_sandbox_ttlin_seconds_serialization(self): + """Template 序列化时应使用正确的 alias sandboxTTLInSeconds""" + from agentrun.sandbox.template import Template + + t = Template(sandbox_ttlin_seconds=7200) + data = t.model_dump(by_alias=True) + assert data["sandboxTTLInSeconds"] == 7200 + + def test_template_input_sandbox_ttlin_seconds_serialization(self): + """TemplateInput.sandbox_ttlin_seconds 序列化应使用 sandboxTTLInSeconds""" + from agentrun.sandbox.model import TemplateInput, TemplateType + + inp = TemplateInput( + template_type=TemplateType.CODE_INTERPRETER, + sandbox_ttlin_seconds=600, + ) + data = inp.model_dump(by_alias=True) + assert data["sandboxTTLInSeconds"] == 600 + + def test_sandbox_idle_ttlin_seconds_from_api_data(self): + """Sandbox.sandbox_idle_ttlin_seconds 应能通过 sandboxIdleTTLInSeconds 正确解析""" + from agentrun.sandbox.sandbox import Sandbox + + api_data = { + "sandboxId": "sb-123", + "sandboxIdleTTLInSeconds": 300, + "sandboxIdleTimeoutSeconds": 600, + } + s = Sandbox.model_validate(api_data, by_alias=True) + assert s.sandbox_idle_ttlin_seconds == 300 + assert s.sandbox_idle_timeout_seconds == 600 + assert s.model_extra.get("sandboxIdleTTLInSeconds") is None + + def test_template_from_inner_object_with_ttl(self): + """Template.from_inner_object 应正确解析 sandboxTTLInSeconds""" + from agentrun.sandbox.template import Template + + class MockDaraModel: + + def to_map(self): + return { + "templateName": "test-template", + "sandboxIdleTimeoutInSeconds": 900, + "sandboxTTLInSeconds": 3600, + } + + t = Template.from_inner_object(MockDaraModel()) + assert t.sandbox_ttlin_seconds == 3600 + assert t.sandbox_idle_timeout_in_seconds == 900