From 0c697cb48eb4513e033e478111095cfe21d00d5c Mon Sep 17 00:00:00 2001 From: RAX4096 Date: Sat, 4 Jul 2026 15:11:56 +0800 Subject: [PATCH 1/6] Fix: messages schema in OpenAI Adapter with ReAct Strategy --- pyproject.toml | 2 +- src/amrita_core/adapters/openai.py | 4 ++++ src/amrita_core/utils.py | 2 +- uv.lock | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b21eda0..d186aa3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "amrita_core" -version = "0.11.0" +version = "0.11.1" description = "High performance, flexible, lightweight agent framework." readme = "README.md" requires-python = ">=3.10,<3.15" diff --git a/src/amrita_core/adapters/openai.py b/src/amrita_core/adapters/openai.py index d404170..79604ab 100644 --- a/src/amrita_core/adapters/openai.py +++ b/src/amrita_core/adapters/openai.py @@ -36,6 +36,7 @@ UniResponse, UniResponseUsage, ) +from amrita_core.utils import model_dump class OpenAIAdapter(ModelAdapter): @@ -51,6 +52,7 @@ async def call_api( preset: ModelPreset = self.preset preset_config: ModelConfig = preset.config config: AmritaConfig = self.config + messages = model_dump(messages) client = openai.AsyncOpenAI( base_url=preset.base_url, api_key=preset.api_key, @@ -171,6 +173,7 @@ async def call_tools( ) else: choice = tool_choice + messages = model_dump(messages) config: AmritaConfig = self.config preset: ModelPreset = self.preset preset_config: ModelConfig = preset.config @@ -195,6 +198,7 @@ async def call_tools( kwargs.update( {"reasoning_effort": preset.thinking_config.thinking_effort} ) + print(messages) completion: ChatCompletion = await client.chat.completions.create( model=model, messages=messages, diff --git a/src/amrita_core/utils.py b/src/amrita_core/utils.py index 554513c..fefc47d 100644 --- a/src/amrita_core/utils.py +++ b/src/amrita_core/utils.py @@ -46,7 +46,7 @@ def _did_you_mean_hint( T = TypeVar("T") -def model_dump(obj: Iterable[BaseModel | dict]) -> Sequence[Any]: +def model_dump(obj: Iterable[BaseModel | Any]) -> Sequence[Any]: return [obj.model_dump() if isinstance(obj, BaseModel) else obj for obj in obj] diff --git a/uv.lock b/uv.lock index f7b5da9..54d053b 100644 --- a/uv.lock +++ b/uv.lock @@ -228,7 +228,7 @@ wheels = [ [[package]] name = "amrita-core" -version = "0.10.4" +version = "0.11.1" source = { editable = "." } dependencies = [ { name = "aiofiles" }, From 5f3b0c6a0f94f91be117f3df9010ebf9f5350e63 Mon Sep 17 00:00:00 2001 From: RAX4096 Date: Sat, 4 Jul 2026 17:32:02 +0800 Subject: [PATCH 2/6] Feat: adapter with more informations --- src/amrita_core/adapters/anthropic.py | 55 ++++++++--- src/amrita_core/adapters/openai.py | 130 +++++++++++++++++++------- src/amrita_core/types/response.py | 43 +++++++++ tests/test_adapter.py | 27 ++++++ 4 files changed, 208 insertions(+), 47 deletions(-) diff --git a/src/amrita_core/adapters/anthropic.py b/src/amrita_core/adapters/anthropic.py index 1069017..7a6e787 100644 --- a/src/amrita_core/adapters/anthropic.py +++ b/src/amrita_core/adapters/anthropic.py @@ -3,6 +3,7 @@ from io import StringIO from amrita_sense.logging import logger +from anthropic.types import Message from pydantic import BaseModel, Field from typing_extensions import override @@ -25,6 +26,7 @@ UniResponse, UniResponseUsage, ) +from amrita_core.types.response import RequestMetadata class AnthropicFunctionSchema(BaseModel): @@ -278,14 +280,22 @@ async def call_api( text_resp.write(event.text) yield event.text last_msg = await resp.get_final_message() + metadata = RequestMetadata( + original_request_id=getattr(resp, "request_id", None), + stop_sequence=getattr(last_msg, "stop_sequence", None), + stop_reason=getattr(last_msg, "stop_reason", None), + model=getattr(last_msg, "model", "__NOT_GIVEN__"), + ) usage: UniResponseUsage[int] = UniResponseUsage[int]( prompt_tokens=last_msg.usage.input_tokens, completion_tokens=last_msg.usage.output_tokens, total_tokens=last_msg.usage.input_tokens + last_msg.usage.output_tokens, + cache_creation=last_msg.usage.cache_creation_input_tokens, + cache_hit=last_msg.usage.cache_read_input_tokens, ) else: - last_msg = await client.messages.create( + last_msg: Message = await client.messages.create( model=preset.model, messages=anthropic_msgs, max_tokens=config.llm.max_tokens, @@ -293,25 +303,33 @@ async def call_api( temperature=preset_config.temperature, **kwargs, ) + metadata = RequestMetadata( + original_request_id=getattr(last_msg, "_request_id", None), + stop_sequence=getattr(last_msg, "stop_sequence", None), + stop_reason=getattr(last_msg, "stop_reason", None), + model=getattr(last_msg, "model", "__NOT_GIVEN__"), + ) + usage = UniResponseUsage[int]( + prompt_tokens=last_msg.usage.input_tokens, + completion_tokens=last_msg.usage.output_tokens, + total_tokens=last_msg.usage.input_tokens + + last_msg.usage.output_tokens, + cache_creation=last_msg.usage.cache_creation_input_tokens, + cache_hit=last_msg.usage.cache_read_input_tokens, + ) for ct in last_msg.content: if isinstance(ct, TextBlock): text_resp.write(ct.text) elif hasattr(ct, "thinking"): - reasoning += ct.thinking + reasoning += ct.thinking # pyright: ignore[reportAttributeAccessIssue] if hasattr(ct, "signature"): - reasoning_signature = ct.signature + reasoning_signature = ct.signature # pyright: ignore[reportAttributeAccessIssue] yield MessageWithMetadata( - content=ct.thinking, + content=ct.thinking, # pyright: ignore[reportAttributeAccessIssue] metadata=MessageMetadataPayload( type="reasoning_chunk", extra_type="thinking" ), ) - usage = UniResponseUsage[int]( - prompt_tokens=last_msg.usage.input_tokens, - completion_tokens=last_msg.usage.output_tokens, - total_tokens=last_msg.usage.input_tokens - + last_msg.usage.output_tokens, - ) text_content = text_resp.getvalue() yield text_content @@ -321,6 +339,7 @@ async def call_api( tool_calls=None, reasoning_content=reasoning or None, reasoning_signature=reasoning_signature or None, + metadata=metadata, ) @override @@ -362,7 +381,7 @@ async def call_tools( | ToolChoiceNoneParam ) = self._convert_tool_choice(tool_choice) - response = await client.messages.create( + response: Message = await client.messages.create( model=preset.model, messages=anthropic_msgs, max_tokens=config.llm.max_tokens, @@ -372,6 +391,12 @@ async def call_tools( tool_choice=anthropic_tool_choice, **kwargs, ) + metadata = RequestMetadata( + original_request_id=getattr(response, "_request_id", None), + stop_sequence=getattr(response, "stop_sequence", None), + stop_reason=getattr(response, "stop_reason", None), + model=getattr(response, "model", "__NOT_GIVEN__"), + ) usage = None if getattr(response, "usage", None) is not None: @@ -380,8 +405,9 @@ async def call_tools( completion_tokens=response.usage.output_tokens, total_tokens=response.usage.input_tokens + response.usage.output_tokens, + cache_creation=response.usage.cache_creation_input_tokens, + cache_hit=response.usage.cache_read_input_tokens, ) - tool_calls = [] reasoning = "" reasoning_signature = "" @@ -398,9 +424,9 @@ async def call_tools( ) ) elif hasattr(block, "thinking"): - reasoning += block.thinking + reasoning += block.thinking # pyright: ignore[reportAttributeAccessIssue] if hasattr(block, "signature"): - reasoning_signature = block.signature + reasoning_signature = block.signature # pyright: ignore[reportAttributeAccessIssue] return UniResponse( role="assistant", content=None, @@ -408,6 +434,7 @@ async def call_tools( usage=usage, reasoning_content=reasoning or None, reasoning_signature=reasoning_signature or None, + metadata=metadata, ) @staticmethod diff --git a/src/amrita_core/adapters/openai.py b/src/amrita_core/adapters/openai.py index 79604ab..395837a 100644 --- a/src/amrita_core/adapters/openai.py +++ b/src/amrita_core/adapters/openai.py @@ -1,5 +1,5 @@ from collections.abc import AsyncGenerator, Iterable, Sequence -from typing import cast +from typing import Literal, cast import openai from amrita_sense.logging import debug_log @@ -36,17 +36,38 @@ UniResponse, UniResponseUsage, ) +from amrita_core.types.response import STOP_REASON, RequestMetadata from amrita_core.utils import model_dump +R2R_MAP: dict[ + Literal["stop", "length", "tool_calls", "content_filter", "function_call"], + STOP_REASON, +] = { + "stop": "stop_sequence", + "length": "max_tokens", + "tool_calls": "tool_use", + "content_filter": "refusal", + "function_call": "tool_use", # WARNING: Backwards compatibility for legacy OpenAI API versions (pre-2023) that return 'function_call' instead of 'tool_calls' +} + class OpenAIAdapter(ModelAdapter): - """OpenAI Protocol Adapter""" + """OpenAI Protocol Adapter + + NOTE: Through OpenAI SDK, we are actually hard to get cache info, each providers' cache informations are different. + So we will not provide `cache info` from OpenAIAdapter. By the way, `stop sequence` is not provided by OpenAI SDK, + so we will not provide this too. + + """ __override__ = True @override async def call_api( - self, messages: Iterable[ChatCompletionMessageParam], **kwargs + self, + messages: Iterable[ChatCompletionMessageParam], + stop: str | list[str] | None = None, + **kwargs, ) -> AsyncGenerator[COMPLETION_RETURNING, None]: """Call OpenAI API to get chat responses""" preset: ModelPreset = self.preset @@ -86,43 +107,64 @@ async def call_api( top_p=preset_config.top_p, temperature=preset_config.temperature, stream=stream, + stop=stop, **kwargs, ) response: str = "" reasoning: str | None = None uni_usage = None + model_name: str = preset.model + meta: RequestMetadata | None = None + # Process streaming response if self.preset.config.stream and isinstance(completion, openai.AsyncStream): - async for chunk in completion: - try: - if chunk.usage: - uni_usage = UniResponseUsage.model_validate( - chunk.usage, from_attributes=True - ) - if ( - reas_chunk := getattr( - chunk.choices[0].delta, "reasoning_content", None - ) - ) is not None: - reas_chunk = str(reas_chunk) - if reasoning is None: - reasoning = reas_chunk - else: - reasoning += reas_chunk - yield MessageWithMetadata( - content=reas_chunk, - metadata=MessageMetadataPayload( - type="reasoning_chunk", extra_type="cot_chunk" - ), - ) - if (chunk := chunk.choices[0].delta.content) is not None: - response += chunk - yield chunk - debug_log(chunk) - except IndexError: - break + async with completion as completion: + req_id: str | None = completion.response.headers.get( + "x-request-id", None + ) + async for chunk in completion: + try: + if chunk.choices[0].finish_reason is not None: + model_name = chunk.model + meta = RequestMetadata( + model=model_name, + original_request_id=req_id, + stop_sequence=None, + stop_reason=( + R2R_MAP[chunk.choices[0].finish_reason] + if chunk.choices[0].finish_reason + else None + ), + ) + if chunk.usage: + uni_usage = UniResponseUsage.model_validate( + chunk.usage, from_attributes=True + ) + + if ( + reas_chunk := getattr( + chunk.choices[0].delta, "reasoning_content", None + ) + ) is not None: + reas_chunk = str(reas_chunk) + if reasoning is None: + reasoning = reas_chunk + else: + reasoning += reas_chunk + yield MessageWithMetadata( + content=reas_chunk, + metadata=MessageMetadataPayload( + type="reasoning_chunk", extra_type="cot_chunk" + ), + ) + if (chunk := chunk.choices[0].delta.content) is not None: + response += chunk + yield chunk + debug_log(chunk) + except IndexError: + break + else: - debug_log(response) if isinstance(completion, ChatCompletion): if ( reas_chunk := getattr( @@ -136,11 +178,22 @@ async def call_api( type="reasoning_chunk", extra_type="cot_chunk" ), ) + meta = RequestMetadata( + model=completion.model, + original_request_id=completion._request_id, + stop_sequence=None, + stop_reason=( + R2R_MAP[completion.choices[0].finish_reason] + if completion.choices[0].finish_reason + else None + ), + ) response = ( completion.choices[0].message.content if completion.choices[0].message.content is not None else "" ) + debug_log(response) yield response if completion.usage: uni_usage = UniResponseUsage.model_validate( @@ -154,6 +207,7 @@ async def call_api( usage=uni_usage, tool_calls=None, reasoning_content=reasoning, + metadata=meta or RequestMetadata(model=preset.model), ) @override @@ -198,7 +252,6 @@ async def call_tools( kwargs.update( {"reasoning_effort": preset.thinking_config.thinking_effort} ) - print(messages) completion: ChatCompletion = await client.chat.completions.create( model=model, messages=messages, @@ -210,6 +263,16 @@ async def call_tools( **kwargs, ) msg = completion.choices[0].message + metadata = RequestMetadata( + model=completion.model, + original_request_id=completion._request_id, + stop_sequence=None, + stop_reason=( + R2R_MAP[completion.choices[0].finish_reason] + if completion.choices[0].finish_reason + else None + ), + ) return UniResponse( role="assistant", tool_calls=( @@ -222,6 +285,7 @@ async def call_tools( ), content=None, reasoning_content=getattr(msg, "reasoning_content", None), + metadata=metadata, ) @override diff --git a/src/amrita_core/types/response.py b/src/amrita_core/types/response.py index a73baa8..7a3678f 100644 --- a/src/amrita_core/types/response.py +++ b/src/amrita_core/types/response.py @@ -2,6 +2,7 @@ import typing from typing import Generic, Literal +from uuid import uuid4 from pydantic import ConfigDict, Field @@ -11,12 +12,50 @@ T = typing.TypeVar("T", None, str, None | typing.Literal[""]) T_INT = typing.TypeVar("T_INT", int, None, int | None) T_TOOL = typing.TypeVar("T_TOOL", list[ToolCall], None, list[ToolCall] | None) +STOP_REASON = Literal[ + "end_turn", + "max_tokens", + "stop_sequence", + "tool_use", + "pause_turn", + "refusal", +] class UniResponseUsage(BaseModel, Generic[T_INT]): prompt_tokens: T_INT + """The number of prompt tokens which were used.""" completion_tokens: T_INT + """The number of completion tokens which were used.""" total_tokens: T_INT + """The total number of tokens which were used.""" + cache_creation: int | None = None + """The number of tokens used to create the cache entry.""" + cache_hit: int | None = None + """The number of tokens read from the cache.""" + + model_config = ConfigDict(extra="allow") + + +class RequestMetadata(BaseModel): + request_id: str = Field( + default_factory=lambda: str(uuid4()), + description="Unique request ID", + ) + original_request_id: str | None = Field( + default=None, + description="Original request ID from Adapter", + ) + model: str = Field(default="__NOT_GIVEN__", description="LLM Used for request") + stop_sequence: str | None = Field( + default=None, + description="Stop sequence", + ) + stop_reason: STOP_REASON | None = Field( + default=None, + description="Stop reason", + ) + model_config = ConfigDict(extra="allow") class UniResponse( @@ -53,3 +92,7 @@ class UniResponse( description="Anthropic thinking signature (required for round-tripping)", exclude_if=lambda x: x is None, ) + metadata: RequestMetadata = Field( + default_factory=RequestMetadata, + description="Request metadata", + ) diff --git a/tests/test_adapter.py b/tests/test_adapter.py index 2c89bd8..ec72979 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -25,6 +25,9 @@ class MockAsyncStream(AsyncStream): def __init__(self, chunks): self.chunks = chunks + self.response = MagicMock() + self.response.headers = {} + self.response.aclose = AsyncMock() def __aiter__(self): return self @@ -81,6 +84,7 @@ async def test_call_api_non_streaming(self, adapter, mock_messages): object="chat.completion", usage={"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}, ) + object.__setattr__(mock_completion, "_request_id", "req_mock_001") with patch("amrita_core.adapters.openai.openai.AsyncOpenAI") as mock_openai: mock_client = AsyncMock() @@ -116,6 +120,7 @@ async def test_call_api_non_streaming_empty_content(self, adapter, mock_messages model="gpt-3.5-turbo", object="chat.completion", ) + object.__setattr__(mock_completion, "_request_id", "req_mock_002") with patch("amrita_core.adapters.openai.openai.AsyncOpenAI") as mock_openai: mock_client = AsyncMock() @@ -157,6 +162,7 @@ async def test_call_tools_auto_choice(self, adapter, mock_messages): model="gpt-3.5-turbo", object="chat.completion", ) + object.__setattr__(mock_completion, "_request_id", "req_mock_003") with patch("amrita_core.adapters.openai.openai.AsyncOpenAI") as mock_openai: mock_client = AsyncMock() @@ -207,6 +213,7 @@ async def test_call_tools_specific_function_choice(self, adapter, mock_messages) model="gpt-3.5-turbo", object="chat.completion", ) + object.__setattr__(mock_completion, "_request_id", "req_mock_004") with patch("amrita_core.adapters.openai.openai.AsyncOpenAI") as mock_openai: mock_client = AsyncMock() @@ -240,6 +247,7 @@ async def test_call_tools_no_tool_calls(self, adapter, mock_messages): model="gpt-3.5-turbo", object="chat.completion", ) + object.__setattr__(mock_completion, "_request_id", "req_mock_005") with patch("amrita_core.adapters.openai.openai.AsyncOpenAI") as mock_openai: mock_client = AsyncMock() @@ -313,6 +321,7 @@ async def test_call_tools_with_string_tool_choice(self, adapter, mock_messages): model="gpt-3.5-turbo", object="chat.completion", ) + object.__setattr__(mock_completion, "_request_id", "req_mock_006") with patch("amrita_core.adapters.openai.openai.AsyncOpenAI") as mock_openai: mock_client = AsyncMock() @@ -736,6 +745,11 @@ async def test_call_api_non_streaming(self, anthropic_adapter, simple_messages): mock_message = MagicMock() mock_message.content = [TextBlock(text="Hello there!", type="text")] mock_message.usage = Usage(input_tokens=10, output_tokens=5, total_tokens=15) + mock_message.model = "claude-3-opus-20240229" + mock_message.stop_sequence = None + mock_message.stop_reason = "end_turn" + # Explicitly set _request_id to None so getattr() on MagicMock returns None + mock_message._request_id = None with patch( "amrita_core.adapters.anthropic.anthropic.AsyncAnthropic" @@ -780,12 +794,16 @@ async def mock_events(): mock_final_message.usage = Usage( input_tokens=10, output_tokens=5, total_tokens=15 ) + mock_final_message.model = "claude-3-opus-20240229" + mock_final_message.stop_sequence = None + mock_final_message.stop_reason = "end_turn" # Create a simple mock context manager with async iteration class SimpleMockContext: def __init__(self, events_gen, final_msg): self._events = events_gen self._final_msg = final_msg + self.request_id = "req_test_123" async def __aenter__(self): return self @@ -846,11 +864,15 @@ async def mock_events(): mock_final_message.usage = Usage( input_tokens=10, output_tokens=0, total_tokens=10 ) + mock_final_message.model = "claude-3-opus-20240229" + mock_final_message.stop_sequence = None + mock_final_message.stop_reason = "end_turn" class SimpleMockContext: def __init__(self, events_gen, final_msg): self._events = events_gen self._final_msg = final_msg + self.request_id = "req_test_123" async def __aenter__(self): return self @@ -900,6 +922,11 @@ async def test_call_api_non_streaming_multiple_blocks( TextBlock(text=" Second part", type="text"), ] mock_message.usage = Usage(input_tokens=15, output_tokens=10, total_tokens=25) + mock_message.model = "claude-3-opus-20240229" + mock_message.stop_sequence = None + mock_message.stop_reason = "end_turn" + # Explicitly set _request_id to None so getattr() on MagicMock returns None + mock_message._request_id = None with patch( "amrita_core.adapters.anthropic.anthropic.AsyncAnthropic" From 41771a6051822b600c14681b1310cecd7b0dc550 Mon Sep 17 00:00:00 2001 From: RAX4096 Date: Sat, 4 Jul 2026 17:37:11 +0800 Subject: [PATCH 3/6] Rub: R2R_Map.get --- src/amrita_core/adapters/openai.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/amrita_core/adapters/openai.py b/src/amrita_core/adapters/openai.py index 395837a..1b47aef 100644 --- a/src/amrita_core/adapters/openai.py +++ b/src/amrita_core/adapters/openai.py @@ -131,7 +131,7 @@ async def call_api( original_request_id=req_id, stop_sequence=None, stop_reason=( - R2R_MAP[chunk.choices[0].finish_reason] + R2R_MAP.get(chunk.choices[0].finish_reason) if chunk.choices[0].finish_reason else None ), @@ -183,7 +183,7 @@ async def call_api( original_request_id=completion._request_id, stop_sequence=None, stop_reason=( - R2R_MAP[completion.choices[0].finish_reason] + R2R_MAP.get(completion.choices[0].finish_reason) if completion.choices[0].finish_reason else None ), @@ -268,7 +268,7 @@ async def call_tools( original_request_id=completion._request_id, stop_sequence=None, stop_reason=( - R2R_MAP[completion.choices[0].finish_reason] + R2R_MAP.get(completion.choices[0].finish_reason) if completion.choices[0].finish_reason else None ), From c0bb5d068f826ee60f4e48a879b315e76be5513b Mon Sep 17 00:00:00 2001 From: RAX4096 Date: Sat, 4 Jul 2026 18:13:49 +0800 Subject: [PATCH 4/6] Docs: remove outdated and add new docs. --- docs/docs/.vitepress/config.mts | 8 +- .../api-reference/classes/ModelAdapter.md | 2 +- .../api-reference/classes/RequestMetadata.md | 34 +++ .../classes/SuspendObjectStream.md | 33 --- .../api-reference/classes/UniResponse.md | 1 + .../api-reference/classes/UniResponseUsage.md | 2 + docs/docs/guide/api-reference/index.md | 17 +- docs/docs/guide/concepts/agent-strategy.md | 16 +- docs/docs/guide/concepts/event.md | 9 +- docs/docs/guide/concepts/suspend.md | 8 +- docs/docs/guide/dependency-intro.md | 2 +- docs/docs/guide/function-implementation.md | 27 +- .../api-reference/classes/RequestMetadata.md | 34 +++ .../classes/SuspendObjectStream.md | 255 ------------------ .../api-reference/classes/UniResponse.md | 1 + .../api-reference/classes/UniResponseUsage.md | 2 + docs/docs/zh/guide/concepts/agent-strategy.md | 16 +- docs/docs/zh/guide/concepts/event.md | 2 +- docs/docs/zh/guide/concepts/suspend.md | 6 +- docs/docs/zh/guide/function-implementation.md | 27 +- 20 files changed, 116 insertions(+), 386 deletions(-) create mode 100644 docs/docs/guide/api-reference/classes/RequestMetadata.md delete mode 100644 docs/docs/guide/api-reference/classes/SuspendObjectStream.md create mode 100644 docs/docs/zh/guide/api-reference/classes/RequestMetadata.md delete mode 100644 docs/docs/zh/guide/api-reference/classes/SuspendObjectStream.md diff --git a/docs/docs/.vitepress/config.mts b/docs/docs/.vitepress/config.mts index 8d4339e..5b506e0 100644 --- a/docs/docs/.vitepress/config.mts +++ b/docs/docs/.vitepress/config.mts @@ -309,8 +309,8 @@ export default withMermaid({ link: "/guide/api-reference/classes/SuspendEnum", }, { - text: "SuspendObjectStream", - link: "/guide/api-reference/classes/SuspendObjectStream", + text: "RequestMetadata", + link: "/guide/api-reference/classes/RequestMetadata", }, { text: "EmbeddingChunk", @@ -663,8 +663,8 @@ export default withMermaid({ link: "/zh/guide/api-reference/classes/SuspendEnum", }, { - text: "SuspendObjectStream", - link: "/zh/guide/api-reference/classes/SuspendObjectStream", + text: "RequestMetadata", + link: "/zh/guide/api-reference/classes/RequestMetadata", }, { text: "EmbeddingChunk", diff --git a/docs/docs/guide/api-reference/classes/ModelAdapter.md b/docs/docs/guide/api-reference/classes/ModelAdapter.md index 1e8853f..1cf92e2 100644 --- a/docs/docs/guide/api-reference/classes/ModelAdapter.md +++ b/docs/docs/guide/api-reference/classes/ModelAdapter.md @@ -8,7 +8,7 @@ The `ModelAdapter` class provides a unified interface for integrating different Adapters are automatically registered with the [`AdapterManager`](#adaptermanager) when defined, unless marked as abstract or explicitly disabled from registration. -> **Note**: The `ModelAdapter` base class has been moved from `amrita_core.protocol` to `amrita_core.base.adapter`. The `amrita_core.protocol` module is now a deprecated re-export wrapper. +> **Note**: The `ModelAdapter` base class has been moved from `amrita_core.protocol` to `amrita_core.base.adapter`. The `amrita_core.protocol` compatibility endpoint was removed in v0.10.x+; import from `amrita_core.base.adapter`. ## Class Definition diff --git a/docs/docs/guide/api-reference/classes/RequestMetadata.md b/docs/docs/guide/api-reference/classes/RequestMetadata.md new file mode 100644 index 0000000..07f3b0f --- /dev/null +++ b/docs/docs/guide/api-reference/classes/RequestMetadata.md @@ -0,0 +1,34 @@ +# RequestMetadata + +`RequestMetadata` captures per-request diagnostic information returned by every adapter call through `UniResponse.metadata`. + +## Properties + +- `request_id` (str): Auto-generated unique request ID (UUID4). Defaults to a new UUID if not provided. +- `original_request_id` (str | None): Original request ID returned by the LLM provider adapter (e.g., OpenAI's `_request_id`, Anthropic's `request_id`). `None` when unavailable. +- `model` (str): The model used for the request. Defaults to `"__NOT_GIVEN__"` when not available (e.g., streaming before the first chunk). +- `stop_sequence` (str | None): The stop sequence that terminated generation, if any. +- `stop_reason` (STOP_REASON | None): Why the generation stopped. One of: + + | Value | Meaning | + | ----------------- | -------------------------- | + | `"end_turn"` | Natural completion | + | `"max_tokens"` | Hit max token limit | + | `"stop_sequence"` | Matched a stop sequence | + | `"tool_use"` | Model called a tool | + | `"pause_turn"` | Anthropic pause turn | + | `"refusal"` | Content filtered / refused | + +## Usage + +```python +from amrita_core.types.response import RequestMetadata + +# Accessed via UniResponse +response: UniResponse = ... +print(response.metadata.model) # e.g. "gpt-4o" +print(response.metadata.stop_reason) # e.g. "end_turn" +print(response.metadata.original_request_id) # Provider's request ID +``` + +> **Note**: `extra="allow"` is configured, so provider-specific fields may appear in addition to the standard ones. diff --git a/docs/docs/guide/api-reference/classes/SuspendObjectStream.md b/docs/docs/guide/api-reference/classes/SuspendObjectStream.md deleted file mode 100644 index 19e3461..0000000 --- a/docs/docs/guide/api-reference/classes/SuspendObjectStream.md +++ /dev/null @@ -1,33 +0,0 @@ -# SuspendObjectStream - -> **Migrated to AmritaSense**. `amrita_core.streaming` is now a deprecated wrapper. - -`SuspendObjectStream` is a producer-single-consumer architecture built on top of AnyIO memory object streams, with built-in suspend/resume and streaming response capabilities. - -**Full API Documentation**: [SuspendObjectStream — AmritaSense](https://sense.amritabot.com/reference/api/suspend-object-stream) - -## Migration - -```python -# Old (deprecated) -from amrita_core.streaming import SuspendObjectStream - -# New -from amrita_sense.streaming import SuspendObjectStream -``` - -## Usage in AmritaCore - -ChatObject uses `SuspendObjectStream` via its `io_stream` attribute (composition since v0.9.1). All streaming interaction methods are accessed through `chat.io_stream.*`: - -| Method | Purpose | -| -------------------------- | -------------------------------------------- | -| `yield_response()` | Send response to queue or callback | -| `get_response_generator()` | Asynchronously iterate over response stream | -| `set_callback_func()` | Set a response callback | -| `wait_to_suspend()` | Wait for suspension externally | -| `resume()` | Resume execution | -| `@suspend` | Decorator for suspendable methods | -| `@suspend_with_tag(tag)` | Decorator for suspendable methods with a tag | - -> **Note**: `callback` and `async for` iteration are **mutually exclusive** — only one method can be used to consume the response stream per instance. diff --git a/docs/docs/guide/api-reference/classes/UniResponse.md b/docs/docs/guide/api-reference/classes/UniResponse.md index 0ca0a69..cf7ee57 100644 --- a/docs/docs/guide/api-reference/classes/UniResponse.md +++ b/docs/docs/guide/api-reference/classes/UniResponse.md @@ -10,6 +10,7 @@ The UniResponse class provides a unified response format. - `tool_calls` (T_TOOL): Tool call results, T_TOOL is a generic parameter - `reasoning_content` (str | None): Reasoning/thinking content from the model, if the model supports it (e.g., o1, Claude with extended thinking) - `reasoning_signature` (str | None): Anthropic thinking signature, required for round-tripping thinking content with Anthropic API +- `metadata` ([RequestMetadata](RequestMetadata.md)): Request metadata containing request ID, model name, stop reason, and original provider request ID ## Description diff --git a/docs/docs/guide/api-reference/classes/UniResponseUsage.md b/docs/docs/guide/api-reference/classes/UniResponseUsage.md index 82839e4..72e271c 100644 --- a/docs/docs/guide/api-reference/classes/UniResponseUsage.md +++ b/docs/docs/guide/api-reference/classes/UniResponseUsage.md @@ -7,6 +7,8 @@ UniResponseUsage class represents usage statistics for responses. - `prompt_tokens` (T_INT): Number of tokens used in the prompt - `completion_tokens` (T_INT): Number of tokens used in the completion (generation) - `total_tokens` (T_INT): Total number of tokens used +- `cache_creation` (int | None): Number of tokens used to create the cache entry (Anthropic prompt caching) +- `cache_hit` (int | None): Number of tokens read from the cache (Anthropic prompt caching) ## Description diff --git a/docs/docs/guide/api-reference/index.md b/docs/docs/guide/api-reference/index.md index 701c285..16b8e0b 100644 --- a/docs/docs/guide/api-reference/index.md +++ b/docs/docs/guide/api-reference/index.md @@ -2,22 +2,7 @@ ## 7.1 Core API Functions -### 7.1.1 init() - Initialization (Deprecated) - -> **Deprecated**: The `init()` function is deprecated since v0.9.0rc1. It is now an empty stub and no longer performs any initialisation. Use `load_amrita()` for async initialisation instead. - -```python -from amrita_core import init - -# No longer necessary - this is now a no-op -init() -``` - -**Purpose**: Previously prepared internal components, initialized Jieba, and loaded built-in modules. Now deprecated. - -**Migration**: Remove any calls to `init()` in your code. Use `load_amrita()` when async initialisation is needed (e.g., for MCP client setup). - -### 7.1.2 load_amrita() - Loading Framework +### 7.1.1 load_amrita() - Loading Framework The `load_amrita()` function asynchronously loads AmritaCore components, particularly when MCP client functionality is enabled. diff --git a/docs/docs/guide/concepts/agent-strategy.md b/docs/docs/guide/concepts/agent-strategy.md index 183b76d..fe9002d 100644 --- a/docs/docs/guide/concepts/agent-strategy.md +++ b/docs/docs/guide/concepts/agent-strategy.md @@ -217,14 +217,14 @@ async def use_builtin_strategies(): ) # Use the agents - chat1 = standard_agent.get_chatobject("What can you do?") - chat2 = hybrid_agent.get_chatobject("Analyze this data") - chat3 = no_action_agent.get_chatobject("Just respond directly") - - async with chat1.begin(), chat2.begin(), chat3.begin(): - response1 = await chat1.full_response() - response2 = await chat2.full_response() - response3 = await chat3.full_response() + chat = standard_agent.get_chatobject("What can you do?") + + chat.begin() + async with chat: + async for chunk in chat.io_stream.get_response_generator(): + content = chunk if isinstance(chunk, str) else chunk.get_content() + print(content, end="", flush=True) + # Chat is cleaned up automatically after exiting context ``` ## Stateful Strategies with StrategyLikedObject diff --git a/docs/docs/guide/concepts/event.md b/docs/docs/guide/concepts/event.md index 540a755..8f60112 100644 --- a/docs/docs/guide/concepts/event.md +++ b/docs/docs/guide/concepts/event.md @@ -1,6 +1,6 @@ # Event System -> **Since v0.9.0rc1**: The event system core (`BaseEvent`, `MatcherFactory`, `EventRegistry`, `MatcherException`, `CancelException`, `PassException`) has been migrated to [AmritaSense](https://sense.amritabot.com). Full documentation at [AmritaSense Event System](https://sense.amritabot.com/guide/advanced/event_system). The `amrita_core.hook.*` modules are now deprecated wrappers. +> **Since v0.9.0rc1**: The event system core (`BaseEvent`, `MatcherFactory`, `EventRegistry`, `MatcherException`, `CancelException`, `PassException`) has been migrated to [AmritaSense](https://sense.amritabot.com). Full documentation at [AmritaSense Event System](https://sense.amritabot.com/guide/advanced/event_system). The `amrita_core.hook.*` compatibility endpoints were removed in v0.10.x+; import directly from `amrita_sense`. ## 3.3.1 Event-Driven Design @@ -76,16 +76,13 @@ You can modify the `event.preset` to switch to a different model preset for the ## 3.3.5 MatcherManager Event Matcher -> **Note**: Since v0.9.0rc1, `MatcherManager` (`MatcherFactory`) has been moved to the `amrita-sense` package. The `amrita_core.hook.matcher` module is now a deprecated re-export wrapper. +> **Note**: Since v0.9.0rc1, `MatcherManager` (`MatcherFactory`) has been moved to the `amrita-sense` package. The `amrita_core.hook.matcher` compatibility endpoint was removed in v0.10.x+; import directly from `amrita_sense`. The [MatcherManager](../api-reference/classes/MatcherManager.md) handles matching events to appropriate handlers: ```python -# From amrita-sense (recommended) +# From amrita-sense from amrita_sense.hook.matcher import MatcherFactory - -# Or via the deprecated re-export (will be removed in a future release) -from amrita_core.hook.matcher import MatcherManager ``` ## 3.3.6 Event Registration and Triggering diff --git a/docs/docs/guide/concepts/suspend.md b/docs/docs/guide/concepts/suspend.md index b8f4d6e..59a2877 100644 --- a/docs/docs/guide/concepts/suspend.md +++ b/docs/docs/guide/concepts/suspend.md @@ -1,6 +1,6 @@ # Suspend and Resume Mechanism -> **Starting from v0.9.0rc1**: `SuspendObjectStream` has been migrated to [AmritaSense](https://sense.amritabot.com). See the full docs at [Execution & Interrupt](https://sense.amritabot.com/guide/concepts/exec_and_interrupt) and [SuspendObjectStream API](https://sense.amritabot.com/reference/api/suspend-object-stream). `amrita_core.streaming` is now a deprecated wrapper. +> **Starting from v0.9.0rc1**: `SuspendObjectStream` has been migrated to [AmritaSense](https://sense.amritabot.com). See the full docs at [Execution & Interrupt](https://sense.amritabot.com/guide/concepts/exec_and_interrupt) and [SuspendObjectStream API](https://sense.amritabot.com/reference/api/suspend-object-stream). The `amrita_core.streaming` compatibility endpoint was removed in v0.10.x+; import directly from `amrita_sense`. **Note: This is an advanced feature for special scenarios. Most users do not need to use it directly.** @@ -195,7 +195,7 @@ async def custom_processing_step(chat_obj): await asyncio.sleep(0.5) # Manual suspension point: blocks only if external wait_to_suspend triggered, otherwise returns immediately - await chat_obj._wait_for_continue() + await chat_obj.io_stream._wait_for_continue() print("Continuing after suspension point...") await asyncio.sleep(0.5) @@ -239,7 +239,7 @@ asyncio.run(main()) - Developers can manually insert it to customize internal business suspension points - When no pending suspend request exists, the call returns immediately without blocking the flow - Based on asynchronous signals, independent of the business execution flow -- **Tag parameter passing**: When calling manually, you can pass a tag parameter: `await chat_obj._wait_for_continue(tag="custom_tag")` +- **Tag parameter passing**: When calling manually, you can pass a tag parameter: `await chat_obj.io_stream._wait_for_continue(tag="custom_tag")` ## Combining Both Interruption Mechanisms @@ -390,7 +390,7 @@ await chat - The `wait_to_suspend` timeout parameter is used to avoid indefinite blocking - **Tag parameters help precise positioning**: In complex flows, using tags enables accurate control of specific breakpoints - This is a low-level capability intended for framework extension, advanced debugging, and custom flow orchestration scenarios -- **Inheritance relationship**: Since `ChatObject` inherits from `SuspendObjectStream`, all suspend/resume methods are available on ChatObject instances +- **Composition relationship**: `ChatObject` uses `SuspendObjectStream` via its `io_stream` attribute (composition since v0.9.1). Access suspend/resume methods through `chat_obj.io_stream.*`. ::: warning Callback and Iterator Are Mutually Exclusive Do not set a callback function and use `get_response_generator()` simultaneously; this will cause a `RuntimeError`. diff --git a/docs/docs/guide/dependency-intro.md b/docs/docs/guide/dependency-intro.md index 71baaf6..9073e36 100644 --- a/docs/docs/guide/dependency-intro.md +++ b/docs/docs/guide/dependency-intro.md @@ -40,7 +40,7 @@ pip install amrita_core | `amrita_core.hook.event.BaseEvent` | `amrita_sense.hook.event.BaseEvent` | | `amrita_core.hook.exception` | `amrita_sense.hook.exception` | -Old import paths still work but are deprecated; they emit a `DeprecationWarning`. +Old import paths were removed in v0.10.x+; import directly from `amrita_sense`. ## Version Requirements diff --git a/docs/docs/guide/function-implementation.md b/docs/docs/guide/function-implementation.md index a15e475..40c50b9 100644 --- a/docs/docs/guide/function-implementation.md +++ b/docs/docs/guide/function-implementation.md @@ -2,26 +2,7 @@ ## 4.1 Initialization and Loading -### 4.1.1 init() Initialization Function (Deprecated) - -> **Deprecated since v0.9.0rc1**: The `init()` function is now a no-op stub. Use `load_amrita()` for async initialisation instead. - -```python -from amrita_core import init - -# No longer necessary — this is now a no-op -init() -``` - -Previously this function performed several key tasks: - -- Set up internal logging -- Initialized Jieba for text processing -- Loaded built-in modules - -These tasks are now handled automatically by the framework or through `amrita-sense`. - -### 4.1.2 load_amrita() Asynchronous Loading +### 4.1.1 load_amrita() Asynchronous Loading The `load_amrita()` function asynchronously loads AmritaCore components, especially when MCP client functionality is enabled: @@ -36,9 +17,9 @@ async def main(): asyncio.run(main()) ``` -### 4.1.3 Configuration Setting and Retrieval +### 4.1.2 Configuration Setting and Retrieval -#### 4.1.3.1 set_config() Setting Configuration +#### 4.1.2.1 set_config() Setting Configuration The `set_config()` function applies a configuration to AmritaCore: @@ -50,7 +31,7 @@ config = AmritaConfig() set_config(config) ``` -#### 4.1.3.2 get_config() Retrieving Configuration +#### 4.1.2.2 get_config() Retrieving Configuration The `get_config()` function retrieves the current AmritaCore configuration: diff --git a/docs/docs/zh/guide/api-reference/classes/RequestMetadata.md b/docs/docs/zh/guide/api-reference/classes/RequestMetadata.md new file mode 100644 index 0000000..ac08c8e --- /dev/null +++ b/docs/docs/zh/guide/api-reference/classes/RequestMetadata.md @@ -0,0 +1,34 @@ +# RequestMetadata + +`RequestMetadata` 捕获每次请求的诊断信息,通过 `UniResponse.metadata` 返回。 + +## 属性 + +- `request_id` (str): 自动生成的唯一请求 ID(UUID4)。未提供时默认为新的 UUID。 +- `original_request_id` (str | None): LLM 提供商适配器返回的原始请求 ID(如 OpenAI 的 `_request_id`、Anthropic 的 `request_id`)。不可用时为 `None`。 +- `model` (str): 请求使用的模型。不可用时默认为 `"__NOT_GIVEN__"`(如流式传输中首个 chunk 到达前)。 +- `stop_sequence` (str | None): 导致生成终止的停止序列。 +- `stop_reason` (STOP_REASON | None): 生成停止的原因。可选值: + + | 值 | 含义 | + | ----------------- | ------------------- | + | `"end_turn"` | 自然完成 | + | `"max_tokens"` | 达到最大 token 限制 | + | `"stop_sequence"` | 匹配到停止序列 | + | `"tool_use"` | 模型调用了工具 | + | `"pause_turn"` | Anthropic 暂停回合 | + | `"refusal"` | 内容被过滤/拒绝 | + +## 用法 + +```python +from amrita_core.types.response import RequestMetadata + +# 通过 UniResponse 访问 +response: UniResponse = ... +print(response.metadata.model) # 例如 "gpt-4o" +print(response.metadata.stop_reason) # 例如 "end_turn" +print(response.metadata.original_request_id) # 提供商的请求 ID +``` + +> **注意**:配置了 `extra="allow"`,因此除标准字段外可能还会出现提供商特定的字段。 diff --git a/docs/docs/zh/guide/api-reference/classes/SuspendObjectStream.md b/docs/docs/zh/guide/api-reference/classes/SuspendObjectStream.md deleted file mode 100644 index 6458d56..0000000 --- a/docs/docs/zh/guide/api-reference/classes/SuspendObjectStream.md +++ /dev/null @@ -1,255 +0,0 @@ -# SuspendObjectStream - -> **已迁移至 AmritaSense**。`amrita_core.streaming` 现为弃用包装器。 - -`SuspendObjectStream` 是 AnyIO 内存对象流之上的生产者-单消费者架构,内建挂起/恢复和流式响应能力。 - -**完整 API 文档**:[SuspendObjectStream — AmritaSense](https://sense.amritabot.com/reference/api/suspend-object-stream) - -## 迁移 - -```python -# 旧(弃用) -from amrita_core.streaming import SuspendObjectStream - -# 新 -from amrita_sense.streaming import SuspendObjectStream -``` - -## 在 AmritaCore 中的使用 - -ChatObject 通过其 `io_stream` 属性使用 `SuspendObjectStream`(自 v0.9.1 起采用组合方式)。所有流式交互方法通过 `chat.io_stream.*` 访问: - -| 方法 | 用途 | -| -------------------------- | ------------------------ | -| `yield_response()` | 向队列或回调发送响应 | -| `get_response_generator()` | 异步迭代响应流 | -| `set_callback_func()` | 设置响应回调 | -| `wait_to_suspend()` | 外部等待挂起 | -| `resume()` | 恢复执行 | -| `@suspend` | 可挂起方法装饰器 | -| `@suspend_with_tag(tag)` | 带标签的可挂起方法装饰器 | - -> **注意**:`callback` 与 `async for` 迭代**互斥**,同一实例只能使用一种方式消费响应流。 - -## 属性 - -- `_send_stream` (ObjectSendStream): AnyIO 发送流,用于产生项目 -- `_receive_stream` (ObjectReceiveStream): AnyIO 接收流,用于消费项目 -- `_callback_fun` (CALLBACK_TYPE | None): 用于直接响应处理的回调函数 -- `_callback_lock` (aiologic.Lock): 用于线程安全回调执行的锁 -- `_queue_done` (bool): 响应队列是否已关闭 -- `_has_consumer` (bool): 是否已有消费者从流中读取 -- `_q_tout` (float | None): 队列超时设置 -- `_suspend_tags` (tuple[str, ...] | None): 当前挂起标签过滤器 -- `__suspend_signal` (asyncio.Future | None): 挂起请求信号 -- `__resume_signal` (asyncio.Future | None): 恢复信号 - -## 方法 - -### 静态方法 - -#### `suspend(func: Callable[..., Any], tag: str | None = None) -> Callable[..., Any]` - -挂起功能的装饰器。在执行装饰的函数之前自动检测挂起信号。 - -**参数**: - -- `func`: 要装饰的协程函数 -- `tag` (str | None): 可选的标签,用于精确断点匹配 - -**返回值**: 支持挂起/恢复的装饰函数 - -**异常**: 如果函数不是协程函数,则抛出 `TypeError` - -#### `suspend_with_tag(tag: str)` - -带标签挂起点的装饰器工厂。 - -**参数**: - -- `tag` (str): 用于断点识别的标签 - -**返回值**: 应用带有指定标签的 `@suspend` 装饰器的装饰器 - -### 实例方法 - -#### `wait_to_suspend(*tags: str, timeout: float | None = None)` - -告诉流挂起并等待它。 - -**参数**: - -- `*tags` (str): 要等待的标签(过滤断点) -- `timeout` (float | None): 等待超时时间。默认为 None(无限等待) - -**异常**: 如果已经在等待挂起,则抛出 `RuntimeError` - -#### `resume() -> None` - -在挂起时恢复执行。 - -#### `_wait_for_continue(tag: str | None = None) -> bool` - -挂起机制的断点。 - -**参数**: - -- `tag` (str | None): 用于断点过滤的标签 - -**返回值**: 如果在运行期间实际等待了则返回 `True`,否则返回 `False` - -#### `yield_response(response: ObjectTypeT) -> None` - -将响应发送到队列或回调函数。 - -**参数**: - -- `response`: 要发送给消费者的数据项 - -**异常**: 如果队列已关闭,则抛出 `RuntimeError` - -#### `set_callback_func(func: CALLBACK_TYPE) -> None` - -设置在产生响应时要执行的回调函数。 - -**参数**: - -- `func` (CALLBACK_TYPE): 在产生响应时要执行的函数 - -**异常**: 如果已经设置了回调函数,则抛出 `RuntimeError` - -#### `yield_response_iteration(iterator: AsyncGenerator[ObjectTypeT, None])` - -将来自异步生成器的响应发送到队列或回调。 - -**参数**: - -- `iterator`: 产生响应项的异步生成器 - -#### `get_response_generator() -> AsyncGenerator[ObjectTypeT, None]` - -返回一个异步生成器,用于迭代队列中的响应。 - -**Yields**: 来自响应队列的数据项 - -**异常**: 如果响应已经被消费,则抛出 `RuntimeError` - -#### `queue_closed() -> bool` - -检查响应队列是否已关闭。 - -**返回值**: 如果队列已关闭则返回 `True`,否则返回 `False` - -#### `set_queue_done() -> None` - -通过放置完成标记来标记响应队列已完成。 - -## 使用示例 - -### 基本流式传输 - -```python -from amrita_core.streaming import SuspendObjectStream - -class MyStream(SuspendObjectStream[str]): - pass - -stream = MyStream() -await stream.yield_response("Hello") -await stream.yield_response("World") -await stream.set_queue_done() - -async for item in stream.get_response_generator(): - print(item) # 打印 "Hello",然后 "World" -``` - -### 带回调 - -```python -async def my_callback(item: str): - print(f"收到: {item}") - -stream = MyStream(callback=my_callback) -await stream.yield_response("Hello") # 立即调用 my_callback("Hello") -``` - -### 挂起/恢复控制 - -```python -import asyncio - -class Processor(SuspendObjectStream[str]): - @SuspendObjectStream.suspend - async def process_step(self, data: str): - return f"已处理: {data}" - -processor = Processor() - -# 外部控制器 -async def controller(): - await processor.wait_to_suspend(timeout=5.0) - print("已挂起!") - processor.resume() - -async def main(): - controller_task = asyncio.create_task(controller()) - - result = await processor.process_step("test") - print(result) # "已处理: test" - - controller_task.cancel() - -asyncio.run(main()) -``` - -### 带标签的断点 - -```python -class AdvancedProcessor(SuspendObjectStream[str]): - @SuspendObjectStream.suspend_with_tag("before_process") - async def preprocess(self, data: str): - return f"预处理: {data}" - - @SuspendObjectStream.suspend_with_tag("after_process") - async def postprocess(self, data: str): - return f"后处理: {data}" - -processor = AdvancedProcessor() - -# 等待特定的带标签断点 -await processor.wait_to_suspend("before_process", timeout=5.0) -# 执行将在 preprocess 方法处暂停 -``` - -## 与 ChatObject 集成 - -`ChatObject` 继承自 `SuspendObjectStream[RESPONSE_TYPE]`,因此所有方法都可在 ChatObject 实例上使用: - -```python -from amrita_core import ChatObject - -chat = ChatObject(...) - -# 直接使用挂起/恢复方法 -await chat.io_stream.wait_to_suspend("custom_tag") -chat.io_stream.resume() - -# 流式传输响应 -async for response in chat.io_stream.get_response_generator(): - print(response) -``` - -## 主要特性 - -- **泛型类型**: 通过泛型参数化支持任何响应类型 -- **内置背压**: 使用 AnyIO 内存对象流进行自动流控制 -- **线程安全**: 回调执行受 aiologic 锁保护 -- **灵活的挂起点**: 支持带标签和不带标签的挂起点 -- **生产者-消费者模式**: 生产者和消费者逻辑的清晰分离 -- **超时安全**: 所有阻塞操作都遵循超时参数 - -## 类型定义 - -- `CALLBACK_TYPE = Callable[[ObjectTypeT], Awaitable[Any]]` -- `ObjectTypeT = TypeVar("ObjectTypeT")` diff --git a/docs/docs/zh/guide/api-reference/classes/UniResponse.md b/docs/docs/zh/guide/api-reference/classes/UniResponse.md index 0ca0a69..a14b551 100644 --- a/docs/docs/zh/guide/api-reference/classes/UniResponse.md +++ b/docs/docs/zh/guide/api-reference/classes/UniResponse.md @@ -10,6 +10,7 @@ The UniResponse class provides a unified response format. - `tool_calls` (T_TOOL): Tool call results, T_TOOL is a generic parameter - `reasoning_content` (str | None): Reasoning/thinking content from the model, if the model supports it (e.g., o1, Claude with extended thinking) - `reasoning_signature` (str | None): Anthropic thinking signature, required for round-tripping thinking content with Anthropic API +- `metadata` ([RequestMetadata](RequestMetadata.md)): 请求元数据,包含请求 ID、模型名称、停止原因和原始提供商请求 ID ## Description diff --git a/docs/docs/zh/guide/api-reference/classes/UniResponseUsage.md b/docs/docs/zh/guide/api-reference/classes/UniResponseUsage.md index 82839e4..43d67f6 100644 --- a/docs/docs/zh/guide/api-reference/classes/UniResponseUsage.md +++ b/docs/docs/zh/guide/api-reference/classes/UniResponseUsage.md @@ -7,6 +7,8 @@ UniResponseUsage class represents usage statistics for responses. - `prompt_tokens` (T_INT): Number of tokens used in the prompt - `completion_tokens` (T_INT): Number of tokens used in the completion (generation) - `total_tokens` (T_INT): Total number of tokens used +- `cache_creation` (int | None): 用于创建缓存条目的 token 数(Anthropic prompt caching) +- `cache_hit` (int | None): 从缓存中读取的 token 数(Anthropic prompt caching) ## Description diff --git a/docs/docs/zh/guide/concepts/agent-strategy.md b/docs/docs/zh/guide/concepts/agent-strategy.md index 8100800..21f0e61 100644 --- a/docs/docs/zh/guide/concepts/agent-strategy.md +++ b/docs/docs/zh/guide/concepts/agent-strategy.md @@ -217,14 +217,14 @@ async def use_builtin_strategies(): ) # 使用这些Agent - chat1 = standard_agent.get_chatobject("你能做什么?") - chat2 = hybrid_agent.get_chatobject("分析这些数据") - chat3 = no_action_agent.get_chatobject("直接回应") - - async with chat1.begin(), chat2.begin(), chat3.begin(): - response1 = await chat1.full_response() - response2 = await chat2.full_response() - response3 = await chat3.full_response() + chat = standard_agent.get_chatobject("你能做什么?") + + chat.begin() + async with chat: + async for chunk in chat.io_stream.get_response_generator(): + content = chunk if isinstance(chunk, str) else chunk.get_content() + print(content, end="", flush=True) + # 对话结束后 chat 自动清理 ``` ## 有状态策略:StrategyLikedObject diff --git a/docs/docs/zh/guide/concepts/event.md b/docs/docs/zh/guide/concepts/event.md index a033198..4f48156 100644 --- a/docs/docs/zh/guide/concepts/event.md +++ b/docs/docs/zh/guide/concepts/event.md @@ -1,6 +1,6 @@ # 事件系统 -> **v0.9.0rc1 起**:事件系统核心(`BaseEvent`、`MatcherFactory`、`EventRegistry`、`MatcherException`、`CancelException`、`PassException`)已迁移至 [AmritaSense](https://sense.amritabot.com)。完整文档见 [AmritaSense 事件系统](https://sense.amritabot.com/guide/advanced/event_system)。`amrita_core.hook.*` 模块现为弃用包装器。 +> **v0.9.0rc1 起**:事件系统核心(`BaseEvent`、`MatcherFactory`、`EventRegistry`、`MatcherException`、`CancelException`、`PassException`)已迁移至 [AmritaSense](https://sense.amritabot.com)。完整文档见 [AmritaSense 事件系统](https://sense.amritabot.com/guide/advanced/event_system)。`amrita_core.hook.*` 兼容端点在 v0.10.x+ 已移除,请直接从 `amrita_sense` 导入。 ## 3.3.1 事件驱动设计 diff --git a/docs/docs/zh/guide/concepts/suspend.md b/docs/docs/zh/guide/concepts/suspend.md index 75b2923..8a42bb6 100644 --- a/docs/docs/zh/guide/concepts/suspend.md +++ b/docs/docs/zh/guide/concepts/suspend.md @@ -1,6 +1,6 @@ # 挂起与恢复机制 -> **v0.9.0rc1 起**:`SuspendObjectStream` 已迁移至 [AmritaSense](https://sense.amritabot.com)。完整文档见 [执行与中断](https://sense.amritabot.com/guide/concepts/exec_and_interrupt) 和 [SuspendObjectStream API](https://sense.amritabot.com/reference/api/suspend-object-stream)。`amrita_core.streaming` 现为弃用包装器。 +> **v0.9.0rc1 起**:`SuspendObjectStream` 已迁移至 [AmritaSense](https://sense.amritabot.com)。完整文档见 [执行与中断](https://sense.amritabot.com/guide/concepts/exec_and_interrupt) 和 [SuspendObjectStream API](https://sense.amritabot.com/reference/api/suspend-object-stream)。`amrita_core.streaming` 兼容端点在 v0.10.x+ 已移除,请直接从 `amrita_sense` 导入。 **注意:这是一个用于特殊场景的高级功能。大多数用户不需要直接使用它。** @@ -189,7 +189,7 @@ async def custom_processing_step(chat_obj): await asyncio.sleep(0.5) # 手动挂起点:仅外部触发 wait_to_suspend 时阻塞,否则立即返回 - await chat_obj._wait_for_continue() + await chat_obj.io_stream._wait_for_continue() print("在挂起点后继续...") await asyncio.sleep(0.5) @@ -233,7 +233,7 @@ asyncio.run(main()) - 支持开发者手动植入,定制业务内部挂点 - 无待处理挂起请求时,调用会立即返回,不阻塞流程 - 基于异步信号实现,独立于业务执行流 -- **tag 参数传递**:手动调用时可传入 tag 参数 `await chat_obj._wait_for_continue(tag="custom_tag")` +- **tag 参数传递**:手动调用时可传入 tag 参数 `await chat_obj.io_stream._wait_for_continue(tag="custom_tag")` ## 组合使用两种中断机制 diff --git a/docs/docs/zh/guide/function-implementation.md b/docs/docs/zh/guide/function-implementation.md index 38c425d..64b69e0 100644 --- a/docs/docs/zh/guide/function-implementation.md +++ b/docs/docs/zh/guide/function-implementation.md @@ -2,26 +2,7 @@ ## 4.1 初始化和加载 -### 4.1.1 init() 初始化函数(已废弃) - -> **从 v0.9.0rc1 起已废弃**:`init()` 函数现在是空操作存根,不再执行任何初始化。请改用 `minimal_init()` 或 `load_amrita()` 进行异步初始化。 - -```python -from amrita_core import init - -# 不再必要 — 现在是一个空操作 -init() -``` - -此函数以前执行几项关键任务: - -- 设置内部日志记录 -- 初始化Jieba进行文本处理 -- 加载内置模块 - -这些任务现在由框架自动处理,或通过 `amrita-sense` 完成。 - -### 4.1.2 load_amrita() 异步加载 +### 4.1.1 load_amrita() 异步加载 `load_amrita()` 函数异步加载AmritaCore组件,特别是当启用了MCP客户端功能时: @@ -36,7 +17,7 @@ async def main(): asyncio.run(main()) ``` -### 4.1.3 minimal_init() 最小化初始化 +### 4.1.2 minimal_init() 最小化初始化 `minimal_init()` 结合了配置设置和异步加载,是推荐的初始化方式: @@ -48,9 +29,9 @@ from amrita_core.config import AmritaConfig await minimal_init(AmritaConfig()) ``` -### 4.1.4 配置设置和检索 +### 4.1.3 配置设置和检索 -#### 4.1.4.1 set_config() 设置配置 +#### 4.1.3.1 set_config() 设置配置 `set_config()` 函数将配置应用到AmritaCore: From 6d8a50fd62f275ef7fdba73218d1af0cc37b9b98 Mon Sep 17 00:00:00 2001 From: RAX4096 Date: Sat, 4 Jul 2026 18:20:44 +0800 Subject: [PATCH 5/6] Fix: attr accesses --- src/amrita_core/adapters/anthropic.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/amrita_core/adapters/anthropic.py b/src/amrita_core/adapters/anthropic.py index 7a6e787..685c249 100644 --- a/src/amrita_core/adapters/anthropic.py +++ b/src/amrita_core/adapters/anthropic.py @@ -291,8 +291,12 @@ async def call_api( completion_tokens=last_msg.usage.output_tokens, total_tokens=last_msg.usage.input_tokens + last_msg.usage.output_tokens, - cache_creation=last_msg.usage.cache_creation_input_tokens, - cache_hit=last_msg.usage.cache_read_input_tokens, + cache_creation=getattr( + last_msg.usage, "cache_creation_input_tokens", None + ), + cache_hit=getattr( + last_msg.usage, "cache_read_input_tokens", None + ), ) else: last_msg: Message = await client.messages.create( @@ -314,8 +318,10 @@ async def call_api( completion_tokens=last_msg.usage.output_tokens, total_tokens=last_msg.usage.input_tokens + last_msg.usage.output_tokens, - cache_creation=last_msg.usage.cache_creation_input_tokens, - cache_hit=last_msg.usage.cache_read_input_tokens, + cache_creation=getattr( + last_msg.usage, "cache_creation_input_tokens", None + ), + cache_hit=getattr(last_msg.usage, "cache_read_input_tokens", None), ) for ct in last_msg.content: if isinstance(ct, TextBlock): @@ -405,8 +411,10 @@ async def call_tools( completion_tokens=response.usage.output_tokens, total_tokens=response.usage.input_tokens + response.usage.output_tokens, - cache_creation=response.usage.cache_creation_input_tokens, - cache_hit=response.usage.cache_read_input_tokens, + cache_creation=getattr( + response.usage, "cache_creation_input_tokens", None + ), + cache_hit=getattr(response.usage, "cache_read_input_tokens", None), ) tool_calls = [] reasoning = "" From cedbe12f6ca18fe3e698bcd4ebd4bcfd0769b91c Mon Sep 17 00:00:00 2001 From: RAX4096 Date: Sat, 4 Jul 2026 20:00:27 +0800 Subject: [PATCH 6/6] lint: docs --- .../guide/api-reference/classes/ChatManager.md | 8 -------- .../api-reference/classes/ClientManager.md | 2 -- .../api-reference/classes/MemoryLimiter.md | 4 ---- .../classes/StrategyLikedObject.md | 14 -------------- docs/docs/guide/concepts/data-backend.md | 14 -------------- docs/docs/guide/concepts/data-containers.md | 12 ------------ docs/docs/guide/concepts/data-misc.md | 18 ------------------ .../api-reference/classes/ClientManager.md | 2 -- docs/docs/zh/guide/concepts/data-backend.md | 14 -------------- docs/docs/zh/guide/concepts/data-containers.md | 12 ------------ docs/docs/zh/guide/concepts/data-misc.md | 18 ------------------ 11 files changed, 118 deletions(-) diff --git a/docs/docs/guide/api-reference/classes/ChatManager.md b/docs/docs/guide/api-reference/classes/ChatManager.md index aa7d4f1..f51281a 100644 --- a/docs/docs/guide/api-reference/classes/ChatManager.md +++ b/docs/docs/guide/api-reference/classes/ChatManager.md @@ -32,16 +32,12 @@ Clean up running chat objects under the specified key, keeping only up to `maxit **Returns:** `bool` — `True` if cleanup was performed, `False` otherwise ---- - ### `get_all_objs() -> list[ChatObjectMeta]` Get metadata for all running chat objects across all sessions. **Returns:** `list[ChatObjectMeta]` — List of all running chat object metadata snapshots ---- - ### `get_objs(session_id: str) -> list[ChatObject]` Get all active chat objects for a given session ID. @@ -52,8 +48,6 @@ Get all active chat objects for a given session ID. **Returns:** `list[ChatObject]` — List of chat objects for the session ---- - ### `async clean_chat_objects(maxitems: int = 10) -> None` Asynchronously clean up all running chat objects across all sessions, limiting each session to `maxitems` objects. @@ -62,8 +56,6 @@ Asynchronously clean up all running chat objects across all sessions, limiting e - `maxitems` (`int`, optional): Maximum number of objects per session. Defaults to `10`. ---- - ### `async add_chat_object(chat_object: ChatObject) -> None` Register a new `ChatObject` instance with the manager. Creates a metadata snapshot and inserts the object at the beginning of the session's list. diff --git a/docs/docs/guide/api-reference/classes/ClientManager.md b/docs/docs/guide/api-reference/classes/ClientManager.md index fe9646a..1526987 100644 --- a/docs/docs/guide/api-reference/classes/ClientManager.md +++ b/docs/docs/guide/api-reference/classes/ClientManager.md @@ -57,8 +57,6 @@ Initializes the ClientManager (runs only once due to singleton pattern). **Note:** Initialization logic executes only on the first instantiation. ---- - _All other methods are inherited from [`MultiClientManager`](MultiClientManager.md):_ - `get_client_by_script(server_script)` - Get client by server script diff --git a/docs/docs/guide/api-reference/classes/MemoryLimiter.md b/docs/docs/guide/api-reference/classes/MemoryLimiter.md index 0527d49..1cc0a67 100644 --- a/docs/docs/guide/api-reference/classes/MemoryLimiter.md +++ b/docs/docs/guide/api-reference/classes/MemoryLimiter.md @@ -66,16 +66,12 @@ Override the default abstract instruction used for context summarization. - `TypeError`: If instruction is not a string - `ValueError`: If instruction is empty ---- - ### `get_abstract_instruction() -> str` Get the current abstract instruction text. **Returns:** `str` ---- - ### `reset_abstract_instruction()` Reset the abstract instruction to the framework default. diff --git a/docs/docs/guide/api-reference/classes/StrategyLikedObject.md b/docs/docs/guide/api-reference/classes/StrategyLikedObject.md index 30befff..1e92325 100644 --- a/docs/docs/guide/api-reference/classes/StrategyLikedObject.md +++ b/docs/docs/guide/api-reference/classes/StrategyLikedObject.md @@ -44,8 +44,6 @@ Called once by the framework when the execution context is ready. Subclasses may **Returns:** `Self` ---- - ### `async single_execute() -> bool` Execute a single agent step for `"agent"` and `"agent-mixed"` category strategies. Called by the framework to perform one iteration of tool calling. @@ -54,8 +52,6 @@ Execute a single agent step for `"agent"` and `"agent-mixed"` category strategie **Note:** This method is used by `"agent"` and `"agent-mixed"` category strategies. `"rag"` and `"workflow"` category strategies should implement `run()` instead. ---- - ### `async run() -> None` Run the complete agent strategy for `"rag"` and `"workflow"` category strategies. Gives full control to the strategy implementation for managing tool calling iterations, context construction, error handling, and response generation. @@ -67,8 +63,6 @@ Run the complete agent strategy for `"rag"` and `"workflow"` category strategies **Note:** This method is used by `"rag"` and `"workflow"` category strategies. `"agent"` and `"agent-mixed"` category strategies should implement `single_execute()` instead. ---- - ### `async call_tool(tool_call: ToolCall) -> str` Execute a single tool call without modifying the agent's context. @@ -83,16 +77,12 @@ Execute a single tool call without modifying the agent's context. **Returns:** `str` — The string response from the tool execution, or a default message if the tool returns `None` ---- - ### `async on_limited() -> None` Handle the event when the agent reaches its tool calling limit. Called when the agent strategy has reached the maximum allowed number of tool calls. **Default behavior:** Sends a notification message to the user about the limit being reached. ---- - ### `async on_exception(exc: BaseException) -> None` Handle exceptions that occur during strategy execution. @@ -101,14 +91,10 @@ Handle exceptions that occur during strategy execution. - `exc` (`BaseException`): The exception that occurred ---- - ### `async on_post_process() -> None` Used to process after all steps are completed successfully. ---- - ### `classmethod get_category() -> Literal["agent", "workflow", "rag", "agent-mixed"]` Get the category of the agent strategy. This is an abstract method that must be implemented by subclasses. diff --git a/docs/docs/guide/concepts/data-backend.md b/docs/docs/guide/concepts/data-backend.md index d1222bb..3f492b5 100644 --- a/docs/docs/guide/concepts/data-backend.md +++ b/docs/docs/guide/concepts/data-backend.md @@ -2,8 +2,6 @@ The **data backend** mechanism decouples memory and ability management from `ChatObject`, enabling pluggable storage backends (in-memory global containers, databases, distributed caches, etc.) without changing the core execution logic. ---- - ## BackendSlots [`BackendSlots`](../api-reference/classes/BackendSlots.md) is a simple dataclass that holds two backend references: @@ -19,8 +17,6 @@ class BackendSlots: `ChatObject` receives a `BackendSlots` instance and delegates all data I/O to it via the workflow nodes `_load_state` and `_commit_memory`. ---- - ## AbilityBackend (Abstract) [`AbilityBackend`](../api-reference/classes/AbilityBackend.md) defines the interface for loading session abilities: @@ -45,8 +41,6 @@ class AbilityBackend: - `load_ability_all()`: returns a fully populated `AbilityContext` - `load_mcp_clients()` / `load_tools()` / `load_presets()`: granular loading, used when `DatabackendOptions` skip flags are set ---- - ## MemoryBackend (Abstract) [`MemoryBackend`](../api-reference/classes/MemoryBackend.md) defines the interface for loading and persisting conversation memory: @@ -65,8 +59,6 @@ class MemoryBackend: - `load_memory()`: called at the start of each `ChatObject` execution - `commit_memory()`: called after completion to persist changes ---- - ## LegacyBackend — Built-in Global Container [`LegacyBackend`](../api-reference/classes/LegacyBackend.md) implements both `AbilityBackend` and `MemoryBackend` using in-process global containers. It is the **default** backend when none is provided: @@ -96,8 +88,6 @@ backend = BackendSlots(ability=LegacyBackend(), memory=LegacyBackend()) > **Note**: `LegacyBackend` stores data **in memory only**. Restart the process and all data is lost. For persistence, implement a custom backend. ---- - ## DatabackendOptions — Fine-Grained Control [`DatabackendOptions`](../api-reference/classes/DatabackendOptions.md) controls which backend operations are skipped during a `ChatObject` run: @@ -117,8 +107,6 @@ options = DatabackendOptions( Pass options to `ChatObject` via the `backend_options` parameter, or to `AgentRuntime.get_chatobject()` via `**kwargs`. ---- - ## Custom Backend Example Implement a custom backend that persists memory to a JSON file: @@ -169,8 +157,6 @@ runtime = AgentRuntime( ) ``` ---- - ## Data Flow Summary ```mermaid diff --git a/docs/docs/guide/concepts/data-containers.md b/docs/docs/guide/concepts/data-containers.md index f683296..9d8c484 100644 --- a/docs/docs/guide/concepts/data-containers.md +++ b/docs/docs/guide/concepts/data-containers.md @@ -2,8 +2,6 @@ AmritaCore provides a set of typed data containers that form the backbone of conversation state, message passing, and context management. These containers are defined in the `amrita_core.types` package and integrate with the [data backend](data-backend.md) for persistence. ---- - ## Message Type The [`Message`](../api-reference/classes/Message.md) class represents a single message in a conversation. It is a generic Pydantic model parameterized by content type: @@ -34,8 +32,6 @@ multi_msg = Message(role="user", content=[ `Message` uses `model_config = ConfigDict(extra="allow")` so additional fields pass through transparently. ---- - ## Content Types AmritaCore supports three built-in content types registered in the `CT_MAP` registry: @@ -72,8 +68,6 @@ content = FileContent(file=File( New content types can be registered via `register_content()` — see [Data Misc](data-misc.md). ---- - ## MemoryModel — Conversation Memory [`MemoryModel`](../api-reference/classes/MemoryModel.md) stores conversation history and context. It inherits from `DirtyAwareBaseModel`, which tracks field modifications (the **dirty-mark** pattern): @@ -102,8 +96,6 @@ memory.clean() # Reset dirty tracking The **dirty-mark** pattern is provided by `DirtyAwareBaseModel` / `DirtyAwareModel`. Child containers (`DirtyList`, `DirtyDict`, `DirtySet`) automatically propagate mutations up to the parent model, enabling ORM-like change tracking. ---- - ## ToolResult [`ToolResult`](../api-reference/classes/ToolResult.md) represents the output of a tool invocation: @@ -126,8 +118,6 @@ CONTENT_LIST_TYPE_ITEM = Message | ToolResult CONTENT_LIST_TYPE = list[CONTENT_LIST_TYPE_ITEM] ``` ---- - ## StateContext — Runtime State [`StateContext`](../api-reference/classes/StateContext.md) is the runtime state container passed to every `ChatObject`. It is a **dataclass** (not a Pydantic model): @@ -151,8 +141,6 @@ state = StateContext( `StateContext` is **lazily initialized** by `ChatObject` via the [data backend](data-backend.md). You normally don't create it yourself — the backend does. ---- - ## AbilityContext [`AbilityContext`](../api-reference/classes/AbilityContext.md) groups the "abilities" available to a session: diff --git a/docs/docs/guide/concepts/data-misc.md b/docs/docs/guide/concepts/data-misc.md index 4d38ac9..e8e0474 100644 --- a/docs/docs/guide/concepts/data-misc.md +++ b/docs/docs/guide/concepts/data-misc.md @@ -2,8 +2,6 @@ This page covers miscellaneous data types that support the core data containers and backend system. ---- - ## ModelConfig — Model Configuration [`ModelConfig`](../api-reference/classes/ModelConfig.md) holds tuning parameters for LLM requests: @@ -30,8 +28,6 @@ model_config = ModelConfig( | `multimodal` | `False` | Enable multimodal input | | `cot_model` | `False` | Strip `\` tags from response | ---- - ## ModelPreset — Model Preset [`ModelPreset`](../api-reference/classes/ModelPreset.md) bundles model identity, endpoint credentials, protocol, and configuration: @@ -55,8 +51,6 @@ preset = ModelPreset( `ModelPreset` also provides `load(path)` / `save(path)` for JSON serialization. ---- - ## ThinkingConfig — Reasoning Configuration [`ThinkingConfig`](../api-reference/classes/ThinkingConfig.md) controls reasoning/thinking features for models that support them: @@ -72,8 +66,6 @@ tc = ThinkingConfig( ) ``` ---- - ## PresetManager — Preset Management [`PresetManager`](../api-reference/classes/PresetManager.md) provides centralized management of `ModelPreset` instances. It is a **singleton** — all sessions share the same instance: @@ -100,8 +92,6 @@ default = manager.get_default_preset() # auto-fallback **Automatic fallback**: If no default is set, `get_default_preset()` picks a random registered preset. Use `test_presets()` for async connectivity checks. ---- - ## UniResponse / UniResponseUsage — Unified Response [`UniResponse`](../api-reference/classes/UniResponse.md) is the unified response format returned by all adapters: @@ -124,8 +114,6 @@ response = UniResponse( All adapter `call_api` / `call_tools` methods yield `UniResponse` instances, providing a vendor-neutral interface. ---- - ## SendMessageWrap — Message Wrapper [`SendMessageWrap`](../api-reference/classes/SendMessageWrap.md) wraps the message list sent to the LLM. It separates the system message (`train`), memory, user query, and any appended end-messages: @@ -151,8 +139,6 @@ wrap.append(Message(role="assistant", content="4")) `SendMessageWrap` is used internally by `ChatObject`'s `context_wrap` and `StrategyContext.original_context`. ---- - ## EmbeddingChunk — Embedding Result [`EmbeddingChunk`](../api-reference/classes/EmbeddingChunk.md) represents a single embedding vector: @@ -168,8 +154,6 @@ chunk = EmbeddingChunk( Returned by embedding adapters via `call_embed()`. Compatible with OpenAI's embedding response format. ---- - ## register_content — Custom Content Types New content types can be registered dynamically: @@ -187,8 +171,6 @@ register_content(MyCustomContent) After registration, `Message` validation automatically deserializes `{"type": "my_type", ...}` dicts into `MyCustomContent` instances. ---- - ## Dirty Tracking `DirtyAwareModel` / `DirtyAwareBaseModel` (in `amrita_core.dirty`) provide automatic mutation tracking for Pydantic models. `MemoryModel` inherits from `DirtyAwareBaseModel`, so: diff --git a/docs/docs/zh/guide/api-reference/classes/ClientManager.md b/docs/docs/zh/guide/api-reference/classes/ClientManager.md index c4f420f..14c8db4 100644 --- a/docs/docs/zh/guide/api-reference/classes/ClientManager.md +++ b/docs/docs/zh/guide/api-reference/classes/ClientManager.md @@ -57,8 +57,6 @@ print(manager1 is manager2) # True - 同一个实例 **注意:** 初始化逻辑仅在第一次实例化时执行。 ---- - _所有其他方法都继承自 [`MultiClientManager`](MultiClientManager.md):_ - `get_client_by_script(server_script)` - 按服务器脚本获取客户端 diff --git a/docs/docs/zh/guide/concepts/data-backend.md b/docs/docs/zh/guide/concepts/data-backend.md index b0d681a..3bbac3d 100644 --- a/docs/docs/zh/guide/concepts/data-backend.md +++ b/docs/docs/zh/guide/concepts/data-backend.md @@ -2,8 +2,6 @@ **数据后端**机制将记忆和能力管理与 `ChatObject` 解耦,使可插拔存储后端(内存全局容器、数据库、分布式缓存等)成为可能,而无需更改核心执行逻辑。 ---- - ## BackendSlots 后端槽位 [`BackendSlots`](../api-reference/classes/BackendSlots.md) 是一个简单的 dataclass,持有两个后端引用: @@ -19,8 +17,6 @@ class BackendSlots: `ChatObject` 接收一个 `BackendSlots` 实例,并通过工作流节点 `_load_state` 和 `_commit_memory` 将所有数据 I/O 委托给它。 ---- - ## AbilityBackend 能力后端(抽象类) [`AbilityBackend`](../api-reference/classes/AbilityBackend.md) 定义了加载会话能力的接口: @@ -45,8 +41,6 @@ class AbilityBackend: - `load_ability_all()`:返回完整填充的 `AbilityContext` - `load_mcp_clients()` / `load_tools()` / `load_presets()`:粒度加载,当设置 `DatabackendOptions` skip 标志时使用 ---- - ## MemoryBackend 记忆后端(抽象类) [`MemoryBackend`](../api-reference/classes/MemoryBackend.md) 定义了加载和持久化对话记忆的接口: @@ -65,8 +59,6 @@ class MemoryBackend: - `load_memory()`:每次 `ChatObject` 执行开始时调用 - `commit_memory()`:完成后调用以持久化更改 ---- - ## LegacyBackend 内置全局容器 [`LegacyBackend`](../api-reference/classes/LegacyBackend.md) 同时实现了 `AbilityBackend` 和 `MemoryBackend`,使用进程内全局容器。当不提供后端时,它是**默认**后端: @@ -96,8 +88,6 @@ backend = BackendSlots(ability=LegacyBackend(), memory=LegacyBackend()) > **注意**:`LegacyBackend` 仅在**内存中**存储数据。重启进程后所有数据将丢失。如需持久化,请实现自定义后端。 ---- - ## DatabackendOptions 细粒度控制 [`DatabackendOptions`](../api-reference/classes/DatabackendOptions.md) 控制在 `ChatObject` 运行期间跳过哪些后端操作: @@ -117,8 +107,6 @@ options = DatabackendOptions( 通过 `backend_options` 参数传递给 `ChatObject`,或通过 `**kwargs` 传递给 `AgentRuntime.get_chatobject()`。 ---- - ## 自定义后端示例 实现一个将记忆持久化到 JSON 文件的自定义后端: @@ -169,8 +157,6 @@ runtime = AgentRuntime( ) ``` ---- - ## 数据流总结 ```mermaid diff --git a/docs/docs/zh/guide/concepts/data-containers.md b/docs/docs/zh/guide/concepts/data-containers.md index 1ef42b4..19a2b28 100644 --- a/docs/docs/zh/guide/concepts/data-containers.md +++ b/docs/docs/zh/guide/concepts/data-containers.md @@ -32,8 +32,6 @@ multi_msg = Message(role="user", content=[ `Message` 使用 `model_config = ConfigDict(extra="allow")`,因此额外字段可以透明传递。 ---- - ## 内容类型 AmritaCore 支持三种内置内容类型,注册在 `CT_MAP` 注册表中: @@ -70,8 +68,6 @@ content = FileContent(file=File( 可以通过 `register_content()` 注册新的内容类型——参见[数据杂项](data-misc.md)。 ---- - ## MemoryModel 记忆模型 [`MemoryModel`](../api-reference/classes/MemoryModel.md) 存储对话历史和上下文。它继承自 `DirtyAwareBaseModel`,可追踪字段修改(**脏标记**模式): @@ -100,8 +96,6 @@ memory.clean() # 重置脏标记 **脏标记**模式由 `DirtyAwareBaseModel` / `DirtyAwareModel` 提供。子容器(`DirtyList`、`DirtyDict`、`DirtySet`)会自动将变更传播到父模型,实现类似 ORM 的变更追踪。 ---- - ## ToolResult 工具结果 [`ToolResult`](../api-reference/classes/ToolResult.md) 表示工具调用的输出: @@ -124,8 +118,6 @@ CONTENT_LIST_TYPE_ITEM = Message | ToolResult CONTENT_LIST_TYPE = list[CONTENT_LIST_TYPE_ITEM] ``` ---- - ## StateContext 运行时状态 [`StateContext`](../api-reference/classes/StateContext.md) 是传递给每个 `ChatObject` 的运行时状态容器。它是一个 **dataclass**(不是 Pydantic 模型): @@ -149,8 +141,6 @@ state = StateContext( `StateContext` 由 `ChatObject` 通过[数据后端](data-backend.md)**延迟初始化**。通常不需要自己创建——后端会处理。 ---- - ## AbilityContext 能力上下文 [`AbilityContext`](../api-reference/classes/AbilityContext.md) 将会话可用的"能力"组织在一起: @@ -168,8 +158,6 @@ ability = AbilityContext( 当不提供参数时,所有字段默认使用**全局单例**管理器——这是 `LegacyBackend` 的行为。 ---- - ## 管理器模式 — Multi\* 与单例管理器 AmritaCore 为管理器类使用两层模式:**`Multi*Manager`** 基类拥有实例级状态,**单例子类**提供全局共享实例。单例是 `AbilityContext` 和 `LegacyBackend` 使用的默认值。 diff --git a/docs/docs/zh/guide/concepts/data-misc.md b/docs/docs/zh/guide/concepts/data-misc.md index 35e27ca..1ed2e77 100644 --- a/docs/docs/zh/guide/concepts/data-misc.md +++ b/docs/docs/zh/guide/concepts/data-misc.md @@ -2,8 +2,6 @@ 本页涵盖支持核心数据容器和后端系统的其他数据类型。 ---- - ## ModelConfig 模型配置 [`ModelConfig`](../api-reference/classes/ModelConfig.md) 保存 LLM 请求的调优参数: @@ -30,8 +28,6 @@ model_config = ModelConfig( | `multimodal` | `False` | 启用多模态输入 | | `cot_model` | `False` | 从响应中去除 `\` 标签 | ---- - ## ModelPreset 模型预设 [`ModelPreset`](../api-reference/classes/ModelPreset.md) 将模型标识、端点凭据、协议和配置捆绑在一起: @@ -55,8 +51,6 @@ preset = ModelPreset( `ModelPreset` 还提供 `load(path)` / `save(path)` 用于 JSON 序列化。 ---- - ## ThinkingConfig 推理配置 [`ThinkingConfig`](../api-reference/classes/ThinkingConfig.md) 控制支持推理/思考功能的模型的推理行为: @@ -72,8 +66,6 @@ tc = ThinkingConfig( ) ``` ---- - ## PresetManager 预设管理 [`PresetManager`](../api-reference/classes/PresetManager.md) 提供 `ModelPreset` 实例的集中管理。它是一个**单例**——所有会话共享同一个实例: @@ -100,8 +92,6 @@ default = manager.get_default_preset() # 自动 fallback **自动 fallback**:如果未设置默认值,`get_default_preset()` 会随机选择一个已注册的预设。使用 `test_presets()` 进行异步连通性检查。 ---- - ## UniResponse / UniResponseUsage 统一响应 [`UniResponse`](../api-reference/classes/UniResponse.md) 是所有适配器返回的统一响应格式: @@ -124,8 +114,6 @@ response = UniResponse( 所有适配器的 `call_api` / `call_tools` 方法都产生 `UniResponse` 实例,提供厂商无关的接口。 ---- - ## SendMessageWrap 消息包装器 [`SendMessageWrap`](../api-reference/classes/SendMessageWrap.md) 包装发送给 LLM 的消息列表。它将系统消息(`train`)、记忆、用户查询和任何附加的结束消息分开: @@ -151,8 +139,6 @@ wrap.append(Message(role="assistant", content="4")) `SendMessageWrap` 被 `ChatObject` 的 `context_wrap` 和 `StrategyContext.original_context` 内部使用。 ---- - ## EmbeddingChunk 嵌入结果 [`EmbeddingChunk`](../api-reference/classes/EmbeddingChunk.md) 表示单个嵌入向量: @@ -168,8 +154,6 @@ chunk = EmbeddingChunk( 由嵌入适配器通过 `call_embed()` 返回。与 OpenAI 的嵌入响应格式兼容。 ---- - ## register_content 自定义内容类型 可以动态注册新的内容类型: @@ -187,8 +171,6 @@ register_content(MyCustomContent) 注册后,`Message` 验证会自动将 `{"type": "my_type", ...}` 字典反序列化为 `MyCustomContent` 实例。 ---- - ## 脏标记追踪 `DirtyAwareModel` / `DirtyAwareBaseModel`(在 `amrita_core.dirty` 中)为 Pydantic 模型提供自动变更追踪。`MemoryModel` 继承自 `DirtyAwareBaseModel`,因此: