Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/en/API_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ The A2A-T SDK exposes only two entry points: the client entry `A2ATClient` (prom
- Some APIs involve LLM calls. When using them, control the call frequency and concurrency based on the concurrency the connected model service can provide and your business latency requirements.
- APIs involving LLM calls apply length protection to `text` inputs: when the input exceeds `A2AT_INPUT_TEXT_MAX_CHARS` configured in `.env` (the `package_data/.env` or the file pointed to by `env_path`), the error code `input.text_too_long` is reported; the default of this configuration item is 16384 (16×1024). Structured data that does not involve LLM calls is not limited.
- The SDK is stateless: negotiation session state travels with the A2A-T metadata (`negotiationContext`) of each message and is not stored inside the SDK; the legacy `start_negotiation` / `receive_negotiation` / `continue_negotiation` state-machine negotiation APIs were deprecated since v1.1.0, emitted a `DeprecationWarning` when called, and were removed in v1.2.0.
- The private template-query methods `_get_prompts` / `_get_prompt` on `A2ATClient` and `A2ATServer` are SDK-internal interfaces. They are not part of the public API, are not guaranteed to be compatible across releases, and may be changed or removed without notice; do not call them directly.

## 1.3 API Descriptions

Expand Down
1 change: 1 addition & 0 deletions docs/zh/API参考.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ A2A-T SDK 对外仅提供两个入口:客户端入口 `A2ATClient`(提示词
- 部分接口涉及到LLM调用,使用时需根据对所接模型服务可提供的并发能力,结合业务时限要求,控制使用API时的调用频率和并发。
- 涉及到LLM调用的API针对文本`text`输入会做长度防护,在输入超过`.env`(`package_data/.env` 或 `env_path` 指向的文件)中配置的 `A2AT_INPUT_TEXT_MAX_CHARS` 字符时,会报错误码 `input.text_too_long` ,该配置项默认配置为 16384(16×1024);不涉及LLM调用的结构化数据不受此限制。
- SDK 无状态:协商会话状态随每条消息的 A2A-T metadata(`negotiationContext`)往返,不保存在 SDK 内部;旧的 `start_negotiation` / `receive_negotiation` / `continue_negotiation` 状态机协商 API 自 v1.1.0 起废弃,调用时发出 `DeprecationWarning`,已在 v1.2.0 版本删除。
- `A2ATClient` 和 `A2ATServer` 的 `_get_prompts` / `_get_prompt` 为 SDK 内部接口,不属于公开 API,不保证跨版本兼容性,可能随时变更或删除,请勿直接调用。

## 1.3 API说明

Expand Down
15 changes: 6 additions & 9 deletions src/a2a_t/client/a2at_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
``..._from_data_with_schema``, each bypassing scenario recognition for one addressed template), the
twelve negotiation content methods
of the shared :class:`~a2a_t.negotiation.generation.content_service.NegotiationContentService`
(eight generation plus four validation methods) and the extension-agnostic template queries of the
:class:`~a2a_t.common.prompt_resources.catalog.TemplateQueryService`.
(eight generation plus four validation methods).
"""

from __future__ import annotations
Expand Down Expand Up @@ -72,9 +71,9 @@ def _parse_template_uri(template_uri: str | TemplateUri | None) -> TemplateUri:


class A2ATClient:
"""Client-side facade of the A2A-T SDK: prompt generation, negotiation, and template queries.
"""Client-side facade of the A2A-T SDK: prompt generation and negotiation.

The facade is the single client entry point of the SDK and exposes four groups of operations:
The facade is the single client entry point of the SDK and exposes three groups of operations:

- **Scenario-recognition prompt generation** — :meth:`generate_task_prompt` normalizes the
caller input, recognizes the scenario, extracts the slots with one LLM call and renders the
Expand All @@ -86,8 +85,6 @@ class A2ATClient:
- **Negotiation content** — the twelve Negotiation-T methods: eight message-generation methods
(``generate_negotiation_{propose,accept,reject,abort}_prompt_from_{data,text}``) and four
message-validation methods (``validate_{...}_prompt_and_data_filling``).
- **Template queries** — :meth:`get_prompts` and :meth:`get_prompt`, the extension-agnostic
catalog queries that never throw.

Every method that addresses a template takes the template URI in its raw string spelling and
parses it fail-fast; use the constants of :mod:`a2a_t.core.standard_templates` instead of
Expand Down Expand Up @@ -774,10 +771,10 @@ def validate_abort_prompt_and_data_filling(
)

# ------------------------------------------------------------------
# Template queries
# Internal template queries
# ------------------------------------------------------------------

def get_prompts(self) -> list[PromptTemplate]:
def _get_prompts(self) -> list[PromptTemplate]:
"""List every template available for the configured language across all A2A-T extensions.

This query never throws: the extension directories are discovered from the configured
Expand All @@ -791,7 +788,7 @@ def get_prompts(self) -> list[PromptTemplate]:
"""
return self._template_queries().get_prompts()

def get_prompt(self, template_uri: str | TemplateUri) -> PromptTemplate | None:
def _get_prompt(self, template_uri: str | TemplateUri) -> PromptTemplate | None:
"""Load one template by its URI, regardless of the extension.

This query never throws for a well-formed template URI: a missing template yields ``None``
Expand Down
15 changes: 6 additions & 9 deletions src/a2a_t/server/a2at_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
(``validate_{task,notification,auth}_prompt_and_data_filling``, the shared core validation pipeline
behind an input-length gate), the twelve negotiation content methods
of the shared :class:`~a2a_t.negotiation.generation.content_service.NegotiationContentService`
(eight generation plus four validation methods) and the extension-agnostic template queries of the
:class:`~a2a_t.common.prompt_resources.catalog.TemplateQueryService`.
(eight generation plus four validation methods).
"""

from __future__ import annotations
Expand Down Expand Up @@ -77,9 +76,9 @@ def _parse_template_uri(template_uri: str | TemplateUri | None) -> TemplateUri:


class A2ATServer:
"""Server-side facade of the A2A-T SDK: prompt compliance, validation, negotiation, and queries.
"""Server-side facade of the A2A-T SDK: prompt compliance, validation, and negotiation.

The facade is the single server entry point of the SDK and exposes four groups of operations:
The facade is the single server entry point of the SDK and exposes three groups of operations:

- **Task prompt compliance** — :meth:`check_task_prompt` validates one processed task prompt
against the scenario, template and slot constraints and reports the outcome in the returned
Expand All @@ -93,8 +92,6 @@ class A2ATServer:
(``generate_negotiation_{propose,accept,reject,abort}_prompt_from_{data,text}``) and four
message-validation methods (``validate_{...}_prompt_and_data_filling``), identical to the
client surface because both peers of a negotiation generate and validate messages.
- **Template queries** — :meth:`get_prompts` and :meth:`get_prompt`, the extension-agnostic
catalog queries that never throw.

Every method that addresses a template takes the template URI in its raw string spelling and
parses it fail-fast; use the constants of :mod:`a2a_t.core.standard_templates` instead of
Expand Down Expand Up @@ -696,10 +693,10 @@ def validate_abort_prompt_and_data_filling(
)

# ------------------------------------------------------------------
# Template queries
# Internal template queries
# ------------------------------------------------------------------

def get_prompts(self) -> list[PromptTemplate]:
def _get_prompts(self) -> list[PromptTemplate]:
"""List every template available for the configured language across all A2A-T extensions.

This query never throws: the extension directories are discovered from the configured
Expand All @@ -713,7 +710,7 @@ def get_prompts(self) -> list[PromptTemplate]:
"""
return self._template_queries().get_prompts()

def get_prompt(self, template_uri: str | TemplateUri) -> PromptTemplate | None:
def _get_prompt(self, template_uri: str | TemplateUri) -> PromptTemplate | None:
"""Load one template by its URI, regardless of the extension.

This query never throws for a well-formed template URI: a missing template yields ``None``
Expand Down
30 changes: 15 additions & 15 deletions tests/client_prompt/test_a2at_client_negotiation_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The suite pins the facade surface added in P7: the twelve negotiation content methods delegating
to the shared :class:`~a2a_t.negotiation.generation.content_service.NegotiationContentService`
plus the template queries, with the raw string template URI boundary failing fast (D16) exactly
plus the internal template queries, with the raw string template URI boundary failing fast (D16) exactly
like the service boundary. The from-data legs run against the real packaged templates with no LLM
(the factory is patched to a stand-in client), the from-text and validation legs run against a
scripted LLM client recording every structured call, and the golden fixtures pin the rendered
Expand Down Expand Up @@ -433,11 +433,11 @@ def _boundary_calls(client: A2ATClient) -> dict[str, Callable[..., Any]]:
"validate_abort_prompt_and_data_filling": lambda uri: client.validate_abort_prompt_and_data_filling(
"message", abort_context, {"type": "object"}, uri
),
"get_prompt": client.get_prompt,
"_get_prompt": client._get_prompt,
}


@pytest.mark.parametrize("method_name", (*NEGOTIATION_API_METHODS, "get_prompt"))
@pytest.mark.parametrize("method_name", (*NEGOTIATION_API_METHODS, "_get_prompt"))
@pytest.mark.parametrize("malformed_uri", _MALFORMED_URIS, ids=["one-segment", "blank", "two-segments"])
def test_template_uri_boundary_rejects_malformed_uris(method_name: str, malformed_uri: str, tmp_path: Path) -> None:
client = build_client(write_env(tmp_path, "zh-CN"))
Expand All @@ -446,7 +446,7 @@ def test_template_uri_boundary_rejects_malformed_uris(method_name: str, malforme
_boundary_calls(client)[method_name](malformed_uri)


@pytest.mark.parametrize("method_name", (*NEGOTIATION_API_METHODS, "get_prompt"))
@pytest.mark.parametrize("method_name", (*NEGOTIATION_API_METHODS, "_get_prompt"))
def test_template_uri_boundary_rejects_a_null_uri(method_name: str, tmp_path: Path) -> None:
client = build_client(write_env(tmp_path, "zh-CN"))

Expand Down Expand Up @@ -622,28 +622,28 @@ def get_prompt(self, template_uri: TemplateUri) -> PromptTemplate | None:
return None


def test_template_queries_delegate_to_the_query_service(tmp_path: Path) -> None:
def test_private_template_queries_delegate_to_the_query_service(tmp_path: Path) -> None:
client = build_client(write_env(tmp_path, "zh-CN"))
recording = RecordingQueryService()
client._template_query_service = recording

assert client.get_prompts() == []
assert client.get_prompt(INFORMATION_NEGOTIATION_PROPOSE_URI) is None
assert client._get_prompts() == []
assert client._get_prompt(INFORMATION_NEGOTIATION_PROPOSE_URI) is None

assert recording.prompts_calls == 1
assert recording.prompt_calls == [INFORMATION_NEGOTIATION_PROPOSE]


# --------------------------------------------------------------------------------------
# template queries over the packaged tree
# internal template queries over the packaged tree
# --------------------------------------------------------------------------------------


@pytest.mark.parametrize("language", LANGUAGES)
def test_get_prompts_lists_every_bundled_template_sorted_by_uri(language: str, tmp_path: Path) -> None:
def test_private_get_prompts_lists_every_bundled_template_sorted_by_uri(language: str, tmp_path: Path) -> None:
client = build_client(write_env(tmp_path, language))

prompts = client.get_prompts()
prompts = client._get_prompts()

assert len(prompts) == 13
uris = [template.template_uri.uri for template in prompts]
Expand All @@ -668,10 +668,10 @@ def test_get_prompts_lists_every_bundled_template_sorted_by_uri(language: str, t
"Authorization-T/authorization-policy-management/v1",
),
)
def test_get_prompt_loads_one_bundled_template(template_uri: str, language: str, tmp_path: Path) -> None:
def test_private_get_prompt_loads_one_bundled_template(template_uri: str, language: str, tmp_path: Path) -> None:
client = build_client(write_env(tmp_path, language))

template = client.get_prompt(template_uri)
template = client._get_prompt(template_uri)

assert template is not None
assert template.template_uri == TemplateUri.parse(template_uri)
Expand All @@ -689,12 +689,12 @@ def test_get_prompt_loads_one_bundled_template(template_uri: str, language: str,
"Task-T/network-layer/unknown-scenario/v1",
),
)
def test_get_prompt_answers_none_for_unknown_templates(unknown_uri: str, tmp_path: Path) -> None:
def test_private_get_prompt_answers_none_for_unknown_templates(unknown_uri: str, tmp_path: Path) -> None:
client = build_client(write_env(tmp_path, "zh-CN"))

assert client.get_prompt(unknown_uri) is None
assert client._get_prompt(unknown_uri) is None


def test_negotiation_api_surface_is_complete() -> None:
for method in (*NEGOTIATION_API_METHODS, "get_prompts", "get_prompt"):
for method in NEGOTIATION_API_METHODS:
assert callable(getattr(A2ATClient, method, None)), f"A2ATClient is missing {method}"
30 changes: 15 additions & 15 deletions tests/prompt_compliance/test_a2at_server_negotiation_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Negotiation content API of the server facade (port of Java ``A2ATServerNegotiationApiTest``).

The server facade exposes the same twelve negotiation content methods and template queries as the
The server facade exposes the same twelve negotiation content methods and internal template queries as the
client facade over the same shared service, so this suite pins the symmetric surface: the
from-data legs render the Java golden fixtures byte-for-byte against the packaged templates, the
from-text and validation legs run against a scripted LLM client, the raw string template URI
Expand Down Expand Up @@ -439,11 +439,11 @@ def _boundary_calls(server: A2ATServer) -> dict[str, Callable[..., Any]]:
"validate_abort_prompt_and_data_filling": lambda uri: server.validate_abort_prompt_and_data_filling(
"message", abort_context, {"type": "object"}, uri
),
"get_prompt": server.get_prompt,
"_get_prompt": server._get_prompt,
}


@pytest.mark.parametrize("method_name", (*NEGOTIATION_API_METHODS, "get_prompt"))
@pytest.mark.parametrize("method_name", (*NEGOTIATION_API_METHODS, "_get_prompt"))
@pytest.mark.parametrize("malformed_uri", _MALFORMED_URIS, ids=["one-segment", "blank", "two-segments"])
def test_template_uri_boundary_rejects_malformed_uris(method_name: str, malformed_uri: str, tmp_path: Path) -> None:
server = build_server(write_env(tmp_path, "zh-CN"))
Expand All @@ -452,7 +452,7 @@ def test_template_uri_boundary_rejects_malformed_uris(method_name: str, malforme
_boundary_calls(server)[method_name](malformed_uri)


@pytest.mark.parametrize("method_name", (*NEGOTIATION_API_METHODS, "get_prompt"))
@pytest.mark.parametrize("method_name", (*NEGOTIATION_API_METHODS, "_get_prompt"))
def test_template_uri_boundary_rejects_a_null_uri(method_name: str, tmp_path: Path) -> None:
server = build_server(write_env(tmp_path, "zh-CN"))

Expand Down Expand Up @@ -627,28 +627,28 @@ def get_prompt(self, template_uri: TemplateUri) -> PromptTemplate | None:
return None


def test_template_queries_delegate_to_the_query_service(tmp_path: Path) -> None:
def test_private_template_queries_delegate_to_the_query_service(tmp_path: Path) -> None:
server = build_server(write_env(tmp_path, "zh-CN"))
recording = RecordingQueryService()
server._template_query_service = recording

assert server.get_prompts() == []
assert server.get_prompt(INFORMATION_NEGOTIATION_PROPOSE_URI) is None
assert server._get_prompts() == []
assert server._get_prompt(INFORMATION_NEGOTIATION_PROPOSE_URI) is None

assert recording.prompts_calls == 1
assert recording.prompt_calls == [INFORMATION_NEGOTIATION_PROPOSE]


# --------------------------------------------------------------------------------------
# template queries over the packaged tree
# internal template queries over the packaged tree
# --------------------------------------------------------------------------------------


@pytest.mark.parametrize("language", LANGUAGES)
def test_get_prompts_lists_every_bundled_template_sorted_by_uri(language: str, tmp_path: Path) -> None:
def test_private_get_prompts_lists_every_bundled_template_sorted_by_uri(language: str, tmp_path: Path) -> None:
server = build_server(write_env(tmp_path, language))

prompts = server.get_prompts()
prompts = server._get_prompts()

assert len(prompts) == 13
uris = [template.template_uri.uri for template in prompts]
Expand All @@ -673,10 +673,10 @@ def test_get_prompts_lists_every_bundled_template_sorted_by_uri(language: str, t
"Authorization-T/authorization-policy-management/v1",
),
)
def test_get_prompt_loads_one_bundled_template(template_uri: str, language: str, tmp_path: Path) -> None:
def test_private_get_prompt_loads_one_bundled_template(template_uri: str, language: str, tmp_path: Path) -> None:
server = build_server(write_env(tmp_path, language))

template = server.get_prompt(template_uri)
template = server._get_prompt(template_uri)

assert template is not None
assert template.template_uri == TemplateUri.parse(template_uri)
Expand All @@ -694,12 +694,12 @@ def test_get_prompt_loads_one_bundled_template(template_uri: str, language: str,
"Task-T/network-layer/unknown-scenario/v1",
),
)
def test_get_prompt_answers_none_for_unknown_templates(unknown_uri: str, tmp_path: Path) -> None:
def test_private_get_prompt_answers_none_for_unknown_templates(unknown_uri: str, tmp_path: Path) -> None:
server = build_server(write_env(tmp_path, "zh-CN"))

assert server.get_prompt(unknown_uri) is None
assert server._get_prompt(unknown_uri) is None


def test_negotiation_api_surface_is_complete() -> None:
for method in (*NEGOTIATION_API_METHODS, "get_prompts", "get_prompt"):
for method in NEGOTIATION_API_METHODS:
assert callable(getattr(A2ATServer, method, None)), f"A2ATServer is missing {method}"
Loading
Loading