From edffe70e24dcde4197b09a87587bbefa3e67a11a Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sun, 16 Mar 2025 14:41:24 +0100 Subject: [PATCH 01/27] Support Thinking part --- main.py | 31 +++++++++++++++++++ pydantic_ai_slim/pydantic_ai/_agent_graph.py | 2 ++ pydantic_ai_slim/pydantic_ai/messages.py | 16 +++++++++- .../pydantic_ai/models/anthropic.py | 17 ++++++++-- 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 000000000..1f4e84fd1 --- /dev/null +++ b/main.py @@ -0,0 +1,31 @@ +import asyncio + +from pydantic_ai import Agent + +agent = Agent(model='anthropic:claude-3-7-sonnet-latest') + + +@agent.tool_plain +def sum(a: int, b: int) -> int: + """Get the sum of two numbers. + + Args: + a: The first number. + b: The second number. + + Returns: + The sum of the two numbers. + """ + return a + b + + +async def main(): + async with agent.iter('Get me the sum of 1 and 2, using the sum tool.') as agent_run: + async for node in agent_run: + print(node) + print() + print(agent_run.result) + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/pydantic_ai_slim/pydantic_ai/_agent_graph.py b/pydantic_ai_slim/pydantic_ai/_agent_graph.py index 51f77f9aa..291bce7d4 100644 --- a/pydantic_ai_slim/pydantic_ai/_agent_graph.py +++ b/pydantic_ai_slim/pydantic_ai/_agent_graph.py @@ -406,6 +406,8 @@ async def _run_stream() -> AsyncIterator[_messages.HandleResponseEvent]: texts.append(part.content) elif isinstance(part, _messages.ToolCallPart): tool_calls.append(part) + elif isinstance(part, _messages.ThinkingPart): + ... else: assert_never(part) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 71b144c89..af4a86fe6 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -393,6 +393,20 @@ def has_content(self) -> bool: return bool(self.content) +@dataclass +class ThinkingPart: + """A thinking response from a model.""" + + content: str + """The thinking content of the response.""" + + signature: str | None = None + """The signature of the thinking.""" + + part_kind: Literal['thinking'] = 'thinking' + """Part type identifier, this is available on all parts as a discriminator.""" + + @dataclass class ToolCallPart: """A tool call from a model.""" @@ -442,7 +456,7 @@ def has_content(self) -> bool: return bool(self.args) -ModelResponsePart = Annotated[Union[TextPart, ToolCallPart], pydantic.Discriminator('part_kind')] +ModelResponsePart = Annotated[Union[TextPart, ToolCallPart, ThinkingPart], pydantic.Discriminator('part_kind')] """A message part returned by a model.""" diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index 53ea363c0..845dd3fb2 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -9,7 +9,7 @@ from json import JSONDecodeError, loads as json_loads from typing import Any, Literal, Union, cast, overload -from anthropic.types import DocumentBlockParam +from anthropic.types import DocumentBlockParam, ThinkingBlock, ThinkingBlockParam from httpx import AsyncClient as AsyncHTTPClient from typing_extensions import assert_never, deprecated @@ -27,6 +27,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -256,13 +257,14 @@ async def _messages_create( try: return await self.client.messages.create( - max_tokens=model_settings.get('max_tokens', 1024), + max_tokens=model_settings.get('max_tokens', 2048), system=system_prompt or NOT_GIVEN, messages=anthropic_messages, model=self._model_name, tools=tools or NOT_GIVEN, tool_choice=tool_choice or NOT_GIVEN, stream=stream, + thinking={'budget_tokens': 1024, 'type': 'enabled'}, temperature=model_settings.get('temperature', NOT_GIVEN), top_p=model_settings.get('top_p', NOT_GIVEN), timeout=model_settings.get('timeout', NOT_GIVEN), @@ -279,6 +281,8 @@ def _process_response(self, response: AnthropicMessage) -> ModelResponse: for item in response.content: if isinstance(item, TextBlock): items.append(TextPart(content=item.text)) + elif isinstance(item, ThinkingBlock): + items.append(ThinkingPart(content=item.thinking, signature=item.signature)) else: assert isinstance(item, ToolUseBlock), 'unexpected item type' items.append( @@ -345,10 +349,17 @@ async def _map_message(self, messages: list[ModelMessage]) -> tuple[str, list[Me user_content_params.append(retry_param) anthropic_messages.append(MessageParam(role='user', content=user_content_params)) elif isinstance(m, ModelResponse): - assistant_content_params: list[TextBlockParam | ToolUseBlockParam] = [] + assistant_content_params: list[TextBlockParam | ToolUseBlockParam | ThinkingBlockParam] = [] for response_part in m.parts: if isinstance(response_part, TextPart): assistant_content_params.append(TextBlockParam(text=response_part.content, type='text')) + elif isinstance(response_part, ThinkingPart): + assert response_part.signature is not None, 'Thinking part must have a signature' + assistant_content_params.append( + ThinkingBlockParam( + thinking=response_part.content, signature=response_part.signature, type='thinking' + ) + ) else: tool_use_block_param = ToolUseBlockParam( id=_guard_tool_call_id(t=response_part, model_source='Anthropic'), From bc849932d900197e04168841563561f8275f95f9 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 1 Apr 2025 16:23:25 +0200 Subject: [PATCH 02/27] push --- main.py | 27 ++++++++++++++----- .../pydantic_ai/models/anthropic.py | 18 ++++++++++--- pydantic_ai_slim/pydantic_ai/models/cohere.py | 2 +- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/main.py b/main.py index 1f4e84fd1..d7c3ea518 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,12 @@ import asyncio from pydantic_ai import Agent +from pydantic_ai.models.anthropic import AnthropicModelSettings -agent = Agent(model='anthropic:claude-3-7-sonnet-latest') +agent = Agent( + model='anthropic:claude-3-7-sonnet-latest', + model_settings=AnthropicModelSettings(anthropic_thinking={'budget_tokens': 1024, 'type': 'enabled'}), +) @agent.tool_plain @@ -20,11 +24,22 @@ def sum(a: int, b: int) -> int: async def main(): - async with agent.iter('Get me the sum of 1 and 2, using the sum tool.') as agent_run: - async for node in agent_run: - print(node) - print() - print(agent_run.result) + result = await agent.run('Get me the sum of 1 and 2, using the sum tool.') + print(result.data) + + result = await agent.run( + 'Sum the previous result with 3.', model='openai:gpt-4o', message_history=result.all_messages() + ) + print(result.data) + + from rich.pretty import pprint + + pprint(result.all_messages()) + # async with agent.iter('Get me the sum of 1 and 2, using the sum tool.') as agent_run: + # async for node in agent_run: + # print(node) + # print() + # print(agent_run.result) if __name__ == '__main__': diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index 5b99241a7..bb6ba141d 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -9,7 +9,6 @@ from json import JSONDecodeError, loads as json_loads from typing import Any, Literal, Union, cast, overload -from anthropic.types import DocumentBlockParam, ThinkingBlock, ThinkingBlockParam from typing_extensions import assert_never from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage @@ -41,6 +40,7 @@ from anthropic.types import ( Base64PDFSourceParam, ContentBlock, + DocumentBlockParam, ImageBlockParam, Message as AnthropicMessage, MessageParam, @@ -56,6 +56,9 @@ TextBlock, TextBlockParam, TextDelta, + ThinkingBlock, + ThinkingBlockParam, + ThinkingConfigParam, ToolChoiceParam, ToolParam, ToolResultBlockParam, @@ -85,7 +88,7 @@ """ -class AnthropicModelSettings(ModelSettings): +class AnthropicModelSettings(ModelSettings, total=False): """Settings used for an Anthropic model request. ALL FIELDS MUST BE `anthropic_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS. @@ -94,7 +97,14 @@ class AnthropicModelSettings(ModelSettings): anthropic_metadata: MetadataParam """An object describing metadata about the request. - Contains `user_id`, an external identifier for the user who is associated with the request.""" + Contains `user_id`, an external identifier for the user who is associated with the request. + """ + + anthropic_thinking: ThinkingConfigParam + """Determine whether the model should generate a thinking block. + + See [the Anthropic docs](https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking) for more information. + """ @dataclass(init=False) @@ -228,7 +238,7 @@ async def _messages_create( tools=tools or NOT_GIVEN, tool_choice=tool_choice or NOT_GIVEN, stream=stream, - thinking={'budget_tokens': 1024, 'type': 'enabled'}, + thinking=model_settings.get('anthropic_thinking', NOT_GIVEN), temperature=model_settings.get('temperature', NOT_GIVEN), top_p=model_settings.get('top_p', NOT_GIVEN), timeout=model_settings.get('timeout', NOT_GIVEN), diff --git a/pydantic_ai_slim/pydantic_ai/models/cohere.py b/pydantic_ai_slim/pydantic_ai/models/cohere.py index 4c7e1faa2..bced64ea3 100644 --- a/pydantic_ai_slim/pydantic_ai/models/cohere.py +++ b/pydantic_ai_slim/pydantic_ai/models/cohere.py @@ -5,7 +5,6 @@ from itertools import chain from typing import Literal, Union, cast -from cohere import TextAssistantMessageContentItem from typing_extensions import assert_never from .. import ModelHTTPError, result @@ -38,6 +37,7 @@ ChatMessageV2, ChatResponse, SystemChatMessageV2, + TextAssistantMessageContentItem, ToolCallV2, ToolCallV2Function, ToolChatMessageV2, From e5202cbd3ad7aa9161309c956f47033beafc270c Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 18 Apr 2025 13:38:16 +0200 Subject: [PATCH 03/27] ignore --- pydantic_ai_slim/pydantic_ai/_agent_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/_agent_graph.py b/pydantic_ai_slim/pydantic_ai/_agent_graph.py index ec0953f09..c3bf329ec 100644 --- a/pydantic_ai_slim/pydantic_ai/_agent_graph.py +++ b/pydantic_ai_slim/pydantic_ai/_agent_graph.py @@ -397,7 +397,7 @@ async def stream( async for _event in stream: pass - async def _run_stream( + async def _run_stream( # noqa: C901 self, ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, NodeRunEndT]] ) -> AsyncIterator[_messages.HandleResponseEvent]: if self._events_iterator is None: From f4b7fde832de4c1745b08afeb28c5996a08e8ae0 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 18 Apr 2025 17:03:34 +0200 Subject: [PATCH 04/27] Add more support for thinking part --- main2.py | 49 +++++++++++++++++++ .../pydantic_ai/models/bedrock.py | 12 +++-- pydantic_ai_slim/pydantic_ai/models/gemini.py | 2 +- pydantic_ai_slim/pydantic_ai/models/groq.py | 29 +++++++++-- .../pydantic_ai/models/mistral.py | 2 +- pydantic_ai_slim/pydantic_ai/models/openai.py | 38 ++++++++++++-- pydantic_ai_slim/pyproject.toml | 2 +- uv.lock | 8 +-- 8 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 main2.py diff --git a/main2.py b/main2.py new file mode 100644 index 000000000..ca3b60f12 --- /dev/null +++ b/main2.py @@ -0,0 +1,49 @@ +from rich.pretty import pprint + +from pydantic_ai import Agent +from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings + +model = OpenAIResponsesModel('o4-mini') +agent = Agent(model=model) +result = agent.run_sync( + 'Tell me the steps to cross the street!', + model_settings=OpenAIResponsesModelSettings( + openai_reasoning_effort='high', + openai_reasoning_generate_summary='detailed', + ), +) +pprint(result.all_messages()) + +# anthropic_agent = Agent('anthropic:claude-3-7-sonnet-latest') +# result = anthropic_agent.run_sync( +# 'Now make analogous steps to cross the river!', +# model_settings=AnthropicModelSettings( +# anthropic_thinking={'type': 'enabled', 'budget_tokens': 1024}, +# ), +# message_history=result.all_messages(), +# ) +# pprint(result.all_messages()) + +# groq_agent = Agent('groq:deepseek-r1-distill-llama-70b') +# result = groq_agent.run_sync( +# 'Tell me the steps to cross the ocean!', +# model_settings=GroqModelSettings(groq_reasoning_format='raw'), +# message_history=result.all_messages(), +# ) +# pprint(result.all_messages()) + +bedrock_agent = Agent('bedrock:us.deepseek.r1-v1:0') +result = bedrock_agent.run_sync( + 'Tell me the steps to cross the ocean!', + # message_history=result.all_messages(), +) +pprint(result.all_messages()) + + +# deepseek_agent = Agent('deepseek:deepseek-reasoner') +# result = deepseek_agent.run_sync( +# 'Tell me the steps to cross the ocean!', +# model_settings=OpenAIModelSettings(openai_reasoning_effort='high'), +# # message_history=result.all_messages(), +# ) +# pprint(result.all_messages()) diff --git a/pydantic_ai_slim/pydantic_ai/models/bedrock.py b/pydantic_ai_slim/pydantic_ai/models/bedrock.py index a8882ba62..d5bd68e81 100644 --- a/pydantic_ai_slim/pydantic_ai/models/bedrock.py +++ b/pydantic_ai_slim/pydantic_ai/models/bedrock.py @@ -27,6 +27,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -252,11 +253,16 @@ async def _process_response(self, response: ConverseResponseTypeDef) -> tuple[Mo items: list[ModelResponsePart] = [] if message := response['output'].get('message'): for item in message['content']: + if reasoning_content := item.get('reasoningContent'): + reasoning_text = reasoning_content.get('reasoningText') + if reasoning_text: + thinking_part = ThinkingPart(content=reasoning_text['text']) + if reasoning_signature := reasoning_text.get('signature'): + thinking_part.signature = reasoning_signature + items.append(thinking_part) if text := item.get('text'): items.append(TextPart(content=text)) - else: - tool_use = item.get('toolUse') - assert tool_use is not None, f'Found a content that is not a text or tool use: {item}' + elif tool_use := item.get('toolUse'): items.append( ToolCallPart( tool_name=tool_use['name'], diff --git a/pydantic_ai_slim/pydantic_ai/models/gemini.py b/pydantic_ai_slim/pydantic_ai/models/gemini.py index b98bfb9b1..ab4eb4ea1 100644 --- a/pydantic_ai_slim/pydantic_ai/models/gemini.py +++ b/pydantic_ai_slim/pydantic_ai/models/gemini.py @@ -73,7 +73,7 @@ """ -class GeminiModelSettings(ModelSettings): +class GeminiModelSettings(ModelSettings, total=False): """Settings used for a Gemini model request. ALL FIELDS MUST BE `gemini_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS. diff --git a/pydantic_ai_slim/pydantic_ai/models/groq.py b/pydantic_ai_slim/pydantic_ai/models/groq.py index 7559e8851..37502b677 100644 --- a/pydantic_ai_slim/pydantic_ai/models/groq.py +++ b/pydantic_ai_slim/pydantic_ai/models/groq.py @@ -23,6 +23,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -82,13 +83,13 @@ """ -class GroqModelSettings(ModelSettings): +class GroqModelSettings(ModelSettings, total=False): """Settings used for a Groq model request. ALL FIELDS MUST BE `groq_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS. """ - # This class is a placeholder for any future groq-specific settings + groq_reasoning_format: Literal['hidden', 'raw', 'parsed'] @dataclass(init=False) @@ -215,6 +216,7 @@ async def _completions_create( timeout=model_settings.get('timeout', NOT_GIVEN), seed=model_settings.get('seed', NOT_GIVEN), presence_penalty=model_settings.get('presence_penalty', NOT_GIVEN), + reasoning_format=model_settings.get('groq_reasoning_format', NOT_GIVEN), frequency_penalty=model_settings.get('frequency_penalty', NOT_GIVEN), logit_bias=model_settings.get('logit_bias', NOT_GIVEN), extra_headers={'User-Agent': get_user_agent()}, @@ -231,7 +233,25 @@ def _process_response(self, response: chat.ChatCompletion) -> ModelResponse: choice = response.choices[0] items: list[ModelResponsePart] = [] if choice.message.content is not None: - items.append(TextPart(content=choice.message.content)) + content = choice.message.content + # The `` tag is only present if `groq_reasoning_format` is set to `raw`. + while '' in content: + before_think, content = content.split('', 1) + if before_think.strip(): + items.append(TextPart(content=before_think)) + + if '' in content: + think_content, content = content.split('', 1) + items.append(ThinkingPart(content=think_content)) + else: + # Malformed tag + items.append(TextPart(content=content)) + content = '' + if content: + items.append(TextPart(content=content)) + # NOTE: The `reasoning` field is only present if `groq_reasoning_format` is set to `parsed`. + if choice.message.reasoning is not None: + items.append(ThinkingPart(content=choice.message.reasoning)) if choice.message.tool_calls is not None: for c in choice.message.tool_calls: items.append(ToolCallPart(tool_name=c.function.name, args=c.function.arguments, tool_call_id=c.id)) @@ -270,6 +290,9 @@ def _map_messages(self, messages: list[ModelMessage]) -> list[chat.ChatCompletio texts.append(item.content) elif isinstance(item, ToolCallPart): tool_calls.append(self._map_tool_call(item)) + elif isinstance(item, ThinkingPart): + # Skip thinking parts when mapping to Groq messages + continue else: assert_never(item) message_param = chat.ChatCompletionAssistantMessageParam(role='assistant') diff --git a/pydantic_ai_slim/pydantic_ai/models/mistral.py b/pydantic_ai_slim/pydantic_ai/models/mistral.py index 8bff0a76f..faabea298 100644 --- a/pydantic_ai_slim/pydantic_ai/models/mistral.py +++ b/pydantic_ai_slim/pydantic_ai/models/mistral.py @@ -91,7 +91,7 @@ """ -class MistralModelSettings(ModelSettings): +class MistralModelSettings(ModelSettings, total=False): """Settings used for a Mistral model request. ALL FIELDS MUST BE `mistral_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS. diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 628dad556..51c3b2d62 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -28,6 +28,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -59,6 +60,7 @@ from openai.types.chat.chat_completion_content_part_input_audio_param import InputAudio from openai.types.responses import ComputerToolParam, FileSearchToolParam, WebSearchToolParam from openai.types.responses.response_input_param import FunctionCallOutput, Message + from openai.types.responses.response_reasoning_item_param import ResponseReasoningItemParam, Summary from openai.types.shared import ReasoningEffort from openai.types.shared_params import Reasoning except ImportError as _import_error: @@ -123,6 +125,9 @@ class OpenAIResponsesModelSettings(OpenAIModelSettings, total=False): """ openai_reasoning_generate_summary: Literal['detailed', 'concise'] + """Deprecated alias for `openai_reasoning_summary`.""" + + openai_reasoning_summary: Literal['detailed', 'concise'] """A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. @@ -546,10 +551,13 @@ def _process_response(self, response: responses.Response) -> ModelResponse: """Process a non-streamed response, and prepare a message to return.""" timestamp = datetime.fromtimestamp(response.created_at, tz=timezone.utc) items: list[ModelResponsePart] = [] - items.append(TextPart(response.output_text)) for item in response.output: - if item.type == 'function_call': + if item.type == 'reasoning': + for summary in item.summary: + items.append(ThinkingPart(content=summary.text, signature=item.id)) + elif item.type == 'function_call': items.append(ToolCallPart(item.name, item.arguments, tool_call_id=item.call_id)) + items.append(TextPart(response.output_text)) return ModelResponse(items, model_name=response.model, timestamp=timestamp) async def _process_streamed_response( @@ -633,11 +641,22 @@ async def _responses_create( def _get_reasoning(self, model_settings: OpenAIResponsesModelSettings) -> Reasoning | NotGiven: reasoning_effort = model_settings.get('openai_reasoning_effort', None) + reasoning_summary = model_settings.get('openai_reasoning_summary', None) reasoning_generate_summary = model_settings.get('openai_reasoning_generate_summary', None) - if reasoning_effort is None and reasoning_generate_summary is None: + if reasoning_summary and reasoning_generate_summary: + raise ValueError('`openai_reasoning_summary` and `openai_reasoning_generate_summary` cannot both be set.') + + if reasoning_generate_summary is not None: + warnings.warn( + '`openai_reasoning_generate_summary` is deprecated, use `openai_reasoning_summary` instead', + DeprecationWarning, + ) + reasoning_summary = reasoning_generate_summary + + if reasoning_effort is None and reasoning_summary is None: return NOT_GIVEN - return Reasoning(effort=reasoning_effort, generate_summary=reasoning_generate_summary) + return Reasoning(effort=reasoning_effort, summary=reasoning_summary) def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[responses.FunctionToolParam]: tools = [self._map_tool_definition(r) for r in model_request_parameters.function_tools] @@ -693,13 +712,24 @@ async def _map_messages( else: assert_never(part) elif isinstance(message, ModelResponse): + thinking_parts: list[ThinkingPart] = [] for item in message.parts: if isinstance(item, TextPart): openai_messages.append(responses.EasyInputMessageParam(role='assistant', content=item.content)) elif isinstance(item, ToolCallPart): openai_messages.append(self._map_tool_call(item)) + elif isinstance(item, ThinkingPart): + thinking_parts.append(item) else: assert_never(item) + if thinking_parts: + openai_messages.append( + ResponseReasoningItemParam( + id=thinking_parts[0].signature, + summary=[Summary(text=item.content, type='summary_text') for item in thinking_parts], + type='reasoning', + ) + ) else: assert_never(message) instructions = self._get_instructions(messages) or NOT_GIVEN diff --git a/pydantic_ai_slim/pyproject.toml b/pydantic_ai_slim/pyproject.toml index 8f0471aa9..67d9535e0 100644 --- a/pydantic_ai_slim/pyproject.toml +++ b/pydantic_ai_slim/pyproject.toml @@ -56,7 +56,7 @@ dependencies = [ # WARNING if you add optional groups, please update docs/install.md logfire = ["logfire>=3.11.0"] # Models -openai = ["openai>=1.74.0"] +openai = ["openai>=1.75.0"] cohere = ["cohere>=5.13.11; platform_system != 'Emscripten'"] vertexai = ["google-auth>=2.36.0", "requests>=2.32.3"] anthropic = ["anthropic>=0.49.0"] diff --git a/uv.lock b/uv.lock index a8c9c0b8e..a77c68c3e 100644 --- a/uv.lock +++ b/uv.lock @@ -2156,7 +2156,7 @@ wheels = [ [[package]] name = "openai" -version = "1.74.0" +version = "1.75.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2168,9 +2168,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/86/c605a6e84da0248f2cebfcd864b5a6076ecf78849245af5e11d2a5ec7977/openai-1.74.0.tar.gz", hash = "sha256:592c25b8747a7cad33a841958f5eb859a785caea9ee22b9e4f4a2ec062236526", size = 427571 } +sdist = { url = "https://files.pythonhosted.org/packages/99/b1/318f5d4c482f19c5fcbcde190801bfaaaec23413cda0b88a29f6897448ff/openai-1.75.0.tar.gz", hash = "sha256:fb3ea907efbdb1bcfd0c44507ad9c961afd7dce3147292b54505ecfd17be8fd1", size = 429492 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/91/8c150f16a96367e14bd7d20e86e0bbbec3080e3eb593e63f21a7f013f8e4/openai-1.74.0-py3-none-any.whl", hash = "sha256:aff3e0f9fb209836382ec112778667027f4fd6ae38bdb2334bc9e173598b092a", size = 644790 }, + { url = "https://files.pythonhosted.org/packages/80/9a/f34f163294345f123673ed03e77c33dee2534f3ac1f9d18120384457304d/openai-1.75.0-py3-none-any.whl", hash = "sha256:fe6f932d2ded3b429ff67cc9ad118c71327db32eb9d32dd723de3acfca337125", size = 646972 }, ] [[package]] @@ -2981,7 +2981,7 @@ requires-dist = [ { name = "logfire", marker = "extra == 'logfire'", specifier = ">=3.11.0" }, { name = "mcp", marker = "python_full_version >= '3.10' and extra == 'mcp'", specifier = ">=1.5.0" }, { name = "mistralai", marker = "extra == 'mistral'", specifier = ">=1.2.5" }, - { name = "openai", marker = "extra == 'openai'", specifier = ">=1.74.0" }, + { name = "openai", marker = "extra == 'openai'", specifier = ">=1.75.0" }, { name = "opentelemetry-api", specifier = ">=1.28.0" }, { name = "prompt-toolkit", marker = "extra == 'cli'", specifier = ">=3" }, { name = "pydantic", specifier = ">=2.10" }, From f0da1814553d4d4fc79d68bc9b0c334452624554 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 19 Apr 2025 09:09:55 +0200 Subject: [PATCH 05/27] Add tests --- main.py | 85 ++--- main2.py | 49 --- pydantic_ai_slim/pydantic_ai/models/openai.py | 2 + tests/conftest.py | 5 +- .../test_anthropic_model_thinking_part.yaml | 213 ++++++++++++ ...t_groq_model_thinking_part_raw_format.yaml | 252 ++++++++++++++ ..._openai_responses_model_thinking_part.yaml | 321 ++++++++++++++++++ tests/models/test_anthropic.py | 101 +++++- tests/models/test_groq.py | 20 +- tests/models/test_openai.py | 77 ++++- 10 files changed, 1012 insertions(+), 113 deletions(-) delete mode 100644 main2.py create mode 100644 tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part.yaml create mode 100644 tests/models/cassettes/test_groq/test_groq_model_thinking_part_raw_format.yaml create mode 100644 tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml diff --git a/main.py b/main.py index d7c3ea518..ca3b60f12 100644 --- a/main.py +++ b/main.py @@ -1,46 +1,49 @@ -import asyncio +from rich.pretty import pprint from pydantic_ai import Agent -from pydantic_ai.models.anthropic import AnthropicModelSettings - -agent = Agent( - model='anthropic:claude-3-7-sonnet-latest', - model_settings=AnthropicModelSettings(anthropic_thinking={'budget_tokens': 1024, 'type': 'enabled'}), +from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings + +model = OpenAIResponsesModel('o4-mini') +agent = Agent(model=model) +result = agent.run_sync( + 'Tell me the steps to cross the street!', + model_settings=OpenAIResponsesModelSettings( + openai_reasoning_effort='high', + openai_reasoning_generate_summary='detailed', + ), ) +pprint(result.all_messages()) + +# anthropic_agent = Agent('anthropic:claude-3-7-sonnet-latest') +# result = anthropic_agent.run_sync( +# 'Now make analogous steps to cross the river!', +# model_settings=AnthropicModelSettings( +# anthropic_thinking={'type': 'enabled', 'budget_tokens': 1024}, +# ), +# message_history=result.all_messages(), +# ) +# pprint(result.all_messages()) + +# groq_agent = Agent('groq:deepseek-r1-distill-llama-70b') +# result = groq_agent.run_sync( +# 'Tell me the steps to cross the ocean!', +# model_settings=GroqModelSettings(groq_reasoning_format='raw'), +# message_history=result.all_messages(), +# ) +# pprint(result.all_messages()) + +bedrock_agent = Agent('bedrock:us.deepseek.r1-v1:0') +result = bedrock_agent.run_sync( + 'Tell me the steps to cross the ocean!', + # message_history=result.all_messages(), +) +pprint(result.all_messages()) -@agent.tool_plain -def sum(a: int, b: int) -> int: - """Get the sum of two numbers. - - Args: - a: The first number. - b: The second number. - - Returns: - The sum of the two numbers. - """ - return a + b - - -async def main(): - result = await agent.run('Get me the sum of 1 and 2, using the sum tool.') - print(result.data) - - result = await agent.run( - 'Sum the previous result with 3.', model='openai:gpt-4o', message_history=result.all_messages() - ) - print(result.data) - - from rich.pretty import pprint - - pprint(result.all_messages()) - # async with agent.iter('Get me the sum of 1 and 2, using the sum tool.') as agent_run: - # async for node in agent_run: - # print(node) - # print() - # print(agent_run.result) - - -if __name__ == '__main__': - asyncio.run(main()) +# deepseek_agent = Agent('deepseek:deepseek-reasoner') +# result = deepseek_agent.run_sync( +# 'Tell me the steps to cross the ocean!', +# model_settings=OpenAIModelSettings(openai_reasoning_effort='high'), +# # message_history=result.all_messages(), +# ) +# pprint(result.all_messages()) diff --git a/main2.py b/main2.py deleted file mode 100644 index ca3b60f12..000000000 --- a/main2.py +++ /dev/null @@ -1,49 +0,0 @@ -from rich.pretty import pprint - -from pydantic_ai import Agent -from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings - -model = OpenAIResponsesModel('o4-mini') -agent = Agent(model=model) -result = agent.run_sync( - 'Tell me the steps to cross the street!', - model_settings=OpenAIResponsesModelSettings( - openai_reasoning_effort='high', - openai_reasoning_generate_summary='detailed', - ), -) -pprint(result.all_messages()) - -# anthropic_agent = Agent('anthropic:claude-3-7-sonnet-latest') -# result = anthropic_agent.run_sync( -# 'Now make analogous steps to cross the river!', -# model_settings=AnthropicModelSettings( -# anthropic_thinking={'type': 'enabled', 'budget_tokens': 1024}, -# ), -# message_history=result.all_messages(), -# ) -# pprint(result.all_messages()) - -# groq_agent = Agent('groq:deepseek-r1-distill-llama-70b') -# result = groq_agent.run_sync( -# 'Tell me the steps to cross the ocean!', -# model_settings=GroqModelSettings(groq_reasoning_format='raw'), -# message_history=result.all_messages(), -# ) -# pprint(result.all_messages()) - -bedrock_agent = Agent('bedrock:us.deepseek.r1-v1:0') -result = bedrock_agent.run_sync( - 'Tell me the steps to cross the ocean!', - # message_history=result.all_messages(), -) -pprint(result.all_messages()) - - -# deepseek_agent = Agent('deepseek:deepseek-reasoner') -# result = deepseek_agent.run_sync( -# 'Tell me the steps to cross the ocean!', -# model_settings=OpenAIModelSettings(openai_reasoning_effort='high'), -# # message_history=result.all_messages(), -# ) -# pprint(result.all_messages()) diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 51c3b2d62..3d35b55eb 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -554,6 +554,8 @@ def _process_response(self, response: responses.Response) -> ModelResponse: for item in response.output: if item.type == 'reasoning': for summary in item.summary: + # NOTE: We use the same id for all summaries because we can merge them on the round trip. + # The providers don't force the signature to be unique. items.append(ThinkingPart(content=summary.text, signature=item.id)) elif item.type == 'function_call': items.append(ToolCallPart(item.name, item.arguments, tool_call_id=item.call_id)) diff --git a/tests/conftest.py b/tests/conftest.py index e614ea20f..5e988afbc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,7 +24,7 @@ from pydantic_ai.messages import BinaryContent from pydantic_ai.models import Model, cached_async_http_client -__all__ = 'IsDatetime', 'IsFloat', 'IsNow', 'IsStr', 'TestEnv', 'ClientWithHandler', 'try_import' +__all__ = 'IsDatetime', 'IsFloat', 'IsInstance', 'IsNow', 'IsStr', 'TestEnv', 'ClientWithHandler', 'try_import' pydantic_ai.models.ALLOW_MODEL_REQUESTS = False @@ -32,12 +32,13 @@ if TYPE_CHECKING: from pydantic_ai.providers.bedrock import BedrockProvider + def IsInstance(*args: Any, **kwargs: Any) -> Any: ... def IsDatetime(*args: Any, **kwargs: Any) -> datetime: ... def IsFloat(*args: Any, **kwargs: Any) -> float: ... def IsNow(*args: Any, **kwargs: Any) -> datetime: ... def IsStr(*args: Any, **kwargs: Any) -> str: ... else: - from dirty_equals import IsDatetime, IsFloat, IsNow as _IsNow, IsStr + from dirty_equals import IsDatetime, IsFloat, IsInstance, IsNow as _IsNow, IsStr def IsNow(*args: Any, **kwargs: Any): # Increase the default value of `delta` to 10 to reduce test flakiness on overburdened machines diff --git a/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part.yaml b/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part.yaml new file mode 100644 index 000000000..37e9b4960 --- /dev/null +++ b/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part.yaml @@ -0,0 +1,213 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '213' + content-type: + - application/json + host: + - api.anthropic.com + method: POST + parsed_body: + max_tokens: 2048 + messages: + - content: + - text: How do I cross the street? + type: text + role: user + model: claude-3-7-sonnet-latest + stream: false + thinking: + budget_tokens: 1024 + type: enabled + uri: https://api.anthropic.com/v1/messages + response: + headers: + connection: + - keep-alive + content-length: + - '1995' + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + content: + - signature: ErUBCkYIAhgCIkD6Sf780fvjL4z6Yhyi47E7OTaBUOozPicKLssA43+GnYsYdzS85o3UQzwQuV+Fu0+H3FEnUKyvBRGa+DDFRIZsEgwmJ7GKgytXI8oSssoaDOQwWyeED8Rn1NGmXSIwoZUSqGbh36VMg5xH0Rp7+9HzCcE1p8r0NFRY/YJ1l9rY6H3tOY55/eBrzfPayiK9Kh0XEK9GAM9LQgQPvaDlvDhdbDFQfcHoClFRoI4r4hgC + thinking: |- + This is a basic question about street safety. I should provide a clear, step-by-step explanation of how to safely cross a street, which is important information for pedestrian safety. + + I'll include: + 1. Finding the right place to cross (crosswalks, intersections) + 2. Checking for traffic signals + 3. Looking both ways for approaching vehicles + 4. Making eye contact with drivers + 5. Walking (not running) across the street + 6. Staying alert while crossing + + I'll keep my answer straightforward but comprehensive enough to cover the safety basics. + type: thinking + - text: "# How to Cross the Street Safely\n\n1. **Find the right place to cross**\n - Use marked crosswalks or intersections + whenever possible\n - Cross at corners where drivers expect pedestrians\n\n2. **Check for traffic signals**\n + \ - Follow pedestrian signals (walk/don't walk) if available\n - At traffic lights, wait for the green light + in your direction\n \n3. **Look both ways before crossing**\n - Look left, right, then left again\n - Continue + watching for traffic as you cross\n \n4. **Make yourself visible**\n - Make eye contact with drivers to ensure + they see you\n - Remove headphones and put away your phone to stay alert\n \n5. **Cross at a steady pace**\n + \ - Walk, don't run, across the street\n - Keep watching for vehicles while crossing\n\nRemember, safety should + always be your priority when crossing streets!" + type: text + id: msg_01SvTnQCjTUgQu19E8qiuHPe + model: claude-3-7-sonnet-20250219 + role: assistant + stop_reason: end_turn + stop_sequence: null + type: message + usage: + cache_creation_input_tokens: 0 + cache_read_input_tokens: 0 + input_tokens: 42 + output_tokens: 332 + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2098' + content-type: + - application/json + host: + - api.anthropic.com + method: POST + parsed_body: + max_tokens: 2048 + messages: + - content: + - text: How do I cross the street? + type: text + role: user + - content: + - signature: ErUBCkYIAhgCIkD6Sf780fvjL4z6Yhyi47E7OTaBUOozPicKLssA43+GnYsYdzS85o3UQzwQuV+Fu0+H3FEnUKyvBRGa+DDFRIZsEgwmJ7GKgytXI8oSssoaDOQwWyeED8Rn1NGmXSIwoZUSqGbh36VMg5xH0Rp7+9HzCcE1p8r0NFRY/YJ1l9rY6H3tOY55/eBrzfPayiK9Kh0XEK9GAM9LQgQPvaDlvDhdbDFQfcHoClFRoI4r4hgC + thinking: |- + This is a basic question about street safety. I should provide a clear, step-by-step explanation of how to safely cross a street, which is important information for pedestrian safety. + + I'll include: + 1. Finding the right place to cross (crosswalks, intersections) + 2. Checking for traffic signals + 3. Looking both ways for approaching vehicles + 4. Making eye contact with drivers + 5. Walking (not running) across the street + 6. Staying alert while crossing + + I'll keep my answer straightforward but comprehensive enough to cover the safety basics. + type: thinking + - text: "# How to Cross the Street Safely\n\n1. **Find the right place to cross**\n - Use marked crosswalks or intersections + whenever possible\n - Cross at corners where drivers expect pedestrians\n\n2. **Check for traffic signals**\n + \ - Follow pedestrian signals (walk/don't walk) if available\n - At traffic lights, wait for the green light + in your direction\n \n3. **Look both ways before crossing**\n - Look left, right, then left again\n - Continue + watching for traffic as you cross\n \n4. **Make yourself visible**\n - Make eye contact with drivers to ensure + they see you\n - Remove headphones and put away your phone to stay alert\n \n5. **Cross at a steady pace**\n + \ - Walk, don't run, across the street\n - Keep watching for vehicles while crossing\n\nRemember, safety should + always be your priority when crossing streets!" + type: text + role: assistant + - content: + - text: Considering the way to cross the street, analogously, how do I cross the river? + type: text + role: user + model: claude-3-7-sonnet-latest + stream: false + thinking: + budget_tokens: 1024 + type: enabled + uri: https://api.anthropic.com/v1/messages + response: + headers: + connection: + - keep-alive + content-length: + - '2684' + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + content: + - signature: ErUBCkYIAhgCIkB2BUY56TQbBYl46yDgVWTsaHGOGtJS3iTMiAnLZZUSqxqwSlIDAu7VINAVKPkt1rPdRmQbD/pnQXmHUWPtg3JMEgwX5++FVgV41/x9mtwaDJHFKMSNqLpHIsc3jSIwus0KQjezK3yrtzqekYfYulN1ZGKj9Jo5JmVIjLpvM4eXKq9E3K1YP7fWB/radhmbKh3pq4qSkEvr+QrxqVUlJoAdTB4LFRD6aWbtXGyzKhgC + thinking: |- + This question is asking me to apply similar principles for crossing a street to crossing a river, using analogy. Let me think about the comparable safety concerns and steps: + + Street crossing safety elements: + 1. Finding the right place to cross (crosswalks, intersections) + 2. Checking for signals/traffic + 3. Looking both ways + 4. Being visible + 5. Crossing at a steady pace + 6. Staying alert + + For river crossing, I'll need to address: + 1. Finding the right place to cross (bridges, shallow areas, designated crossing points) + 2. Checking conditions (current, depth) + 3. Looking for hazards + 4. Safety equipment/visibility + 5. Crossing method and pace + 6. Staying alert for changing conditions + + I'll structure my answer similarly to the street crossing response, focusing on safety while acknowledging the different hazards and methods relevant to river crossing. + type: thinking + - text: |- + # How to Cross a River Safely + + 1. **Find the right place to cross** + - Use bridges or designated crossing points whenever possible + - Look for shallow areas with slow-moving water if no bridge is available + - Avoid areas with strong currents, rapids, or debris + + 2. **Check the river conditions** + - Assess water depth, speed, and clarity before attempting to cross + - Check for underwater hazards like slippery rocks or drop-offs + - Consider recent weather (rain can create dangerous conditions) + + 3. **Look upstream and downstream** + - Scan for approaching hazards like floating debris or boats + - Identify a safe exit point on the opposite bank + + 4. **Prepare properly** + - Wear appropriate footwear with good grip + - Consider using a walking stick for balance and depth testing + - Secure belongings in waterproof containers + + 5. **Cross with caution** + - Face upstream and move diagonally across with the current + - Maintain three points of contact when possible + - Move steadily but don't rush + + Remember, unlike streets, rivers are natural environments with changing conditions. If the crossing looks risky, it's best to find another route or seek assistance! + type: text + id: msg_01W2NZXnzuNgCkVbvyBywWKb + model: claude-3-7-sonnet-20250219 + role: assistant + stop_reason: end_turn + stop_sequence: null + type: message + usage: + cache_creation_input_tokens: 0 + cache_read_input_tokens: 0 + input_tokens: 266 + output_tokens: 479 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_groq/test_groq_model_thinking_part_raw_format.yaml b/tests/models/cassettes/test_groq/test_groq_model_thinking_part_raw_format.yaml new file mode 100644 index 000000000..41dcdfde9 --- /dev/null +++ b/tests/models/cassettes/test_groq/test_groq_model_thinking_part_raw_format.yaml @@ -0,0 +1,252 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '155' + content-type: + - application/json + host: + - api.groq.com + method: POST + parsed_body: + messages: + - content: How do I cross the street? + role: user + model: deepseek-r1-distill-llama-70b + n: 1 + reasoning_format: raw + stream: false + uri: https://api.groq.com/openai/v1/chat/completions + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + cache-control: + - private, max-age=0, no-store, no-cache, must-revalidate + connection: + - keep-alive + content-length: + - '1635' + content-type: + - application/json + transfer-encoding: + - chunked + vary: + - Origin, Accept-Encoding + parsed_body: + choices: + - finish_reason: stop + index: 0 + logprobs: null + message: + content: |- + + + + + To cross the street safely, follow these steps: + + 1. **Find a Safe Crossing Point**: Look for a pedestrian crossing, traffic lights, or a crosswalk. Avoid crossing in areas with poor visibility or heavy traffic. + + 2. **Check for Traffic**: Before stepping onto the road, ensure all lanes are clear of oncoming vehicles. Make eye contact with drivers to confirm they see you. + + 3. **Obey Traffic Signals**: Wait for the pedestrian signal (green light or "Walk" sign) before crossing. If there are no signals, wait until traffic comes to a stop. + + 4. **Cross with Caution**: Walk briskly and directly across the street without stopping. Avoid distractions like using your phone while crossing. + + 5. **Be Visible**: Wear bright or reflective clothing, especially at night or in low-light conditions, to make yourself more visible to drivers. + + 6. **Follow Pedestrian Laws**: Adhere to local pedestrian rules and regulations to avoid accidents and fines. + + By following these steps, you can cross the street safely and responsibly. + role: assistant + created: 1745046533 + id: chatcmpl-41790d7f-3b36-42e9-8374-ce55210ff385 + model: deepseek-r1-distill-llama-70b + object: chat.completion + system_fingerprint: fp_454c494f52 + usage: + completion_time: 0.785454545 + completion_tokens: 216 + prompt_time: 0.003477838 + prompt_tokens: 10 + queue_time: 0.08876919900000001 + total_time: 0.788932383 + total_tokens: 226 + usage_breakdown: + models: null + x_groq: + id: req_01js6e1x3hfsa9rqx89gca29n5 + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1337' + content-type: + - application/json + cookie: + - __cf_bm=AF.Ol.TFODMAv0.2dOLSK_2mACTSi.4LTWymDTsY_hE-1745046534-1.0.1.1-._jRopI2RR3rO7BMoDMWDrQuo8podhSRa4NIbcWKLq6qe4N4E78HYZv7MYn700csbopraN9KdJWNUWXf2O6s_ut9VYVndfLGwhiizBn1fAg + host: + - api.groq.com + method: POST + parsed_body: + messages: + - content: How do I cross the street? + role: user + - content: |2- + + + To cross the street safely, follow these steps: + + 1. **Find a Safe Crossing Point**: Look for a pedestrian crossing, traffic lights, or a crosswalk. Avoid crossing in areas with poor visibility or heavy traffic. + + 2. **Check for Traffic**: Before stepping onto the road, ensure all lanes are clear of oncoming vehicles. Make eye contact with drivers to confirm they see you. + + 3. **Obey Traffic Signals**: Wait for the pedestrian signal (green light or "Walk" sign) before crossing. If there are no signals, wait until traffic comes to a stop. + + 4. **Cross with Caution**: Walk briskly and directly across the street without stopping. Avoid distractions like using your phone while crossing. + + 5. **Be Visible**: Wear bright or reflective clothing, especially at night or in low-light conditions, to make yourself more visible to drivers. + + 6. **Follow Pedestrian Laws**: Adhere to local pedestrian rules and regulations to avoid accidents and fines. + + By following these steps, you can cross the street safely and responsibly. + role: assistant + - content: Considering the way to cross the street, analogously, how do I cross the river? + role: user + model: deepseek-r1-distill-llama-70b + n: 1 + reasoning_format: raw + stream: false + uri: https://api.groq.com/openai/v1/chat/completions + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + cache-control: + - private, max-age=0, no-store, no-cache, must-revalidate + connection: + - keep-alive + content-length: + - '5632' + content-type: + - application/json + transfer-encoding: + - chunked + vary: + - Origin, Accept-Encoding + parsed_body: + choices: + - finish_reason: stop + index: 0 + logprobs: null + message: + content: |- + + Alright, so the user just asked, "How do I cross the river?" after I explained how to cross the street. They're drawing an analogy between crossing the street and crossing a river, which is interesting. I need to figure out how to respond in a way that's both helpful and makes sense. + + First, I should think about why they're asking this. Maybe they're planning a hiking trip or some outdoor activity where they might encounter a river without a bridge. Or perhaps it's a metaphor for overcoming obstacles. But given the previous question about crossing the street, it's more likely they're looking for practical advice on safely crossing a river. + + I should break it down into steps, similar to how I explained crossing the street. Let's see, when crossing a river, the main concerns are safety, choosing the right spot, using proper techniques, and being prepared. I should address each of these areas. + + I'll start by assessing the situation. They need to check the water's depth, current, and temperature. These factors are crucial because they determine the difficulty and danger of crossing. Next, choosing a safe crossing point is essential—looking for shallow areas with slower currents. + + Then, I should talk about using a walking stick for balance. It's a simple tool that can make a big difference in stability. Crossing at an angle upstream makes sense because it helps counter the current's push. I should explain that to avoid being carried downstream. + + Wearing appropriate footwear is important too. They don't want to slip on wet rocks. Holding hands in a group can provide mutual support, which is both safe and practical. I should mention that if the current is too strong, they should avoid wading and instead look for a safer method, like a log or rope. + + Planning for emergencies is also key. They need to know what to do if they fall—floating on their back reduces exhaustion and keeps their head above water. Finally, checking the weather is wise to avoid unexpected issues like rising water levels. + + I should make sure the response is clear and step-by-step, just like the previous one. Using similar structure will help the user follow along easily. I also want to encourage preparedness and caution, emphasizing safety throughout. + + Maybe I should also consider mentioning alternatives, like finding a bridge or a shallow point, in case crossing isn't safe. It's important they know when it's better to seek a different route. Overall, the response should be thorough but concise, covering all necessary aspects without being overwhelming. + + + Crossing a river can be challenging and dangerous if not done properly. Here's an analogy to crossing the street, applied to crossing a river safely: + + --- + + ### 1. **Assess the Situation** + - Just as you would look for traffic when crossing the street, evaluate the river's conditions: water depth, current strength, and temperature. + - Avoid crossing during heavy rain or when the water is murky, as it may hide hazards like rocks or deep holes. + + --- + + ### 2. **Choose a Safe Crossing Point** + - Look for a shallow point with slower-moving water, similar to how you'd choose a crosswalk or traffic light to cross the street. + - Avoid areas with fast currents or deep water, as they can sweep you away. + + --- + + ### 3. **Use a Walking Stick** + - Just as you might use a cane for stability on uneven ground, use a sturdy walking stick or branch to help balance and test the riverbed for secure footing. + + --- + + ### 4. **Cross at an Angle** + - When entering the water, face upstream at a slight angle to counteract the current, much like you would position yourself to cross the street at a diagonal to avoid oncoming traffic. + + --- + + ### 5. **Wear Appropriate Footwear** + - Just as you wear shoes to cross the street, wear sturdy, water-friendly footwear with good traction to prevent slipping on wet rocks. + + --- + + ### 6. **Hold Hands if in a Group** + - If you're crossing with others, hold hands in a line to provide mutual support, similar to how you might guide a child or someone else across the street. + + --- + + ### 7. **Avoid Wading in Strong Currents** + - If the current is too strong to wade through safely, do not attempt to cross. Instead, look for a safer method, such as using a log, raft, or rope to assist in crossing. + + --- + + ### 8. **Plan for Emergencies** + - Just as you would know what to do if a car approaches while crossing the street, be prepared for emergencies while crossing the river. If you fall, float on your back with your feet downstream to avoid injury. + + --- + + ### 9. **Check for Weather Conditions** + - Just as you would check for traffic lights before crossing the street, check the weather before crossing the river. Rising water levels or flash floods can make the river dangerous. + + --- + + By being prepared, cautious, and methodical, you can safely "cross the river," just as you would cross the street. Always prioritize safety and avoid taking unnecessary risks! + role: assistant + created: 1745046534 + id: chatcmpl-8045307b-7978-42bb-b092-86ac18b59b47 + model: deepseek-r1-distill-llama-70b + object: chat.completion + system_fingerprint: fp_454c494f52 + usage: + completion_time: 3.781818182 + completion_tokens: 1040 + prompt_time: 0.013254186 + prompt_tokens: 243 + queue_time: 0.08608507 + total_time: 3.795072368 + total_tokens: 1283 + usage_breakdown: + models: null + x_groq: + id: req_01js6e1y1ffk3b9mbmr0y9xzcj + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml b/tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml new file mode 100644 index 000000000..17153fac5 --- /dev/null +++ b/tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml @@ -0,0 +1,321 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '150' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: How do I cross the street? + role: user + model: o3-mini + reasoning: + effort: high + summary: detailed + stream: false + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '5918' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '25096' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + created_at: 1745045557 + error: null + id: resp_68034835d12481919c80a7fd8dbe6f7e08c845d2be9bcdd8 + incomplete_details: null + instructions: null + max_output_tokens: null + metadata: {} + model: o3-mini-2025-01-31 + object: response + output: + - id: rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8 + summary: + - text: |- + **Providing street crossing instructions** + + The user seems to be asking how to cross the street, which could be literal or metaphorical. I think it’s best to offer clear and responsible instructions for street crossing. I’ll include general safety tips, like using crosswalks and obeying traffic signals. I need to emphasize that I'm not a substitute for professional safety advice. I'll make sure to mention looking both ways and waiting for safe signals before crossing. + type: summary_text + - text: |- + **Clarifying street crossing instructions** + + The user is asking how to cross the street safely, which I'll take seriously. My response will include essential safety steps, noting that this isn’t professional advice. I need to emphasize using crosswalks and waiting for the appropriate signals before crossing. I should remind the user to look both ways and remain cautious of vehicles. Additionally, I’ll clarify that local laws and guidelines should be followed, ensuring a comprehensive and responsible set of instructions. + type: summary_text + - text: |- + **Outlining street crossing safety** + + Step 6 involves being aware of bicyclists while crossing. I need to remind the user not to cross if they have impaired vision or hearing. This question seems to focus on pedestrian safety, and I should provide clear instructions while including a disclaimer about responsibility. I'll suggest using designated crosswalks, waiting for signals, and continuously checking for vehicles. Additionally, I’ll emphasize local rules, and if there’s uncertainty, wait for a clearer signal. It’s essential to exercise caution and consider distractions, and I’ll stress that these instructions are general advice. + type: summary_text + - text: |- + **Creating safe crossing instructions** + + I need to ensure that my instructions for crossing the street are safe and appropriate. I'll provide step-by-step guidance in bullet points to keep it clear. The steps to follow include: (i) approaching an intersection and looking for a crosswalk, (ii) waiting for a break in traffic while checking for pedestrian signals, (iii) looking left, right, and then left again, (iv) crossing when it's safe while still being aware of traffic, and (v) remaining cautious, especially on busy roads. I'll include additional notes about traffic buttons if available. + type: summary_text + type: reasoning + - content: + - annotations: [] + text: |- + I'm happy to help! Here are some general guidelines for crossing the street safely. Please keep in mind that local traffic laws and conditions may vary, so always follow any posted signs, signals, or recommendations from local authorities: + + 1. Find a safe place to cross. Look for a designated crosswalk or intersection with pedestrian signals. If you’re not near one, choose a spot where drivers have a clear view of you and can see you from a distance. + + 2. Wait for the proper signal. If there’s a pedestrian signal, wait until it shows that it’s safe to cross ("Walk" or a green figure). Even if you have the right-of-way, double-check the traffic around you before stepping off the curb. + + 3. Look and listen before crossing. Adopt the "Look Left, Right, Left" habit: +   • Look left (toward oncoming traffic) +   • Look right (ensure there’s no immediate traffic) +   • Look left again, as vehicles might be turning into the street from that direction + Additionally, listen for any approaching vehicles or warning sounds that could indicate vehicles are near. + + 4. Cross at a steady pace. Once you’re sure it’s safe, cross the street confidently and stay within the walk zone. Continue to be aware of your surroundings as you cross because sometimes drivers (especially those turning) may not expect pedestrians. + + 5. Stay alert and avoid distractions. It’s best to avoid using your phone or wearing headphones at high volume while crossing so you can hear or see any potential hazards. + + 6. Be extra cautious at intersections without signals or crosswalks. In these cases, slow down, make eye contact with drivers if possible, and ensure that arriving vehicles have seen you before proceeding. + + Remember that these are general tips for pedestrian safety. If you have any special needs or face challenging road conditions, consider seeking advice from local transportation or pedestrian safety organizations. Stay safe out there! + type: output_text + id: msg_6803484e20008191bf9ba9c29fa0f28c08c845d2be9bcdd8 + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + reasoning: + effort: high + summary: detailed + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 13 + input_tokens_details: + cached_tokens: 0 + output_tokens: 2050 + output_tokens_details: + reasoning_tokens: 1664 + total_tokens: 2063 + user: null + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4747' + content-type: + - application/json + cookie: + - __cf_bm=MeWm6yd0MGioe.U3TgGh5yaUktLEgNnI2ILVKPaWg4A-1745045582-1.0.1.1-K6NmWQoHfkd0nbzdNOXYSXkrbJ1Io92eI2tFQNgwjnaNDT5SHT.qyHuZmeT6ZD2RxNoBoZrLMFvqGulOvhVEAx7hMg8RaQPVD9AmazVcJ_c; + _cfuvid=n3.7dkUOBcq_XoPuavENgXlauzeGxbu3bXY4bHDK5Xk-1745045582952-0.0.1.1-604800000 + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: How do I cross the street? + role: user + - content: |- + I'm happy to help! Here are some general guidelines for crossing the street safely. Please keep in mind that local traffic laws and conditions may vary, so always follow any posted signs, signals, or recommendations from local authorities: + + 1. Find a safe place to cross. Look for a designated crosswalk or intersection with pedestrian signals. If you’re not near one, choose a spot where drivers have a clear view of you and can see you from a distance. + + 2. Wait for the proper signal. If there’s a pedestrian signal, wait until it shows that it’s safe to cross ("Walk" or a green figure). Even if you have the right-of-way, double-check the traffic around you before stepping off the curb. + + 3. Look and listen before crossing. Adopt the "Look Left, Right, Left" habit: +   • Look left (toward oncoming traffic) +   • Look right (ensure there’s no immediate traffic) +   • Look left again, as vehicles might be turning into the street from that direction + Additionally, listen for any approaching vehicles or warning sounds that could indicate vehicles are near. + + 4. Cross at a steady pace. Once you’re sure it’s safe, cross the street confidently and stay within the walk zone. Continue to be aware of your surroundings as you cross because sometimes drivers (especially those turning) may not expect pedestrians. + + 5. Stay alert and avoid distractions. It’s best to avoid using your phone or wearing headphones at high volume while crossing so you can hear or see any potential hazards. + + 6. Be extra cautious at intersections without signals or crosswalks. In these cases, slow down, make eye contact with drivers if possible, and ensure that arriving vehicles have seen you before proceeding. + + Remember that these are general tips for pedestrian safety. If you have any special needs or face challenging road conditions, consider seeking advice from local transportation or pedestrian safety organizations. Stay safe out there! + role: assistant + - id: rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8 + summary: + - text: |- + **Providing street crossing instructions** + + The user seems to be asking how to cross the street, which could be literal or metaphorical. I think it’s best to offer clear and responsible instructions for street crossing. I’ll include general safety tips, like using crosswalks and obeying traffic signals. I need to emphasize that I'm not a substitute for professional safety advice. I'll make sure to mention looking both ways and waiting for safe signals before crossing. + type: summary_text + - text: |- + **Clarifying street crossing instructions** + + The user is asking how to cross the street safely, which I'll take seriously. My response will include essential safety steps, noting that this isn’t professional advice. I need to emphasize using crosswalks and waiting for the appropriate signals before crossing. I should remind the user to look both ways and remain cautious of vehicles. Additionally, I’ll clarify that local laws and guidelines should be followed, ensuring a comprehensive and responsible set of instructions. + type: summary_text + - text: |- + **Outlining street crossing safety** + + Step 6 involves being aware of bicyclists while crossing. I need to remind the user not to cross if they have impaired vision or hearing. This question seems to focus on pedestrian safety, and I should provide clear instructions while including a disclaimer about responsibility. I'll suggest using designated crosswalks, waiting for signals, and continuously checking for vehicles. Additionally, I’ll emphasize local rules, and if there’s uncertainty, wait for a clearer signal. It’s essential to exercise caution and consider distractions, and I’ll stress that these instructions are general advice. + type: summary_text + - text: |- + **Creating safe crossing instructions** + + I need to ensure that my instructions for crossing the street are safe and appropriate. I'll provide step-by-step guidance in bullet points to keep it clear. The steps to follow include: (i) approaching an intersection and looking for a crosswalk, (ii) waiting for a break in traffic while checking for pedestrian signals, (iii) looking left, right, and then left again, (iv) crossing when it's safe while still being aware of traffic, and (v) remaining cautious, especially on busy roads. I'll include additional notes about traffic buttons if available. + type: summary_text + type: reasoning + - content: Considering the way to cross the street, analogously, how do I cross the river? + role: user + model: o3-mini + reasoning: + effort: high + summary: detailed + stream: false + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '6450' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '24185' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + created_at: 1745045583 + error: null + id: resp_6803484f19a88191b9ea975d7cfbbe8408c845d2be9bcdd8 + incomplete_details: null + instructions: null + max_output_tokens: null + metadata: {} + model: o3-mini-2025-01-31 + object: response + output: + - id: rs_68034858dc588191bc3a6801c23e728f08c845d2be9bcdd8 + summary: + - text: |- + **Exploring river crossing analogy** + + The user’s question seems playful, comparing crossing a street to crossing a river. I think it might be humorous while still needing some real safety advice. Just like for street crossing, I could mention to evaluate conditions for the river, such as water level and obstacles. However, I need to tread carefully, as river crossings can be much more dangerous than they seem. Balancing humor with safety in the answer will be important! + type: summary_text + - text: |- + **Providing river crossing guidelines** + + I want to give some practical advice for safely crossing a river. Key points include evaluating the water's depth and speed, looking for designated crossing points like bridges, and ensuring local conditions are safe. It’s essential to use proper methods, like finding a bridge or boat. I should also clarify that I’m not a certified expert, so consulting professionals for serious situations is wise. Overall, my guidelines will emphasize safety in a friendly, helpful way while keeping things lighthearted! + type: summary_text + - text: |- + **Providing cautious river crossing guidance** + + I can stress that while the guidelines for crossing a river might seem similar to crossing a street, the risks are significantly higher. It's important not to assume that what’s safe on a road is automatically safe for a river. I’ll include a disclaimer about not being a safety expert and emphasize consulting professionals for hazardous conditions. I’ll structure the answer with bullet points, including key instructions for safely choosing designated crossings like bridges. The wording will be clear and straightforward! + type: summary_text + type: reasoning + - content: + - annotations: [] + text: |- + While crossing a river might seem analogous in concept to crossing a street, it’s important to recognize that water environments are inherently more dynamic and hazardous than predictable road conditions. That said, here are some guidelines that parallel the careful approach you’d take when crossing a street: + + 1. Plan Your Route and Choose a Designated Crossing Point +  • Just as you’d look for a crosswalk or an intersection when crossing a street, look for a safe, well-known crossing point over the river. This might be a bridge, a ford (a shallow place to cross), or a series of stepping stones. +  • Avoid random or unmarked spots where the river narrows or looks deceptively calm. + + 2. Evaluate Conditions Before You Approach +  • Whereas you’d check for oncoming traffic and pedestrian signals at a street, observe the river’s conditions before you cross. Is the water fast-moving or deep? Are there hazards like rocks, sudden drop-offs, or debris? +  • Consider weather conditions, recent rainfall, and time of day, as these can affect both water levels and current strength. + + 3. Prepare with the Right Gear and Skills +  • When crossing a street, you rely mainly on your alertness and the right-of-way signals. For a river, ensure you’re equipped appropriately—wear sturdy, non-slip footwear, and if necessary, use a life jacket or personal flotation device. +  • Only attempt a river crossing on foot if you’re confident in your balance and if the water’s depth and current are truly manageable. In many cases, using a boat or waiting for a safer condition is a much better choice. + + 4. Proceed Cautiously, Step by Step +  • In a street crossing, you look both ways before stepping off the curb. On a river, take your time and cross slowly while constantly reassessing conditions. If wading, test the water with your foot first and maintain a steady pace. +  • Avoid distractions and stay alert to sudden changes in water flow or unexpected obstacles. + + 5. Have a Contingency Plan +  • Just as you’d stop and wait at a street if the signal hasn’t changed, be prepared to back out if conditions deteriorate. Let someone know your plan and expected route, and if you’re uncertain, consider waiting for assistance or finding an alternate crossing. +  • In remote or unpredictable river environments, it’s crucial to have a method of communication like a cell phone or a whistle in case you need help. + + 6. Know Your Limits +  • It’s similar to knowing when to wait for a break in traffic. Assess your own abilities and the risks involved. If the conditions seem too dangerous—even if a crossing point is technically available—it’s best not to risk it. When in doubt, seek local advice or professional help. + + Remember, these are general guidelines and not a substitute for professional advice, especially when dealing with natural hazards. Crossing a river safely requires careful consideration of factors that don’t come into play when crossing a street. Always prioritize your safety and, if ever unsure, reach out for help or choose an alternative route. + type: output_text + id: msg_68034865ac18819189215b3a2e33cb4e08c845d2be9bcdd8 + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + reasoning: + effort: high + summary: detailed + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 424 + input_tokens_details: + cached_tokens: 0 + output_tokens: 2033 + output_tokens_details: + reasoning_tokens: 1408 + total_tokens: 2457 + user: null + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/test_anthropic.py b/tests/models/test_anthropic.py index 2e7d66071..1915c995b 100644 --- a/tests/models/test_anthropic.py +++ b/tests/models/test_anthropic.py @@ -22,6 +22,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -29,7 +30,7 @@ from pydantic_ai.result import Usage from pydantic_ai.settings import ModelSettings -from ..conftest import IsDatetime, IsNow, IsStr, TestEnv, raise_if_exception, try_import +from ..conftest import IsDatetime, IsInstance, IsNow, IsStr, TestEnv, raise_if_exception, try_import from .mock_async_stream import MockAsyncStream with try_import() as imports_successful: @@ -62,6 +63,7 @@ pytestmark = [ pytest.mark.skipif(not imports_successful(), reason='anthropic not installed'), pytest.mark.anyio, + pytest.mark.vcr, ] # Type variable for generic AsyncStream @@ -345,7 +347,6 @@ async def get_location(loc_name: str) -> str: ) -@pytest.mark.vcr async def test_multiple_parallel_tool_calls(allow_model_requests: None): async def retrieve_entity_info(name: str) -> str: """Get the knowledge about the given entity.""" @@ -555,7 +556,6 @@ async def my_tool(first: str, second: str) -> int: assert tool_called -@pytest.mark.vcr() async def test_image_url_input(allow_model_requests: None, anthropic_api_key: str): m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(api_key=anthropic_api_key)) agent = Agent(m) @@ -571,7 +571,6 @@ async def test_image_url_input(allow_model_requests: None, anthropic_api_key: st ) -@pytest.mark.vcr() async def test_image_url_input_invalid_mime_type(allow_model_requests: None, anthropic_api_key: str): m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(api_key=anthropic_api_key)) agent = Agent(m) @@ -619,7 +618,6 @@ def test_model_status_error(allow_model_requests: None) -> None: ) -@pytest.mark.vcr() async def test_document_binary_content_input( allow_model_requests: None, anthropic_api_key: str, document_content: BinaryContent ): @@ -632,7 +630,6 @@ async def test_document_binary_content_input( ) -@pytest.mark.vcr() async def test_document_url_input(allow_model_requests: None, anthropic_api_key: str): m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key)) agent = Agent(m) @@ -645,7 +642,6 @@ async def test_document_url_input(allow_model_requests: None, anthropic_api_key: ) -@pytest.mark.vcr() async def test_text_document_url_input(allow_model_requests: None, anthropic_api_key: str): m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key)) agent = Agent(m) @@ -684,7 +680,6 @@ def test_init_with_provider_string(env: TestEnv): assert model.client is not None -@pytest.mark.vcr() async def test_anthropic_model_instructions(allow_model_requests: None, anthropic_api_key: str): m = AnthropicModel('claude-3-opus-latest', provider=AnthropicProvider(api_key=anthropic_api_key)) agent = Agent(m) @@ -707,3 +702,93 @@ def simple_instructions(): ), ] ) + + +async def test_anthropic_model_thinking_part(allow_model_requests: None, anthropic_api_key: str): + m = AnthropicModel('claude-3-7-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key)) + settings = AnthropicModelSettings(anthropic_thinking={'type': 'enabled', 'budget_tokens': 1024}) + agent = Agent(m, model_settings=settings) + + result = await agent.run('How do I cross the street?') + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + ThinkingPart( + content="""\ +This is a basic question about street safety. I should provide a clear, step-by-step explanation of how to safely cross a street, which is important information for pedestrian safety. + +I'll include: +1. Finding the right place to cross (crosswalks, intersections) +2. Checking for traffic signals +3. Looking both ways for approaching vehicles +4. Making eye contact with drivers +5. Walking (not running) across the street +6. Staying alert while crossing + +I'll keep my answer straightforward but comprehensive enough to cover the safety basics.\ +""", + signature='ErUBCkYIAhgCIkD6Sf780fvjL4z6Yhyi47E7OTaBUOozPicKLssA43+GnYsYdzS85o3UQzwQuV+Fu0+H3FEnUKyvBRGa+DDFRIZsEgwmJ7GKgytXI8oSssoaDOQwWyeED8Rn1NGmXSIwoZUSqGbh36VMg5xH0Rp7+9HzCcE1p8r0NFRY/YJ1l9rY6H3tOY55/eBrzfPayiK9Kh0XEK9GAM9LQgQPvaDlvDhdbDFQfcHoClFRoI4r4hgC', + ), + TextPart(content=IsStr()), + ], + model_name='claude-3-7-sonnet-20250219', + timestamp=IsDatetime(), + ), + ] + ) + + result = await agent.run( + 'Considering the way to cross the street, analogously, how do I cross the river?', + message_history=result.all_messages(), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[IsInstance(ThinkingPart), IsInstance(TextPart)], + model_name='claude-3-7-sonnet-20250219', + timestamp=IsDatetime(), + ), + ModelRequest( + parts=[ + UserPromptPart( + content='Considering the way to cross the street, analogously, how do I cross the river?', + timestamp=IsDatetime(), + ) + ] + ), + ModelResponse( + parts=[ + ThinkingPart( + content="""\ +This question is asking me to apply similar principles for crossing a street to crossing a river, using analogy. Let me think about the comparable safety concerns and steps: + +Street crossing safety elements: +1. Finding the right place to cross (crosswalks, intersections) +2. Checking for signals/traffic +3. Looking both ways +4. Being visible +5. Crossing at a steady pace +6. Staying alert + +For river crossing, I'll need to address: +1. Finding the right place to cross (bridges, shallow areas, designated crossing points) +2. Checking conditions (current, depth) +3. Looking for hazards +4. Safety equipment/visibility +5. Crossing method and pace +6. Staying alert for changing conditions + +I'll structure my answer similarly to the street crossing response, focusing on safety while acknowledging the different hazards and methods relevant to river crossing.\ +""", + signature='ErUBCkYIAhgCIkB2BUY56TQbBYl46yDgVWTsaHGOGtJS3iTMiAnLZZUSqxqwSlIDAu7VINAVKPkt1rPdRmQbD/pnQXmHUWPtg3JMEgwX5++FVgV41/x9mtwaDJHFKMSNqLpHIsc3jSIwus0KQjezK3yrtzqekYfYulN1ZGKj9Jo5JmVIjLpvM4eXKq9E3K1YP7fWB/radhmbKh3pq4qSkEvr+QrxqVUlJoAdTB4LFRD6aWbtXGyzKhgC', + ), + TextPart(content=IsStr()), + ], + model_name='claude-3-7-sonnet-20250219', + timestamp=IsDatetime(), + ), + ] + ) diff --git a/tests/models/test_groq.py b/tests/models/test_groq.py index 7c170f147..f7ad0ca3f 100644 --- a/tests/models/test_groq.py +++ b/tests/models/test_groq.py @@ -27,6 +27,7 @@ ToolReturnPart, UserPromptPart, ) +from pydantic_ai.models.groq import GroqModelSettings from pydantic_ai.usage import Usage from ..conftest import IsDatetime, IsNow, IsStr, raise_if_exception, try_import @@ -56,6 +57,7 @@ pytestmark = [ pytest.mark.skipif(not imports_successful(), reason='groq not installed'), pytest.mark.anyio, + pytest.mark.vcr, ] @@ -504,7 +506,6 @@ async def test_no_delta(allow_model_requests: None): assert result.is_complete -@pytest.mark.vcr() async def test_image_url_input(allow_model_requests: None, groq_api_key: str): m = GroqModel('llama-3.2-11b-vision-preview', provider=GroqProvider(api_key=groq_api_key)) agent = Agent(m) @@ -544,7 +545,6 @@ async def test_audio_as_binary_content_input(allow_model_requests: None, media_t await agent.run(['hello', BinaryContent(data=base64_content, media_type=media_type)]) -@pytest.mark.vcr() async def test_image_as_binary_content_input( allow_model_requests: None, groq_api_key: str, image_content: BinaryContent ) -> None: @@ -588,7 +588,6 @@ async def test_init_with_provider_string(): assert model.client is not None -@pytest.mark.vcr() async def test_groq_model_instructions(allow_model_requests: None, groq_api_key: str): m = GroqModel('llama-3.3-70b-versatile', provider=GroqProvider(api_key=groq_api_key)) agent = Agent(m, instructions='You are a helpful assistant.') @@ -607,3 +606,18 @@ async def test_groq_model_instructions(allow_model_requests: None, groq_api_key: ), ] ) + + +async def test_groq_model_thinking_part_raw_format(allow_model_requests: None, groq_api_key: str): + m = GroqModel('deepseek-r1-distill-llama-70b', provider=GroqProvider(api_key=groq_api_key)) + settings = GroqModelSettings(groq_reasoning_format='raw') + agent = Agent(m, model_settings=settings) + + result = await agent.run('How do I cross the street?') + assert result.all_messages() == snapshot() + + result = await agent.run( + 'Considering the way to cross the street, analogously, how do I cross the river?', + message_history=result.all_messages(), + ) + assert result.all_messages() == snapshot() diff --git a/tests/models/test_openai.py b/tests/models/test_openai.py index 5fba5fef8..8edff33d8 100644 --- a/tests/models/test_openai.py +++ b/tests/models/test_openai.py @@ -23,16 +23,18 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, ) from pydantic_ai.models.gemini import GeminiModel +from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings from pydantic_ai.providers.google_gla import GoogleGLAProvider from pydantic_ai.result import Usage from pydantic_ai.settings import ModelSettings -from ..conftest import IsDatetime, IsNow, IsStr, raise_if_exception, try_import +from ..conftest import IsDatetime, IsInstance, IsNow, IsStr, raise_if_exception, try_import from .mock_async_stream import MockAsyncStream with try_import() as imports_successful: @@ -64,6 +66,7 @@ pytestmark = [ pytest.mark.skipif(not imports_successful(), reason='openai not installed'), pytest.mark.anyio, + pytest.mark.vcr, ] @@ -567,7 +570,6 @@ async def test_system_prompt_role( @pytest.mark.parametrize('system_prompt_role', ['system', 'developer']) -@pytest.mark.vcr async def test_openai_o1_mini_system_role( allow_model_requests: None, system_prompt_role: Literal['system', 'developer'], @@ -644,7 +646,6 @@ async def test_image_url_input(allow_model_requests: None): ) -@pytest.mark.vcr() async def test_image_as_binary_content_input( allow_model_requests: None, image_content: BinaryContent, openai_api_key: str ): @@ -655,7 +656,6 @@ async def test_image_as_binary_content_input( assert result.output == snapshot('The fruit in the image is a kiwi.') -@pytest.mark.vcr() async def test_audio_as_binary_content_input( allow_model_requests: None, audio_content: BinaryContent, openai_api_key: str ): @@ -681,7 +681,6 @@ def test_model_status_error(allow_model_requests: None) -> None: assert str(exc_info.value) == snapshot("status_code: 500, model_name: gpt-4o, body: {'error': 'test error'}") -@pytest.mark.vcr() @pytest.mark.parametrize('model_name', ['o3-mini', 'gpt-4o-mini', 'gpt-4.5-preview']) async def test_max_completion_tokens(allow_model_requests: None, model_name: str, openai_api_key: str): m = OpenAIModel(model_name, provider=OpenAIProvider(api_key=openai_api_key)) @@ -691,7 +690,6 @@ async def test_max_completion_tokens(allow_model_requests: None, model_name: str assert result.output == IsStr() -@pytest.mark.vcr() async def test_multiple_agent_tool_calls(allow_model_requests: None, gemini_api_key: str, openai_api_key: str): gemini_model = GeminiModel('gemini-2.0-flash-exp', provider=GoogleGLAProvider(api_key=gemini_api_key)) openai_model = OpenAIModel('gpt-4o-mini', provider=OpenAIProvider(api_key=openai_api_key)) @@ -721,7 +719,6 @@ async def get_capital(country: str) -> str: assert result.output == snapshot('The capital of England is London.') -@pytest.mark.vcr() async def test_user_id(allow_model_requests: None, openai_api_key: str): # This test doesn't do anything, it's just here to ensure that calls with `user` don't cause errors, including type. # Since we use VCR, creating tests with an `httpx.Transport` is not possible. @@ -1226,7 +1223,6 @@ class MyModel(BaseModel): ) -@pytest.mark.vcr() async def test_openai_instructions(allow_model_requests: None, openai_api_key: str): m = OpenAIModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key)) agent = Agent(m, instructions='You are a helpful assistant.') @@ -1247,7 +1243,6 @@ async def test_openai_instructions(allow_model_requests: None, openai_api_key: s ) -@pytest.mark.vcr() async def test_openai_model_without_system_prompt(allow_model_requests: None, openai_api_key: str): m = OpenAIModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key)) agent = Agent(m, system_prompt='You are a potato.') @@ -1257,7 +1252,6 @@ async def test_openai_model_without_system_prompt(allow_model_requests: None, op ) -@pytest.mark.vcr() async def test_openai_instructions_with_tool_calls_keep_instructions(allow_model_requests: None, openai_api_key: str): m = OpenAIModel('gpt-4.1-mini', provider=OpenAIProvider(api_key=openai_api_key)) agent = Agent(m, instructions='You are a helpful assistant.') @@ -1293,3 +1287,66 @@ async def get_temperature(city: str) -> float: ), ] ) + + +async def test_openai_responses_model_thinking_part(allow_model_requests: None, openai_api_key: str): + m = OpenAIResponsesModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key)) + settings = OpenAIResponsesModelSettings(openai_reasoning_effort='high', openai_reasoning_summary='detailed') + agent = Agent(m, model_settings=settings) + + result = await agent.run('How do I cross the street?') + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), + ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), + ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), + ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), + TextPart(content=IsStr()), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ] + ) + + result = await agent.run( + 'Considering the way to cross the street, analogously, how do I cross the river?', + message_history=result.all_messages(), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), + ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), + ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), + ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), + TextPart(content=IsStr()), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ModelRequest( + parts=[ + UserPromptPart( + content='Considering the way to cross the street, analogously, how do I cross the river?', + timestamp=IsDatetime(), + ) + ] + ), + ModelResponse( + parts=[ + ThinkingPart(content=IsStr(), signature='rs_68034858dc588191bc3a6801c23e728f08c845d2be9bcdd8'), + ThinkingPart(content=IsStr(), signature='rs_68034858dc588191bc3a6801c23e728f08c845d2be9bcdd8'), + ThinkingPart(content=IsStr(), signature='rs_68034858dc588191bc3a6801c23e728f08c845d2be9bcdd8'), + TextPart(content=IsStr()), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ] + ) From 3b92cc08da0cce3504cec88e38dc6db66c7efb97 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 19 Apr 2025 14:49:54 +0200 Subject: [PATCH 06/27] Add tests --- main.py | 21 +- .../pydantic_ai/models/__init__.py | 6 + .../pydantic_ai/models/anthropic.py | 8 + pydantic_ai_slim/pydantic_ai/models/gemini.py | 51 ++- pydantic_ai_slim/pydantic_ai/models/groq.py | 6 +- pydantic_ai_slim/pydantic_ai/models/openai.py | 2 +- .../test_gemini_model_thinking_part.yaml | 278 ++++++++++++++ .../test_groq_model_thinking_part.yaml | 345 ++++++++++++++++++ ...t_groq_model_thinking_part_raw_format.yaml | 252 ------------- tests/models/test_gemini.py | 112 +++++- tests/models/test_groq.py | 212 ++++++++++- tests/models/test_openai.py | 8 +- tests/models/test_openai_responses.py | 2 +- 13 files changed, 1020 insertions(+), 283 deletions(-) create mode 100644 tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml create mode 100644 tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml delete mode 100644 tests/models/cassettes/test_groq/test_groq_model_thinking_part_raw_format.yaml diff --git a/main.py b/main.py index ca3b60f12..7c6355c3a 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ from rich.pretty import pprint from pydantic_ai import Agent +from pydantic_ai.models.gemini import GeminiModelSettings from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings model = OpenAIResponsesModel('o4-mini') @@ -32,12 +33,12 @@ # ) # pprint(result.all_messages()) -bedrock_agent = Agent('bedrock:us.deepseek.r1-v1:0') -result = bedrock_agent.run_sync( - 'Tell me the steps to cross the ocean!', - # message_history=result.all_messages(), -) -pprint(result.all_messages()) +# bedrock_agent = Agent('bedrock:us.deepseek.r1-v1:0') +# result = bedrock_agent.run_sync( +# 'Tell me the steps to cross the ocean!', +# # message_history=result.all_messages(), +# ) +# pprint(result.all_messages()) # deepseek_agent = Agent('deepseek:deepseek-reasoner') @@ -47,3 +48,11 @@ # # message_history=result.all_messages(), # ) # pprint(result.all_messages()) + +gemini_agent = Agent('google-gla:gemini-2.5-flash-preview-04-17') +result = gemini_agent.run_sync( + 'And how do I cross the ocean?', + model_settings=GeminiModelSettings(gemini_thinking_config={'thinking_budget': 1024, 'include_thoughts': True}), + message_history=result.all_messages(), +) +pprint(result.all_messages()) diff --git a/pydantic_ai_slim/pydantic_ai/models/__init__.py b/pydantic_ai_slim/pydantic_ai/models/__init__.py index e09721960..3824b5371 100644 --- a/pydantic_ai_slim/pydantic_ai/models/__init__.py +++ b/pydantic_ai_slim/pydantic_ai/models/__init__.py @@ -194,6 +194,8 @@ 'o1-mini-2024-09-12', 'o1-preview', 'o1-preview-2024-09-12', + 'o3', + 'o3-2025-04-16', 'o3-mini', 'o3-mini-2025-01-31', 'openai:chatgpt-4o-latest', @@ -245,6 +247,10 @@ 'openai:o1-preview-2024-09-12', 'openai:o3-mini', 'openai:o3-mini-2025-01-31', + 'openai:o3', + 'openai:o3-2025-04-16', + 'openai:o4-mini', + 'openai:o4-mini-2025-04-16', 'test', ], ) diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index 36b621905..f902939ea 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -1,6 +1,7 @@ from __future__ import annotations as _annotations import io +import warnings from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator from contextlib import asynccontextmanager from dataclasses import dataclass, field @@ -8,6 +9,7 @@ from json import JSONDecodeError, loads as json_loads from typing import Any, Literal, Union, cast, overload +from anthropic.types import RedactedThinkingBlock from typing_extensions import assert_never from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage @@ -264,6 +266,12 @@ def _process_response(self, response: AnthropicMessage) -> ModelResponse: for item in response.content: if isinstance(item, TextBlock): items.append(TextPart(content=item.text)) + elif isinstance(item, RedactedThinkingBlock): + warnings.warn( + 'PydanticAI currently does not handle redacted thinking blocks. ' + 'If you have a suggestion on how we should handle them, please open an issue.', + UserWarning, + ) elif isinstance(item, ThinkingBlock): items.append(ThinkingPart(content=item.thinking, signature=item.signature)) else: diff --git a/pydantic_ai_slim/pydantic_ai/models/gemini.py b/pydantic_ai_slim/pydantic_ai/models/gemini.py index ab4eb4ea1..c2c30f433 100644 --- a/pydantic_ai_slim/pydantic_ai/models/gemini.py +++ b/pydantic_ai_slim/pydantic_ai/models/gemini.py @@ -30,6 +30,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -81,6 +82,15 @@ class GeminiModelSettings(ModelSettings, total=False): gemini_safety_settings: list[GeminiSafetySettings] + gemini_thinking_config: ThinkingConfig + """Thinking is on by default in both the API and AI Studio. + + Being on by default doesn't mean the model will send back thoughts. For that, you need to set `include_thoughts` + to `True`. If you want to turn it off, set `thinking_budget` to `0`. + + See more about it on . + """ + @dataclass(init=False) class GeminiModel(Model): @@ -223,7 +233,9 @@ async def _make_request( generation_config['presence_penalty'] = presence_penalty if (frequency_penalty := model_settings.get('frequency_penalty')) is not None: generation_config['frequency_penalty'] = frequency_penalty - if (gemini_safety_settings := model_settings.get('gemini_safety_settings')) != []: + if (thinkingConfig := model_settings.get('gemini_thinking_config')) is not None: + generation_config['thinking_config'] = thinkingConfig + if (gemini_safety_settings := model_settings.get('gemini_safety_settings')) is not None: request_data['safetySettings'] = gemini_safety_settings if generation_config: request_data['generationConfig'] = generation_config @@ -499,6 +511,16 @@ class GeminiSafetySettings(TypedDict): """ +class ThinkingConfig(TypedDict, total=False): + """The thinking features configuration.""" + + include_thoughts: Annotated[bool, pydantic.Field(alias='includeThoughts')] + """Indicates whether to include thoughts in the response. If true, thoughts are returned only if the model supports thought and thoughts are available.""" + + thinking_budget: Annotated[int, pydantic.Field(alias='thinkingBudget')] + """Indicates the thinking budget in tokens.""" + + class _GeminiGenerationConfig(TypedDict, total=False): """Schema for an API request to the Gemini API. @@ -513,6 +535,7 @@ class _GeminiGenerationConfig(TypedDict, total=False): presence_penalty: float frequency_penalty: float stop_sequences: list[str] + thinking_config: ThinkingConfig class _GeminiContent(TypedDict): @@ -525,6 +548,8 @@ def _content_model_response(m: ModelResponse) -> _GeminiContent: for item in m.parts: if isinstance(item, ToolCallPart): parts.append(_function_call_part_from_call(item)) + elif isinstance(item, ThinkingPart): + parts.append(_GeminiTextPart(text=item.content, thought=True)) elif isinstance(item, TextPart): if item.content: parts.append(_GeminiTextPart(text=item.content)) @@ -533,33 +558,38 @@ def _content_model_response(m: ModelResponse) -> _GeminiContent: return _GeminiContent(role='model', parts=parts) -class _GeminiTextPart(TypedDict): +class _BasePart(TypedDict): + thought: NotRequired[bool] + """Indicates if the part is thought from the model.""" + + +class _GeminiTextPart(_BasePart): text: str -class _GeminiInlineData(TypedDict): +class _GeminiInlineData(_BasePart): data: str mime_type: Annotated[str, pydantic.Field(alias='mimeType')] -class _GeminiInlineDataPart(TypedDict): +class _GeminiInlineDataPart(_BasePart): """See .""" inline_data: Annotated[_GeminiInlineData, pydantic.Field(alias='inlineData')] -class _GeminiFileData(TypedDict): +class _GeminiFileData(_BasePart): """See .""" file_uri: Annotated[str, pydantic.Field(alias='fileUri')] mime_type: Annotated[str, pydantic.Field(alias='mimeType')] -class _GeminiFileDataPart(TypedDict): +class _GeminiFileDataPart(_BasePart): file_data: Annotated[_GeminiFileData, pydantic.Field(alias='fileData')] -class _GeminiFunctionCallPart(TypedDict): +class _GeminiFunctionCallPart(_BasePart): function_call: Annotated[_GeminiFunctionCall, pydantic.Field(alias='functionCall')] @@ -573,7 +603,12 @@ def _process_response_from_parts( items: list[ModelResponsePart] = [] for part in parts: if 'text' in part: - items.append(TextPart(content=part['text'])) + # NOTE: Google doesn't include the `thought` field anymore. We add it just in case they decide to change + # their mind and start including it again. + if part.get('thought'): # pragma: no cover + items.append(ThinkingPart(content=part['text'])) + else: + items.append(TextPart(content=part['text'])) elif 'function_call' in part: items.append(ToolCallPart(tool_name=part['function_call']['name'], args=part['function_call']['args'])) elif 'function_response' in part: diff --git a/pydantic_ai_slim/pydantic_ai/models/groq.py b/pydantic_ai_slim/pydantic_ai/models/groq.py index 37502b677..2b91d575b 100644 --- a/pydantic_ai_slim/pydantic_ai/models/groq.py +++ b/pydantic_ai_slim/pydantic_ai/models/groq.py @@ -232,6 +232,9 @@ def _process_response(self, response: chat.ChatCompletion) -> ModelResponse: timestamp = datetime.fromtimestamp(response.created, tz=timezone.utc) choice = response.choices[0] items: list[ModelResponsePart] = [] + # NOTE: The `reasoning` field is only present if `groq_reasoning_format` is set to `parsed`. + if choice.message.reasoning is not None: + items.append(ThinkingPart(content=choice.message.reasoning)) if choice.message.content is not None: content = choice.message.content # The `` tag is only present if `groq_reasoning_format` is set to `raw`. @@ -249,9 +252,6 @@ def _process_response(self, response: chat.ChatCompletion) -> ModelResponse: content = '' if content: items.append(TextPart(content=content)) - # NOTE: The `reasoning` field is only present if `groq_reasoning_format` is set to `parsed`. - if choice.message.reasoning is not None: - items.append(ThinkingPart(content=choice.message.reasoning)) if choice.message.tool_calls is not None: for c in choice.message.tool_calls: items.append(ToolCallPart(tool_name=c.function.name, args=c.function.arguments, tool_call_id=c.id)) diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 3d35b55eb..fb777f5b6 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -551,6 +551,7 @@ def _process_response(self, response: responses.Response) -> ModelResponse: """Process a non-streamed response, and prepare a message to return.""" timestamp = datetime.fromtimestamp(response.created_at, tz=timezone.utc) items: list[ModelResponsePart] = [] + items.append(TextPart(response.output_text)) for item in response.output: if item.type == 'reasoning': for summary in item.summary: @@ -559,7 +560,6 @@ def _process_response(self, response: responses.Response) -> ModelResponse: items.append(ThinkingPart(content=summary.text, signature=item.id)) elif item.type == 'function_call': items.append(ToolCallPart(item.name, item.arguments, tool_call_id=item.call_id)) - items.append(TextPart(response.output_text)) return ModelResponse(items, model_name=response.model, timestamp=timestamp) async def _process_streamed_response( diff --git a/tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml b/tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml new file mode 100644 index 000000000..1e81eda9c --- /dev/null +++ b/tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml @@ -0,0 +1,278 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '150' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: How do I cross the street? + role: user + model: o3-mini + reasoning: + effort: high + summary: detailed + stream: false + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '5163' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '20560' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + created_at: 1745064959 + error: null + id: resp_680393ff82488191a7d0850bf0dd99a004f0817ea037a07b + incomplete_details: null + instructions: null + max_output_tokens: null + metadata: {} + model: o3-mini-2025-01-31 + object: response + output: + - id: rs_6803940b4d188191838f9713b14072ae04f0817ea037a07b + summary: + - text: |- + **Providing street-crossing instructions** + + The user's question about crossing the street seems straightforward, but I want to ensure I provide safe instructions. It's important to adhere to safety guidelines while being clear. I need to include disclaimers about not being a substitute for professional advice or local regulations. My instructions can be general: stop, look both ways, use crosswalks, and follow traffic signals if they're available. I'll also mention that location-specific rules may apply, just to be thorough. + type: summary_text + - text: |- + **Recalling street-crossing instructions** + + I need to summarize safe street-crossing instructions, making sure to highlight key safety tips. I'll emphasize to look left, right, and then left again, and to use crosswalks where available. It’s important to clarify that I’m not a city planner or a safety professional, so my guidance is general in nature. I also want to include reminders about not assuming drivers will see you, and stressing the need for caution when no crosswalks are present. + type: summary_text + - text: "**Providing safe crossing instructions**\n\nI’m preparing step-by-step instructions for crossing the street + safely, emphasizing key guidelines. I want to ensure drivers are cautious and remind everyone to avoid distractions + like phones. My steps will be clear: \n\n1. Stop at the edge of the sidewalk. \n2. Look left, right, and left + again to check for traffic. \n3. Wait for a clear gap in the traffic. \n4. Follow any pedestrian signals or + signs. \n5. Cross the street when it’s safe. \n6. Stay alert for vehicles while crossing. \n\nI'll include + a disclaimer that this isn’t professional advice and encourage using good judgment." + type: summary_text + type: reasoning + - content: + - annotations: [] + text: |- + Here are guidelines for safely crossing a street. Remember that these tips are general and may need to be adjusted depending on your local traffic laws and the specific situation. They are not a substitute for professional safety advice. + + 1. Before you approach the street: +  • Use the sidewalk if available and get to the curb or edge of the road. +  • If you’re a child or feel unsure, try to have an adult accompany you. + + 2. When you're ready to cross: +  • Look carefully in all directions—start by looking left, then right, and left again. In countries where you drive on the left you’ll want to adjust accordingly. +  • Listen for vehicles and be aware of turning cars, which might not be immediately in your line of sight. +  • Make eye contact with drivers if possible so that you know they see you. + + 3. Use designated crossing areas whenever possible: +  • If there's a pedestrian crosswalk, use it. Crosswalks and traffic signals are there to help manage the flow of both vehicles and pedestrians. +  • If there’s a “Walk” signal, wait until it’s on before crossing. Even if the signal turns green for pedestrians, always take an extra moment to ensure that approaching drivers are stopping. + + 4. While crossing: +  • Continue to remain alert and avoid distractions like cell phones or headphones that could prevent you from noticing approaching traffic. +  • Walk at a steady pace and stay in the crosswalk until you have completely reached the other side. + + 5. After crossing: +  • Once you’ve safely reached the other side, continue to be aware of any vehicles that might be turning or reversing. + + Always be cautious—even if you have the right-of-way—and understand that it’s better to wait a moment longer than risk being caught off guard. Stay safe! + type: output_text + id: msg_68039413525c8191aca9aa8f886eaf5d04f0817ea037a07b + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + reasoning: + effort: high + summary: detailed + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 13 + input_tokens_details: + cached_tokens: 0 + output_tokens: 2028 + output_tokens_details: + reasoning_tokens: 1664 + total_tokens: 2041 + user: null + status: + code: 200 + message: OK +- request: + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3904' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + method: POST + parsed_body: + contents: + - parts: + - text: How do I cross the street? + role: user + - parts: + - text: |- + **Providing street-crossing instructions** + + The user's question about crossing the street seems straightforward, but I want to ensure I provide safe instructions. It's important to adhere to safety guidelines while being clear. I need to include disclaimers about not being a substitute for professional advice or local regulations. My instructions can be general: stop, look both ways, use crosswalks, and follow traffic signals if they're available. I'll also mention that location-specific rules may apply, just to be thorough. + thought: true + - text: |- + **Recalling street-crossing instructions** + + I need to summarize safe street-crossing instructions, making sure to highlight key safety tips. I'll emphasize to look left, right, and then left again, and to use crosswalks where available. It’s important to clarify that I’m not a city planner or a safety professional, so my guidance is general in nature. I also want to include reminders about not assuming drivers will see you, and stressing the need for caution when no crosswalks are present. + thought: true + - text: "**Providing safe crossing instructions**\n\nI’m preparing step-by-step instructions for crossing the street + safely, emphasizing key guidelines. I want to ensure drivers are cautious and remind everyone to avoid distractions + like phones. My steps will be clear: \n\n1. Stop at the edge of the sidewalk. \n2. Look left, right, and left + again to check for traffic. \n3. Wait for a clear gap in the traffic. \n4. Follow any pedestrian signals or + signs. \n5. Cross the street when it’s safe. \n6. Stay alert for vehicles while crossing. \n\nI'll include + a disclaimer that this isn’t professional advice and encourage using good judgment." + thought: true + - text: |- + Here are guidelines for safely crossing a street. Remember that these tips are general and may need to be adjusted depending on your local traffic laws and the specific situation. They are not a substitute for professional safety advice. + + 1. Before you approach the street: +  • Use the sidewalk if available and get to the curb or edge of the road. +  • If you’re a child or feel unsure, try to have an adult accompany you. + + 2. When you're ready to cross: +  • Look carefully in all directions—start by looking left, then right, and left again. In countries where you drive on the left you’ll want to adjust accordingly. +  • Listen for vehicles and be aware of turning cars, which might not be immediately in your line of sight. +  • Make eye contact with drivers if possible so that you know they see you. + + 3. Use designated crossing areas whenever possible: +  • If there's a pedestrian crosswalk, use it. Crosswalks and traffic signals are there to help manage the flow of both vehicles and pedestrians. +  • If there’s a “Walk” signal, wait until it’s on before crossing. Even if the signal turns green for pedestrians, always take an extra moment to ensure that approaching drivers are stopping. + + 4. While crossing: +  • Continue to remain alert and avoid distractions like cell phones or headphones that could prevent you from noticing approaching traffic. +  • Walk at a steady pace and stay in the crosswalk until you have completely reached the other side. + + 5. After crossing: +  • Once you’ve safely reached the other side, continue to be aware of any vehicles that might be turning or reversing. + + Always be cautious—even if you have the right-of-way—and understand that it’s better to wait a moment longer than risk being caught off guard. Stay safe! + role: model + - parts: + - text: Considering the way to cross the street, analogously, how do I cross the river? + role: user + generationConfig: + thinking_config: + includeThoughts: true + thinkingBudget: 1024 + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-04-17:generateContent + response: + headers: + alt-svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + content-length: + - '3753' + content-type: + - application/json; charset=UTF-8 + server-timing: + - gfet4t7; dur=13980 + transfer-encoding: + - chunked + vary: + - Origin + - X-Origin + - Referer + parsed_body: + candidates: + - content: + parts: + - text: |- + Okay, let's draw an analogy between crossing a street and crossing a river, applying the safety principles from the street crossing guide to the river environment. + + Think of the **river** as being like the **street** – a natural barrier you need to get across. The **hazards** on the river are different from vehicles, but they are still things that can harm you. + + Here's the analogous guide for crossing a river: + + 1. **Before you approach the river:** + * Just as you use a sidewalk to get to the street's edge, use a trail or the riverbank to get to a spot where you can assess the river. + * If you’re inexperienced with rivers or unsure about the conditions, try to have someone experienced accompany you. + + 2. **When you're ready to cross:** + * Just as you look and listen for vehicles, carefully **assess the river conditions**. Look in all directions (upstream, downstream, across): + * How fast is the current moving? (Like checking vehicle speed). + * How deep does the water look? (Like judging the width and how much time you have). + * Are there obstacles in the water (rocks, logs)? (Like parked cars or road hazards). + * Is the bottom visible and does it look stable? (Like checking the road surface). + * Check upstream for potential hazards coming towards you (like debris). + * Listen to the river – the sound can tell you if the current is very strong or if there are rapids. + * Acknowledge the river's power – just as you make eye contact with drivers, respect that the river can be dangerous and doesn't care if you're trying to cross. + + 3. **Use designated crossing areas whenever possible:** + * If there's a **bridge or a ferry**, use it. These are like the crosswalks and traffic signals – the safest, established ways to cross, often managing the "flow" (of water below, or people/boats on the river). + * If you must wade or swim, look for the safest possible **crossing point** – maybe a wider, shallower section, a known ford, or a spot with a less turbulent current. This is like choosing a crosswalk instead of crossing anywhere. + + 4. **While crossing:** + * Just as you stay alert and avoid distractions, **focus completely on the crossing**. Don't be looking at your phone or distracted by conversation if you are actively navigating the water. + * Move with purpose, but carefully. If wading, maintain your balance against the current and watch your footing. If swimming, focus on your technique and direction. Stay aware of where you are relative to your intended path and the river's flow. + + 5. **After crossing:** + * Once you’ve safely reached the other side, take a moment to ensure you are truly out of the main flow and on stable ground. Be aware of the riverbank conditions. + + **Analogous Takeaway:** + + Just as you wouldn't just run blindly into a busy street, you shouldn't just jump into a river without understanding its conditions and choosing the safest method and location to cross. Be cautious, assess the "traffic" (current, depth, obstacles), and use the available "infrastructure" (bridges, ferries, established crossing points) whenever possible. + role: model + finishReason: STOP + index: 0 + modelVersion: gemini-2.5-flash-preview-04-17 + usageMetadata: + candidatesTokenCount: 1519 + promptTokenCount: 801 + promptTokensDetails: + - modality: TEXT + tokenCount: 801 + thoughtsTokenCount: 794 + totalTokenCount: 2320 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml b/tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml new file mode 100644 index 000000000..1f3205a57 --- /dev/null +++ b/tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml @@ -0,0 +1,345 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '219' + content-type: + - application/json + host: + - api.groq.com + method: POST + parsed_body: + messages: + - content: You are a chef. + role: system + - content: I want a recipe to cook Uruguayan alfajores. + role: user + model: deepseek-r1-distill-llama-70b + n: 1 + reasoning_format: raw + stream: false + uri: https://api.groq.com/openai/v1/chat/completions + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + cache-control: + - private, max-age=0, no-store, no-cache, must-revalidate + connection: + - keep-alive + content-length: + - '6639' + content-type: + - application/json + transfer-encoding: + - chunked + vary: + - Origin, Accept-Encoding + parsed_body: + choices: + - finish_reason: stop + index: 0 + logprobs: null + message: + content: "\nOkay, so I want to make Uruguayan alfajores. I've heard they're these delicious South American + cookies filled with dulce de leche and coated in powdered sugar. I'm not entirely sure about the exact steps, + but I'll try to figure it out.\n\nFirst, I need to gather the ingredients. The user provided a list, so I can + refer to that. It includes all-purpose flour, cornstarch, baking powder, salt, unsalted butter, powdered sugar, + eggs, vanilla extract, dulce de leche, and powdered sugar for coating. I think I have most of these in my pantry + except maybe cornstarch and dulce de leche. I'll have to make sure to get those.\n\nNow, the instructions. Preheating + the oven to 300°F (150°C) seems standard for cookies. They mentioned using a baking sheet lined with parchment + paper, so I'll prepare that. \n\nNext, making the dough. It involves mixing dry ingredients and then creaming + butter and sugar. Wait, the powdered sugar is used here, right? So I need to make sure I have enough. Then adding + the egg and vanilla. I'm a bit confused about the cornstarch part—do I mix it with the flour before adding to + the wet ingredients? I think that's what the user meant. So, the dry ingredients are flour, cornstarch, baking + powder, and salt. I should whisk those together first.\n\nOnce the dough comes together, I need to wrap it and + let it rest. Maybe 30 minutes is enough. Then, rolling it out to about 1/4 inch thickness. I'm not sure if that's + too thick or thin. I'll try to roll it evenly. Cutting into circles, probably using a cookie cutter or a glass. + The user mentioned a 2-inch diameter, so I'll aim for that.\n\nBaking for 15-20 minutes until the edges are lightly + golden. I should keep an eye on them to prevent burning. Letting them cool on the sheet for 5 minutes before moving + to a wire rack to cool completely. That makes sense because cookies can break if moved too soon.\n\nAssembling + the alfajores: spreading dulce de leche on one half and sandwiching. I'm a bit worried about handling the cookies; + they might be fragile. I'll be gentle. Then coating in powdered sugar. Maybe I should do this after they're filled + to ensure the sugar sticks properly.\n\nWait, the user mentioned two options for coating: either just powdered + sugar or a chocolate coating. I think I'll stick to the traditional powdered sugar for now. Maybe next time I + can try the chocolate version.\n\nI'm a bit concerned about the dulce de leche. If I can't find it in stores, + I might have to make it myself. The user provided a method for that, which is good. Heating condensed milk until + it thickens. I should be careful with that because it can burn easily.\n\nAlso, when forming the dough, if it's + too sticky, I might need to add a bit more flour, but I shouldn't overdo it. Maybe the cornstarch helps with the + texture. I'm not sure how the cornstarch affects the cookies—maybe it makes them lighter or crisper.\n\nI should + also make sure the butter is softened to room temperature to cream it properly with the powdered sugar. If the + butter is too hard, the mixture might not come together smoothly.\n\nAnother thing is the baking time. Since the + oven is at a lower temperature, the cookies might take a bit longer. I'll check them after 15 minutes and maybe + extend if needed. They shouldn't be too dark around the edges.\n\nWhen cooling, it's important they are completely + cool before handling, otherwise, they might break when trying to sandwich them. I'll let them sit on the rack + for at least 30 minutes.\n\nI'm thinking about how to store them. The user mentioned an airtight container at + room temperature for up to a week. Maybe I can freeze some if I don't plan to eat them all soon.\n\nI'm a bit + nervous about the assembly. The dulce de leche can be messy. I'll make sure to have a plate or tray ready to place + the assembled cookies before coating them in powdered sugar.\n\nOverall, it seems manageable. I'll take it step + by step, follow the measurements closely, and taste as I go. If something doesn't feel right, I'll adjust. I'm + excited to try these Uruguayan alfajores!\n\n\nTo make Uruguayan alfajores, follow these organized steps + for a delightful baking experience:\n\n### Ingredients:\n- 2 cups all-purpose flour\n- 1 cup cornstarch\n- 1 tsp + baking powder\n- 1/2 tsp salt\n- 1 cup unsalted butter, softened\n- 1 cup powdered sugar\n- 1 egg\n- 1 tsp vanilla + extract\n- Dulce de leche (store-bought or homemade)\n- Powdered sugar for coating\n\n### Instructions:\n\n1. + **Preheat Oven:**\n - Preheat your oven to 300°F (150°C). Line a baking sheet with parchment paper.\n\n2. **Prepare + Dough:**\n - In a bowl, whisk together flour, cornstarch, baking powder, and salt.\n - In another bowl, cream + butter and powdered sugar until smooth. Add egg and vanilla, mixing well.\n - Gradually incorporate the dry + ingredients into the wet mixture until a dough forms. Wrap and let rest for 30 minutes.\n\n3. **Roll and Cut:**\n + \ - Roll dough to 1/4 inch thickness. Cut into 2-inch circles using a cutter or glass.\n\n4. **Bake:**\n - + Place cookies on the prepared baking sheet, bake for 15-20 minutes until edges are lightly golden. Cool on the + sheet for 5 minutes, then transfer to a wire rack to cool completely.\n\n5. **Assemble Alfajores:**\n - Spread + a layer of dulce de leche on one cookie half. Sandwich with another cookie. Handle gently to avoid breaking.\n\n6. + **Coat with Powdered Sugar:**\n - Roll each alfajor in powdered sugar, pressing gently to adhere.\n\n7. **Optional + Chocolate Coating:**\n - For a chocolate version, melt chocolate and dip alfajores, then chill to set.\n\n8. + **Storage:**\n - Store in an airtight container at room temperature for up to a week. Freeze for longer storage.\n\n### + Tips:\n- Ensure butter is softened for smooth creaming.\n- Check cookies after 15 minutes to avoid over-browning.\n- + Allow cookies to cool completely before handling.\n- Homemade dulce de leche can be made by heating condensed + milk until thickened.\n\nEnjoy your traditional Uruguayan alfajores with a cup of coffee or tea!" + role: assistant + created: 1745064185 + id: chatcmpl-9748c1af-1065-410a-969a-d7fb48039fbb + model: deepseek-r1-distill-llama-70b + object: chat.completion + system_fingerprint: fp_454c494f52 + usage: + completion_time: 5.141818182 + completion_tokens: 1414 + prompt_time: 0.00448192 + prompt_tokens: 21 + queue_time: 0.086872184 + total_time: 5.146300102 + total_tokens: 1435 + usage_breakdown: + models: null + x_groq: + id: req_01js6ywkh6e62vxj89vrs19wpn + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2333' + content-type: + - application/json + cookie: + - __cf_bm=8pcJXf3Ub9vLuMV_UKGfpaNNmqKlFWWsKoRxh_beZt4-1745064190-1.0.1.1-UjG7Yf6THFmT2XO7TCCBqim8hVwv2b2T0DBzEUr7ybE1tga1y_9tH3BOhsAhbFhHvCc8iHZoTLwTEDERDaEvfG54.LPb3iXdOxTIoAsgWRQ + host: + - api.groq.com + method: POST + parsed_body: + messages: + - content: You are a chef. + role: system + - content: I want a recipe to cook Uruguayan alfajores. + role: user + - content: |2- + + + To make Uruguayan alfajores, follow these organized steps for a delightful baking experience: + + ### Ingredients: + - 2 cups all-purpose flour + - 1 cup cornstarch + - 1 tsp baking powder + - 1/2 tsp salt + - 1 cup unsalted butter, softened + - 1 cup powdered sugar + - 1 egg + - 1 tsp vanilla extract + - Dulce de leche (store-bought or homemade) + - Powdered sugar for coating + + ### Instructions: + + 1. **Preheat Oven:** + - Preheat your oven to 300°F (150°C). Line a baking sheet with parchment paper. + + 2. **Prepare Dough:** + - In a bowl, whisk together flour, cornstarch, baking powder, and salt. + - In another bowl, cream butter and powdered sugar until smooth. Add egg and vanilla, mixing well. + - Gradually incorporate the dry ingredients into the wet mixture until a dough forms. Wrap and let rest for 30 minutes. + + 3. **Roll and Cut:** + - Roll dough to 1/4 inch thickness. Cut into 2-inch circles using a cutter or glass. + + 4. **Bake:** + - Place cookies on the prepared baking sheet, bake for 15-20 minutes until edges are lightly golden. Cool on the sheet for 5 minutes, then transfer to a wire rack to cool completely. + + 5. **Assemble Alfajores:** + - Spread a layer of dulce de leche on one cookie half. Sandwich with another cookie. Handle gently to avoid breaking. + + 6. **Coat with Powdered Sugar:** + - Roll each alfajor in powdered sugar, pressing gently to adhere. + + 7. **Optional Chocolate Coating:** + - For a chocolate version, melt chocolate and dip alfajores, then chill to set. + + 8. **Storage:** + - Store in an airtight container at room temperature for up to a week. Freeze for longer storage. + + ### Tips: + - Ensure butter is softened for smooth creaming. + - Check cookies after 15 minutes to avoid over-browning. + - Allow cookies to cool completely before handling. + - Homemade dulce de leche can be made by heating condensed milk until thickened. + + Enjoy your traditional Uruguayan alfajores with a cup of coffee or tea! + role: assistant + - content: Considering the Uruguayan recipe, how can I cook the Argentinian one? + role: user + model: deepseek-r1-distill-llama-70b + n: 1 + reasoning_format: parsed + stream: false + uri: https://api.groq.com/openai/v1/chat/completions + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + cache-control: + - private, max-age=0, no-store, no-cache, must-revalidate + connection: + - keep-alive + content-length: + - '7861' + content-type: + - application/json + transfer-encoding: + - chunked + vary: + - Origin, Accept-Encoding + parsed_body: + choices: + - finish_reason: stop + index: 0 + logprobs: null + message: + content: |- + To create authentic Argentinian alfajores, follow these organized steps, which highlight the unique characteristics and differences from the Uruguayan version: + + ### Ingredients: + - **For the Cookies:** + - 2 cups all-purpose flour + - 1/2 cup cornstarch + - 1/4 cup unsweetened cocoa powder (optional for chocolate version) + - 1 teaspoon baking powder + - 1/2 teaspoon salt + - 1 cup unsalted butter, softened + - 1 cup powdered sugar + - 1 egg + - 1 teaspoon vanilla extract + + - **For the Filling:** + - Dulce de leche (store-bought or homemade) + - Optional: caramel sauce, chopped nuts, or cinnamon for added flavor + + - **For Coating:** + - Powdered sugar + - Optional: cinnamon, shredded coconut, or melted chocolate + + ### Instructions: + + 1. **Prepare the Dough:** + - In a large bowl, whisk together the flour, cornstarch, cocoa powder (if using), baking powder, and salt. + - In another bowl, cream the softened butter and powdered sugar until smooth. Add the egg and vanilla extract, mixing well. + - Gradually incorporate the dry ingredients into the wet mixture until a dough forms. Wrap the dough in plastic wrap and let it rest for 30 minutes. + + 2. **Roll and Cut the Cookies:** + - Roll the dough to about 1/4 inch thickness on a lightly floured surface. + - Use a round cookie cutter or the rim of a glass to cut out circles of dough. Argentinian alfajores are often slightly larger than their Uruguayan counterparts. + + 3. **Bake the Cookies:** + - Preheat the oven to 300°F (150°C). Line a baking sheet with parchment paper. + - Place the cookie rounds on the prepared baking sheet, leaving about 1 inch of space between each cookie. + - Bake for 15-20 minutes, or until the edges are lightly golden. Allow the cookies to cool on the baking sheet for 5 minutes before transferring them to a wire rack to cool completely. + + 4. **Prepare the Filling:** + - Use store-bought dulce de leche or make your own by heating sweetened condensed milk until thickened. + - Optional: Stir in caramel sauce or chopped nuts into the dulce de leche for added flavor. + + 5. **Assemble the Alfajores:** + - Once the cookies are completely cool, spread a generous amount of dulce de leche on the flat side of one cookie. + - Sandwich with another cookie, pressing gently to adhere. For an extra touch, roll the edges in chopped nuts or shredded coconut. + + 6. **Coat with Powdered Sugar or Chocolate:** + - Roll each alfajor in powdered sugar, pressing gently to ensure it adheres. + - Optional: Melt chocolate and dip the alfajores, then sprinkle with cinnamon or coconut before the chocolate sets. + + 7. **Allow to Set:** + - Let the alfajores sit for about 30 minutes to allow the flavors to meld together. + + 8. **Serve and Enjoy:** + - Serve with a cup of coffee or tea. Store any leftovers in an airtight container at room temperature for up to a week. + + ### Tips: + - **Dough Handling:** Ensure the butter is softened for a smooth dough, and don’t overwork the dough to maintain the cookies' tender texture. + - **Baking:** Keep an eye on the cookies after 15 minutes to prevent over-browning. + - **Filling Variations:** Experiment with different fillings like caramel or nuts to create unique flavor profiles. + - **Coating Options:** Try different coatings such as cinnamon or coconut for a varied texture and taste. + + By following these steps, you'll create authentic Argentinian alfajores that capture the rich flavors and traditions of this beloved South American treat. + reasoning: | + Alright, so I want to make Argentinian alfajores after successfully making the Uruguayan ones. I know that both countries have their own versions of alfajores, but I'm not entirely sure how they differ. Maybe the ingredients or the preparation steps are slightly different? I should probably start by researching what makes Argentinian alfajores unique compared to the Uruguayan ones. + + First, I remember that in the Uruguayan recipe, the cookies were more delicate, and the filling was a generous amount of dulce de leche. The process involved making the dough from scratch, baking the cookies, and then assembling them with the filling and sometimes coating them in powdered sugar or chocolate. + + For Argentinian alfajores, I think the cookies might have a different texture—perhaps they're crunchier or have a different flavor profile. Maybe the type of flour used is different, or there's an addition like cocoa powder for a chocolate version. Also, the filling might have variations, like adding caramel or nuts to the dulce de leche. + + I should look up some authentic Argentinian recipes to see the differences. Maybe the method of making the dough is slightly different, or the baking time and temperature vary. I also wonder if Argentinian alfajores are typically coated in something else besides powdered sugar, like cinnamon or coconut flakes. + + Another thing to consider is the size and shape of the cookies. Uruguayan alfajores might be smaller and rounder, while Argentinian ones could be larger or have a different shape. I should also check if there are any additional steps, like toasting the cookies or adding a layer of meringue. + + I need to make sure I have all the necessary ingredients. If the Argentinian version requires something different, like a specific type of flour or a particular flavoring, I'll need to adjust my shopping list. Also, if the filling is different, I might need to prepare it in a different way or add extra ingredients. + + I should also think about the assembly process. Maybe Argentinian alfajores are sandwiched with more filling or have a different way of sealing the cookies together. Perhaps they're rolled in coconut after being filled, or there's a step involving dipping them in chocolate. + + It would be helpful to watch a video or read a detailed recipe from an Argentinian source to get a better understanding. That way, I can follow the traditional method and ensure that my alfajores turn out authentic. I should also consider any tips or tricks that Argentinian bakers use to make their alfajores special. + + Finally, I need to plan the timing. Making alfajores can be a bit time-consuming, especially if you're making the dulce de leche from scratch. I should allocate enough time for preparing the dough, baking the cookies, and assembling the alfajores. + + Overall, the key steps I think I'll need to follow are: + + 1. Research authentic Argentinian alfajores recipes to identify unique ingredients and steps. + 2. Adjust the ingredient list based on the differences from the Uruguayan version. + 3. Prepare the dough according to the Argentinian method, which might involve different mixing techniques or additional ingredients. + 4. Bake the cookies, possibly at a different temperature or for a different duration. + 5. Prepare the filling, which might include variations like caramel or nuts. + 6. Assemble the alfajores, possibly adding extra coatings or layers. + 7. Allow the alfajores to set before serving, to ensure the flavors meld together. + + By carefully following these steps and paying attention to the unique aspects of Argentinian alfajores, I should be able to create a delicious and authentic batch that captures the essence of this traditional South American treat. + role: assistant + created: 1745064190 + id: chatcmpl-994aa228-883a-498c-8b20-9655d770b697 + model: deepseek-r1-distill-llama-70b + object: chat.completion + system_fingerprint: fp_454c494f52 + usage: + completion_time: 5.781818182 + completion_tokens: 1590 + prompt_time: 0.027524888 + prompt_tokens: 524 + queue_time: 0.08761817100000001 + total_time: 5.80934307 + total_tokens: 2114 + usage_breakdown: + models: null + x_groq: + id: req_01js6ywrxgffwv0vdp3pcfhx05 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_groq/test_groq_model_thinking_part_raw_format.yaml b/tests/models/cassettes/test_groq/test_groq_model_thinking_part_raw_format.yaml deleted file mode 100644 index 41dcdfde9..000000000 --- a/tests/models/cassettes/test_groq/test_groq_model_thinking_part_raw_format.yaml +++ /dev/null @@ -1,252 +0,0 @@ -interactions: -- request: - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '155' - content-type: - - application/json - host: - - api.groq.com - method: POST - parsed_body: - messages: - - content: How do I cross the street? - role: user - model: deepseek-r1-distill-llama-70b - n: 1 - reasoning_format: raw - stream: false - uri: https://api.groq.com/openai/v1/chat/completions - response: - headers: - alt-svc: - - h3=":443"; ma=86400 - cache-control: - - private, max-age=0, no-store, no-cache, must-revalidate - connection: - - keep-alive - content-length: - - '1635' - content-type: - - application/json - transfer-encoding: - - chunked - vary: - - Origin, Accept-Encoding - parsed_body: - choices: - - finish_reason: stop - index: 0 - logprobs: null - message: - content: |- - - - - - To cross the street safely, follow these steps: - - 1. **Find a Safe Crossing Point**: Look for a pedestrian crossing, traffic lights, or a crosswalk. Avoid crossing in areas with poor visibility or heavy traffic. - - 2. **Check for Traffic**: Before stepping onto the road, ensure all lanes are clear of oncoming vehicles. Make eye contact with drivers to confirm they see you. - - 3. **Obey Traffic Signals**: Wait for the pedestrian signal (green light or "Walk" sign) before crossing. If there are no signals, wait until traffic comes to a stop. - - 4. **Cross with Caution**: Walk briskly and directly across the street without stopping. Avoid distractions like using your phone while crossing. - - 5. **Be Visible**: Wear bright or reflective clothing, especially at night or in low-light conditions, to make yourself more visible to drivers. - - 6. **Follow Pedestrian Laws**: Adhere to local pedestrian rules and regulations to avoid accidents and fines. - - By following these steps, you can cross the street safely and responsibly. - role: assistant - created: 1745046533 - id: chatcmpl-41790d7f-3b36-42e9-8374-ce55210ff385 - model: deepseek-r1-distill-llama-70b - object: chat.completion - system_fingerprint: fp_454c494f52 - usage: - completion_time: 0.785454545 - completion_tokens: 216 - prompt_time: 0.003477838 - prompt_tokens: 10 - queue_time: 0.08876919900000001 - total_time: 0.788932383 - total_tokens: 226 - usage_breakdown: - models: null - x_groq: - id: req_01js6e1x3hfsa9rqx89gca29n5 - status: - code: 200 - message: OK -- request: - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1337' - content-type: - - application/json - cookie: - - __cf_bm=AF.Ol.TFODMAv0.2dOLSK_2mACTSi.4LTWymDTsY_hE-1745046534-1.0.1.1-._jRopI2RR3rO7BMoDMWDrQuo8podhSRa4NIbcWKLq6qe4N4E78HYZv7MYn700csbopraN9KdJWNUWXf2O6s_ut9VYVndfLGwhiizBn1fAg - host: - - api.groq.com - method: POST - parsed_body: - messages: - - content: How do I cross the street? - role: user - - content: |2- - - - To cross the street safely, follow these steps: - - 1. **Find a Safe Crossing Point**: Look for a pedestrian crossing, traffic lights, or a crosswalk. Avoid crossing in areas with poor visibility or heavy traffic. - - 2. **Check for Traffic**: Before stepping onto the road, ensure all lanes are clear of oncoming vehicles. Make eye contact with drivers to confirm they see you. - - 3. **Obey Traffic Signals**: Wait for the pedestrian signal (green light or "Walk" sign) before crossing. If there are no signals, wait until traffic comes to a stop. - - 4. **Cross with Caution**: Walk briskly and directly across the street without stopping. Avoid distractions like using your phone while crossing. - - 5. **Be Visible**: Wear bright or reflective clothing, especially at night or in low-light conditions, to make yourself more visible to drivers. - - 6. **Follow Pedestrian Laws**: Adhere to local pedestrian rules and regulations to avoid accidents and fines. - - By following these steps, you can cross the street safely and responsibly. - role: assistant - - content: Considering the way to cross the street, analogously, how do I cross the river? - role: user - model: deepseek-r1-distill-llama-70b - n: 1 - reasoning_format: raw - stream: false - uri: https://api.groq.com/openai/v1/chat/completions - response: - headers: - alt-svc: - - h3=":443"; ma=86400 - cache-control: - - private, max-age=0, no-store, no-cache, must-revalidate - connection: - - keep-alive - content-length: - - '5632' - content-type: - - application/json - transfer-encoding: - - chunked - vary: - - Origin, Accept-Encoding - parsed_body: - choices: - - finish_reason: stop - index: 0 - logprobs: null - message: - content: |- - - Alright, so the user just asked, "How do I cross the river?" after I explained how to cross the street. They're drawing an analogy between crossing the street and crossing a river, which is interesting. I need to figure out how to respond in a way that's both helpful and makes sense. - - First, I should think about why they're asking this. Maybe they're planning a hiking trip or some outdoor activity where they might encounter a river without a bridge. Or perhaps it's a metaphor for overcoming obstacles. But given the previous question about crossing the street, it's more likely they're looking for practical advice on safely crossing a river. - - I should break it down into steps, similar to how I explained crossing the street. Let's see, when crossing a river, the main concerns are safety, choosing the right spot, using proper techniques, and being prepared. I should address each of these areas. - - I'll start by assessing the situation. They need to check the water's depth, current, and temperature. These factors are crucial because they determine the difficulty and danger of crossing. Next, choosing a safe crossing point is essential—looking for shallow areas with slower currents. - - Then, I should talk about using a walking stick for balance. It's a simple tool that can make a big difference in stability. Crossing at an angle upstream makes sense because it helps counter the current's push. I should explain that to avoid being carried downstream. - - Wearing appropriate footwear is important too. They don't want to slip on wet rocks. Holding hands in a group can provide mutual support, which is both safe and practical. I should mention that if the current is too strong, they should avoid wading and instead look for a safer method, like a log or rope. - - Planning for emergencies is also key. They need to know what to do if they fall—floating on their back reduces exhaustion and keeps their head above water. Finally, checking the weather is wise to avoid unexpected issues like rising water levels. - - I should make sure the response is clear and step-by-step, just like the previous one. Using similar structure will help the user follow along easily. I also want to encourage preparedness and caution, emphasizing safety throughout. - - Maybe I should also consider mentioning alternatives, like finding a bridge or a shallow point, in case crossing isn't safe. It's important they know when it's better to seek a different route. Overall, the response should be thorough but concise, covering all necessary aspects without being overwhelming. - - - Crossing a river can be challenging and dangerous if not done properly. Here's an analogy to crossing the street, applied to crossing a river safely: - - --- - - ### 1. **Assess the Situation** - - Just as you would look for traffic when crossing the street, evaluate the river's conditions: water depth, current strength, and temperature. - - Avoid crossing during heavy rain or when the water is murky, as it may hide hazards like rocks or deep holes. - - --- - - ### 2. **Choose a Safe Crossing Point** - - Look for a shallow point with slower-moving water, similar to how you'd choose a crosswalk or traffic light to cross the street. - - Avoid areas with fast currents or deep water, as they can sweep you away. - - --- - - ### 3. **Use a Walking Stick** - - Just as you might use a cane for stability on uneven ground, use a sturdy walking stick or branch to help balance and test the riverbed for secure footing. - - --- - - ### 4. **Cross at an Angle** - - When entering the water, face upstream at a slight angle to counteract the current, much like you would position yourself to cross the street at a diagonal to avoid oncoming traffic. - - --- - - ### 5. **Wear Appropriate Footwear** - - Just as you wear shoes to cross the street, wear sturdy, water-friendly footwear with good traction to prevent slipping on wet rocks. - - --- - - ### 6. **Hold Hands if in a Group** - - If you're crossing with others, hold hands in a line to provide mutual support, similar to how you might guide a child or someone else across the street. - - --- - - ### 7. **Avoid Wading in Strong Currents** - - If the current is too strong to wade through safely, do not attempt to cross. Instead, look for a safer method, such as using a log, raft, or rope to assist in crossing. - - --- - - ### 8. **Plan for Emergencies** - - Just as you would know what to do if a car approaches while crossing the street, be prepared for emergencies while crossing the river. If you fall, float on your back with your feet downstream to avoid injury. - - --- - - ### 9. **Check for Weather Conditions** - - Just as you would check for traffic lights before crossing the street, check the weather before crossing the river. Rising water levels or flash floods can make the river dangerous. - - --- - - By being prepared, cautious, and methodical, you can safely "cross the river," just as you would cross the street. Always prioritize safety and avoid taking unnecessary risks! - role: assistant - created: 1745046534 - id: chatcmpl-8045307b-7978-42bb-b092-86ac18b59b47 - model: deepseek-r1-distill-llama-70b - object: chat.completion - system_fingerprint: fp_454c494f52 - usage: - completion_time: 3.781818182 - completion_tokens: 1040 - prompt_time: 0.013254186 - prompt_tokens: 243 - queue_time: 0.08608507 - total_time: 3.795072368 - total_tokens: 1283 - usage_breakdown: - models: null - x_groq: - id: req_01js6e1y1ffk3b9mbmr0y9xzcj - status: - code: 200 - message: OK -version: 1 diff --git a/tests/models/test_gemini.py b/tests/models/test_gemini.py index ddf9a91bf..58daa3a9d 100644 --- a/tests/models/test_gemini.py +++ b/tests/models/test_gemini.py @@ -25,6 +25,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -46,11 +47,13 @@ _GeminiTools, _GeminiUsageMetaData, ) +from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings from pydantic_ai.providers.google_gla import GoogleGLAProvider +from pydantic_ai.providers.openai import OpenAIProvider from pydantic_ai.result import Usage from pydantic_ai.tools import ToolDefinition -from ..conftest import ClientWithHandler, IsDatetime, IsNow, IsStr, TestEnv +from ..conftest import ClientWithHandler, IsDatetime, IsInstance, IsNow, IsStr, TestEnv pytestmark = pytest.mark.anyio @@ -1066,3 +1069,110 @@ async def get_temperature(location: dict[str, CurrentLocation]) -> float: # pra assert result.output == snapshot( 'I need a location dictionary to use the `get_temperature` function. I cannot provide the temperature in Tokyo without more information.\n' ) + + +@pytest.mark.vcr() +async def test_gemini_model_thinking_part(allow_model_requests: None, gemini_api_key: str, openai_api_key: str): + openai_model = OpenAIResponsesModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key)) + gemini_model = GeminiModel('gemini-2.5-flash-preview-04-17', provider=GoogleGLAProvider(api_key=gemini_api_key)) + agent = Agent(openai_model) + + # We call OpenAI to get the thinking parts, because Google disabled the thoughts in the API. + # See https://github.com/pydantic/pydantic-ai/issues/793 for more details. + result = await agent.run( + 'How do I cross the street?', + model_settings=OpenAIResponsesModelSettings( + openai_reasoning_effort='high', openai_reasoning_summary='detailed' + ), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + IsInstance(TextPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ] + ) + + result = await agent.run( + 'Considering the way to cross the street, analogously, how do I cross the river?', + model=gemini_model, + message_history=result.all_messages(), + model_settings=GeminiModelSettings( + gemini_thinking_config={'thinking_budget': 1024, 'include_thoughts': True}, + ), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + IsInstance(TextPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ModelRequest( + parts=[ + UserPromptPart( + content='Considering the way to cross the street, analogously, how do I cross the river?', + timestamp=IsDatetime(), + ) + ] + ), + ModelResponse( + parts=[ + TextPart( + content="""\ +Okay, let's draw an analogy between crossing a street and crossing a river, applying the safety principles from the street crossing guide to the river environment. + +Think of the **river** as being like the **street** – a natural barrier you need to get across. The **hazards** on the river are different from vehicles, but they are still things that can harm you. + +Here's the analogous guide for crossing a river: + +1. **Before you approach the river:** + * Just as you use a sidewalk to get to the street's edge, use a trail or the riverbank to get to a spot where you can assess the river. + * If you’re inexperienced with rivers or unsure about the conditions, try to have someone experienced accompany you. + +2. **When you're ready to cross:** + * Just as you look and listen for vehicles, carefully **assess the river conditions**. Look in all directions (upstream, downstream, across): + * How fast is the current moving? (Like checking vehicle speed). + * How deep does the water look? (Like judging the width and how much time you have). + * Are there obstacles in the water (rocks, logs)? (Like parked cars or road hazards). + * Is the bottom visible and does it look stable? (Like checking the road surface). + * Check upstream for potential hazards coming towards you (like debris). + * Listen to the river – the sound can tell you if the current is very strong or if there are rapids. + * Acknowledge the river's power – just as you make eye contact with drivers, respect that the river can be dangerous and doesn't care if you're trying to cross. + +3. **Use designated crossing areas whenever possible:** + * If there's a **bridge or a ferry**, use it. These are like the crosswalks and traffic signals – the safest, established ways to cross, often managing the "flow" (of water below, or people/boats on the river). + * If you must wade or swim, look for the safest possible **crossing point** – maybe a wider, shallower section, a known ford, or a spot with a less turbulent current. This is like choosing a crosswalk instead of crossing anywhere. + +4. **While crossing:** + * Just as you stay alert and avoid distractions, **focus completely on the crossing**. Don't be looking at your phone or distracted by conversation if you are actively navigating the water. + * Move with purpose, but carefully. If wading, maintain your balance against the current and watch your footing. If swimming, focus on your technique and direction. Stay aware of where you are relative to your intended path and the river's flow. + +5. **After crossing:** + * Once you’ve safely reached the other side, take a moment to ensure you are truly out of the main flow and on stable ground. Be aware of the riverbank conditions. + +**Analogous Takeaway:** + +Just as you wouldn't just run blindly into a busy street, you shouldn't just jump into a river without understanding its conditions and choosing the safest method and location to cross. Be cautious, assess the "traffic" (current, depth, obstacles), and use the available "infrastructure" (bridges, ferries, established crossing points) whenever possible.\ +""" + ) + ], + model_name='gemini-2.5-flash-preview-04-17', + timestamp=IsDatetime(), + ), + ] + ) diff --git a/tests/models/test_groq.py b/tests/models/test_groq.py index f7ad0ca3f..568aab0f2 100644 --- a/tests/models/test_groq.py +++ b/tests/models/test_groq.py @@ -23,6 +23,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -30,7 +31,7 @@ from pydantic_ai.models.groq import GroqModelSettings from pydantic_ai.usage import Usage -from ..conftest import IsDatetime, IsNow, IsStr, raise_if_exception, try_import +from ..conftest import IsDatetime, IsInstance, IsNow, IsStr, raise_if_exception, try_import from .mock_async_stream import MockAsyncStream with try_import() as imports_successful: @@ -608,16 +609,213 @@ async def test_groq_model_instructions(allow_model_requests: None, groq_api_key: ) -async def test_groq_model_thinking_part_raw_format(allow_model_requests: None, groq_api_key: str): +async def test_groq_model_thinking_part(allow_model_requests: None, groq_api_key: str): m = GroqModel('deepseek-r1-distill-llama-70b', provider=GroqProvider(api_key=groq_api_key)) settings = GroqModelSettings(groq_reasoning_format='raw') - agent = Agent(m, model_settings=settings) + agent = Agent(m, instructions='You are a chef.', model_settings=settings) - result = await agent.run('How do I cross the street?') - assert result.all_messages() == snapshot() + result = await agent.run('I want a recipe to cook Uruguayan alfajores.') + assert result.all_messages() == snapshot( + [ + ModelRequest( + parts=[UserPromptPart(content='I want a recipe to cook Uruguayan alfajores.', timestamp=IsDatetime())], + instructions='You are a chef.', + ), + ModelResponse( + parts=[ + ThinkingPart(content=IsStr()), + TextPart( + content="""\ + + +To make Uruguayan alfajores, follow these organized steps for a delightful baking experience: + +### Ingredients: +- 2 cups all-purpose flour +- 1 cup cornstarch +- 1 tsp baking powder +- 1/2 tsp salt +- 1 cup unsalted butter, softened +- 1 cup powdered sugar +- 1 egg +- 1 tsp vanilla extract +- Dulce de leche (store-bought or homemade) +- Powdered sugar for coating + +### Instructions: + +1. **Preheat Oven:** + - Preheat your oven to 300°F (150°C). Line a baking sheet with parchment paper. + +2. **Prepare Dough:** + - In a bowl, whisk together flour, cornstarch, baking powder, and salt. + - In another bowl, cream butter and powdered sugar until smooth. Add egg and vanilla, mixing well. + - Gradually incorporate the dry ingredients into the wet mixture until a dough forms. Wrap and let rest for 30 minutes. + +3. **Roll and Cut:** + - Roll dough to 1/4 inch thickness. Cut into 2-inch circles using a cutter or glass. + +4. **Bake:** + - Place cookies on the prepared baking sheet, bake for 15-20 minutes until edges are lightly golden. Cool on the sheet for 5 minutes, then transfer to a wire rack to cool completely. + +5. **Assemble Alfajores:** + - Spread a layer of dulce de leche on one cookie half. Sandwich with another cookie. Handle gently to avoid breaking. + +6. **Coat with Powdered Sugar:** + - Roll each alfajor in powdered sugar, pressing gently to adhere. + +7. **Optional Chocolate Coating:** + - For a chocolate version, melt chocolate and dip alfajores, then chill to set. + +8. **Storage:** + - Store in an airtight container at room temperature for up to a week. Freeze for longer storage. + +### Tips: +- Ensure butter is softened for smooth creaming. +- Check cookies after 15 minutes to avoid over-browning. +- Allow cookies to cool completely before handling. +- Homemade dulce de leche can be made by heating condensed milk until thickened. + +Enjoy your traditional Uruguayan alfajores with a cup of coffee or tea!\ +""" + ), + ], + model_name='deepseek-r1-distill-llama-70b', + timestamp=IsDatetime(), + ), + ] + ) result = await agent.run( - 'Considering the way to cross the street, analogously, how do I cross the river?', + 'Considering the Uruguayan recipe, how can I cook the Argentinian one?', message_history=result.all_messages(), + model_settings=GroqModelSettings(groq_reasoning_format='parsed'), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest( + parts=[UserPromptPart(content='I want a recipe to cook Uruguayan alfajores.', timestamp=IsDatetime())], + instructions='You are a chef.', + ), + ModelResponse( + parts=[IsInstance(ThinkingPart), IsInstance(TextPart)], + model_name='deepseek-r1-distill-llama-70b', + timestamp=IsDatetime(), + ), + ModelRequest( + parts=[ + UserPromptPart( + content='Considering the Uruguayan recipe, how can I cook the Argentinian one?', + timestamp=IsDatetime(), + ) + ], + instructions='You are a chef.', + ), + ModelResponse( + parts=[ + ThinkingPart( + content="""\ +Alright, so I want to make Argentinian alfajores after successfully making the Uruguayan ones. I know that both countries have their own versions of alfajores, but I'm not entirely sure how they differ. Maybe the ingredients or the preparation steps are slightly different? I should probably start by researching what makes Argentinian alfajores unique compared to the Uruguayan ones. + +First, I remember that in the Uruguayan recipe, the cookies were more delicate, and the filling was a generous amount of dulce de leche. The process involved making the dough from scratch, baking the cookies, and then assembling them with the filling and sometimes coating them in powdered sugar or chocolate. + +For Argentinian alfajores, I think the cookies might have a different texture—perhaps they're crunchier or have a different flavor profile. Maybe the type of flour used is different, or there's an addition like cocoa powder for a chocolate version. Also, the filling might have variations, like adding caramel or nuts to the dulce de leche. + +I should look up some authentic Argentinian recipes to see the differences. Maybe the method of making the dough is slightly different, or the baking time and temperature vary. I also wonder if Argentinian alfajores are typically coated in something else besides powdered sugar, like cinnamon or coconut flakes. + +Another thing to consider is the size and shape of the cookies. Uruguayan alfajores might be smaller and rounder, while Argentinian ones could be larger or have a different shape. I should also check if there are any additional steps, like toasting the cookies or adding a layer of meringue. + +I need to make sure I have all the necessary ingredients. If the Argentinian version requires something different, like a specific type of flour or a particular flavoring, I'll need to adjust my shopping list. Also, if the filling is different, I might need to prepare it in a different way or add extra ingredients. + +I should also think about the assembly process. Maybe Argentinian alfajores are sandwiched with more filling or have a different way of sealing the cookies together. Perhaps they're rolled in coconut after being filled, or there's a step involving dipping them in chocolate. + +It would be helpful to watch a video or read a detailed recipe from an Argentinian source to get a better understanding. That way, I can follow the traditional method and ensure that my alfajores turn out authentic. I should also consider any tips or tricks that Argentinian bakers use to make their alfajores special. + +Finally, I need to plan the timing. Making alfajores can be a bit time-consuming, especially if you're making the dulce de leche from scratch. I should allocate enough time for preparing the dough, baking the cookies, and assembling the alfajores. + +Overall, the key steps I think I'll need to follow are: + +1. Research authentic Argentinian alfajores recipes to identify unique ingredients and steps. +2. Adjust the ingredient list based on the differences from the Uruguayan version. +3. Prepare the dough according to the Argentinian method, which might involve different mixing techniques or additional ingredients. +4. Bake the cookies, possibly at a different temperature or for a different duration. +5. Prepare the filling, which might include variations like caramel or nuts. +6. Assemble the alfajores, possibly adding extra coatings or layers. +7. Allow the alfajores to set before serving, to ensure the flavors meld together. + +By carefully following these steps and paying attention to the unique aspects of Argentinian alfajores, I should be able to create a delicious and authentic batch that captures the essence of this traditional South American treat. +""" + ), + TextPart( + content="""\ +To create authentic Argentinian alfajores, follow these organized steps, which highlight the unique characteristics and differences from the Uruguayan version: + +### Ingredients: +- **For the Cookies:** + - 2 cups all-purpose flour + - 1/2 cup cornstarch + - 1/4 cup unsweetened cocoa powder (optional for chocolate version) + - 1 teaspoon baking powder + - 1/2 teaspoon salt + - 1 cup unsalted butter, softened + - 1 cup powdered sugar + - 1 egg + - 1 teaspoon vanilla extract + +- **For the Filling:** + - Dulce de leche (store-bought or homemade) + - Optional: caramel sauce, chopped nuts, or cinnamon for added flavor + +- **For Coating:** + - Powdered sugar + - Optional: cinnamon, shredded coconut, or melted chocolate + +### Instructions: + +1. **Prepare the Dough:** + - In a large bowl, whisk together the flour, cornstarch, cocoa powder (if using), baking powder, and salt. + - In another bowl, cream the softened butter and powdered sugar until smooth. Add the egg and vanilla extract, mixing well. + - Gradually incorporate the dry ingredients into the wet mixture until a dough forms. Wrap the dough in plastic wrap and let it rest for 30 minutes. + +2. **Roll and Cut the Cookies:** + - Roll the dough to about 1/4 inch thickness on a lightly floured surface. + - Use a round cookie cutter or the rim of a glass to cut out circles of dough. Argentinian alfajores are often slightly larger than their Uruguayan counterparts. + +3. **Bake the Cookies:** + - Preheat the oven to 300°F (150°C). Line a baking sheet with parchment paper. + - Place the cookie rounds on the prepared baking sheet, leaving about 1 inch of space between each cookie. + - Bake for 15-20 minutes, or until the edges are lightly golden. Allow the cookies to cool on the baking sheet for 5 minutes before transferring them to a wire rack to cool completely. + +4. **Prepare the Filling:** + - Use store-bought dulce de leche or make your own by heating sweetened condensed milk until thickened. + - Optional: Stir in caramel sauce or chopped nuts into the dulce de leche for added flavor. + +5. **Assemble the Alfajores:** + - Once the cookies are completely cool, spread a generous amount of dulce de leche on the flat side of one cookie. + - Sandwich with another cookie, pressing gently to adhere. For an extra touch, roll the edges in chopped nuts or shredded coconut. + +6. **Coat with Powdered Sugar or Chocolate:** + - Roll each alfajor in powdered sugar, pressing gently to ensure it adheres. + - Optional: Melt chocolate and dip the alfajores, then sprinkle with cinnamon or coconut before the chocolate sets. + +7. **Allow to Set:** + - Let the alfajores sit for about 30 minutes to allow the flavors to meld together. + +8. **Serve and Enjoy:** + - Serve with a cup of coffee or tea. Store any leftovers in an airtight container at room temperature for up to a week. + +### Tips: +- **Dough Handling:** Ensure the butter is softened for a smooth dough, and don’t overwork the dough to maintain the cookies' tender texture. +- **Baking:** Keep an eye on the cookies after 15 minutes to prevent over-browning. +- **Filling Variations:** Experiment with different fillings like caramel or nuts to create unique flavor profiles. +- **Coating Options:** Try different coatings such as cinnamon or coconut for a varied texture and taste. + +By following these steps, you'll create authentic Argentinian alfajores that capture the rich flavors and traditions of this beloved South American treat.\ +""" + ), + ], + model_name='deepseek-r1-distill-llama-70b', + timestamp=IsDatetime(), + ), + ] ) - assert result.all_messages() == snapshot() diff --git a/tests/models/test_openai.py b/tests/models/test_openai.py index 8edff33d8..d5966ce4c 100644 --- a/tests/models/test_openai.py +++ b/tests/models/test_openai.py @@ -34,7 +34,7 @@ from pydantic_ai.result import Usage from pydantic_ai.settings import ModelSettings -from ..conftest import IsDatetime, IsInstance, IsNow, IsStr, raise_if_exception, try_import +from ..conftest import IsDatetime, IsNow, IsStr, raise_if_exception, try_import from .mock_async_stream import MockAsyncStream with try_import() as imports_successful: @@ -1300,11 +1300,11 @@ async def test_openai_responses_model_thinking_part(allow_model_requests: None, ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), ModelResponse( parts=[ + TextPart(content=IsStr()), ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), - TextPart(content=IsStr()), ], model_name='o3-mini-2025-01-31', timestamp=IsDatetime(), @@ -1321,11 +1321,11 @@ async def test_openai_responses_model_thinking_part(allow_model_requests: None, ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), ModelResponse( parts=[ + TextPart(content=IsStr()), ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), ThinkingPart(content=IsStr(), signature='rs_68034841ab2881918a8c210e3d988b9208c845d2be9bcdd8'), - TextPart(content=IsStr()), ], model_name='o3-mini-2025-01-31', timestamp=IsDatetime(), @@ -1340,10 +1340,10 @@ async def test_openai_responses_model_thinking_part(allow_model_requests: None, ), ModelResponse( parts=[ + TextPart(content=IsStr()), ThinkingPart(content=IsStr(), signature='rs_68034858dc588191bc3a6801c23e728f08c845d2be9bcdd8'), ThinkingPart(content=IsStr(), signature='rs_68034858dc588191bc3a6801c23e728f08c845d2be9bcdd8'), ThinkingPart(content=IsStr(), signature='rs_68034858dc588191bc3a6801c23e728f08c845d2be9bcdd8'), - TextPart(content=IsStr()), ], model_name='o3-mini-2025-01-31', timestamp=IsDatetime(), diff --git a/tests/models/test_openai_responses.py b/tests/models/test_openai_responses.py index 29f15efc7..7b9021dd9 100644 --- a/tests/models/test_openai_responses.py +++ b/tests/models/test_openai_responses.py @@ -123,7 +123,7 @@ async def test_openai_responses_reasoning_generate_summary(allow_model_requests: agent = Agent( model=model, model_settings=OpenAIResponsesModelSettings( - openai_reasoning_generate_summary='concise', + openai_reasoning_summary='concise', openai_truncation='auto', ), ) From 985991bd034c83d2d8844ab1ef0155b0048a6663 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 19 Apr 2025 15:10:04 +0200 Subject: [PATCH 07/27] pass tests --- tests/models/test_gemini.py | 11 ++++++++--- tests/models/test_openai.py | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/models/test_gemini.py b/tests/models/test_gemini.py index 58daa3a9d..f07044e79 100644 --- a/tests/models/test_gemini.py +++ b/tests/models/test_gemini.py @@ -47,13 +47,11 @@ _GeminiTools, _GeminiUsageMetaData, ) -from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings from pydantic_ai.providers.google_gla import GoogleGLAProvider -from pydantic_ai.providers.openai import OpenAIProvider from pydantic_ai.result import Usage from pydantic_ai.tools import ToolDefinition -from ..conftest import ClientWithHandler, IsDatetime, IsInstance, IsNow, IsStr, TestEnv +from ..conftest import ClientWithHandler, IsDatetime, IsInstance, IsNow, IsStr, TestEnv, try_import pytestmark = pytest.mark.anyio @@ -1073,6 +1071,13 @@ async def get_temperature(location: dict[str, CurrentLocation]) -> float: # pra @pytest.mark.vcr() async def test_gemini_model_thinking_part(allow_model_requests: None, gemini_api_key: str, openai_api_key: str): + with try_import() as imports_successful: + from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings + from pydantic_ai.providers.openai import OpenAIProvider + + if not imports_successful(): # pragma: no cover + pytest.skip('OpenAI is not installed') + openai_model = OpenAIResponsesModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key)) gemini_model = GeminiModel('gemini-2.5-flash-preview-04-17', provider=GoogleGLAProvider(api_key=gemini_api_key)) agent = Agent(openai_model) diff --git a/tests/models/test_openai.py b/tests/models/test_openai.py index d5966ce4c..6be457ad9 100644 --- a/tests/models/test_openai.py +++ b/tests/models/test_openai.py @@ -29,7 +29,6 @@ UserPromptPart, ) from pydantic_ai.models.gemini import GeminiModel -from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings from pydantic_ai.providers.google_gla import GoogleGLAProvider from pydantic_ai.result import Usage from pydantic_ai.settings import ModelSettings @@ -54,6 +53,8 @@ from pydantic_ai.models.openai import ( OpenAIModel, OpenAIModelSettings, + OpenAIResponsesModel, + OpenAIResponsesModelSettings, OpenAISystemPromptRole, _OpenAIJsonSchema, # pyright: ignore[reportPrivateUsage] ) From 0cb280e7b63b7634b4516cc1cdf77021796798b7 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sun, 20 Apr 2025 10:42:11 +0200 Subject: [PATCH 08/27] fix pipeline --- tests/models/test_groq.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/models/test_groq.py b/tests/models/test_groq.py index 568aab0f2..6737acced 100644 --- a/tests/models/test_groq.py +++ b/tests/models/test_groq.py @@ -28,7 +28,6 @@ ToolReturnPart, UserPromptPart, ) -from pydantic_ai.models.groq import GroqModelSettings from pydantic_ai.usage import Usage from ..conftest import IsDatetime, IsInstance, IsNow, IsStr, raise_if_exception, try_import @@ -48,7 +47,7 @@ from groq.types.chat.chat_completion_message_tool_call import Function from groq.types.completion_usage import CompletionUsage - from pydantic_ai.models.groq import GroqModel + from pydantic_ai.models.groq import GroqModel, GroqModelSettings from pydantic_ai.providers.groq import GroqProvider # note: we use Union here so that casting works with Python 3.9 From f3600f7ca3c986f7704d1eca5f3128e3aee1f178 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sun, 20 Apr 2025 11:22:34 +0200 Subject: [PATCH 09/27] Implement streaming --- main.py | 58 ----- .../pydantic_ai/_parts_manager.py | 76 +++++- .../pydantic_ai/_thinking_part.py | 29 +++ pydantic_ai_slim/pydantic_ai/messages.py | 52 +++- .../pydantic_ai/models/anthropic.py | 24 +- pydantic_ai_slim/pydantic_ai/models/groq.py | 20 +- pydantic_ai_slim/pydantic_ai/models/openai.py | 5 +- ..._anthropic_model_thinking_part_stream.yaml | 232 ++++++++++++++++++ tests/models/test_anthropic.py | 16 ++ 9 files changed, 429 insertions(+), 83 deletions(-) delete mode 100644 main.py create mode 100644 pydantic_ai_slim/pydantic_ai/_thinking_part.py create mode 100644 tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml diff --git a/main.py b/main.py deleted file mode 100644 index 7c6355c3a..000000000 --- a/main.py +++ /dev/null @@ -1,58 +0,0 @@ -from rich.pretty import pprint - -from pydantic_ai import Agent -from pydantic_ai.models.gemini import GeminiModelSettings -from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings - -model = OpenAIResponsesModel('o4-mini') -agent = Agent(model=model) -result = agent.run_sync( - 'Tell me the steps to cross the street!', - model_settings=OpenAIResponsesModelSettings( - openai_reasoning_effort='high', - openai_reasoning_generate_summary='detailed', - ), -) -pprint(result.all_messages()) - -# anthropic_agent = Agent('anthropic:claude-3-7-sonnet-latest') -# result = anthropic_agent.run_sync( -# 'Now make analogous steps to cross the river!', -# model_settings=AnthropicModelSettings( -# anthropic_thinking={'type': 'enabled', 'budget_tokens': 1024}, -# ), -# message_history=result.all_messages(), -# ) -# pprint(result.all_messages()) - -# groq_agent = Agent('groq:deepseek-r1-distill-llama-70b') -# result = groq_agent.run_sync( -# 'Tell me the steps to cross the ocean!', -# model_settings=GroqModelSettings(groq_reasoning_format='raw'), -# message_history=result.all_messages(), -# ) -# pprint(result.all_messages()) - -# bedrock_agent = Agent('bedrock:us.deepseek.r1-v1:0') -# result = bedrock_agent.run_sync( -# 'Tell me the steps to cross the ocean!', -# # message_history=result.all_messages(), -# ) -# pprint(result.all_messages()) - - -# deepseek_agent = Agent('deepseek:deepseek-reasoner') -# result = deepseek_agent.run_sync( -# 'Tell me the steps to cross the ocean!', -# model_settings=OpenAIModelSettings(openai_reasoning_effort='high'), -# # message_history=result.all_messages(), -# ) -# pprint(result.all_messages()) - -gemini_agent = Agent('google-gla:gemini-2.5-flash-preview-04-17') -result = gemini_agent.run_sync( - 'And how do I cross the ocean?', - model_settings=GeminiModelSettings(gemini_thinking_config={'thinking_budget': 1024, 'include_thoughts': True}), - message_history=result.all_messages(), -) -pprint(result.all_messages()) diff --git a/pydantic_ai_slim/pydantic_ai/_parts_manager.py b/pydantic_ai_slim/pydantic_ai/_parts_manager.py index 0471f0d59..8ecfc6387 100644 --- a/pydantic_ai_slim/pydantic_ai/_parts_manager.py +++ b/pydantic_ai_slim/pydantic_ai/_parts_manager.py @@ -25,6 +25,8 @@ PartStartEvent, TextPart, TextPartDelta, + ThinkingPart, + ThinkingPartDelta, ToolCallPart, ToolCallPartDelta, ) @@ -86,8 +88,7 @@ def handle_text_delta( A `PartStartEvent` if a new part was created, or a `PartDeltaEvent` if an existing part was updated. Raises: - UnexpectedModelBehavior: If attempting to apply text content to a part that is - not a TextPart. + UnexpectedModelBehavior: If attempting to apply text content to a part that is not a TextPart. """ existing_text_part_and_index: tuple[TextPart, int] | None = None @@ -122,6 +123,77 @@ def handle_text_delta( self._parts[part_index] = part_delta.apply(existing_text_part) return PartDeltaEvent(index=part_index, delta=part_delta) + def handle_thinking_delta( + self, + *, + vendor_part_id: Hashable | None, + content: str | None = None, + signature: str | None = None, + ) -> ModelResponseStreamEvent: + """Handle incoming thinking content, creating or updating a ThinkingPart in the manager as appropriate. + + When `vendor_part_id` is None, the latest part is updated if it exists and is a ThinkingPart; + otherwise, a new ThinkingPart is created. When a non-None ID is specified, the ThinkingPart corresponding + to that vendor ID is either created or updated. + + Args: + vendor_part_id: The ID the vendor uses to identify this piece + of thinking. If None, a new part will be created unless the latest part is already + a ThinkingPart. + content: The thinking content to append to the appropriate ThinkingPart. + signature: An optional signature for the thinking content. + + Returns: + A `PartStartEvent` if a new part was created, or a `PartDeltaEvent` if an existing part was updated. + + Raises: + UnexpectedModelBehavior: If attempting to apply a thinking delta to a part that is not a ThinkingPart. + """ + existing_thinking_part_and_index: tuple[ThinkingPart, int] | None = None + + if vendor_part_id is None: + # If the vendor_part_id is None, check if the latest part is a ThinkingPart to update + if self._parts: + part_index = len(self._parts) - 1 + latest_part = self._parts[part_index] + if isinstance(latest_part, ThinkingPart): + existing_thinking_part_and_index = latest_part, part_index + else: + # Otherwise, attempt to look up an existing ThinkingPart by vendor_part_id + part_index = self._vendor_id_to_part_index.get(vendor_part_id) + if part_index is not None: + existing_part = self._parts[part_index] + if not isinstance(existing_part, ThinkingPart): + raise UnexpectedModelBehavior(f'Cannot apply a thinking delta to {existing_part=}') + existing_thinking_part_and_index = existing_part, part_index + + if existing_thinking_part_and_index is None: + if content is not None: + # There is no existing thinking part that should be updated, so create a new one + new_part_index = len(self._parts) + part = ThinkingPart(content=content) + if vendor_part_id is not None: + self._vendor_id_to_part_index[vendor_part_id] = new_part_index + self._parts.append(part) + return PartStartEvent(index=new_part_index, part=part) + else: + raise UnexpectedModelBehavior('Cannot create a ThinkingPart with no content') + else: + if content is not None: + # Update the existing ThinkingPart with the new content delta + existing_thinking_part, part_index = existing_thinking_part_and_index + part_delta = ThinkingPartDelta(content_delta=content) + self._parts[part_index] = part_delta.apply(existing_thinking_part) + return PartDeltaEvent(index=part_index, delta=part_delta) + elif signature is not None: + # Update the existing ThinkingPart with the new signature delta + existing_thinking_part, part_index = existing_thinking_part_and_index + part_delta = ThinkingPartDelta(signature_delta=signature) + self._parts[part_index] = part_delta.apply(existing_thinking_part) + return PartDeltaEvent(index=part_index, delta=part_delta) + else: + raise UnexpectedModelBehavior('Cannot update a ThinkingPart with no content or signature') + def handle_tool_call_delta( self, *, diff --git a/pydantic_ai_slim/pydantic_ai/_thinking_part.py b/pydantic_ai_slim/pydantic_ai/_thinking_part.py new file mode 100644 index 000000000..bfabbba21 --- /dev/null +++ b/pydantic_ai_slim/pydantic_ai/_thinking_part.py @@ -0,0 +1,29 @@ +from pydantic_ai.messages import TextPart, ThinkingPart + +START_THINK_TAG = '' +END_THINK_TAG = '' + + +def split_content_into_text_and_thinking(content: str) -> list[ThinkingPart | TextPart]: + """Split a string into text and thinking parts. + + Some models don't return the thinking part as a separate part, but rather as a tag in the content. + This function splits the content into text and thinking parts. + + We use the `` tag because that's how Groq uses it in the `raw` format, so instead of using `` or + something else, we just match the tag to make it easier for other models that don't support the `ThinkingPart`. + """ + parts: list[ThinkingPart | TextPart] = [] + while START_THINK_TAG in content: + before_think, content = content.split(START_THINK_TAG, 1) + if before_think.strip(): + parts.append(TextPart(content=before_think)) + if END_THINK_TAG in content: + think_content, content = content.split(END_THINK_TAG, 1) + parts.append(ThinkingPart(content=think_content)) + else: + parts.append(TextPart(content=content)) + content = '' + if content: + parts.append(TextPart(content=content)) + return parts diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 3d4611a5c..ed58ce372 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -637,6 +637,54 @@ def apply(self, part: ModelResponsePart) -> TextPart: return replace(part, content=part.content + self.content_delta) +@dataclass +class ThinkingPartDelta: + """A partial update (delta) for a `ThinkingPart` to append new thinking content.""" + + content_delta: str | None = None + """The incremental thinking content to add to the existing `ThinkingPart` content.""" + + signature_delta: str | None = None + """Optional signature delta. + + Note this is never treated as a delta — it can replace None. + """ + + part_delta_kind: Literal['thinking'] = 'thinking' + """Part delta type identifier, used as a discriminator.""" + + @overload + def apply(self, part: ModelResponsePart) -> ThinkingPart: ... + + @overload + def apply(self, part: ModelResponsePart | ThinkingPartDelta) -> ThinkingPart | ThinkingPartDelta: ... + + def apply(self, part: ModelResponsePart | ThinkingPartDelta) -> ThinkingPart | ThinkingPartDelta: + """Apply this thinking delta to an existing `ThinkingPart`. + + Args: + part: The existing model response part, which must be a `ThinkingPart`. + + Returns: + A new `ThinkingPart` with updated thinking content. + + Raises: + ValueError: If `part` is not a `ThinkingPart`. + """ + if isinstance(part, ThinkingPart): + return replace(part, content=part.content + self.content_delta if self.content_delta else None) + elif isinstance(part, ThinkingPartDelta): + if self.content_delta is None and self.signature_delta is None: + raise ValueError('Cannot apply ThinkingPartDelta with no content or signature') + if self.signature_delta is not None: + return replace(part, signature_delta=self.signature_delta) + if self.content_delta is not None: + return replace(part, content_delta=self.content_delta) + raise ValueError( + f'Cannot apply ThinkingPartDeltas to non-ThinkingParts or non-ThinkingPartDeltas ({part=}, {self=})' + ) + + @dataclass class ToolCallPartDelta: """A partial update (delta) for a `ToolCallPart` to modify tool name, arguments, or tool call ID.""" @@ -752,7 +800,9 @@ def _apply_to_part(self, part: ToolCallPart) -> ToolCallPart: return part -ModelResponsePartDelta = Annotated[Union[TextPartDelta, ToolCallPartDelta], pydantic.Discriminator('part_delta_kind')] +ModelResponsePartDelta = Annotated[ + Union[TextPartDelta, ThinkingPartDelta, ToolCallPartDelta], pydantic.Discriminator('part_delta_kind') +] """A partial update (delta) for any model response part.""" diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index f902939ea..317a65158 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -9,7 +9,6 @@ from json import JSONDecodeError, loads as json_loads from typing import Any, Literal, Union, cast, overload -from anthropic.types import RedactedThinkingBlock from typing_extensions import assert_never from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage @@ -61,12 +60,15 @@ RawMessageStartEvent, RawMessageStopEvent, RawMessageStreamEvent, + RedactedThinkingBlock, + SignatureDelta, TextBlock, TextBlockParam, TextDelta, ThinkingBlock, ThinkingBlockParam, ThinkingConfigParam, + ThinkingDelta, ToolChoiceParam, ToolParam, ToolResultBlockParam, @@ -122,10 +124,6 @@ class AnthropicModel(Model): Internally, this uses the [Anthropic Python client](https://github.com/anthropics/anthropic-sdk-python) to interact with the API. Apart from `__init__`, all methods are private or match those of the base class. - - !!! note - The `AnthropicModel` class does not yet support streaming responses. - We anticipate adding support for streaming responses in a near-term future release. """ client: AsyncAnthropic = field(repr=False) @@ -463,10 +461,18 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: async for event in self._response: self._usage += _map_usage(event) + breakpoint() + if isinstance(event, RawContentBlockStartEvent): current_block = event.content_block if isinstance(current_block, TextBlock) and current_block.text: yield self._parts_manager.handle_text_delta(vendor_part_id='content', content=current_block.text) + elif isinstance(current_block, ThinkingBlock): + yield self._parts_manager.handle_thinking_delta( + vendor_part_id='thinking', + content=current_block.thinking, + signature=current_block.signature, + ) elif isinstance(current_block, ToolUseBlock): maybe_event = self._parts_manager.handle_tool_call_delta( vendor_part_id=current_block.id, @@ -480,6 +486,14 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: elif isinstance(event, RawContentBlockDeltaEvent): if isinstance(event.delta, TextDelta): yield self._parts_manager.handle_text_delta(vendor_part_id='content', content=event.delta.text) + elif isinstance(event.delta, ThinkingDelta): + yield self._parts_manager.handle_thinking_delta( + vendor_part_id='thinking', content=event.delta.thinking + ) + elif isinstance(event.delta, SignatureDelta): + yield self._parts_manager.handle_thinking_delta( + vendor_part_id='thinking', signature=event.delta.signature + ) elif ( current_block and event.delta.type == 'input_json_delta' and isinstance(current_block, ToolUseBlock) ): diff --git a/pydantic_ai_slim/pydantic_ai/models/groq.py b/pydantic_ai_slim/pydantic_ai/models/groq.py index 2b91d575b..bb9d644aa 100644 --- a/pydantic_ai_slim/pydantic_ai/models/groq.py +++ b/pydantic_ai_slim/pydantic_ai/models/groq.py @@ -9,6 +9,8 @@ from typing_extensions import assert_never +from pydantic_ai._thinking_part import split_content_into_text_and_thinking + from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage from .._utils import guard_tool_call_id as _guard_tool_call_id from ..messages import ( @@ -236,22 +238,8 @@ def _process_response(self, response: chat.ChatCompletion) -> ModelResponse: if choice.message.reasoning is not None: items.append(ThinkingPart(content=choice.message.reasoning)) if choice.message.content is not None: - content = choice.message.content - # The `` tag is only present if `groq_reasoning_format` is set to `raw`. - while '' in content: - before_think, content = content.split('', 1) - if before_think.strip(): - items.append(TextPart(content=before_think)) - - if '' in content: - think_content, content = content.split('', 1) - items.append(ThinkingPart(content=think_content)) - else: - # Malformed tag - items.append(TextPart(content=content)) - content = '' - if content: - items.append(TextPart(content=content)) + # NOTE: The `` tag is only present if `groq_reasoning_format` is set to `raw`. + items.extend(split_content_into_text_and_thinking(choice.message.content)) if choice.message.tool_calls is not None: for c in choice.message.tool_calls: items.append(ToolCallPart(tool_name=c.function.name, args=c.function.arguments, tool_call_id=c.id)) diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index fb777f5b6..5d828fef9 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -11,6 +11,7 @@ from typing_extensions import assert_never +from pydantic_ai._thinking_part import split_content_into_text_and_thinking from pydantic_ai.providers import Provider, infer_provider from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage @@ -302,7 +303,7 @@ def _process_response(self, response: chat.ChatCompletion) -> ModelResponse: choice = response.choices[0] items: list[ModelResponsePart] = [] if choice.message.content is not None: - items.append(TextPart(choice.message.content)) + items.extend(split_content_into_text_and_thinking(choice.message.content)) if choice.message.tool_calls is not None: for c in choice.message.tool_calls: items.append(ToolCallPart(c.function.name, c.function.arguments, tool_call_id=c.id)) @@ -340,6 +341,8 @@ async def _map_messages(self, messages: list[ModelMessage]) -> list[chat.ChatCom for item in message.parts: if isinstance(item, TextPart): texts.append(item.content) + elif isinstance(item, ThinkingPart): + texts.append(f'\n{item.content}\n') elif isinstance(item, ToolCallPart): tool_calls.append(self._map_tool_call(item)) else: diff --git a/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml b/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml new file mode 100644 index 000000000..dd5431c19 --- /dev/null +++ b/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml @@ -0,0 +1,232 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '212' + content-type: + - application/json + host: + - api.anthropic.com + method: POST + parsed_body: + max_tokens: 2048 + messages: + - content: + - text: How do I cross the street? + type: text + role: user + model: claude-3-7-sonnet-latest + stream: true + thinking: + budget_tokens: 1024 + type: enabled + uri: https://api.anthropic.com/v1/messages + response: + body: + string: |+ + event: message_start + data: {"type":"message_start","message":{"id":"msg_01KjexrtHzmTnEa1LAy97Lb6","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":42,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":9}} } + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""} } + + event: ping + data: {"type": "ping"} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Crossing the street is a basic safety"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" skill that many people learn as children,"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" but it's still an important question since doing"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" it safely can prevent accidents.\n\nI"}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"'ll provide a clear, step"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"-by-step explanation of how to cross a"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" street safely:\n\n1. Find a designate"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"d crossing point if possible (crosswalk, pedest"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"rian crossing, intersection)\n2. Stop at"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" the edge of the street\n3. Look in"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" all directions for oncoming traffic (typically"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" left, right, then left again in"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" countries where cars drive on the right)"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"\n4. If there's a"}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" pedestrian signal, wait for the \"WALK\""} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" signal\n5. Even with a walk"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" signal, check for turning vehicles\n6. Cross"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" when it's clear, walking at a steady pace"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"\n7. Continue to be aware"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" of traffic while crossing\n8. Don"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"'t use electronic devices or get"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" distracted while crossing\n\nI'll make"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" this comprehensive but straightforward."}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"ErUBCkYIAhgCIkDybqBMLATs8XSxwHam7hE+UafXykLhcwhwlchZqXbbhjYzku3ZovDhjzBYSOqT6VHlHvyW7vPFj4mFRrT1eVJIEgyC52poyJ1+xPKSkWcaDOO7nw/ZkmqqzD8V+yIwQ+tIRaS/Wv1p6GzSmRk7rKsexeBCwhz9V8JVzbyT4M80lAkb7UTdBYjP9wNsNBiUKh01hEXi8GGJckNWYRfwT50CGGNhdxhsTXhtkQkCfRgC"} } + + event: content_block_stop + data: {"type":"content_block_stop","index":0 } + + event: content_block_start + data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"# How to Cross the Street Safely\n\n1. **Find a designated crossing point**"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" whenever possible, such as a crosswalk, intersection"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":", or pedestrian crossing.\n\n2. **"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Stop at the curb**"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" or edge of the road.\n\n3. **"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Look in all directions for traffic"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"**:\n - Look left, then"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" right, then left again (in countries where traffic"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" drives on the right)\n - Look right"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":", then left, then right again ("} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"in countries where traffic drives on the left)"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"\n\n4. **Use pedestrian signals**"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" if available:\n - Wait"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" for the \"WALK\" signal or"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" green walking figure\n - Don"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"'t start crossing if the signal"}} + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" is flashing or showing \"DON'T"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" WALK\"\n\n5. **Make"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" eye contact** with nearby drivers to ensure they see"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" you.\n\n6. **Cross"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" at a steady pace** when"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" the way is clear.\n\n7. **Stay"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" alert** while crossing - continue watching"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" for traffic and don't use electronic devices"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":".\n\n8. **Use extra"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" caution** at night or in bad weather when"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" visibility is reduced.\n\nRemember"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" that traffic rules and patterns vary by country"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":", so always follow local regulations"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" and customs."} } + + event: content_block_stop + data: {"type":"content_block_stop","index":1 } + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":455} } + + event: message_stop + data: {"type":"message_stop" } + + headers: + cache-control: + - no-cache + connection: + - keep-alive + content-type: + - text/event-stream; charset=utf-8 + transfer-encoding: + - chunked + status: + code: 200 + message: OK +version: 1 +... diff --git a/tests/models/test_anthropic.py b/tests/models/test_anthropic.py index 1915c995b..0efdd9cae 100644 --- a/tests/models/test_anthropic.py +++ b/tests/models/test_anthropic.py @@ -792,3 +792,19 @@ async def test_anthropic_model_thinking_part(allow_model_requests: None, anthrop ), ] ) + + +async def test_anthropic_model_thinking_part_stream(allow_model_requests: None, anthropic_api_key: str): + m = AnthropicModel('claude-3-7-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key)) + settings = AnthropicModelSettings(anthropic_thinking={'type': 'enabled', 'budget_tokens': 1024}) + agent = Agent(m, model_settings=settings) + + event_parts: list[Any] = [] + async with agent.iter(user_prompt='How do I cross the street?') as agent_run: + async for node in agent_run: + if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node): + async with node.stream(agent_run.ctx) as request_stream: + async for event in request_stream: + event_parts.append(event) + + assert event_parts == snapshot() From b57502e25afc79ec3646a51a769ae17cc2045858 Mon Sep 17 00:00:00 2001 From: David Montague <35119617+dmontagu@users.noreply.github.com> Date: Mon, 21 Apr 2025 15:59:06 -0600 Subject: [PATCH 10/27] Minor cleanup --- pydantic_ai_slim/pydantic_ai/_agent_graph.py | 6 +++++- pydantic_ai_slim/pydantic_ai/models/anthropic.py | 2 -- pydantic_ai_slim/pydantic_ai/models/gemini.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/_agent_graph.py b/pydantic_ai_slim/pydantic_ai/_agent_graph.py index c3bf329ec..0b9c6fd96 100644 --- a/pydantic_ai_slim/pydantic_ai/_agent_graph.py +++ b/pydantic_ai_slim/pydantic_ai/_agent_graph.py @@ -414,7 +414,11 @@ async def _run_stream() -> AsyncIterator[_messages.HandleResponseEvent]: elif isinstance(part, _messages.ToolCallPart): tool_calls.append(part) elif isinstance(part, _messages.ThinkingPart): - ... + # We don't need to do anything with thinking parts in this tool-calling node. + # We need to handle text parts in case there are no tool calls and/or the desired output comes + # from the text, but thinking parts should not directly influence the execution of tools or + # determination of the next node of graph execution here. + pass else: assert_never(part) diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index 317a65158..20f54c50c 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -461,8 +461,6 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: async for event in self._response: self._usage += _map_usage(event) - breakpoint() - if isinstance(event, RawContentBlockStartEvent): current_block = event.content_block if isinstance(current_block, TextBlock) and current_block.text: diff --git a/pydantic_ai_slim/pydantic_ai/models/gemini.py b/pydantic_ai_slim/pydantic_ai/models/gemini.py index c2c30f433..87e50a28d 100644 --- a/pydantic_ai_slim/pydantic_ai/models/gemini.py +++ b/pydantic_ai_slim/pydantic_ai/models/gemini.py @@ -603,8 +603,8 @@ def _process_response_from_parts( items: list[ModelResponsePart] = [] for part in parts: if 'text' in part: - # NOTE: Google doesn't include the `thought` field anymore. We add it just in case they decide to change - # their mind and start including it again. + # NOTE: Google doesn't include the `thought` field anymore. We handle this here in case they decide to + # change their mind and start including it again. if part.get('thought'): # pragma: no cover items.append(ThinkingPart(content=part['text'])) else: From a04533f16417467c779dc30b913c0804af32274a Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 11:51:11 +0200 Subject: [PATCH 11/27] Support Thinking part --- pydantic_ai_slim/pydantic_ai/models/cohere.py | 6 +- pydantic_ai_slim/pydantic_ai/models/openai.py | 18 +- ..._anthropic_model_thinking_part_stream.yaml | 122 ++++---- .../test_cohere_model_thinking_part.yaml | 290 ++++++++++++++++++ tests/models/test_anthropic.py | 71 ++++- tests/models/test_cohere.py | 113 ++++++- 6 files changed, 553 insertions(+), 67 deletions(-) create mode 100644 tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml diff --git a/pydantic_ai_slim/pydantic_ai/models/cohere.py b/pydantic_ai_slim/pydantic_ai/models/cohere.py index c4017958c..dcee22e6a 100644 --- a/pydantic_ai_slim/pydantic_ai/models/cohere.py +++ b/pydantic_ai_slim/pydantic_ai/models/cohere.py @@ -4,6 +4,7 @@ from dataclasses import dataclass, field from typing import Literal, Union, cast +from pydantic_ai._thinking_part import split_content_into_text_and_thinking from typing_extensions import assert_never from .. import ModelHTTPError, usage @@ -16,6 +17,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -181,7 +183,7 @@ def _process_response(self, response: ChatResponse) -> ModelResponse: # While Cohere's API returns a list, it only does that for future proofing # and currently only one item is being returned. choice = response.message.content[0] - parts.append(TextPart(choice.text)) + parts.extend(split_content_into_text_and_thinking(choice.text)) for c in response.message.tool_calls or []: if c.function and c.function.name and c.function.arguments: parts.append( @@ -205,6 +207,8 @@ def _map_messages(self, messages: list[ModelMessage]) -> list[ChatMessageV2]: for item in message.parts: if isinstance(item, TextPart): texts.append(item.content) + elif isinstance(item, ThinkingPart): + texts.append(f'\n{item.content}\n') elif isinstance(item, ToolCallPart): tool_calls.append(self._map_tool_call(item)) else: diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 5d828fef9..8a0593282 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -61,7 +61,7 @@ from openai.types.chat.chat_completion_content_part_input_audio_param import InputAudio from openai.types.responses import ComputerToolParam, FileSearchToolParam, WebSearchToolParam from openai.types.responses.response_input_param import FunctionCallOutput, Message - from openai.types.responses.response_reasoning_item_param import ResponseReasoningItemParam, Summary + from openai.types.responses.response_reasoning_item_param import Summary from openai.types.shared import ReasoningEffort from openai.types.shared_params import Reasoning except ImportError as _import_error: @@ -729,8 +729,8 @@ async def _map_messages( assert_never(item) if thinking_parts: openai_messages.append( - ResponseReasoningItemParam( - id=thinking_parts[0].signature, + responses.ResponseReasoningItemParam( + id=thinking_parts[0].signature or '', summary=[Summary(text=item.content, type='summary_text') for item in thinking_parts], type='reasoning', ) @@ -874,6 +874,11 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: if isinstance(chunk, responses.ResponseCompletedEvent): self._usage += _map_usage(chunk.response) + elif isinstance(chunk, responses.ResponseAudioDeltaEvent): + # TODO(Marcelo): The reasoning events are getting this type. + # See https://github.com/openai/openai-python/issues/2311 for more details. + pass + elif isinstance(chunk, responses.ResponseContentPartAddedEvent): pass # there's nothing we need to do here @@ -913,6 +918,13 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: args=chunk.item.arguments, tool_call_id=chunk.item.id, ) + elif isinstance(chunk.item, responses.ResponseReasoningItem): + content = chunk.item.summary[0].text if chunk.item.summary else '' + yield self._parts_manager.handle_thinking_delta(vendor_part_id=chunk.item.id, content=content) + elif isinstance(chunk.item, responses.ResponseOutputMessage): + pass + else: + raise RuntimeError(f'Unexpected item type: {type(chunk.item)}') elif isinstance(chunk, responses.ResponseOutputItemDoneEvent): # NOTE: We only need this if the tool call deltas don't include the final info. diff --git a/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml b/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml index dd5431c19..928492948 100644 --- a/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml +++ b/tests/models/cassettes/test_anthropic/test_anthropic_model_thinking_part_stream.yaml @@ -31,190 +31,190 @@ interactions: body: string: |+ event: message_start - data: {"type":"message_start","message":{"id":"msg_01KjexrtHzmTnEa1LAy97Lb6","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":42,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":9}} } + data: {"type":"message_start","message":{"id":"msg_01EMVWy3UmSrXx8QDqDVJu3J","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":42,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":2}} } event: content_block_start - data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""} } + data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""} } event: ping data: {"type": "ping"} event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Crossing the street is a basic safety"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"This"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" skill that many people learn as children,"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" is a straightforward question about"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" but it's still an important question since doing"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" pedestrian safety. I should provide clear, practical"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" it safely can prevent accidents.\n\nI"}} + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" advice on how to safely cross a"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"'ll provide a clear, step"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" street, which is an everyday"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"-by-step explanation of how to cross a"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" activity that everyone should know how to"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" street safely:\n\n1. Find a designate"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" do properly.\n\nI'll include"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"d crossing point if possible (crosswalk, pedest"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" the basic steps:\n1. Fin"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"rian crossing, intersection)\n2. Stop at"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"d an appropriate place to cross (cross"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" the edge of the street\n3. Look in"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"walk, intersection)\n2. Stop at"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" all directions for oncoming traffic (typically"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" the edge of the street\n3. Look"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" left, right, then left again in"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" both ways (left-right-left in"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" countries where cars drive on the right)"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" countries with right-side driving;"}} event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"\n4. If there's a"}} + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" right-left-right in left"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" pedestrian signal, wait for the \"WALK\""} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"-side driving countries)\n4. Listen"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" signal\n5. Even with a walk"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" for oncoming traffic\n5."} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" signal, check for turning vehicles\n6. Cross"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" Cross when clear, or when"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" when it's clear, walking at a steady pace"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" pedestrian signals indicate it's safe"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"\n7. Continue to be aware"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"\n6. Stay alert while crossing\n7. Walk"}} event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" of traffic while crossing\n8. Don"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":", don't run\n\nI'll keep"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"'t use electronic devices or get"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" my answer concise and clear"}} event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" distracted while crossing\n\nI'll make"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":", as this is a straight"} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" this comprehensive but straightforward."}} + data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"forward safety question."} } event: content_block_delta - data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"ErUBCkYIAhgCIkDybqBMLATs8XSxwHam7hE+UafXykLhcwhwlchZqXbbhjYzku3ZovDhjzBYSOqT6VHlHvyW7vPFj4mFRrT1eVJIEgyC52poyJ1+xPKSkWcaDOO7nw/ZkmqqzD8V+yIwQ+tIRaS/Wv1p6GzSmRk7rKsexeBCwhz9V8JVzbyT4M80lAkb7UTdBYjP9wNsNBiUKh01hEXi8GGJckNWYRfwT50CGGNhdxhsTXhtkQkCfRgC"} } + data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"ErUBCkYIAhgCIkBae3G0d++H4kiUhcpgulrNH2UtKjeUX71wQ7N236PJ7eiWX0qBJjRPoCjmIXcax+Vikx6EQIabVzpoRW3IPrgiEgy2bMcGLce7+wzJBBoaDOE//A8Scwe+4aexWiIw5O8E22fvilfUPxe4t3HJl/+6zKSePdDr2SI5DAzjH8tecOlVH8TQ9axJ4yZg9+sGKh2s08YMFuwuRmro7qe9xH39ZjwnVb/c493it57aiBgC"} } event: content_block_stop - data: {"type":"content_block_stop","index":0 } + data: {"type":"content_block_stop","index":0 } event: content_block_start - data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""} } + data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"# How to Cross the Street Safely\n\n1. **Find a designated crossing point**"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"# How to Cross the Street Safely\n\n1. **"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" whenever possible, such as a crosswalk, intersection"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Find the right spot to cross**"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":", or pedestrian crossing.\n\n2. **"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"\n - Use a crosswalk,"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Stop at the curb**"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" pedestrian crossing, or intersection"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" or edge of the road.\n\n3. **"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" when available\n -"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Look in all directions for traffic"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" Avoid crossing between parked cars or on"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"**:\n - Look left, then"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" blind curves\n\n2. **"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" right, then left again (in countries where traffic"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Stop at the curb or"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" drives on the right)\n - Look right"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" edge of the road**"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":", then left, then right again ("} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"\n\n3. **Look both ways carefully"}} event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"in countries where traffic drives on the left)"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"**\n - Look left, then"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"\n\n4. **Use pedestrian signals**"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" right, then left again ("} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" if available:\n - Wait"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"in right-driving countries)\n -"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" for the \"WALK\" signal or"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" Look right, then left,"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" green walking figure\n - Don"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" then right again (in left-driving countries)"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"'t start crossing if the signal"}} + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"\n\n4. **Wait for traffic"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" is flashing or showing \"DON'T"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" to clear or signals to change**\n "} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" WALK\"\n\n5. **Make"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"- At traffic lights, cross only"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" eye contact** with nearby drivers to ensure they see"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" when you have the \"walk\" signal\n "} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" you.\n\n6. **Cross"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"- Make eye contact with drivers to"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" at a steady pace** when"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" ensure they see you\n\n5"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" the way is clear.\n\n7. **Stay"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":". **Cross at a steady pace**"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" alert** while crossing - continue watching"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" - walk, don't run"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" for traffic and don't use electronic devices"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"\n \n6. **Stay alert"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":".\n\n8. **Use extra"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"** - continue looking for traffic"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" caution** at night or in bad weather when"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" as you cross\n\n7."} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" visibility is reduced.\n\nRemember"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" **Use extra caution in"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" that traffic rules and patterns vary by country"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" poor weather or low visibility"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":", so always follow local regulations"} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"**\n\nRemember that local traffic laws may vary"} } event: content_block_delta - data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" and customs."} } + data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":" depending on your location."}} event: content_block_stop - data: {"type":"content_block_stop","index":1 } + data: {"type":"content_block_stop","index":1 } event: message_delta - data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":455} } + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":394}} event: message_stop - data: {"type":"message_stop" } + data: {"type":"message_stop" } headers: cache-control: diff --git a/tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml b/tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml new file mode 100644 index 000000000..91c98f8fe --- /dev/null +++ b/tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml @@ -0,0 +1,290 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '150' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: How do I cross the street? + role: user + model: o3-mini + reasoning: + effort: high + summary: detailed + stream: false + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '6076' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '21876' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + created_at: 1745304052 + error: null + id: resp_680739f4ad748191bd11096967c37c8b048efc3f8b2a068e + incomplete_details: null + instructions: null + max_output_tokens: null + metadata: {} + model: o3-mini-2025-01-31 + object: response + output: + - id: rs_680739fcbe148191ab03787b75642840048efc3f8b2a068e + summary: + - text: |- + **Understanding street crossing instructions** + + The user wants to know how to cross the street, which seems straightforward but could be ambiguous. I see this as a request for safety instructions regarding vehicular crossing. It’s essential to provide safe and clear advice but also to clarify that this is informational and not legal advice. I need to remind the user to follow local road codes as well. I want to ensure I help while being careful in my wording. + type: summary_text + - text: |- + **Providing street crossing guidance** + + It seems the user's question about crossing the street could involve personal safety instructions, so I need to be careful with my response. I can offer step-by-step advice: First, look for traffic before stepping off the curb, then check left, right, and left again. If at a crosswalk, use signals and wait for a gap in traffic. It’s crucial I clarify that I’m not a substitute for professional advice and to always follow local traffic laws for safety. + type: summary_text + - text: |- + **Providing safe street crossing instructions** + + I want to emphasize the importance of using crosswalks whenever possible and being extremely cautious of traffic. Distractions, like mobile phones, can make crossing dangerous, so staying alert is essential. It's crucial to obey traffic signals, especially because rules can differ in various countries. I’ll share step-by-step safe crossing instructions, beginning with finding a designated crossing point and waiting for signals. Each step will stress remaining vigilant while crossing and using caution to ensure safety. Finally, I’ll clarify that these are general guidelines and I’m not a safety expert. + type: summary_text + - text: |- + **Giving street crossing advice** + + When crossing the street, I advise waiting for a pedestrian signal if there is one to ensure safety. If there isn't a signal, it’s crucial to look left, right, and then left again to check for oncoming traffic. Cross quickly while staying aware of potential hazards. Always be cautious, even with a signal, as vehicles may turn or not yield correctly. Avoid distractions like mobile phones during this process. Lastly, I should remind the user to check local traffic laws, as I'm not an expert. + type: summary_text + type: reasoning + - content: + - annotations: [] + text: |- + Here are some general guidelines to help you cross the street safely. Please note that these are general tips and might need to be adapted based on your surroundings, local traffic laws, and conditions: + + 1. Before you approach the street, find a safe place to cross. Whenever possible, use a designated crosswalk, pedestrian crossing, or an intersection with traffic signals. + + 2. If there’s a pedestrian signal (a “walk” sign), wait for it to indicate that it’s your turn to cross. Even when the signal shows it’s safe, always stay alert for any vehicles that might not yet have stopped. + + 3. Stand at the curb and face traffic. This allows you to see oncoming vehicles and make eye contact with drivers when possible. + + 4. Look both ways: +   • First, look to your left. +   • Then, look to your right. +   • Look left again before stepping off the curb. + This helps ensure you catch vehicles approaching from either direction, especially in countries where traffic drives on the right. (In countries where traffic drives on the left, simply reverse the order accordingly.) + + 5. Make sure there’s a sufficient gap between vehicles before you start crossing. Even if you feel safe, take a moment to confirm that arriving vehicles are far enough away or are stopping. + + 6. Continue looking for oncoming traffic as you cross. Even if you’re in the crosswalk, drivers may not always see you or might be turning unexpectedly. + + 7. Avoid distractions like using your phone or wearing headphones at a high volume. Being fully alert can make a big difference in your safety. + + 8. Walk at a steady pace—you don’t need to run, but don’t dawdle either. Keeping a consistent pace helps drivers predict your movement. + + Remember that local road rules and conditions can vary. In some areas, there may be additional safety measures (like pedestrian islands or flashing beacons) that you should take advantage of when crossing. If you’re unsure about any specific local guidelines, it might be a good idea to check with local traffic authorities or community resources for further advice. + + Stay safe and always be aware of your surroundings when crossing any street! + type: output_text + id: msg_68073a09486c8191860c5516e97332d7048efc3f8b2a068e + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + reasoning: + effort: high + summary: detailed + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 13 + input_tokens_details: + cached_tokens: 0 + output_tokens: 1909 + output_tokens_details: + reasoning_tokens: 1472 + total_tokens: 1922 + user: null + status: + code: 200 + message: OK +- request: + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4740' + content-type: + - application/json + host: + - api.cohere.com + method: POST + parsed_body: + messages: + - content: How do I cross the street? + role: user + - content: + - text: |- + Here are some general guidelines to help you cross the street safely. Please note that these are general tips and might need to be adapted based on your surroundings, local traffic laws, and conditions: + + 1. Before you approach the street, find a safe place to cross. Whenever possible, use a designated crosswalk, pedestrian crossing, or an intersection with traffic signals. + + 2. If there’s a pedestrian signal (a “walk” sign), wait for it to indicate that it’s your turn to cross. Even when the signal shows it’s safe, always stay alert for any vehicles that might not yet have stopped. + + 3. Stand at the curb and face traffic. This allows you to see oncoming vehicles and make eye contact with drivers when possible. + + 4. Look both ways: +   • First, look to your left. +   • Then, look to your right. +   • Look left again before stepping off the curb. + This helps ensure you catch vehicles approaching from either direction, especially in countries where traffic drives on the right. (In countries where traffic drives on the left, simply reverse the order accordingly.) + + 5. Make sure there’s a sufficient gap between vehicles before you start crossing. Even if you feel safe, take a moment to confirm that arriving vehicles are far enough away or are stopping. + + 6. Continue looking for oncoming traffic as you cross. Even if you’re in the crosswalk, drivers may not always see you or might be turning unexpectedly. + + 7. Avoid distractions like using your phone or wearing headphones at a high volume. Being fully alert can make a big difference in your safety. + + 8. Walk at a steady pace—you don’t need to run, but don’t dawdle either. Keeping a consistent pace helps drivers predict your movement. + + Remember that local road rules and conditions can vary. In some areas, there may be additional safety measures (like pedestrian islands or flashing beacons) that you should take advantage of when crossing. If you’re unsure about any specific local guidelines, it might be a good idea to check with local traffic authorities or community resources for further advice. + + Stay safe and always be aware of your surroundings when crossing any street! + + + **Understanding street crossing instructions** + + The user wants to know how to cross the street, which seems straightforward but could be ambiguous. I see this as a request for safety instructions regarding vehicular crossing. It’s essential to provide safe and clear advice but also to clarify that this is informational and not legal advice. I need to remind the user to follow local road codes as well. I want to ensure I help while being careful in my wording. + + + + **Providing street crossing guidance** + + It seems the user's question about crossing the street could involve personal safety instructions, so I need to be careful with my response. I can offer step-by-step advice: First, look for traffic before stepping off the curb, then check left, right, and left again. If at a crosswalk, use signals and wait for a gap in traffic. It’s crucial I clarify that I’m not a substitute for professional advice and to always follow local traffic laws for safety. + + + + **Providing safe street crossing instructions** + + I want to emphasize the importance of using crosswalks whenever possible and being extremely cautious of traffic. Distractions, like mobile phones, can make crossing dangerous, so staying alert is essential. It's crucial to obey traffic signals, especially because rules can differ in various countries. I’ll share step-by-step safe crossing instructions, beginning with finding a designated crossing point and waiting for signals. Each step will stress remaining vigilant while crossing and using caution to ensure safety. Finally, I’ll clarify that these are general guidelines and I’m not a safety expert. + + + + **Giving street crossing advice** + + When crossing the street, I advise waiting for a pedestrian signal if there is one to ensure safety. If there isn't a signal, it’s crucial to look left, right, and then left again to check for oncoming traffic. Cross quickly while staying aware of potential hazards. Always be cautious, even with a signal, as vehicles may turn or not yield correctly. Avoid distractions like mobile phones during this process. Lastly, I should remind the user to check local traffic laws, as I'm not an expert. + + type: text + role: assistant + - content: Considering the way to cross the street, analogously, how do I cross the river? + role: user + model: command-r7b-12-2024 + stream: false + uri: https://api.cohere.com/v2/chat + response: + headers: + access-control-expose-headers: + - X-Debug-Trace-ID + alt-svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + cache-control: + - no-cache, no-store, no-transform, must-revalidate, private, max-age=0 + content-type: + - application/json + expires: + - Thu, 01 Jan 1970 00:00:00 UTC + num_chars: + - '7193' + num_tokens: + - '1759' + pragma: + - no-cache + transfer-encoding: + - chunked + vary: + - Origin + parsed_body: + finish_reason: COMPLETE + id: 51cedafe-2e40-4941-ba6d-c364dbc356d3 + message: + content: + - text: |- + Crossing a river can be a different challenge compared to crossing a street, and the approach to safety and navigation will vary. Here are some considerations and steps to help you cross a river safely: + + 1. **Determine the River's Characteristics:** + - **Width and Depth:** Measure or estimate the width of the river. Very wide rivers may require a boat or bridge for safe crossing. Also, assess the depth; shallow areas might be safe to wade through, while deeper sections may require a different method. + - **Current and Flow:** Understand the river's current. Strong currents can make swimming dangerous and may carry debris. Always check the flow rate and direction before attempting to cross. + - **Hazards:** Look for potential hazards like rocks, logs, or underwater obstacles that could cause injury or damage to equipment. + + 2. **Choose a Safe Crossing Method:** + - **Fording:** If the river is shallow and the current is gentle, you might be able to ford the river. This involves walking through the water, often with the help of a sturdy stick or pole for balance and support. Always test the depth and current before attempting to ford. + - **Swimming:** Swimming across a river is a challenging and potentially dangerous option. It requires strong swimming skills, endurance, and knowledge of river currents. Always swim with a buddy and be aware of your surroundings. + - **Boat or Raft:** Using a boat, raft, or even an inflatable tube can be a safe and efficient way to cross. Ensure the boat is sturdy, properly equipped, and that you have the necessary safety gear, such as life jackets and a first-aid kit. + - **Bridge or Ferry:** If available, use a bridge or a ferry service. These are typically the safest and most reliable methods for crossing a river. + + 3. **Prepare and Pack Essential Items:** + - **Life Jacket/Personal Floatation Device (PFD):** Always wear a life jacket or PFD when crossing a river, especially if swimming or using a boat. + - **First-Aid Kit:** Carry a basic first-aid kit to handle any minor injuries that might occur during the crossing. + - **Map and Compass:** Navigate the river and its surroundings with the help of a map and compass, especially if you're in an unfamiliar area. + - **Communication Device:** Have a means of communication, such as a satellite phone or a personal locator beacon, especially in remote areas. + - **Dry Clothing and Shelter:** Pack extra dry clothing and a waterproof shelter (like a tarp or tent) in case you get wet or need to wait out bad weather. + + 4. **Follow River Safety Guidelines:** + - **Stay on Marked Paths:** If there are designated river paths or trails, use them. These routes are often designed to be safer and less prone to hazards. + - **Avoid Hazards:** Be cautious of slippery rocks, strong currents, and hidden underwater obstacles. Never swim or ford in areas with known dangers. + - **Group Safety:** If crossing with others, stay together. It's easier to keep an eye on each other and provide assistance if needed. + + 5. **Be Prepared for Emergencies:** + - **Know Emergency Procedures:** Familiarize yourself with river rescue techniques and procedures. Learn how to signal for help and basic survival skills. + - **Carry Emergency Supplies:** Pack emergency supplies, including a whistle, a bright-colored cloth to signal, and a signal mirror (if available). + - **Leave a Plan:** Inform someone on the riverbank about your crossing plans, including your expected time of return. This person can raise the alarm if you don't return as scheduled. + + Remember, crossing a river can be a challenging and potentially dangerous endeavor. Always prioritize safety, and if in doubt, seek professional guidance or assistance from experienced river guides or local authorities. It's better to be over-prepared than caught off guard in a river crossing situation. + type: text + role: assistant + usage: + billed_units: + input_tokens: 954 + output_tokens: 805 + tokens: + input_tokens: 1457 + output_tokens: 807 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/test_anthropic.py b/tests/models/test_anthropic.py index 0efdd9cae..4364ca097 100644 --- a/tests/models/test_anthropic.py +++ b/tests/models/test_anthropic.py @@ -16,13 +16,18 @@ from pydantic_ai.messages import ( BinaryContent, DocumentUrl, + FinalResultEvent, ImageUrl, ModelRequest, ModelResponse, + PartDeltaEvent, + PartStartEvent, RetryPromptPart, SystemPromptPart, TextPart, + TextPartDelta, ThinkingPart, + ThinkingPartDelta, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -807,4 +812,68 @@ async def test_anthropic_model_thinking_part_stream(allow_model_requests: None, async for event in request_stream: event_parts.append(event) - assert event_parts == snapshot() + assert event_parts == snapshot( + [ + PartStartEvent(index=0, part=ThinkingPart(content='')), + FinalResultEvent(tool_name=None, tool_call_id=None), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent(index=0, delta=IsInstance(ThinkingPartDelta)), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + signature_delta='ErUBCkYIAhgCIkBae3G0d++H4kiUhcpgulrNH2UtKjeUX71wQ7N236PJ7eiWX0qBJjRPoCjmIXcax+Vikx6EQIabVzpoRW3IPrgiEgy2bMcGLce7+wzJBBoaDOE//A8Scwe+4aexWiIw5O8E22fvilfUPxe4t3HJl/+6zKSePdDr2SI5DAzjH8tecOlVH8TQ9axJ4yZg9+sGKh2s08YMFuwuRmro7qe9xH39ZjwnVb/c493it57aiBgC' + ), + ), + PartStartEvent(index=1, part=IsInstance(TextPart)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + PartDeltaEvent(index=1, delta=IsInstance(TextPartDelta)), + ] + ) diff --git a/tests/models/test_cohere.py b/tests/models/test_cohere.py index 85f09daeb..f3cb2d86f 100644 --- a/tests/models/test_cohere.py +++ b/tests/models/test_cohere.py @@ -17,6 +17,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -24,7 +25,7 @@ from pydantic_ai.tools import RunContext from pydantic_ai.usage import Usage -from ..conftest import IsDatetime, IsNow, raise_if_exception, try_import +from ..conftest import IsDatetime, IsInstance, IsNow, raise_if_exception, try_import with try_import() as imports_successful: import cohere @@ -391,3 +392,113 @@ def simple_instructions(ctx: RunContext): ), ] ) + + +@pytest.mark.vcr() +async def test_cohere_model_thinking_part(allow_model_requests: None, co_api_key: str, openai_api_key: str): + with try_import() as imports_successful: + from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings + from pydantic_ai.providers.openai import OpenAIProvider + + if not imports_successful(): # pragma: no cover + pytest.skip('OpenAI is not installed') + + openai_model = OpenAIResponsesModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key)) + co_model = CohereModel('command-r7b-12-2024', provider=CohereProvider(api_key=co_api_key)) + agent = Agent(openai_model) + + # We call OpenAI to get the thinking parts, because Google disabled the thoughts in the API. + # See https://github.com/pydantic/pydantic-ai/issues/793 for more details. + result = await agent.run( + 'How do I cross the street?', + model_settings=OpenAIResponsesModelSettings( + openai_reasoning_effort='high', openai_reasoning_summary='detailed' + ), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + IsInstance(TextPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ] + ) + + result = await agent.run( + 'Considering the way to cross the street, analogously, how do I cross the river?', + model=co_model, + message_history=result.all_messages(), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + IsInstance(TextPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ModelRequest( + parts=[ + UserPromptPart( + content='Considering the way to cross the street, analogously, how do I cross the river?', + timestamp=IsDatetime(), + ) + ] + ), + ModelResponse( + parts=[ + TextPart( + content="""\ +Crossing a river can be a different challenge compared to crossing a street, and the approach to safety and navigation will vary. Here are some considerations and steps to help you cross a river safely: + +1. **Determine the River's Characteristics:** + - **Width and Depth:** Measure or estimate the width of the river. Very wide rivers may require a boat or bridge for safe crossing. Also, assess the depth; shallow areas might be safe to wade through, while deeper sections may require a different method. + - **Current and Flow:** Understand the river's current. Strong currents can make swimming dangerous and may carry debris. Always check the flow rate and direction before attempting to cross. + - **Hazards:** Look for potential hazards like rocks, logs, or underwater obstacles that could cause injury or damage to equipment. + +2. **Choose a Safe Crossing Method:** + - **Fording:** If the river is shallow and the current is gentle, you might be able to ford the river. This involves walking through the water, often with the help of a sturdy stick or pole for balance and support. Always test the depth and current before attempting to ford. + - **Swimming:** Swimming across a river is a challenging and potentially dangerous option. It requires strong swimming skills, endurance, and knowledge of river currents. Always swim with a buddy and be aware of your surroundings. + - **Boat or Raft:** Using a boat, raft, or even an inflatable tube can be a safe and efficient way to cross. Ensure the boat is sturdy, properly equipped, and that you have the necessary safety gear, such as life jackets and a first-aid kit. + - **Bridge or Ferry:** If available, use a bridge or a ferry service. These are typically the safest and most reliable methods for crossing a river. + +3. **Prepare and Pack Essential Items:** + - **Life Jacket/Personal Floatation Device (PFD):** Always wear a life jacket or PFD when crossing a river, especially if swimming or using a boat. + - **First-Aid Kit:** Carry a basic first-aid kit to handle any minor injuries that might occur during the crossing. + - **Map and Compass:** Navigate the river and its surroundings with the help of a map and compass, especially if you're in an unfamiliar area. + - **Communication Device:** Have a means of communication, such as a satellite phone or a personal locator beacon, especially in remote areas. + - **Dry Clothing and Shelter:** Pack extra dry clothing and a waterproof shelter (like a tarp or tent) in case you get wet or need to wait out bad weather. + +4. **Follow River Safety Guidelines:** + - **Stay on Marked Paths:** If there are designated river paths or trails, use them. These routes are often designed to be safer and less prone to hazards. + - **Avoid Hazards:** Be cautious of slippery rocks, strong currents, and hidden underwater obstacles. Never swim or ford in areas with known dangers. + - **Group Safety:** If crossing with others, stay together. It's easier to keep an eye on each other and provide assistance if needed. + +5. **Be Prepared for Emergencies:** + - **Know Emergency Procedures:** Familiarize yourself with river rescue techniques and procedures. Learn how to signal for help and basic survival skills. + - **Carry Emergency Supplies:** Pack emergency supplies, including a whistle, a bright-colored cloth to signal, and a signal mirror (if available). + - **Leave a Plan:** Inform someone on the riverbank about your crossing plans, including your expected time of return. This person can raise the alarm if you don't return as scheduled. + +Remember, crossing a river can be a challenging and potentially dangerous endeavor. Always prioritize safety, and if in doubt, seek professional guidance or assistance from experienced river guides or local authorities. It's better to be over-prepared than caught off guard in a river crossing situation.\ +""" + ) + ], + model_name='command-r7b-12-2024', + timestamp=IsDatetime(), + ), + ] + ) From 5fced6ae770d20c8e20be9443b57fd57a5ac104d Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 11:53:00 +0200 Subject: [PATCH 12/27] Support Thinking part --- pydantic_ai_slim/pydantic_ai/messages.py | 4 ++ pydantic_ai_slim/pydantic_ai/models/cohere.py | 3 +- .../test_cohere_model_thinking_part.yaml | 48 ++++++------- .../test_gemini_model_thinking_part.yaml | 64 ++++++++--------- .../test_groq_model_thinking_part.yaml | 2 +- ..._openai_responses_model_thinking_part.yaml | 72 +++++++++---------- tests/models/test_gemini.py | 4 +- tests/models/test_groq.py | 2 +- 8 files changed, 102 insertions(+), 97 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index ed58ce372..c93bc57d5 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -492,6 +492,10 @@ class ThinkingPart: part_kind: Literal['thinking'] = 'thinking' """Part type identifier, this is available on all parts as a discriminator.""" + def has_content(self) -> bool: + """Return `True` if the thinking content is non-empty.""" + return bool(self.content) + @dataclass class ToolCallPart: diff --git a/pydantic_ai_slim/pydantic_ai/models/cohere.py b/pydantic_ai_slim/pydantic_ai/models/cohere.py index dcee22e6a..3c9be22fb 100644 --- a/pydantic_ai_slim/pydantic_ai/models/cohere.py +++ b/pydantic_ai_slim/pydantic_ai/models/cohere.py @@ -4,9 +4,10 @@ from dataclasses import dataclass, field from typing import Literal, Union, cast -from pydantic_ai._thinking_part import split_content_into_text_and_thinking from typing_extensions import assert_never +from pydantic_ai._thinking_part import split_content_into_text_and_thinking + from .. import ModelHTTPError, usage from .._utils import generate_tool_call_id as _generate_tool_call_id, guard_tool_call_id as _guard_tool_call_id from ..messages import ( diff --git a/tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml b/tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml index 91c98f8fe..42ab722be 100644 --- a/tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml +++ b/tests/models/cassettes/test_cohere/test_cohere_model_thinking_part.yaml @@ -60,22 +60,22 @@ interactions: - text: |- **Understanding street crossing instructions** - The user wants to know how to cross the street, which seems straightforward but could be ambiguous. I see this as a request for safety instructions regarding vehicular crossing. It’s essential to provide safe and clear advice but also to clarify that this is informational and not legal advice. I need to remind the user to follow local road codes as well. I want to ensure I help while being careful in my wording. + The user wants to know how to cross the street, which seems straightforward but could be ambiguous. I see this as a request for safety instructions regarding vehicular crossing. It's essential to provide safe and clear advice but also to clarify that this is informational and not legal advice. I need to remind the user to follow local road codes as well. I want to ensure I help while being careful in my wording. type: summary_text - text: |- **Providing street crossing guidance** - It seems the user's question about crossing the street could involve personal safety instructions, so I need to be careful with my response. I can offer step-by-step advice: First, look for traffic before stepping off the curb, then check left, right, and left again. If at a crosswalk, use signals and wait for a gap in traffic. It’s crucial I clarify that I’m not a substitute for professional advice and to always follow local traffic laws for safety. + It seems the user's question about crossing the street could involve personal safety instructions, so I need to be careful with my response. I can offer step-by-step advice: First, look for traffic before stepping off the curb, then check left, right, and left again. If at a crosswalk, use signals and wait for a gap in traffic. It's crucial I clarify that I'm not a substitute for professional advice and to always follow local traffic laws for safety. type: summary_text - text: |- **Providing safe street crossing instructions** - I want to emphasize the importance of using crosswalks whenever possible and being extremely cautious of traffic. Distractions, like mobile phones, can make crossing dangerous, so staying alert is essential. It's crucial to obey traffic signals, especially because rules can differ in various countries. I’ll share step-by-step safe crossing instructions, beginning with finding a designated crossing point and waiting for signals. Each step will stress remaining vigilant while crossing and using caution to ensure safety. Finally, I’ll clarify that these are general guidelines and I’m not a safety expert. + I want to emphasize the importance of using crosswalks whenever possible and being extremely cautious of traffic. Distractions, like mobile phones, can make crossing dangerous, so staying alert is essential. It's crucial to obey traffic signals, especially because rules can differ in various countries. I'll share step-by-step safe crossing instructions, beginning with finding a designated crossing point and waiting for signals. Each step will stress remaining vigilant while crossing and using caution to ensure safety. Finally, I'll clarify that these are general guidelines and I'm not a safety expert. type: summary_text - text: |- **Giving street crossing advice** - When crossing the street, I advise waiting for a pedestrian signal if there is one to ensure safety. If there isn't a signal, it’s crucial to look left, right, and then left again to check for oncoming traffic. Cross quickly while staying aware of potential hazards. Always be cautious, even with a signal, as vehicles may turn or not yield correctly. Avoid distractions like mobile phones during this process. Lastly, I should remind the user to check local traffic laws, as I'm not an expert. + When crossing the street, I advise waiting for a pedestrian signal if there is one to ensure safety. If there isn't a signal, it's crucial to look left, right, and then left again to check for oncoming traffic. Cross quickly while staying aware of potential hazards. Always be cautious, even with a signal, as vehicles may turn or not yield correctly. Avoid distractions like mobile phones during this process. Lastly, I should remind the user to check local traffic laws, as I'm not an expert. type: summary_text type: reasoning - content: @@ -85,25 +85,25 @@ interactions: 1. Before you approach the street, find a safe place to cross. Whenever possible, use a designated crosswalk, pedestrian crossing, or an intersection with traffic signals. - 2. If there’s a pedestrian signal (a “walk” sign), wait for it to indicate that it’s your turn to cross. Even when the signal shows it’s safe, always stay alert for any vehicles that might not yet have stopped. + 2. If there's a pedestrian signal (a "walk" sign), wait for it to indicate that it's your turn to cross. Even when the signal shows it's safe, always stay alert for any vehicles that might not yet have stopped. 3. Stand at the curb and face traffic. This allows you to see oncoming vehicles and make eye contact with drivers when possible. 4. Look both ways: -   • First, look to your left. -   • Then, look to your right. -   • Look left again before stepping off the curb. + • First, look to your left. + • Then, look to your right. + • Look left again before stepping off the curb. This helps ensure you catch vehicles approaching from either direction, especially in countries where traffic drives on the right. (In countries where traffic drives on the left, simply reverse the order accordingly.) - 5. Make sure there’s a sufficient gap between vehicles before you start crossing. Even if you feel safe, take a moment to confirm that arriving vehicles are far enough away or are stopping. + 5. Make sure there's a sufficient gap between vehicles before you start crossing. Even if you feel safe, take a moment to confirm that arriving vehicles are far enough away or are stopping. - 6. Continue looking for oncoming traffic as you cross. Even if you’re in the crosswalk, drivers may not always see you or might be turning unexpectedly. + 6. Continue looking for oncoming traffic as you cross. Even if you're in the crosswalk, drivers may not always see you or might be turning unexpectedly. 7. Avoid distractions like using your phone or wearing headphones at a high volume. Being fully alert can make a big difference in your safety. - 8. Walk at a steady pace—you don’t need to run, but don’t dawdle either. Keeping a consistent pace helps drivers predict your movement. + 8. Walk at a steady pace—you don't need to run, but don't dawdle either. Keeping a consistent pace helps drivers predict your movement. - Remember that local road rules and conditions can vary. In some areas, there may be additional safety measures (like pedestrian islands or flashing beacons) that you should take advantage of when crossing. If you’re unsure about any specific local guidelines, it might be a good idea to check with local traffic authorities or community resources for further advice. + Remember that local road rules and conditions can vary. In some areas, there may be additional safety measures (like pedestrian islands or flashing beacons) that you should take advantage of when crossing. If you're unsure about any specific local guidelines, it might be a good idea to check with local traffic authorities or community resources for further advice. Stay safe and always be aware of your surroundings when crossing any street! type: output_text @@ -164,50 +164,50 @@ interactions: 1. Before you approach the street, find a safe place to cross. Whenever possible, use a designated crosswalk, pedestrian crossing, or an intersection with traffic signals. - 2. If there’s a pedestrian signal (a “walk” sign), wait for it to indicate that it’s your turn to cross. Even when the signal shows it’s safe, always stay alert for any vehicles that might not yet have stopped. + 2. If there's a pedestrian signal (a "walk" sign), wait for it to indicate that it's your turn to cross. Even when the signal shows it's safe, always stay alert for any vehicles that might not yet have stopped. 3. Stand at the curb and face traffic. This allows you to see oncoming vehicles and make eye contact with drivers when possible. 4. Look both ways: -   • First, look to your left. -   • Then, look to your right. -   • Look left again before stepping off the curb. + • First, look to your left. + • Then, look to your right. + • Look left again before stepping off the curb. This helps ensure you catch vehicles approaching from either direction, especially in countries where traffic drives on the right. (In countries where traffic drives on the left, simply reverse the order accordingly.) - 5. Make sure there’s a sufficient gap between vehicles before you start crossing. Even if you feel safe, take a moment to confirm that arriving vehicles are far enough away or are stopping. + 5. Make sure there's a sufficient gap between vehicles before you start crossing. Even if you feel safe, take a moment to confirm that arriving vehicles are far enough away or are stopping. - 6. Continue looking for oncoming traffic as you cross. Even if you’re in the crosswalk, drivers may not always see you or might be turning unexpectedly. + 6. Continue looking for oncoming traffic as you cross. Even if you're in the crosswalk, drivers may not always see you or might be turning unexpectedly. 7. Avoid distractions like using your phone or wearing headphones at a high volume. Being fully alert can make a big difference in your safety. - 8. Walk at a steady pace—you don’t need to run, but don’t dawdle either. Keeping a consistent pace helps drivers predict your movement. + 8. Walk at a steady pace—you don't need to run, but don't dawdle either. Keeping a consistent pace helps drivers predict your movement. - Remember that local road rules and conditions can vary. In some areas, there may be additional safety measures (like pedestrian islands or flashing beacons) that you should take advantage of when crossing. If you’re unsure about any specific local guidelines, it might be a good idea to check with local traffic authorities or community resources for further advice. + Remember that local road rules and conditions can vary. In some areas, there may be additional safety measures (like pedestrian islands or flashing beacons) that you should take advantage of when crossing. If you're unsure about any specific local guidelines, it might be a good idea to check with local traffic authorities or community resources for further advice. Stay safe and always be aware of your surroundings when crossing any street! **Understanding street crossing instructions** - The user wants to know how to cross the street, which seems straightforward but could be ambiguous. I see this as a request for safety instructions regarding vehicular crossing. It’s essential to provide safe and clear advice but also to clarify that this is informational and not legal advice. I need to remind the user to follow local road codes as well. I want to ensure I help while being careful in my wording. + The user wants to know how to cross the street, which seems straightforward but could be ambiguous. I see this as a request for safety instructions regarding vehicular crossing. It's essential to provide safe and clear advice but also to clarify that this is informational and not legal advice. I need to remind the user to follow local road codes as well. I want to ensure I help while being careful in my wording. **Providing street crossing guidance** - It seems the user's question about crossing the street could involve personal safety instructions, so I need to be careful with my response. I can offer step-by-step advice: First, look for traffic before stepping off the curb, then check left, right, and left again. If at a crosswalk, use signals and wait for a gap in traffic. It’s crucial I clarify that I’m not a substitute for professional advice and to always follow local traffic laws for safety. + It seems the user's question about crossing the street could involve personal safety instructions, so I need to be careful with my response. I can offer step-by-step advice: First, look for traffic before stepping off the curb, then check left, right, and left again. If at a crosswalk, use signals and wait for a gap in traffic. It's crucial I clarify that I'm not a substitute for professional advice and to always follow local traffic laws for safety. **Providing safe street crossing instructions** - I want to emphasize the importance of using crosswalks whenever possible and being extremely cautious of traffic. Distractions, like mobile phones, can make crossing dangerous, so staying alert is essential. It's crucial to obey traffic signals, especially because rules can differ in various countries. I’ll share step-by-step safe crossing instructions, beginning with finding a designated crossing point and waiting for signals. Each step will stress remaining vigilant while crossing and using caution to ensure safety. Finally, I’ll clarify that these are general guidelines and I’m not a safety expert. + I want to emphasize the importance of using crosswalks whenever possible and being extremely cautious of traffic. Distractions, like mobile phones, can make crossing dangerous, so staying alert is essential. It's crucial to obey traffic signals, especially because rules can differ in various countries. I'll share step-by-step safe crossing instructions, beginning with finding a designated crossing point and waiting for signals. Each step will stress remaining vigilant while crossing and using caution to ensure safety. Finally, I'll clarify that these are general guidelines and I'm not a safety expert. **Giving street crossing advice** - When crossing the street, I advise waiting for a pedestrian signal if there is one to ensure safety. If there isn't a signal, it’s crucial to look left, right, and then left again to check for oncoming traffic. Cross quickly while staying aware of potential hazards. Always be cautious, even with a signal, as vehicles may turn or not yield correctly. Avoid distractions like mobile phones during this process. Lastly, I should remind the user to check local traffic laws, as I'm not an expert. + When crossing the street, I advise waiting for a pedestrian signal if there is one to ensure safety. If there isn't a signal, it's crucial to look left, right, and then left again to check for oncoming traffic. Cross quickly while staying aware of potential hazards. Always be cautious, even with a signal, as vehicles may turn or not yield correctly. Avoid distractions like mobile phones during this process. Lastly, I should remind the user to check local traffic laws, as I'm not an expert. type: text role: assistant diff --git a/tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml b/tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml index 1e81eda9c..cf90bafe0 100644 --- a/tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml +++ b/tests/models/cassettes/test_gemini/test_gemini_model_thinking_part.yaml @@ -65,14 +65,14 @@ interactions: - text: |- **Recalling street-crossing instructions** - I need to summarize safe street-crossing instructions, making sure to highlight key safety tips. I'll emphasize to look left, right, and then left again, and to use crosswalks where available. It’s important to clarify that I’m not a city planner or a safety professional, so my guidance is general in nature. I also want to include reminders about not assuming drivers will see you, and stressing the need for caution when no crosswalks are present. + I need to summarize safe street-crossing instructions, making sure to highlight key safety tips. I'll emphasize to look left, right, and then left again, and to use crosswalks where available. It's important to clarify that I'm not a city planner or a safety professional, so my guidance is general in nature. I also want to include reminders about not assuming drivers will see you, and stressing the need for caution when no crosswalks are present. type: summary_text - - text: "**Providing safe crossing instructions**\n\nI’m preparing step-by-step instructions for crossing the street + - text: "**Providing safe crossing instructions**\n\nI'm preparing step-by-step instructions for crossing the street safely, emphasizing key guidelines. I want to ensure drivers are cautious and remind everyone to avoid distractions like phones. My steps will be clear: \n\n1. Stop at the edge of the sidewalk. \n2. Look left, right, and left again to check for traffic. \n3. Wait for a clear gap in the traffic. \n4. Follow any pedestrian signals or - signs. \n5. Cross the street when it’s safe. \n6. Stay alert for vehicles while crossing. \n\nI'll include - a disclaimer that this isn’t professional advice and encourage using good judgment." + signs. \n5. Cross the street when it's safe. \n6. Stay alert for vehicles while crossing. \n\nI'll include + a disclaimer that this isn't professional advice and encourage using good judgment." type: summary_text type: reasoning - content: @@ -81,26 +81,26 @@ interactions: Here are guidelines for safely crossing a street. Remember that these tips are general and may need to be adjusted depending on your local traffic laws and the specific situation. They are not a substitute for professional safety advice. 1. Before you approach the street: -  • Use the sidewalk if available and get to the curb or edge of the road. -  • If you’re a child or feel unsure, try to have an adult accompany you. + • Use the sidewalk if available and get to the curb or edge of the road. + • If you're a child or feel unsure, try to have an adult accompany you. 2. When you're ready to cross: -  • Look carefully in all directions—start by looking left, then right, and left again. In countries where you drive on the left you’ll want to adjust accordingly. -  • Listen for vehicles and be aware of turning cars, which might not be immediately in your line of sight. -  • Make eye contact with drivers if possible so that you know they see you. + • Look carefully in all directions—start by looking left, then right, and left again. In countries where you drive on the left you'll want to adjust accordingly. + • Listen for vehicles and be aware of turning cars, which might not be immediately in your line of sight. + • Make eye contact with drivers if possible so that you know they see you. 3. Use designated crossing areas whenever possible: -  • If there's a pedestrian crosswalk, use it. Crosswalks and traffic signals are there to help manage the flow of both vehicles and pedestrians. -  • If there’s a “Walk” signal, wait until it’s on before crossing. Even if the signal turns green for pedestrians, always take an extra moment to ensure that approaching drivers are stopping. + • If there's a pedestrian crosswalk, use it. Crosswalks and traffic signals are there to help manage the flow of both vehicles and pedestrians. + • If there's a "Walk" signal, wait until it's on before crossing. Even if the signal turns green for pedestrians, always take an extra moment to ensure that approaching drivers are stopping. 4. While crossing: -  • Continue to remain alert and avoid distractions like cell phones or headphones that could prevent you from noticing approaching traffic. -  • Walk at a steady pace and stay in the crosswalk until you have completely reached the other side. + • Continue to remain alert and avoid distractions like cell phones or headphones that could prevent you from noticing approaching traffic. + • Walk at a steady pace and stay in the crosswalk until you have completely reached the other side. 5. After crossing: -  • Once you’ve safely reached the other side, continue to be aware of any vehicles that might be turning or reversing. + • Once you've safely reached the other side, continue to be aware of any vehicles that might be turning or reversing. - Always be cautious—even if you have the right-of-way—and understand that it’s better to wait a moment longer than risk being caught off guard. Stay safe! + Always be cautious—even if you have the right-of-way—and understand that it's better to wait a moment longer than risk being caught off guard. Stay safe! type: output_text id: msg_68039413525c8191aca9aa8f886eaf5d04f0817ea037a07b role: assistant @@ -163,39 +163,39 @@ interactions: - text: |- **Recalling street-crossing instructions** - I need to summarize safe street-crossing instructions, making sure to highlight key safety tips. I'll emphasize to look left, right, and then left again, and to use crosswalks where available. It’s important to clarify that I’m not a city planner or a safety professional, so my guidance is general in nature. I also want to include reminders about not assuming drivers will see you, and stressing the need for caution when no crosswalks are present. + I need to summarize safe street-crossing instructions, making sure to highlight key safety tips. I'll emphasize to look left, right, and then left again, and to use crosswalks where available. It's important to clarify that I'm not a city planner or a safety professional, so my guidance is general in nature. I also want to include reminders about not assuming drivers will see you, and stressing the need for caution when no crosswalks are present. thought: true - - text: "**Providing safe crossing instructions**\n\nI’m preparing step-by-step instructions for crossing the street + - text: "**Providing safe crossing instructions**\n\nI'm preparing step-by-step instructions for crossing the street safely, emphasizing key guidelines. I want to ensure drivers are cautious and remind everyone to avoid distractions like phones. My steps will be clear: \n\n1. Stop at the edge of the sidewalk. \n2. Look left, right, and left again to check for traffic. \n3. Wait for a clear gap in the traffic. \n4. Follow any pedestrian signals or - signs. \n5. Cross the street when it’s safe. \n6. Stay alert for vehicles while crossing. \n\nI'll include - a disclaimer that this isn’t professional advice and encourage using good judgment." + signs. \n5. Cross the street when it's safe. \n6. Stay alert for vehicles while crossing. \n\nI'll include + a disclaimer that this isn't professional advice and encourage using good judgment." thought: true - text: |- Here are guidelines for safely crossing a street. Remember that these tips are general and may need to be adjusted depending on your local traffic laws and the specific situation. They are not a substitute for professional safety advice. 1. Before you approach the street: -  • Use the sidewalk if available and get to the curb or edge of the road. -  • If you’re a child or feel unsure, try to have an adult accompany you. + • Use the sidewalk if available and get to the curb or edge of the road. + • If you're a child or feel unsure, try to have an adult accompany you. 2. When you're ready to cross: -  • Look carefully in all directions—start by looking left, then right, and left again. In countries where you drive on the left you’ll want to adjust accordingly. -  • Listen for vehicles and be aware of turning cars, which might not be immediately in your line of sight. -  • Make eye contact with drivers if possible so that you know they see you. + • Look carefully in all directions—start by looking left, then right, and left again. In countries where you drive on the left you'll want to adjust accordingly. + • Listen for vehicles and be aware of turning cars, which might not be immediately in your line of sight. + • Make eye contact with drivers if possible so that you know they see you. 3. Use designated crossing areas whenever possible: -  • If there's a pedestrian crosswalk, use it. Crosswalks and traffic signals are there to help manage the flow of both vehicles and pedestrians. -  • If there’s a “Walk” signal, wait until it’s on before crossing. Even if the signal turns green for pedestrians, always take an extra moment to ensure that approaching drivers are stopping. + • If there's a pedestrian crosswalk, use it. Crosswalks and traffic signals are there to help manage the flow of both vehicles and pedestrians. + • If there's a "Walk" signal, wait until it's on before crossing. Even if the signal turns green for pedestrians, always take an extra moment to ensure that approaching drivers are stopping. 4. While crossing: -  • Continue to remain alert and avoid distractions like cell phones or headphones that could prevent you from noticing approaching traffic. -  • Walk at a steady pace and stay in the crosswalk until you have completely reached the other side. + • Continue to remain alert and avoid distractions like cell phones or headphones that could prevent you from noticing approaching traffic. + • Walk at a steady pace and stay in the crosswalk until you have completely reached the other side. 5. After crossing: -  • Once you’ve safely reached the other side, continue to be aware of any vehicles that might be turning or reversing. + • Once you've safely reached the other side, continue to be aware of any vehicles that might be turning or reversing. - Always be cautious—even if you have the right-of-way—and understand that it’s better to wait a moment longer than risk being caught off guard. Stay safe! + Always be cautious—even if you have the right-of-way—and understand that it's better to wait a moment longer than risk being caught off guard. Stay safe! role: model - parts: - text: Considering the way to cross the street, analogously, how do I cross the river? @@ -234,7 +234,7 @@ interactions: 1. **Before you approach the river:** * Just as you use a sidewalk to get to the street's edge, use a trail or the riverbank to get to a spot where you can assess the river. - * If you’re inexperienced with rivers or unsure about the conditions, try to have someone experienced accompany you. + * If you're inexperienced with rivers or unsure about the conditions, try to have someone experienced accompany you. 2. **When you're ready to cross:** * Just as you look and listen for vehicles, carefully **assess the river conditions**. Look in all directions (upstream, downstream, across): @@ -255,7 +255,7 @@ interactions: * Move with purpose, but carefully. If wading, maintain your balance against the current and watch your footing. If swimming, focus on your technique and direction. Stay aware of where you are relative to your intended path and the river's flow. 5. **After crossing:** - * Once you’ve safely reached the other side, take a moment to ensure you are truly out of the main flow and on stable ground. Be aware of the riverbank conditions. + * Once you've safely reached the other side, take a moment to ensure you are truly out of the main flow and on stable ground. Be aware of the riverbank conditions. **Analogous Takeaway:** diff --git a/tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml b/tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml index 1f3205a57..e030025de 100644 --- a/tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml +++ b/tests/models/cassettes/test_groq/test_groq_model_thinking_part.yaml @@ -285,7 +285,7 @@ interactions: - Serve with a cup of coffee or tea. Store any leftovers in an airtight container at room temperature for up to a week. ### Tips: - - **Dough Handling:** Ensure the butter is softened for a smooth dough, and don’t overwork the dough to maintain the cookies' tender texture. + - **Dough Handling:** Ensure the butter is softened for a smooth dough, and don't overwork the dough to maintain the cookies' tender texture. - **Baking:** Keep an eye on the cookies after 15 minutes to prevent over-browning. - **Filling Variations:** Experiment with different fillings like caramel or nuts to create unique flavor profiles. - **Coating Options:** Try different coatings such as cinnamon or coconut for a varied texture and taste. diff --git a/tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml b/tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml index 17153fac5..923ddff9b 100644 --- a/tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml +++ b/tests/models/cassettes/test_openai/test_openai_responses_model_thinking_part.yaml @@ -60,17 +60,17 @@ interactions: - text: |- **Providing street crossing instructions** - The user seems to be asking how to cross the street, which could be literal or metaphorical. I think it’s best to offer clear and responsible instructions for street crossing. I’ll include general safety tips, like using crosswalks and obeying traffic signals. I need to emphasize that I'm not a substitute for professional safety advice. I'll make sure to mention looking both ways and waiting for safe signals before crossing. + The user seems to be asking how to cross the street, which could be literal or metaphorical. I think it's best to offer clear and responsible instructions for street crossing. I'll include general safety tips, like using crosswalks and obeying traffic signals. I need to emphasize that I'm not a substitute for professional safety advice. I'll make sure to mention looking both ways and waiting for safe signals before crossing. type: summary_text - text: |- **Clarifying street crossing instructions** - The user is asking how to cross the street safely, which I'll take seriously. My response will include essential safety steps, noting that this isn’t professional advice. I need to emphasize using crosswalks and waiting for the appropriate signals before crossing. I should remind the user to look both ways and remain cautious of vehicles. Additionally, I’ll clarify that local laws and guidelines should be followed, ensuring a comprehensive and responsible set of instructions. + The user is asking how to cross the street safely, which I'll take seriously. My response will include essential safety steps, noting that this isn't professional advice. I need to emphasize using crosswalks and waiting for the appropriate signals before crossing. I should remind the user to look both ways and remain cautious of vehicles. Additionally, I'll clarify that local laws and guidelines should be followed, ensuring a comprehensive and responsible set of instructions. type: summary_text - text: |- **Outlining street crossing safety** - Step 6 involves being aware of bicyclists while crossing. I need to remind the user not to cross if they have impaired vision or hearing. This question seems to focus on pedestrian safety, and I should provide clear instructions while including a disclaimer about responsibility. I'll suggest using designated crosswalks, waiting for signals, and continuously checking for vehicles. Additionally, I’ll emphasize local rules, and if there’s uncertainty, wait for a clearer signal. It’s essential to exercise caution and consider distractions, and I’ll stress that these instructions are general advice. + Step 6 involves being aware of bicyclists while crossing. I need to remind the user not to cross if they have impaired vision or hearing. This question seems to focus on pedestrian safety, and I should provide clear instructions while including a disclaimer about responsibility. I'll suggest using designated crosswalks, waiting for signals, and continuously checking for vehicles. Additionally, I'll emphasize local rules, and if there's uncertainty, wait for a clearer signal. It's essential to exercise caution and consider distractions, and I'll stress that these instructions are general advice. type: summary_text - text: |- **Creating safe crossing instructions** @@ -83,19 +83,19 @@ interactions: text: |- I'm happy to help! Here are some general guidelines for crossing the street safely. Please keep in mind that local traffic laws and conditions may vary, so always follow any posted signs, signals, or recommendations from local authorities: - 1. Find a safe place to cross. Look for a designated crosswalk or intersection with pedestrian signals. If you’re not near one, choose a spot where drivers have a clear view of you and can see you from a distance. + 1. Find a safe place to cross. Look for a designated crosswalk or intersection with pedestrian signals. If you're not near one, choose a spot where drivers have a clear view of you and can see you from a distance. - 2. Wait for the proper signal. If there’s a pedestrian signal, wait until it shows that it’s safe to cross ("Walk" or a green figure). Even if you have the right-of-way, double-check the traffic around you before stepping off the curb. + 2. Wait for the proper signal. If there's a pedestrian signal, wait until it shows that it's safe to cross ("Walk" or a green figure). Even if you have the right-of-way, double-check the traffic around you before stepping off the curb. 3. Look and listen before crossing. Adopt the "Look Left, Right, Left" habit: -   • Look left (toward oncoming traffic) -   • Look right (ensure there’s no immediate traffic) -   • Look left again, as vehicles might be turning into the street from that direction + • Look left (toward oncoming traffic) + • Look right (ensure there's no immediate traffic) + • Look left again, as vehicles might be turning into the street from that direction Additionally, listen for any approaching vehicles or warning sounds that could indicate vehicles are near. - 4. Cross at a steady pace. Once you’re sure it’s safe, cross the street confidently and stay within the walk zone. Continue to be aware of your surroundings as you cross because sometimes drivers (especially those turning) may not expect pedestrians. + 4. Cross at a steady pace. Once you're sure it's safe, cross the street confidently and stay within the walk zone. Continue to be aware of your surroundings as you cross because sometimes drivers (especially those turning) may not expect pedestrians. - 5. Stay alert and avoid distractions. It’s best to avoid using your phone or wearing headphones at high volume while crossing so you can hear or see any potential hazards. + 5. Stay alert and avoid distractions. It's best to avoid using your phone or wearing headphones at high volume while crossing so you can hear or see any potential hazards. 6. Be extra cautious at intersections without signals or crosswalks. In these cases, slow down, make eye contact with drivers if possible, and ensure that arriving vehicles have seen you before proceeding. @@ -158,19 +158,19 @@ interactions: - content: |- I'm happy to help! Here are some general guidelines for crossing the street safely. Please keep in mind that local traffic laws and conditions may vary, so always follow any posted signs, signals, or recommendations from local authorities: - 1. Find a safe place to cross. Look for a designated crosswalk or intersection with pedestrian signals. If you’re not near one, choose a spot where drivers have a clear view of you and can see you from a distance. + 1. Find a safe place to cross. Look for a designated crosswalk or intersection with pedestrian signals. If you're not near one, choose a spot where drivers have a clear view of you and can see you from a distance. - 2. Wait for the proper signal. If there’s a pedestrian signal, wait until it shows that it’s safe to cross ("Walk" or a green figure). Even if you have the right-of-way, double-check the traffic around you before stepping off the curb. + 2. Wait for the proper signal. If there's a pedestrian signal, wait until it shows that it's safe to cross ("Walk" or a green figure). Even if you have the right-of-way, double-check the traffic around you before stepping off the curb. 3. Look and listen before crossing. Adopt the "Look Left, Right, Left" habit: -   • Look left (toward oncoming traffic) -   • Look right (ensure there’s no immediate traffic) -   • Look left again, as vehicles might be turning into the street from that direction + • Look left (toward oncoming traffic) + • Look right (ensure there's no immediate traffic) + • Look left again, as vehicles might be turning into the street from that direction Additionally, listen for any approaching vehicles or warning sounds that could indicate vehicles are near. - 4. Cross at a steady pace. Once you’re sure it’s safe, cross the street confidently and stay within the walk zone. Continue to be aware of your surroundings as you cross because sometimes drivers (especially those turning) may not expect pedestrians. + 4. Cross at a steady pace. Once you're sure it's safe, cross the street confidently and stay within the walk zone. Continue to be aware of your surroundings as you cross because sometimes drivers (especially those turning) may not expect pedestrians. - 5. Stay alert and avoid distractions. It’s best to avoid using your phone or wearing headphones at high volume while crossing so you can hear or see any potential hazards. + 5. Stay alert and avoid distractions. It's best to avoid using your phone or wearing headphones at high volume while crossing so you can hear or see any potential hazards. 6. Be extra cautious at intersections without signals or crosswalks. In these cases, slow down, make eye contact with drivers if possible, and ensure that arriving vehicles have seen you before proceeding. @@ -181,17 +181,17 @@ interactions: - text: |- **Providing street crossing instructions** - The user seems to be asking how to cross the street, which could be literal or metaphorical. I think it’s best to offer clear and responsible instructions for street crossing. I’ll include general safety tips, like using crosswalks and obeying traffic signals. I need to emphasize that I'm not a substitute for professional safety advice. I'll make sure to mention looking both ways and waiting for safe signals before crossing. + The user seems to be asking how to cross the street, which could be literal or metaphorical. I think it's best to offer clear and responsible instructions for street crossing. I'll include general safety tips, like using crosswalks and obeying traffic signals. I need to emphasize that I'm not a substitute for professional safety advice. I'll make sure to mention looking both ways and waiting for safe signals before crossing. type: summary_text - text: |- **Clarifying street crossing instructions** - The user is asking how to cross the street safely, which I'll take seriously. My response will include essential safety steps, noting that this isn’t professional advice. I need to emphasize using crosswalks and waiting for the appropriate signals before crossing. I should remind the user to look both ways and remain cautious of vehicles. Additionally, I’ll clarify that local laws and guidelines should be followed, ensuring a comprehensive and responsible set of instructions. + The user is asking how to cross the street safely, which I'll take seriously. My response will include essential safety steps, noting that this isn't professional advice. I need to emphasize using crosswalks and waiting for the appropriate signals before crossing. I should remind the user to look both ways and remain cautious of vehicles. Additionally, I'll clarify that local laws and guidelines should be followed, ensuring a comprehensive and responsible set of instructions. type: summary_text - text: |- **Outlining street crossing safety** - Step 6 involves being aware of bicyclists while crossing. I need to remind the user not to cross if they have impaired vision or hearing. This question seems to focus on pedestrian safety, and I should provide clear instructions while including a disclaimer about responsibility. I'll suggest using designated crosswalks, waiting for signals, and continuously checking for vehicles. Additionally, I’ll emphasize local rules, and if there’s uncertainty, wait for a clearer signal. It’s essential to exercise caution and consider distractions, and I’ll stress that these instructions are general advice. + Step 6 involves being aware of bicyclists while crossing. I need to remind the user not to cross if they have impaired vision or hearing. This question seems to focus on pedestrian safety, and I should provide clear instructions while including a disclaimer about responsibility. I'll suggest using designated crosswalks, waiting for signals, and continuously checking for vehicles. Additionally, I'll emphasize local rules, and if there's uncertainty, wait for a clearer signal. It's essential to exercise caution and consider distractions, and I'll stress that these instructions are general advice. type: summary_text - text: |- **Creating safe crossing instructions** @@ -243,48 +243,48 @@ interactions: - text: |- **Exploring river crossing analogy** - The user’s question seems playful, comparing crossing a street to crossing a river. I think it might be humorous while still needing some real safety advice. Just like for street crossing, I could mention to evaluate conditions for the river, such as water level and obstacles. However, I need to tread carefully, as river crossings can be much more dangerous than they seem. Balancing humor with safety in the answer will be important! + The user's question seems playful, comparing crossing a street to crossing a river. I think it might be humorous while still needing some real safety advice. Just like for street crossing, I could mention to evaluate conditions for the river, such as water level and obstacles. However, I need to tread carefully, as river crossings can be much more dangerous than they seem. Balancing humor with safety in the answer will be important! type: summary_text - text: |- **Providing river crossing guidelines** - I want to give some practical advice for safely crossing a river. Key points include evaluating the water's depth and speed, looking for designated crossing points like bridges, and ensuring local conditions are safe. It’s essential to use proper methods, like finding a bridge or boat. I should also clarify that I’m not a certified expert, so consulting professionals for serious situations is wise. Overall, my guidelines will emphasize safety in a friendly, helpful way while keeping things lighthearted! + I want to give some practical advice for safely crossing a river. Key points include evaluating the water's depth and speed, looking for designated crossing points like bridges, and ensuring local conditions are safe. It's essential to use proper methods, like finding a bridge or boat. I should also clarify that I'm not a certified expert, so consulting professionals for serious situations is wise. Overall, my guidelines will emphasize safety in a friendly, helpful way while keeping things lighthearted! type: summary_text - text: |- **Providing cautious river crossing guidance** - I can stress that while the guidelines for crossing a river might seem similar to crossing a street, the risks are significantly higher. It's important not to assume that what’s safe on a road is automatically safe for a river. I’ll include a disclaimer about not being a safety expert and emphasize consulting professionals for hazardous conditions. I’ll structure the answer with bullet points, including key instructions for safely choosing designated crossings like bridges. The wording will be clear and straightforward! + I can stress that while the guidelines for crossing a river might seem similar to crossing a street, the risks are significantly higher. It's important not to assume that what's safe on a road is automatically safe for a river. I'll include a disclaimer about not being a safety expert and emphasize consulting professionals for hazardous conditions. I'll structure the answer with bullet points, including key instructions for safely choosing designated crossings like bridges. The wording will be clear and straightforward! type: summary_text type: reasoning - content: - annotations: [] text: |- - While crossing a river might seem analogous in concept to crossing a street, it’s important to recognize that water environments are inherently more dynamic and hazardous than predictable road conditions. That said, here are some guidelines that parallel the careful approach you’d take when crossing a street: + While crossing a river might seem analogous in concept to crossing a street, it's important to recognize that water environments are inherently more dynamic and hazardous than predictable road conditions. That said, here are some guidelines that parallel the careful approach you'd take when crossing a street: 1. Plan Your Route and Choose a Designated Crossing Point -  • Just as you’d look for a crosswalk or an intersection when crossing a street, look for a safe, well-known crossing point over the river. This might be a bridge, a ford (a shallow place to cross), or a series of stepping stones. -  • Avoid random or unmarked spots where the river narrows or looks deceptively calm. + • Just as you'd look for a crosswalk or an intersection when crossing a street, look for a safe, well-known crossing point over the river. This might be a bridge, a ford (a shallow place to cross), or a series of stepping stones. + • Avoid random or unmarked spots where the river narrows or looks deceptively calm. 2. Evaluate Conditions Before You Approach -  • Whereas you’d check for oncoming traffic and pedestrian signals at a street, observe the river’s conditions before you cross. Is the water fast-moving or deep? Are there hazards like rocks, sudden drop-offs, or debris? -  • Consider weather conditions, recent rainfall, and time of day, as these can affect both water levels and current strength. + • Whereas you'd check for oncoming traffic and pedestrian signals at a street, observe the river's conditions before you cross. Is the water fast-moving or deep? Are there hazards like rocks, sudden drop-offs, or debris? + • Consider weather conditions, recent rainfall, and time of day, as these can affect both water levels and current strength. 3. Prepare with the Right Gear and Skills -  • When crossing a street, you rely mainly on your alertness and the right-of-way signals. For a river, ensure you’re equipped appropriately—wear sturdy, non-slip footwear, and if necessary, use a life jacket or personal flotation device. -  • Only attempt a river crossing on foot if you’re confident in your balance and if the water’s depth and current are truly manageable. In many cases, using a boat or waiting for a safer condition is a much better choice. + • When crossing a street, you rely mainly on your alertness and the right-of-way signals. For a river, ensure you're equipped appropriately—wear sturdy, non-slip footwear, and if necessary, use a life jacket or personal flotation device. + • Only attempt a river crossing on foot if you're confident in your balance and if the water's depth and current are truly manageable. In many cases, using a boat or waiting for a safer condition is a much better choice. 4. Proceed Cautiously, Step by Step -  • In a street crossing, you look both ways before stepping off the curb. On a river, take your time and cross slowly while constantly reassessing conditions. If wading, test the water with your foot first and maintain a steady pace. -  • Avoid distractions and stay alert to sudden changes in water flow or unexpected obstacles. + • In a street crossing, you look both ways before stepping off the curb. On a river, take your time and cross slowly while constantly reassessing conditions. If wading, test the water with your foot first and maintain a steady pace. + • Avoid distractions and stay alert to sudden changes in water flow or unexpected obstacles. 5. Have a Contingency Plan -  • Just as you’d stop and wait at a street if the signal hasn’t changed, be prepared to back out if conditions deteriorate. Let someone know your plan and expected route, and if you’re uncertain, consider waiting for assistance or finding an alternate crossing. -  • In remote or unpredictable river environments, it’s crucial to have a method of communication like a cell phone or a whistle in case you need help. + • Just as you'd stop and wait at a street if the signal hasn't changed, be prepared to back out if conditions deteriorate. Let someone know your plan and expected route, and if you're uncertain, consider waiting for assistance or finding an alternate crossing. + • In remote or unpredictable river environments, it's crucial to have a method of communication like a cell phone or a whistle in case you need help. 6. Know Your Limits -  • It’s similar to knowing when to wait for a break in traffic. Assess your own abilities and the risks involved. If the conditions seem too dangerous—even if a crossing point is technically available—it’s best not to risk it. When in doubt, seek local advice or professional help. + • It's similar to knowing when to wait for a break in traffic. Assess your own abilities and the risks involved. If the conditions seem too dangerous—even if a crossing point is technically available—it's best not to risk it. When in doubt, seek local advice or professional help. - Remember, these are general guidelines and not a substitute for professional advice, especially when dealing with natural hazards. Crossing a river safely requires careful consideration of factors that don’t come into play when crossing a street. Always prioritize your safety and, if ever unsure, reach out for help or choose an alternative route. + Remember, these are general guidelines and not a substitute for professional advice, especially when dealing with natural hazards. Crossing a river safely requires careful consideration of factors that don't come into play when crossing a street. Always prioritize your safety and, if ever unsure, reach out for help or choose an alternative route. type: output_text id: msg_68034865ac18819189215b3a2e33cb4e08c845d2be9bcdd8 role: assistant diff --git a/tests/models/test_gemini.py b/tests/models/test_gemini.py index f07044e79..837dc1524 100644 --- a/tests/models/test_gemini.py +++ b/tests/models/test_gemini.py @@ -1147,7 +1147,7 @@ async def test_gemini_model_thinking_part(allow_model_requests: None, gemini_api 1. **Before you approach the river:** * Just as you use a sidewalk to get to the street's edge, use a trail or the riverbank to get to a spot where you can assess the river. - * If you’re inexperienced with rivers or unsure about the conditions, try to have someone experienced accompany you. + * If you're inexperienced with rivers or unsure about the conditions, try to have someone experienced accompany you. 2. **When you're ready to cross:** * Just as you look and listen for vehicles, carefully **assess the river conditions**. Look in all directions (upstream, downstream, across): @@ -1168,7 +1168,7 @@ async def test_gemini_model_thinking_part(allow_model_requests: None, gemini_api * Move with purpose, but carefully. If wading, maintain your balance against the current and watch your footing. If swimming, focus on your technique and direction. Stay aware of where you are relative to your intended path and the river's flow. 5. **After crossing:** - * Once you’ve safely reached the other side, take a moment to ensure you are truly out of the main flow and on stable ground. Be aware of the riverbank conditions. + * Once you've safely reached the other side, take a moment to ensure you are truly out of the main flow and on stable ground. Be aware of the riverbank conditions. **Analogous Takeaway:** diff --git a/tests/models/test_groq.py b/tests/models/test_groq.py index 6737acced..7abf6ecc9 100644 --- a/tests/models/test_groq.py +++ b/tests/models/test_groq.py @@ -804,7 +804,7 @@ async def test_groq_model_thinking_part(allow_model_requests: None, groq_api_key - Serve with a cup of coffee or tea. Store any leftovers in an airtight container at room temperature for up to a week. ### Tips: -- **Dough Handling:** Ensure the butter is softened for a smooth dough, and don’t overwork the dough to maintain the cookies' tender texture. +- **Dough Handling:** Ensure the butter is softened for a smooth dough, and don't overwork the dough to maintain the cookies' tender texture. - **Baking:** Keep an eye on the cookies after 15 minutes to prevent over-browning. - **Filling Variations:** Experiment with different fillings like caramel or nuts to create unique flavor profiles. - **Coating Options:** Try different coatings such as cinnamon or coconut for a varied texture and taste. From eaf70e178a16bff3fe2dd58fccb63b86f62f20f6 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 15:03:10 +0200 Subject: [PATCH 13/27] Support Thinking part --- .../pydantic_ai/models/function.py | 3 ++- pydantic_ai_slim/pydantic_ai/models/test.py | 23 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/models/function.py b/pydantic_ai_slim/pydantic_ai/models/function.py index de57f1d42..79324b549 100644 --- a/pydantic_ai_slim/pydantic_ai/models/function.py +++ b/pydantic_ai_slim/pydantic_ai/models/function.py @@ -24,6 +24,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserContent, @@ -263,7 +264,7 @@ def _estimate_usage(messages: Iterable[ModelMessage]) -> usage.Usage: assert_never(part) elif isinstance(message, ModelResponse): for part in message.parts: - if isinstance(part, TextPart): + if isinstance(part, (TextPart, ThinkingPart)): response_tokens += _estimate_string_tokens(part.content) elif isinstance(part, ToolCallPart): call = part diff --git a/pydantic_ai_slim/pydantic_ai/models/test.py b/pydantic_ai_slim/pydantic_ai/models/test.py index 5f2982f71..56c530d0c 100644 --- a/pydantic_ai_slim/pydantic_ai/models/test.py +++ b/pydantic_ai_slim/pydantic_ai/models/test.py @@ -6,7 +6,7 @@ from contextlib import asynccontextmanager from dataclasses import InitVar, dataclass, field from datetime import date, datetime, timedelta -from typing import Any, Literal +from typing import Any, Literal, assert_never import pydantic_core @@ -19,6 +19,7 @@ ModelResponseStreamEvent, RetryPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, ) @@ -240,7 +241,7 @@ def __post_init__(self, _messages: Iterable[ModelMessage]): async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: for i, part in enumerate(self._structured_response.parts): - if isinstance(part, TextPart): + if isinstance(part, (TextPart, ThinkingPart)): text = part.content *words, last_word = text.split(' ') words = [f'{word} ' for word in words] @@ -249,14 +250,22 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: mid = len(text) // 2 words = [text[:mid], text[mid:]] self._usage += _get_string_usage('') - yield self._parts_manager.handle_text_delta(vendor_part_id=i, content='') - for word in words: - self._usage += _get_string_usage(word) - yield self._parts_manager.handle_text_delta(vendor_part_id=i, content=word) - else: + if part.part_kind == 'text': + yield self._parts_manager.handle_text_delta(vendor_part_id=i, content='') + for word in words: + self._usage += _get_string_usage(word) + yield self._parts_manager.handle_text_delta(vendor_part_id=i, content=word) + else: + yield self._parts_manager.handle_thinking_delta(vendor_part_id=i, content='') + for word in words: + self._usage += _get_string_usage(word) + yield self._parts_manager.handle_thinking_delta(vendor_part_id=i, content=word) + elif isinstance(part, ToolCallPart): yield self._parts_manager.handle_tool_call_part( vendor_part_id=i, tool_name=part.tool_name, args=part.args, tool_call_id=part.tool_call_id ) + else: + assert_never(part) @property def model_name(self) -> str: From aef5b474c660e7ccd3f2917129fb796a8e70d1c1 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 15:22:08 +0200 Subject: [PATCH 14/27] Support Thinking part --- .../pydantic_ai/models/mistral.py | 7 +- .../test_openai_model_thinking_part.yaml | 310 ++++++++++++++++++ tests/models/test_mistral.py | 67 +++- tests/models/test_openai.py | 62 +++- 4 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 tests/models/cassettes/test_openai/test_openai_model_thinking_part.yaml diff --git a/pydantic_ai_slim/pydantic_ai/models/mistral.py b/pydantic_ai_slim/pydantic_ai/models/mistral.py index faabea298..0ec3a63a9 100644 --- a/pydantic_ai_slim/pydantic_ai/models/mistral.py +++ b/pydantic_ai_slim/pydantic_ai/models/mistral.py @@ -11,6 +11,8 @@ from httpx import Timeout from typing_extensions import assert_never +from pydantic_ai._thinking_part import split_content_into_text_and_thinking + from .. import ModelHTTPError, UnexpectedModelBehavior, _utils from .._utils import generate_tool_call_id as _generate_tool_call_id, now_utc as _now_utc from ..messages import ( @@ -25,6 +27,7 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -316,7 +319,7 @@ def _process_response(self, response: MistralChatCompletionResponse) -> ModelRes parts: list[ModelResponsePart] = [] if text := _map_content(content): - parts.append(TextPart(content=text)) + parts.extend(split_content_into_text_and_thinking(text)) if isinstance(tool_calls, list): for tool_call in tool_calls: @@ -474,6 +477,8 @@ def _map_messages(self, messages: list[ModelMessage]) -> list[MistralMessages]: for part in message.parts: if isinstance(part, TextPart): content_chunks.append(MistralTextChunk(text=part.content)) + elif isinstance(part, ThinkingPart): + content_chunks.append(MistralTextChunk(text=f'{part.content}')) elif isinstance(part, ToolCallPart): tool_calls.append(self._map_tool_call(part)) else: diff --git a/tests/models/cassettes/test_openai/test_openai_model_thinking_part.yaml b/tests/models/cassettes/test_openai/test_openai_model_thinking_part.yaml new file mode 100644 index 000000000..2466cdfe7 --- /dev/null +++ b/tests/models/cassettes/test_openai/test_openai_model_thinking_part.yaml @@ -0,0 +1,310 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '150' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: How do I cross the street? + role: user + model: o3-mini + reasoning: + effort: high + summary: detailed + stream: false + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '5667' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '21167' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + created_at: 1745327921 + error: null + id: resp_680797310bbc8191971fff5a405113940ed3ec3064b5efac + incomplete_details: null + instructions: null + max_output_tokens: null + metadata: {} + model: o3-mini-2025-01-31 + object: response + output: + - id: rs_6807973b20f48191bfc1575c3aff7ad70ed3ec3064b5efac + summary: + - text: |- + **Evaluating crossing instructions** + + The user's question about crossing the street seems a bit ambiguous. I suspect they're looking for safety instructions. Given the nature of the request, I should provide clear guidance to ensure safety while crossing. Simple instructions like looking both ways, waiting for the walk signal, and crossing carefully would be appropriate. I want to make sure I'm being responsible and offering helpful, real-life safety information tailored to their query. + type: summary_text + - text: |- + **Clarifying street crossing instructions** + + The user's request, "How do I cross the street?" seems focused on safe pedestrian practices. I believe it's important to clarify and gather context, like asking which street or location they're referencing. Basic safety advice includes looking both ways before crossing, waiting for a clear signal, using crosswalks, and staying aware of surroundings. However, I want to ensure I'm providing guidance that's appropriate and responsible, clearly acknowledging that I'm not a traffic safety professional and encouraging consulting official sources. + type: summary_text + - text: |- + **Providing crossing instructions** + + I need to give clear guidance on crossing the street safely. Key tips include using crosswalks, waiting for walk signals, and looking both ways before crossing. If there are no signals, I should advise waiting for a safe gap in traffic. I'll outline instructions in a friendly tone, featuring a disclaimer that I'm not a professional safety expert. My final list will emphasize awareness and caution, incorporating phrases like, "Don't assume drivers see you," and refer to local traffic laws for the best practices. + type: summary_text + - text: |- + **Compiling crossing instructions** + + I want to make sure my answer provides safe, step-by-step guidelines for crossing the street. It's crucial to stress using crosswalks and following local laws. I'll structure the instructions clearly, starting with a contextual title: "Steps to Safely Cross the Street." The points will include waiting at the edge, looking both ways, waiting for safe gaps in traffic, and remaining alert while crossing. I need to add a disclaimer that this advice is general and should complement local road regulations. + type: summary_text + type: reasoning + - content: + - annotations: [] + text: |- + When crossing the street, safety is the top priority. Always remember that conditions and local traffic rules may vary, so use your best judgment. Here are some general guidelines: + + 1. Find a designated crossing area. If there's a crosswalk or pedestrian signal, use it. These are designed to protect pedestrians. + + 2. Before you step off the curb, pause and look both ways: + • Look to your left. + • Look to your right. + • Look to your left again. + Even if you have a "walk" signal, make sure no vehicles (or bicycles) are approaching. + + 3. Make sure drivers can see you. Even when you have the right-of-way, not all drivers may be alert to your presence. + + 4. If there's a traffic light: + • Wait for the pedestrian signal to indicate it's safe to cross. + • Continue looking out for turning vehicles or unexpected hazards. + + 5. If there isn't a crosswalk or signal: + • Wait for a clear gap in traffic. + • Ensure that drivers have enough time to see and yield for you. + + 6. While crossing: + • Walk at a steady pace—don't run or stop suddenly. + • Keep your eyes on the street so you can react if a vehicle behaves unpredictably. + • Avoid distractions like using your phone or wearing headphones at a high volume. + + 7. Once you're safely across, remain alert to any potential hazards even on the sidewalk. + + These steps are general and meant to help you think through your approach. If you're crossing in an unfamiliar area or in a country with different road rules, it might be a good idea to research local pedestrian guidelines or ask a local for tips. + + Remember, no set of instructions replaces your own caution and attention when navigating traffic. Stay safe! + type: output_text + id: msg_680797458c0481918f9943dc8aec1c520ed3ec3064b5efac + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + reasoning: + effort: high + summary: detailed + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 13 + input_tokens_details: + cached_tokens: 0 + output_tokens: 1900 + output_tokens_details: + reasoning_tokens: 1536 + total_tokens: 1913 + user: null + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4308' + content-type: + - application/json + cookie: + - __cf_bm=t_2d_d5Tn1HQUcewbzItSqtWw1T_nqMVuoVZQQZg2Rk-1745327942-1.0.1.1-4luN5TUFhOQegehIbCloJiy1B616TXFYdp5YrM5cCM8jXDhb2R9woJs5kUT_5P.EAkFLkdzxXKg00GCsSh7W3.xcC.hzAi1u9BA19SnIHJI; + _cfuvid=m8KdsS_VWmRjfmd_23E7BCHBuwP2UxMHz7L3abFekwE-1745327942248-0.0.1.1-604800000 + host: + - api.openai.com + method: POST + parsed_body: + messages: + - content: How do I cross the street? + role: user + - content: |- + When crossing the street, safety is the top priority. Always remember that conditions and local traffic rules may vary, so use your best judgment. Here are some general guidelines: + + 1. Find a designated crossing area. If there's a crosswalk or pedestrian signal, use it. These are designed to protect pedestrians. + + 2. Before you step off the curb, pause and look both ways: + • Look to your left. + • Look to your right. + • Look to your left again. + Even if you have a "walk" signal, make sure no vehicles (or bicycles) are approaching. + + 3. Make sure drivers can see you. Even when you have the right-of-way, not all drivers may be alert to your presence. + + 4. If there's a traffic light: + • Wait for the pedestrian signal to indicate it's safe to cross. + • Continue looking out for turning vehicles or unexpected hazards. + + 5. If there isn't a crosswalk or signal: + • Wait for a clear gap in traffic. + • Ensure that drivers have enough time to see and yield for you. + + 6. While crossing: + • Walk at a steady pace—don't run or stop suddenly. + • Keep your eyes on the street so you can react if a vehicle behaves unpredictably. + • Avoid distractions like using your phone or wearing headphones at a high volume. + + 7. Once you're safely across, remain alert to any potential hazards even on the sidewalk. + + These steps are general and meant to help you think through your approach. If you're crossing in an unfamiliar area or in a country with different road rules, it might be a good idea to research local pedestrian guidelines or ask a local for tips. + + Remember, no set of instructions replaces your own caution and attention when navigating traffic. Stay safe! + + + **Evaluating crossing instructions** + + The user's question about crossing the street seems a bit ambiguous. I suspect they're looking for safety instructions. Given the nature of the request, I should provide clear guidance to ensure safety while crossing. Simple instructions like looking both ways, waiting for the walk signal, and crossing carefully would be appropriate. I want to make sure I'm being responsible and offering helpful, real-life safety information tailored to their query. + + + + **Clarifying street crossing instructions** + + The user's request, "How do I cross the street?" seems focused on safe pedestrian practices. I believe it's important to clarify and gather context, like asking which street or location they're referencing. Basic safety advice includes looking both ways before crossing, waiting for a clear signal, using crosswalks, and staying aware of surroundings. However, I want to ensure I'm providing guidance that's appropriate and responsible, clearly acknowledging that I'm not a traffic safety professional and encouraging consulting official sources. + + + + **Providing crossing instructions** + + I need to give clear guidance on crossing the street safely. Key tips include using crosswalks, waiting for walk signals, and looking both ways before crossing. If there are no signals, I should advise waiting for a safe gap in traffic. I'll outline instructions in a friendly tone, featuring a disclaimer that I'm not a professional safety expert. My final list will emphasize awareness and caution, incorporating phrases like, "Don't assume drivers see you," and refer to local traffic laws for the best practices. + + + + **Compiling crossing instructions** + + I want to make sure my answer provides safe, step-by-step guidelines for crossing the street. It's crucial to stress using crosswalks and following local laws. I'll structure the instructions clearly, starting with a contextual title: "Steps to Safely Cross the Street." The points will include waiting at the edge, looking both ways, waiting for safe gaps in traffic, and remaining alert while crossing. I need to add a disclaimer that this advice is general and should complement local road regulations. + + role: assistant + - content: Considering the way to cross the street, analogously, how do I cross the river? + role: user + model: o3-mini + n: 1 + reasoning_effort: high + stream: false + uri: https://api.openai.com/v1/chat/completions + response: + headers: + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '3912' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '21626' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + choices: + - finish_reason: stop + index: 0 + message: + annotations: [] + content: "Crossing a river safely can be a lot more challenging than crossing a street—you need to consider factors + like water depth, flow rate, temperature, and the terrain. While I'm not a professional outdoorsman or safety + expert, here are some general guidelines you might consider, keeping in mind that every river and situation is + different:\n\n1. Plan Ahead and Evaluate the Situation \n • Assess the river's width, depth, and current. \n • + Determine water temperature (cold water can lead to hypothermia quickly). \n • Look for natural features that + might create a safe crossing point (e.g., a ford, stepping stones, or gradual, shallow water). \n • Whenever + possible, use man-made crossings like bridges or ferries rather than attempting to ford the river on your own.\n\n2. + Choose Your Crossing Point Carefully \n • If you see a naturally shallow area with a gentle current and stable + riverbed, that might be a better option. \n • Look for signs that others have crossed there safely before. \n • + Avoid areas with slippery rocks, sudden drop-offs, or fast-moving water.\n\n3. Prepare for the Crossing \n • + Consider wearing proper footwear that provides good grip. \n • Remove heavy or cumbersome items that could make + you lose balance. \n • If you have one available, use a sturdy stick or pole for extra balance. \n • If possible, + don't cross alone—having someone with you increases safety. \n • Use a life jacket or personal flotation device + (PFD), especially if the water is deep or moving swiftly.\n\n4. Test the Conditions \n • Before fully committing, + carefully test the water with your foot to see how stable it is. \n • Check if the riverbed is firm enough to + support your weight, ensuring there's no hidden risk of slipping or sudden drops.\n\n5. Crossing Technique \n • + Step slowly and deliberately, testing each foothold as you go. \n • Face upstream so you can see the water's + movement and maintain your balance using any stable features (like large rocks or riverbanks) as support. \n • + Keep your body low and your center of gravity steady—this can help you react if the footing becomes unstable.\n • + If using the water (i.e., wading), move perpendicular to the current rather than directly against it to minimize + the force against you.\n\n6. Know When to Turn Back \n • If at any point conditions seem too dangerous (e.g., + unexpected strong currents, high water levels, or inclement weather), or you feel unsteady, don't force the crossing.\n • + It's far safer to look for an alternate route (like a bridge or a ferry), even if it means a longer detour.\n\nRemember, + these guidelines are provided for informational purposes only. Different rivers—even those that look similar—can + have very different conditions, and what works in one situation might not be safe in another. Always prioritize + your personal safety by assessing the risks, and when in doubt, seek advice from local experts or authorities + who know the area.\n\nStay safe and plan carefully before attempting to cross any river!" + refusal: null + role: assistant + created: 1745327942 + id: chatcmpl-BP7ocN6qxho4C1UzUJWnU5tPJno55 + model: o3-mini-2025-01-31 + object: chat.completion + service_tier: default + system_fingerprint: fp_2d4670fb9a + usage: + completion_tokens: 2437 + completion_tokens_details: + accepted_prediction_tokens: 0 + audio_tokens: 0 + reasoning_tokens: 1792 + rejected_prediction_tokens: 0 + prompt_tokens: 822 + prompt_tokens_details: + audio_tokens: 0 + cached_tokens: 0 + total_tokens: 3259 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/test_mistral.py b/tests/models/test_mistral.py index 3a4981629..ff68840fe 100644 --- a/tests/models/test_mistral.py +++ b/tests/models/test_mistral.py @@ -22,13 +22,14 @@ RetryPromptPart, SystemPromptPart, TextPart, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, VideoUrl, ) -from ..conftest import IsDatetime, IsNow, raise_if_exception, try_import +from ..conftest import IsDatetime, IsNow, IsStr, raise_if_exception, try_import from .mock_async_stream import MockAsyncStream with try_import() as imports_successful: @@ -53,14 +54,16 @@ from mistralai.types.basemodel import Unset as MistralUnset from pydantic_ai.models.mistral import MistralModel, MistralStreamedResponse + from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings from pydantic_ai.providers.mistral import MistralProvider + from pydantic_ai.providers.openai import OpenAIProvider # note: we use Union here so that casting works with Python 3.9 MockChatCompletion = Union[MistralChatCompletionResponse, Exception] MockCompletionEvent = Union[MistralCompletionEvent, Exception] pytestmark = [ - pytest.mark.skipif(not imports_successful(), reason='mistral not installed'), + pytest.mark.skipif(not imports_successful(), reason='mistral or openai not installed'), pytest.mark.anyio, ] @@ -1862,3 +1865,63 @@ async def test_mistral_model_instructions(allow_model_requests: None, mistral_ap ), ] ) + + +async def test_mistral_model_thinking_part(allow_model_requests: None, openai_api_key: str, mistral_api_key: str): + openai_model = OpenAIResponsesModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key)) + settings = OpenAIResponsesModelSettings(openai_reasoning_effort='high', openai_reasoning_summary='detailed') + agent = Agent(openai_model, model_settings=settings) + + result = await agent.run('How do I cross the street?') + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + TextPart(content=IsStr()), + ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ] + ) + + mistral_model = MistralModel('mistral-large-latest', provider=MistralProvider(api_key=mistral_api_key)) + result = await agent.run( + 'Considering the way to cross the street, analogously, how do I cross the river?', + model=mistral_model, + message_history=result.all_messages(), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + TextPart(content=IsStr()), + ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ModelRequest( + parts=[ + UserPromptPart( + content='Considering the way to cross the street, analogously, how do I cross the river?', + timestamp=IsDatetime(), + ) + ] + ), + ModelResponse( + parts=[TextPart(content=IsStr())], + model_name='mistral-large-latest', + timestamp=IsDatetime(), + ), + ] + ) diff --git a/tests/models/test_openai.py b/tests/models/test_openai.py index 6be457ad9..f7741930e 100644 --- a/tests/models/test_openai.py +++ b/tests/models/test_openai.py @@ -33,7 +33,7 @@ from pydantic_ai.result import Usage from pydantic_ai.settings import ModelSettings -from ..conftest import IsDatetime, IsNow, IsStr, raise_if_exception, try_import +from ..conftest import IsDatetime, IsInstance, IsNow, IsStr, raise_if_exception, try_import from .mock_async_stream import MockAsyncStream with try_import() as imports_successful: @@ -1351,3 +1351,63 @@ async def test_openai_responses_model_thinking_part(allow_model_requests: None, ), ] ) + + +async def test_openai_model_thinking_part(allow_model_requests: None, openai_api_key: str): + provider = OpenAIProvider(api_key=openai_api_key) + responses_model = OpenAIResponsesModel('o3-mini', provider=provider) + settings = OpenAIResponsesModelSettings(openai_reasoning_effort='high', openai_reasoning_summary='detailed') + agent = Agent(responses_model, model_settings=settings) + + result = await agent.run('How do I cross the street?') + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + TextPart(content=IsStr()), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ] + ) + + result = await agent.run( + 'Considering the way to cross the street, analogously, how do I cross the river?', + model=OpenAIModel('o3-mini', provider=provider), + message_history=result.all_messages(), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + TextPart(content=IsStr()), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + IsInstance(ThinkingPart), + ], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ModelRequest( + parts=[ + UserPromptPart( + content='Considering the way to cross the street, analogously, how do I cross the river?', + timestamp=IsDatetime(), + ) + ] + ), + ModelResponse( + parts=[TextPart(content=IsStr())], + model_name='o3-mini-2025-01-31', + timestamp=IsDatetime(), + ), + ] + ) From 4e92754c8a93fdae4de0fff6d90c3b4755a8fb5e Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 15:36:07 +0200 Subject: [PATCH 15/27] Support Thinking part --- pydantic_ai_slim/pydantic_ai/models/test.py | 3 +- .../test_mistral_model_thinking_part.yaml | 313 ++++++++++++++++++ tests/models/test_mistral.py | 17 +- 3 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 tests/models/cassettes/test_mistral/test_mistral_model_thinking_part.yaml diff --git a/pydantic_ai_slim/pydantic_ai/models/test.py b/pydantic_ai_slim/pydantic_ai/models/test.py index 56c530d0c..68b095cdf 100644 --- a/pydantic_ai_slim/pydantic_ai/models/test.py +++ b/pydantic_ai_slim/pydantic_ai/models/test.py @@ -6,9 +6,10 @@ from contextlib import asynccontextmanager from dataclasses import InitVar, dataclass, field from datetime import date, datetime, timedelta -from typing import Any, Literal, assert_never +from typing import Any, Literal import pydantic_core +from typing_extensions import assert_never from .. import _utils from ..messages import ( diff --git a/tests/models/cassettes/test_mistral/test_mistral_model_thinking_part.yaml b/tests/models/cassettes/test_mistral/test_mistral_model_thinking_part.yaml new file mode 100644 index 000000000..4dd395182 --- /dev/null +++ b/tests/models/cassettes/test_mistral/test_mistral_model_thinking_part.yaml @@ -0,0 +1,313 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '150' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: How do I cross the street? + role: user + model: o3-mini + reasoning: + effort: high + summary: detailed + stream: false + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '6152' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '22216' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + created_at: 1745328846 + error: null + id: resp_68079acebbfc819189ec20e1e5bf525d0493b22e4095129c + incomplete_details: null + instructions: null + max_output_tokens: null + metadata: {} + model: o3-mini-2025-01-31 + object: response + output: + - id: rs_68079ad7f0588191af64f067e7314d840493b22e4095129c + summary: + - text: |- + **Clarifying street crossing instructions** + + The user's question, "How do I cross the street?" seems ambiguous and might require clarification. Is this about physical crossing instructions or safety? I think it's essential to provide basic safety guidelines for pedestrians. However, there's also a chance this could be interpreted as seeking help for self-harm, so I need to respond carefully. I aim to produce a safe response that focuses on pedestrian safety. Let's ensure the guidance is clear and helpful. + type: summary_text + - text: |- + **Providing safe crossing instructions** + + When addressing the question "How do I cross the street?" I need to focus on pedestrian safety. It's important to provide clear guidance without jumping to any self-harm interpretations. I'll mention basic steps like using crosswalks when available, waiting for a green signal, and looking both ways before crossing. It's essential to add a disclaimer that I'm not a safety expert, but I aim to offer helpful general safety advice. Let's ensure the instructions are straightforward and practical. + type: summary_text + - text: |- + **Drafting pedestrian safety tips** + + I need to provide clear guidance on safely crossing the street while including a disclaimer that I'm not a licensed safety professional. My tips will cover following local road rules, stopping at the curb, and looking for traffic. This question might seem philosophical, but I'll assume a practical approach. I'll create step-by-step instructions like assessing traffic, using crosswalks, and remaining aware of blind spots. I'll also emphasize the importance of staying alert and following pedestrian signals, all while disclosing that my advice is general and should be double-checked against local laws. + type: summary_text + - text: |- + **Preparing street crossing guidance** + + I need to ensure I address different crossing scenarios, like when there isn't a clear crosswalk or if it's a busy street with or without traffic signals. My goal is to offer general guidelines to cover these situations. If the user asks, "How do I cross the street?" I can provide detailed instructions. I'll focus on creating a clear and informative text message to communicate the final answer effectively. + type: summary_text + type: reasoning + - content: + - annotations: [] + text: |- + I'm not a traffic safety expert, but here are some general guidelines that many people follow when crossing a street safely. Remember that local rules and conditions might vary, so always follow local traffic laws and pay close attention to your surroundings. + + 1. Find a Designated Crossing Point + • Look for crosswalks, pedestrian signals, or marked intersections. These areas are designed for safe crossing. + • If no crosswalk is available, choose a spot where you have clear visibility of oncoming traffic in all directions. + + 2. Stop at the Curb or Edge of the Road + • Before stepping off the curb, pause to assess the situation. + • Resist the urge to step into the street immediately—this helps you avoid unpredictable traffic behavior. + + 3. Look and Listen + • Look left, then right, and left again. In some places, you might need to check right a second time depending on the flow of traffic. + • Pay attention to the sound of approaching vehicles or any signals that indicate vehicles may be turning into your path. + • Remove or lower distractions like headphones so you can be fully aware of your environment. + + 4. Follow Pedestrian Signals (if available) + • If you're at an intersection with traffic signals, wait for the "Walk" signal. + • Even when the signal is in your favor, ensure that any turning vehicles (cars or bikes) see you and are stopping. + + 5. Make Eye Contact + • If possible, make eye contact with drivers who might be turning. This can help ensure that they see you and are taking appropriate action. + + 6. Cross Quickly and Carefully + • Once you've determined that it's safe, proceed at a steady pace. + • Continue to be alert while you cross, watching for any unexpected vehicle movements. + + 7. Stay on the Sidewalk Once You've Crossed + • After reaching the other side, stick to areas designated for pedestrians rather than walking immediately back into the roadway. + + These suggestions are meant to help you think through pedestrian safety. Different regions may have additional rules or different signals, so if you're unsure, it might help to check local guidelines or ask someone familiar with the area. Stay safe! + type: output_text + id: msg_68079ae3ee808191ad9b31642244f3340493b22e4095129c + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + reasoning: + effort: high + summary: detailed + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 13 + input_tokens_details: + cached_tokens: 0 + output_tokens: 1789 + output_tokens_details: + reasoning_tokens: 1344 + total_tokens: 1802 + user: null + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '5019' + content-type: + - application/json + host: + - api.mistral.ai + method: POST + parsed_body: + messages: + - content: How do I cross the street? + role: user + - content: + - text: |- + I'm not a traffic safety expert, but here are some general guidelines that many people follow when crossing a street safely. Remember that local rules and conditions might vary, so always follow local traffic laws and pay close attention to your surroundings. + + 1. Find a Designated Crossing Point + • Look for crosswalks, pedestrian signals, or marked intersections. These areas are designed for safe crossing. + • If no crosswalk is available, choose a spot where you have clear visibility of oncoming traffic in all directions. + + 2. Stop at the Curb or Edge of the Road + • Before stepping off the curb, pause to assess the situation. + • Resist the urge to step into the street immediately—this helps you avoid unpredictable traffic behavior. + + 3. Look and Listen + • Look left, then right, and left again. In some places, you might need to check right a second time depending on the flow of traffic. + • Pay attention to the sound of approaching vehicles or any signals that indicate vehicles may be turning into your path. + • Remove or lower distractions like headphones so you can be fully aware of your environment. + + 4. Follow Pedestrian Signals (if available) + • If you're at an intersection with traffic signals, wait for the "Walk" signal. + • Even when the signal is in your favor, ensure that any turning vehicles (cars or bikes) see you and are stopping. + + 5. Make Eye Contact + • If possible, make eye contact with drivers who might be turning. This can help ensure that they see you and are taking appropriate action. + + 6. Cross Quickly and Carefully + • Once you've determined that it's safe, proceed at a steady pace. + • Continue to be alert while you cross, watching for any unexpected vehicle movements. + + 7. Stay on the Sidewalk Once You've Crossed + • After reaching the other side, stick to areas designated for pedestrians rather than walking immediately back into the roadway. + + These suggestions are meant to help you think through pedestrian safety. Different regions may have additional rules or different signals, so if you're unsure, it might help to check local guidelines or ask someone familiar with the area. Stay safe! + type: text + - text: |- + **Clarifying street crossing instructions** + + The user's question, "How do I cross the street?" seems ambiguous and might require clarification. Is this about physical crossing instructions or safety? I think it's essential to provide basic safety guidelines for pedestrians. However, there's also a chance this could be interpreted as seeking help for self-harm, so I need to respond carefully. I aim to produce a safe response that focuses on pedestrian safety. Let's ensure the guidance is clear and helpful. + type: text + - text: |- + **Providing safe crossing instructions** + + When addressing the question "How do I cross the street?" I need to focus on pedestrian safety. It's important to provide clear guidance without jumping to any self-harm interpretations. I'll mention basic steps like using crosswalks when available, waiting for a green signal, and looking both ways before crossing. It's essential to add a disclaimer that I'm not a safety expert, but I aim to offer helpful general safety advice. Let's ensure the instructions are straightforward and practical. + type: text + - text: |- + **Drafting pedestrian safety tips** + + I need to provide clear guidance on safely crossing the street while including a disclaimer that I'm not a licensed safety professional. My tips will cover following local road rules, stopping at the curb, and looking for traffic. This question might seem philosophical, but I'll assume a practical approach. I'll create step-by-step instructions like assessing traffic, using crosswalks, and remaining aware of blind spots. I'll also emphasize the importance of staying alert and following pedestrian signals, all while disclosing that my advice is general and should be double-checked against local laws. + type: text + - text: |- + **Preparing street crossing guidance** + + I need to ensure I address different crossing scenarios, like when there isn't a clear crosswalk or if it's a busy street with or without traffic signals. My goal is to offer general guidelines to cover these situations. If the user asks, "How do I cross the street?" I can provide detailed instructions. I'll focus on creating a clear and informative text message to communicate the final answer effectively. + type: text + prefix: false + role: assistant + tool_calls: [] + - content: Considering the way to cross the street, analogously, how do I cross the river? + role: user + model: mistral-large-latest + n: 1 + stream: false + top_p: 1.0 + uri: https://api.mistral.ai/v1/chat/completions + response: + headers: + access-control-allow-origin: + - '*' + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '3153' + content-type: + - application/json + ratelimitbysize-limit: + - '5000000' + ratelimitbysize-query-cost: + - '32028' + ratelimitbysize-remaining: + - '4967972' + ratelimitbysize-reset: + - '31' + transfer-encoding: + - chunked + parsed_body: + choices: + - finish_reason: stop + index: 0 + message: + content: |- + Crossing a river can be more complex and potentially dangerous than crossing a street, so it's important to approach this situation with caution. Here are some general guidelines, but remember that safety should always be your top priority. If a river seems too deep, too fast, or otherwise unsafe, do not attempt to cross it. + + 1. **Assess the River:** + - **Depth:** Determine if the river is shallow enough to wade through. If the water is above your knees, it can be difficult and dangerous to cross due to the current. + - **Flow Rate:** Pay close attention to how fast the water is moving. Even shallow water can be dangerous if the current is strong. + - **Visibility:** Clear water makes it easier to see the riverbed and any potential hazards. + + 2. **Find a Safe Crossing Point:** + - Look for a wide, shallow section of the river with a gentle current. + - Avoid areas with rapids, deep pools, or steep drops. + - Bridges, fallen logs, or shallow fords are good places to consider crossing. + + 3. **Check for Hazards:** + - Look for submerged rocks, logs, or other debris that could trip you or cause you to lose your footing. + - Be aware of any wildlife that might be present, such as snakes or aggressive animals. + + 4. **Use a Stick or Pole:** + - A sturdy stick or pole can help you maintain balance and probe the riverbed for hidden obstacles or changes in depth. + + 5. **Face Upstream:** + - When crossing, face upstream and move sideways. This helps you maintain balance against the current. + + 6. **Take Small Steps:** + - Shuffle your feet rather than taking large steps. This helps you feel the riverbed and maintain stability. + + 7. **Stay Calm:** + - If you lose your footing, try to stay calm and float with the current until you can regain your balance or reach shallower water. + + 8. **Use a Rope (if available):** + - If you have a rope, you can secure it to a sturdy object on one side of the river and use it as a guide while crossing. + + 9. **Cross in a Group (if possible):** + - Crossing with others can provide additional support and safety. You can form a human chain by holding hands or using ropes to assist each other. + + 10. **Dry Yourself Afterwards:** + - Once you've crossed, make sure to dry off and warm up, especially if the water was cold. Hypothermia can be a risk even in relatively warm conditions. + + **Safety First:** + - **Never cross a river if you are unsure about your abilities or the safety of the crossing point.** + - **Always prioritize your safety and the safety of others.** + - **If in doubt, seek an alternative route or wait for conditions to improve.** + + These guidelines are meant to help you think through river crossing safety. Different rivers and conditions might require specific approaches, so always use your best judgment and prioritize safety. + role: assistant + tool_calls: null + created: 1745328869 + id: a088e80a476e44edaaa959a1ff08f358 + model: mistral-large-latest + object: chat.completion + usage: + completion_tokens: 691 + prompt_tokens: 1036 + total_tokens: 1727 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/test_mistral.py b/tests/models/test_mistral.py index ff68840fe..a0c5f099a 100644 --- a/tests/models/test_mistral.py +++ b/tests/models/test_mistral.py @@ -1867,6 +1867,7 @@ async def test_mistral_model_instructions(allow_model_requests: None, mistral_ap ) +@pytest.mark.vcr() async def test_mistral_model_thinking_part(allow_model_requests: None, openai_api_key: str, mistral_api_key: str): openai_model = OpenAIResponsesModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key)) settings = OpenAIResponsesModelSettings(openai_reasoning_effort='high', openai_reasoning_summary='detailed') @@ -1879,10 +1880,10 @@ async def test_mistral_model_thinking_part(allow_model_requests: None, openai_ap ModelResponse( parts=[ TextPart(content=IsStr()), - ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), - ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), - ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), - ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ThinkingPart(content=IsStr(), signature='rs_68079ad7f0588191af64f067e7314d840493b22e4095129c'), + ThinkingPart(content=IsStr(), signature='rs_68079ad7f0588191af64f067e7314d840493b22e4095129c'), + ThinkingPart(content=IsStr(), signature='rs_68079ad7f0588191af64f067e7314d840493b22e4095129c'), + ThinkingPart(content=IsStr(), signature='rs_68079ad7f0588191af64f067e7314d840493b22e4095129c'), ], model_name='o3-mini-2025-01-31', timestamp=IsDatetime(), @@ -1902,10 +1903,10 @@ async def test_mistral_model_thinking_part(allow_model_requests: None, openai_ap ModelResponse( parts=[ TextPart(content=IsStr()), - ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), - ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), - ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), - ThinkingPart(content=IsStr(), signature='rs_68079629f6048191a22448740e41d3290a3deaf82386dfb9'), + ThinkingPart(content=IsStr(), signature='rs_68079ad7f0588191af64f067e7314d840493b22e4095129c'), + ThinkingPart(content=IsStr(), signature='rs_68079ad7f0588191af64f067e7314d840493b22e4095129c'), + ThinkingPart(content=IsStr(), signature='rs_68079ad7f0588191af64f067e7314d840493b22e4095129c'), + ThinkingPart(content=IsStr(), signature='rs_68079ad7f0588191af64f067e7314d840493b22e4095129c'), ], model_name='o3-mini-2025-01-31', timestamp=IsDatetime(), From 805088f8e9edfeb0d92cad1486ec8b50af4fff5b Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 16:45:52 +0200 Subject: [PATCH 16/27] Add support for bedrock --- .../pydantic_ai/models/bedrock.py | 3 + pydantic_ai_slim/pydantic_ai/models/openai.py | 2 + tests/conftest.py | 5 + .../test_bedrock_model_thinking_part.yaml | 198 ++++++++++++++++++ .../test_deepseek_model_thinking_part.yaml | 91 ++++++++ tests/models/test_bedrock.py | 49 ++++- tests/models/test_deepseek.py | 42 ++++ 7 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml create mode 100644 tests/models/cassettes/test_deepseek/test_deepseek_model_thinking_part.yaml create mode 100644 tests/models/test_deepseek.py diff --git a/pydantic_ai_slim/pydantic_ai/models/bedrock.py b/pydantic_ai_slim/pydantic_ai/models/bedrock.py index d5bd68e81..1b3f48059 100644 --- a/pydantic_ai_slim/pydantic_ai/models/bedrock.py +++ b/pydantic_ai_slim/pydantic_ai/models/bedrock.py @@ -425,6 +425,9 @@ async def _map_messages( for item in m.parts: if isinstance(item, TextPart): content.append({'text': item.content}) + elif isinstance(item, ThinkingPart): + # NOTE: We don't pass the thinking part to Bedrock since it raises an error. + pass else: assert isinstance(item, ToolCallPart) content.append(self._map_tool_call(item)) diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 419ad9a1f..07650d506 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -303,6 +303,8 @@ def _process_response(self, response: chat.ChatCompletion) -> ModelResponse: timestamp = datetime.fromtimestamp(response.created, tz=timezone.utc) choice = response.choices[0] items: list[ModelResponsePart] = [] + if reasoning_content := getattr(choice.message, 'reasoning_content', None): + items.append(ThinkingPart(content=reasoning_content)) if choice.message.content is not None: items.extend(split_content_into_text_and_thinking(choice.message.content)) if choice.message.tool_calls is not None: diff --git a/tests/conftest.py b/tests/conftest.py index 5e988afbc..312d13f24 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -246,6 +246,11 @@ def document_content(assets_path: Path) -> BinaryContent: return BinaryContent(data=pdf_bytes, media_type='application/pdf') +@pytest.fixture(scope='session') +def deepseek_api_key() -> str: + return os.getenv('DEEPSEEK_API_KEY', 'mock-api-key') + + @pytest.fixture(scope='session') def openai_api_key() -> str: return os.getenv('OPENAI_API_KEY', 'mock-api-key') diff --git a/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml b/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml new file mode 100644 index 000000000..08131b9ee --- /dev/null +++ b/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml @@ -0,0 +1,198 @@ +interactions: +- request: + body: '{"messages": [{"role": "user", "content": [{"text": "How do I cross the street?"}]}], "system": [], "inferenceConfig": + {}}' + headers: + amz-sdk-invocation-id: + - !!binary | + YTJmYWNmMWYtYjBhNy00MWZmLWFmZDEtY2RmYTdjZDgyZjAz + amz-sdk-request: + - !!binary | + YXR0ZW1wdD0x + content-length: + - '122' + content-type: + - !!binary | + YXBwbGljYXRpb24vanNvbg== + method: POST + uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.deepseek.r1-v1%3A0/converse + response: + headers: + connection: + - keep-alive + content-length: + - '4209' + content-type: + - application/json + parsed_body: + metrics: + latencyMs: 16152 + output: + message: + content: + - text: "\n\nCrossing the street safely involves following key steps to ensure your safety. Here's a clear guide:\n\n1. + **Find a Safe Spot**: \n - Use a **crosswalk** or pedestrian signal if available. Look for marked zebra crossings, + traffic lights, or overhead signs. \n - If none are nearby, choose a location with clear visibility in both + directions. Avoid curves, hills, or parked cars that block your view.\n\n2. **Check Traffic Signals**: \n - + Obey pedestrian signals (e.g., \"Walk\" or green man). Wait for the signal to indicate it's safe. \n - If + there are no signals, wait until traffic is fully stopped or there's a large enough gap to cross safely.\n\n3. + **Look Both Ways**: \n - **Left, right, then left again** (or right first in countries where traffic drives + on the left, like the UK or Japan). \n - Listen for approaching vehicles, bikes, or motorcycles. Remove headphones + and stay focused.\n\n4. **Make Eye Contact**: \n - Ensure drivers see you before stepping into the road. + Don't assume they'll stop, even at crosswalks.\n\n5. **Cross Carefully**: \n - Walk briskly, don't run. Stay + alert and keep looking for traffic as you cross. \n - If there's a median (middle island), treat it as two + separate crossings. Pause there if needed.\n\n6. **Stay Visible**: \n - At night, wear reflective clothing + or carry a light. Avoid crossing in poorly lit areas.\n\n7. **Special Situations**: \n - **Roundabouts**: + Cross one direction at a time, using splitter islands. \n - **Children**: Hold hands with adults and reinforce + the \"stop, look, listen\" rule. \n - **Turning vehicles**: Watch for cars turning into your path, even if + you have a \"Walk\" signal.\n\n**Safety Tips**: \n- Never assume a vehicle will stop. \n- Avoid distractions + like phones. \n- Follow local laws (e.g., jaywalking fines). \n\nBy staying alert and patient, you can cross + safely in any environment! \U0001F6B6♂️\U0001F6A6" + - reasoningContent: + reasoningText: + text: | + Okay, the user is asking how to cross the street. Let me think about the best way to explain this. First, I need to cover the basic steps clearly. They might be a child, a tourist unfamiliar with local traffic rules, or someone who needs a refresher. I should start with finding a safe place to cross. + + So, step one: look for a crosswalk or pedestrian crossing. If there's a traffic light, mention obeying the signals. Wait for the green light or the walk signal. But if there's no crosswalk, they should choose a spot with good visibility, where they can see oncoming traffic from both directions. + + Next, checking for traffic. They need to look left and right, and maybe even listen for cars. Remind them that in some countries, traffic drives on the left, so the direction to look first might vary. Also, watch out for bicycles or motorcycles that might be less visible. + + Making eye contact with drivers is important. Sometimes drivers might not stop, so it's safer to ensure they've been seen. Then, cross straight and stay alert. Don't run, but walk briskly. Avoid distractions like phones or headphones. + + If there's a median, mention using it as a halfway point. For areas without marked crossings, emphasize extra caution. Maybe add tips about crossing at night, like wearing reflective clothing. Also, remind them to follow local laws, like jaywalking rules. + + Wait, I should check if I missed anything. Oh, maybe mention looking both ways even if there's a signal, in case a car runs a red light. Also, teaching kids to hold an adult's hand. And note that in some places, turning vehicles might have a green light while pedestrians are crossing, so checking for turning cars is important. + + Hmm, should I structure this in numbered steps? That might be clearer. Let me organize the information step by step, making sure each point is concise. Avoid jargon, keep it simple. Maybe add some safety tips at the end, like not assuming vehicles will stop. Yeah, that makes sense. Let me put it all together now. + role: assistant + stopReason: end_turn + usage: + inputTokens: 12 + outputTokens: 890 + totalTokens: 902 + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "user", "content": [{"text": "How do I cross the street?"}]}, {"role": "assistant", "content": + [{"text": "\n\nCrossing the street safely involves following key steps to ensure your safety. Here''s a clear guide:\n\n1. + **Find a Safe Spot**: \n - Use a **crosswalk** or pedestrian signal if available. Look for marked zebra crossings, + traffic lights, or overhead signs. \n - If none are nearby, choose a location with clear visibility in both directions. + Avoid curves, hills, or parked cars that block your view.\n\n2. **Check Traffic Signals**: \n - Obey pedestrian signals + (e.g., \"Walk\" or green man). Wait for the signal to indicate it\u2019s safe. \n - If there are no signals, wait + until traffic is fully stopped or there\u2019s a large enough gap to cross safely.\n\n3. **Look Both Ways**: \n - + **Left, right, then left again** (or right first in countries where traffic drives on the left, like the UK or Japan). \n - + Listen for approaching vehicles, bikes, or motorcycles. Remove headphones and stay focused.\n\n4. **Make Eye Contact**: \n - + Ensure drivers see you before stepping into the road. Don\u2019t assume they\u2019ll stop, even at crosswalks.\n\n5. + **Cross Carefully**: \n - Walk briskly, don\u2019t run. Stay alert and keep looking for traffic as you cross. \n - + If there\u2019s a median (middle island), treat it as two separate crossings. Pause there if needed.\n\n6. **Stay Visible**: \n - + At night, wear reflective clothing or carry a light. Avoid crossing in poorly lit areas.\n\n7. **Special Situations**: \n - + **Roundabouts**: Cross one direction at a time, using splitter islands. \n - **Children**: Hold hands with adults + and reinforce the \"stop, look, listen\" rule. \n - **Turning vehicles**: Watch for cars turning into your path, + even if you have a \"Walk\" signal.\n\n**Safety Tips**: \n- Never assume a vehicle will stop. \n- Avoid distractions + like phones. \n- Follow local laws (e.g., jaywalking fines). \n\nBy staying alert and patient, you can cross safely + in any environment! \ud83d\udeb6\u2642\ufe0f\ud83d\udea6"}]}, {"role": "user", "content": [{"text": "Considering the + way to cross the street, analogously, how do I cross the river?"}]}], "system": [], "inferenceConfig": {}}' + headers: + amz-sdk-invocation-id: + - !!binary | + YWI5ZDlhOGEtNTRhOC00MWUwLThlNmEtYjkwZmM5ZDZiNjRi + amz-sdk-request: + - !!binary | + YXR0ZW1wdD0x + content-length: + - '2284' + content-type: + - !!binary | + YXBwbGljYXRpb24vanNvbg== + method: POST + uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.deepseek.r1-v1%3A0/converse + response: + headers: + connection: + - keep-alive + content-length: + - '6938' + content-type: + - application/json + parsed_body: + metrics: + latencyMs: 20753 + output: + message: + content: + - text: "\n\nCrossing a river, like crossing a street, requires careful planning, awareness of hazards, and methodical + execution. Here's an analogous guide to river crossing, prioritizing safety and strategy:\n\n---\n\n### **1. + Assess the Situation** (Analogous to *Finding a Safe Spot*) \n - **Research the river**: Understand its depth, + current strength, and seasonal behavior (e.g., snowmelt can increase flow). \n - **Look for hazards**: Rapids, + submerged rocks, logs (\"strainers\"), deep pools, or unstable banks. \n - **Choose a crossing point**: \n + \ - **Wider sections** often mean slower currents. \n - **Shallow, calm areas** with visible gravel + or sand bottoms. \n - Avoid bends where currents are stronger. \n\n---\n\n### **2. Prepare Yourself** + (Analogous to *Checking Traffic Signals*) \n - **Wear proper footwear**: Sturdy shoes with grip for slippery + rocks. \n - **Unbuckle backpack straps**: In case you fall, you can shed weight quickly. \n - **Use a + walking stick or pole**: For stability against the current. \n - **Form a team**: Cross with others for support + (link arms or form a human chain). \n\n---\n\n### **3. Test the Current** (Analogous to *Looking Both Ways*) + \ \n - **Enter slowly**: Wade in upstream of your target exit point (the current will push you downstream). + \ \n - **Face upstream**: Lean into the current, planting your feet firmly. \n - **Probe depth**: Use your + stick to test the riverbed ahead. If water reaches your thighs, reconsider crossing. \n\n---\n\n### **4. Cross + Strategically** (Analogous to *Crossing Carefully*) \n - **Move diagonally downstream**: Angle your path + to work with the current. \n - **Shuffle sideways**: Don't lift your feet high; slide them to maintain balance. + \ \n - **Stay low**: Bend your knees to lower your center of gravity. \n\n---\n\n### **5. Watch for Hidden + Dangers** (Analogous to *Making Eye Contact*) \n - **Avoid \"strainers\"**: Branches or logs that water flows + through—these can trap you. \n - **Beware of slippery rocks**: Algae-covered surfaces are treacherous. \n + \ - **Cold water risk**: Hypothermia can set in quickly—move fast in icy rivers. \n\n---\n\n### **6. Exit + Safely** (Analogous to *Staying Visible*) \n - **Climb out downstream**: Currents are weaker near banks. + Use vegetation or rocks for support. \n - **Dry off and reassess**: Change clothes if wet to avoid hypothermia. + \ \n\n---\n\n### **Special Situations**: \n - **Flooded rivers**: Never attempt—even shallow floodwater can + have deadly force. \n - **Swiftwater**: Use a rope system or turn back if unmanageable. \n - **Wildlife**: + In some regions, be aware of animals (e.g., snakes, hippos, crocodiles). \n\n---\n\n### **Safety Tips**: \n + \ - **Never cross alone** if avoidable. \n - **If you fall**: Float on your back, feet downstream, and paddle + to shore. \n - **Know when to quit**: If uncertain, find another route or wait for conditions to improve. + \ \n\n---\n\n**Analogy Recap**: \nJust as you'd prioritize crosswalks and traffic signals on a street, rivers + demand respect for natural forces. Preparation, patience, and adaptability are key to safely navigating both! + \U0001F30A\U0001F6B6♀️" + - reasoningContent: + reasoningText: + text: "Okay, the user is asking how to cross a river, using the analogy of crossing a street. Let me start + by understanding the original question and the previous answer. The user wanted to know how to cross a street + safely, and I provided a detailed step-by-step guide. Now, they want a similar approach for crossing a river.\n\nFirst, + I need to figure out the key elements of crossing a street and map them to river crossing. The street guide + involved safety, checking surroundings, using proper signals, etc. For a river, safety is even more critical + because rivers have currents, variable depths, and hidden hazards. \n\nI should start by breaking down the + process into steps similar to the street crossing: preparation, assessing the situation, choosing the right + spot, checking for dangers, using proper techniques, and post-crossing actions. \n\nPreparation would involve + researching the river, checking weather, and having the right gear. Assessing the river's conditions is + like checking traffic signals; you need to know the depth, current speed, and hazards. Choosing the crossing + point is analogous to finding a safe spot on the street. \n\nDangers in a river include strong currents, + slippery rocks, and cold water. Techniques like facing upstream, using a pole for stability, and moving + diagonally could be similar to looking both ways and making eye contact with drivers. Team crossing strategies + would mirror group safety in street crossing. \n\nI need to mention special situations like cold water risks, + flood conditions, and wildlife. Safety tips should emphasize never crossing alone, avoiding flooded rivers, + and knowing when to turn back. \n\nWait, the user might be thinking metaphorically, but the previous answer + was literal. Should I address both literal and metaphorical aspects? The original question was literal, + so maybe stick to the literal approach here. \n\nAlso, consider the user's possible scenarios: are they + hiking, in an emergency, or just curious? Since the street answer was practical, the river answer should + be too. Provide actionable steps with clear analogies to each part of the street guide. \n\nMake sure to + highlight the importance of preparation and caution, as rivers can be unpredictable. Mention tools like + a walking stick, proper footwear, and checking for alternative routes. Compare each step to the street crossing + steps to maintain the analogy. \n\nCheck for any missing elements. In street crossing, we had visibility, + signals, looking both ways. For rivers, it's depth, current, obstacles. Also, post-crossing actions like + drying off and reassessing. \n\nI should structure the answer similarly: numbered steps with key points, + then safety tips. Use analogies in each section. Make sure the language is clear and easy to follow, avoiding + technical jargon but still informative. \n\nDouble-check for accuracy. For example, facing upstream when + crossing to brace against the current, using a pole for stability, moving diagonally. Yes, those are standard + river crossing techniques. Also, mention the importance of unbuckling backpacks in case you need to shed + weight quickly. \n\nInclude warnings about hypothermia from cold water, the danger of flooded rivers even + if they seem calm. Emphasize that turning back is a valid option if it's too risky. \n\nAlright, I think + that covers the main points. Structure it step-by-step, mirror the street crossing guide, and ensure safety + is the top priority throughout.\n" + role: assistant + stopReason: end_turn + usage: + inputTokens: 488 + outputTokens: 1427 + totalTokens: 1915 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_deepseek/test_deepseek_model_thinking_part.yaml b/tests/models/cassettes/test_deepseek/test_deepseek_model_thinking_part.yaml new file mode 100644 index 000000000..d1486d891 --- /dev/null +++ b/tests/models/cassettes/test_deepseek/test_deepseek_model_thinking_part.yaml @@ -0,0 +1,91 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '118' + content-type: + - application/json + host: + - api.deepseek.com + method: POST + parsed_body: + messages: + - content: How do I cross the street? + role: user + model: deepseek-reasoner + n: 1 + stream: false + uri: https://api.deepseek.com/chat/completions + response: + headers: + access-control-allow-credentials: + - 'true' + connection: + - keep-alive + content-length: + - '4148' + content-type: + - application/json + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - origin, access-control-request-method, access-control-request-headers + parsed_body: + choices: + - finish_reason: stop + index: 0 + logprobs: null + message: + content: "Crossing the street safely involves careful observation and following traffic rules. Here's a step-by-step + guide:\n\n1. **Find a Safe Spot**: \n - Use a **crosswalk** or intersection with pedestrian signals whenever + possible. \n - Avoid crossing between parked cars, curves, or hills where visibility is poor. \n\n2. **Check + Traffic Signals**: \n - Wait for the \"walk\" signal or green light. If there's a button, press it to activate + the pedestrian signal. \n - **Never cross** on a red light, flashing \"don't walk,\" or steady hand signal. + \ \n\n3. **Look Both Ways**: \n - **Look left, right, and left again** (or right, left, right in countries + with left-side traffic). \n - Keep checking for cars, bikes, or motorcycles while crossing. \n\n4. **Make + Eye Contact**: \n - Ensure drivers see you before stepping into the road, especially at stop signs or turning + lanes. \n\n5. **Stay Focused**: \n - Avoid distractions like phones, headphones, or conversations. \n - + Walk briskly (don't run) and follow a straight path. \n\n6. **Watch for Turning Vehicles**: \n - Be cautious + of cars turning into your path, even if you have the right of way. \n\n7. **Be Visible**: \n - At night, wear + reflective clothing or use a flashlight. \n\n8. **Use Pedestrian Infrastructure**: \n - Opt for bridges, tunnels, + or islands if available. \n\n9. **Follow Local Laws**: \n - Avoid jaywalking—it's illegal in many areas and + increases accident risk. \n\n**Remember**: Never assume drivers will stop. Prioritize safety over speed, and + stay alert until you've fully crossed." + reasoning_content: |- + Okay, the user is asking how to cross the street. Let me think about how to approach this. First, I need to make sure I cover all the essential steps clearly. Maybe start with finding a safe place to cross. Crosswalks and intersections are important here. I should mention looking for traffic lights or pedestrian signals. + + Wait, what about places without crosswalks? Maybe advise them to choose a well-lit area with a clear view of traffic. Then, checking both directions for cars. But in some countries, traffic is on the left, so I should mention looking left and right, then left again, or something like that. Wait, maybe just say look both ways multiple times to be safe. + + Oh, and making eye contact with drivers if possible. That's a good point. Also, stay visible, maybe avoid distractions like phones. Don't forget to walk, not run, and keep looking while crossing. What about if there's a button to press at the crosswalk? Maybe include that step. Also, explain different signals like the walking person and the hand signal. Maybe warn against crossing on a red hand or don't walk signal. + + What about situations where there's no traffic light? Like a stop sign? The pedestrian has the right of way there, but still need to be cautious. Also, remind them to watch for turning vehicles, even if they have the right of way. Maybe add something about using pedestrian bridges or tunnels if available. Oh, and be extra careful at night or in bad weather. Wear reflective clothing if possible. + + I should structure this step by step. Start with finding a safe spot, checking signals, looking both ways, making eye contact, staying focused, walking straight across, and continuing to look. Maybe include additional tips like using pedestrian infrastructure, being cautious at night, and following local laws. Also, emphasize not assuming drivers see you. Did I miss anything? Maybe jaywalking? Advise against it. Alright, I think that covers the basics. Need to present it clearly and concisely. + role: assistant + created: 1745330951 + id: 181d9669-2b3a-445e-bd13-2ebff2c378f6 + model: deepseek-reasoner + object: chat.completion + system_fingerprint: fp_5417b77867_prod0225 + usage: + completion_tokens: 789 + completion_tokens_details: + reasoning_tokens: 415 + prompt_cache_hit_tokens: 0 + prompt_cache_miss_tokens: 12 + prompt_tokens: 12 + prompt_tokens_details: + cached_tokens: 0 + total_tokens: 801 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/test_bedrock.py b/tests/models/test_bedrock.py index a214f03d1..a749b1001 100644 --- a/tests/models/test_bedrock.py +++ b/tests/models/test_bedrock.py @@ -4,7 +4,6 @@ from typing import Any import pytest -from dirty_equals import IsInstance from inline_snapshot import snapshot from typing_extensions import TypedDict @@ -25,6 +24,7 @@ SystemPromptPart, TextPart, TextPartDelta, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -32,7 +32,7 @@ ) from pydantic_ai.usage import Usage -from ..conftest import IsDatetime, try_import +from ..conftest import IsDatetime, IsInstance, IsStr, try_import with try_import() as imports_successful: from pydantic_ai.models.bedrock import BedrockConverseModel, BedrockModelSettings @@ -581,3 +581,48 @@ async def test_bedrock_multiple_documents_in_history( assert result.output == snapshot( 'Based on the documents you\'ve shared, both Document 1.pdf and Document 2.pdf contain the text "Dummy PDF file". These appear to be placeholder or sample PDF documents rather than files with substantial content.' ) + + +async def test_bedrock_model_thinking_part(allow_model_requests: None, bedrock_provider: BedrockProvider): + m = BedrockConverseModel('us.deepseek.r1-v1:0', provider=bedrock_provider) + agent = Agent(m) + + result = await agent.run('How do I cross the street?') + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[TextPart(content=IsStr()), ThinkingPart(content=IsStr())], + model_name='us.deepseek.r1-v1:0', + timestamp=IsDatetime(), + ), + ] + ) + + result = await agent.run( + 'Considering the way to cross the street, analogously, how do I cross the river?', + message_history=result.all_messages(), + ) + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[IsInstance(TextPart), IsInstance(ThinkingPart)], + model_name='us.deepseek.r1-v1:0', + timestamp=IsDatetime(), + ), + ModelRequest( + parts=[ + UserPromptPart( + content='Considering the way to cross the street, analogously, how do I cross the river?', + timestamp=IsDatetime(), + ) + ] + ), + ModelResponse( + parts=[IsInstance(TextPart), IsInstance(ThinkingPart)], + model_name='us.deepseek.r1-v1:0', + timestamp=IsDatetime(), + ), + ] + ) diff --git a/tests/models/test_deepseek.py b/tests/models/test_deepseek.py new file mode 100644 index 000000000..f361ca3a5 --- /dev/null +++ b/tests/models/test_deepseek.py @@ -0,0 +1,42 @@ +from __future__ import annotations as _annotations + +from typing import Union + +import pytest +from inline_snapshot import snapshot + +from pydantic_ai import Agent +from pydantic_ai.messages import ModelRequest, ModelResponse, TextPart, ThinkingPart, UserPromptPart +from pydantic_ai.models.openai import OpenAIModel +from pydantic_ai.providers.deepseek import DeepSeekProvider + +from ..conftest import IsDatetime, IsStr, try_import + +with try_import() as imports_successful: + from openai.types import chat + + # note: we use Union here so that casting works with Python 3.9 + MockChatCompletion = Union[chat.ChatCompletion, Exception] + MockChatCompletionChunk = Union[chat.ChatCompletionChunk, Exception] + +pytestmark = [ + pytest.mark.skipif(not imports_successful(), reason='openai not installed'), + pytest.mark.anyio, + pytest.mark.vcr, +] + + +async def test_deepseek_model_thinking_part(allow_model_requests: None, deepseek_api_key: str): + deepseek_model = OpenAIModel('deepseek-reasoner', provider=DeepSeekProvider(api_key=deepseek_api_key)) + agent = Agent(model=deepseek_model) + result = await agent.run('How do I cross the street?') + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ThinkingPart(content=IsStr()), TextPart(content=IsStr())], + model_name='deepseek-reasoner', + timestamp=IsDatetime(), + ), + ] + ) From d8c58613a7b35453e7cf88ebf570b80e79beb0c2 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 17:13:08 +0200 Subject: [PATCH 17/27] add tests for bedrock --- .../pydantic_ai/models/bedrock.py | 10 + ...st_bedrock_model_thinking_part_stream.yaml | 2053 +++++++++++++++++ tests/models/test_bedrock.py | 815 +++++++ 3 files changed, 2878 insertions(+) create mode 100644 tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part_stream.yaml diff --git a/pydantic_ai_slim/pydantic_ai/models/bedrock.py b/pydantic_ai_slim/pydantic_ai/models/bedrock.py index 1b3f48059..f9e1a66ad 100644 --- a/pydantic_ai_slim/pydantic_ai/models/bedrock.py +++ b/pydantic_ai_slim/pydantic_ai/models/bedrock.py @@ -2,6 +2,7 @@ import functools import typing +import warnings from collections.abc import AsyncIterator, Iterable, Iterator, Mapping from contextlib import asynccontextmanager from dataclasses import dataclass, field @@ -541,6 +542,15 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: if 'contentBlockDelta' in chunk: index = chunk['contentBlockDelta']['contentBlockIndex'] delta = chunk['contentBlockDelta']['delta'] + if 'reasoningContent' in delta: + if text := delta['reasoningContent'].get('text'): + yield self._parts_manager.handle_thinking_delta(vendor_part_id=index, content=text) + else: # pragma: no cover + warnings.warn( + f'Only text reasoning content is supported yet, but you got {delta["reasoningContent"]}. ' + 'Please report this to the maintainers.', + UserWarning, + ) if 'text' in delta: yield self._parts_manager.handle_text_delta(vendor_part_id=index, content=delta['text']) if 'toolUse' in delta: diff --git a/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part_stream.yaml b/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part_stream.yaml new file mode 100644 index 000000000..47e913b75 --- /dev/null +++ b/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part_stream.yaml @@ -0,0 +1,2053 @@ +interactions: +- request: + body: '{"messages": [{"role": "user", "content": [{"text": "How do I cross the street?"}]}], "system": [], "inferenceConfig": + {}}' + headers: + amz-sdk-invocation-id: + - !!binary | + MzRkY2NkNWYtNDQ3YS00OGVhLThjZTgtZTBjM2E2OWY3ZDdj + amz-sdk-request: + - !!binary | + YXR0ZW1wdD0x + content-length: + - '122' + content-type: + - !!binary | + YXBwbGljYXRpb24vanNvbg== + method: POST + uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.deepseek.r1-v1%3A0/converse-stream + response: + body: + string: !!binary | + AAAAiAAAAFJh0RTiCzpldmVudC10eXBlBwAMbWVzc2FnZVN0YXJ0DTpjb250ZW50LXR5cGUHABBh + cHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsicCI6ImFiY2RlZmdoaWprIiwi + cm9sZSI6ImFzc2lzdGFudCJ9t/VqyQAAANYAAABXlpiRxws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQi + OnsidGV4dCI6Ik9rYXkifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdI + SUoifb7Si98AAADsAAAAV30JMeALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u + dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl + bnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIsIHNv + In19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVW + V1hZWjAxMjM0NSJ9jQqdtQAAAMIAAABXA/ighQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4 + dCI6IiB0aGUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wIn0lybAuAAAA0AAAAFcZ2GRnCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsi + cmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHVzZXIgaXMifX0sInAiOiJhYmNkZWZnaGlqa2xt + bm9wcXJzdHV2d3h5eiJ9NJ5RiAAAAPkAAABX1QkpEgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsi + dGV4dCI6IiBhc2tpbmcgaG93IHRvIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpB + QkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3OCJ9iscBxQAAANoAAABXU2h8xgs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7 + InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBjcm9zcyB0aGUgc3RyZWV0In19LCJwIjoiYWJj + ZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBIn3CZHHdAAAA0AAAAFcZ2GRnCzpldmVudC10eXBlBwAR + Y29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3Nh + Z2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5n + Q29udGVudCI6eyJ0ZXh0IjoiLiBMZXQgbWUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2 + d3h5eiJ9PF/W4QAAAL0AAABXeHrXmws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpj + b250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29u + dGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiB0 + aGluayJ9fSwicCI6ImFiY2RlZmdoaSJ9uayrTwAAAO8AAABXOqlLMAs6ZXZlbnQtdHlwZQcAEWNv + bnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl + LXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0Nv + bnRlbnQiOnsidGV4dCI6IiBhYm91dCBob3cifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2 + d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyIn1qNN/XAAAAvQAAAFd4etebCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsi + cmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHRvIGFwcHJvYWNoIn19LCJwIjoiYWJjIn03zXZC + AAAAwgAAAFcD+KCFCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHRoaXMuIEZpcnN0 + In19LCJwIjoiYWJjZGVmZ2gifdK6XBcAAADXAAAAV6v4uHcLOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50 + Ijp7InRleHQiOiIsIEkgbmVlZCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJD + REVGRyJ9uGXA2wAAAOgAAABXiImXIAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpj + b250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29u + dGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiB0 + byBtYWtlIHN1cmUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpL + TE1OT1BRUlMiffpdrvkAAADQAAAAVxnYZGcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQi + OiIgSSBjb3ZlciJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6In1yJupYAAAA2wAA + AFduCFV2CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFw + cGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6 + MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGFsbCB0aGUgYmFzaWMifX0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFIn1XGl8aAAAAxgAAAFf2eAZFCzpl + dmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9u + L2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEi + OnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHN0ZXBzLCJ9fSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxIn1Inw9PAAAAvwAAAFcCuoT7CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0Ijoi + IGJ1dCBhbHNvIn19LCJwIjoiYWJjZGVmZ2gifTkF6KUAAADVAAAAV9E46xcLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIgY29uc2lkZXIgZGlmZmVyZW50In19LCJwIjoiYWJjZGVmZ2hp + amtsbW5vcHFyc3QifQYIT2kAAAD0AAAAVy2Z7aMLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tE + ZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZl + bnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRl + eHQiOiIgc2NlbmFyaW9zLiBNYXliZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6 + QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowIn0en7cxAAAAywAAAFcO6ML0CzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29u + aW5nQ29udGVudCI6eyJ0ZXh0IjoiIHN0YXJ0IHdpdGggdGhlIn19LCJwIjoiYWJjZGVmZ2hpamts + bW4ifdnWiaMAAADTAAAAV154HrcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u + dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl + bnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgb2J2 + aW91czoifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCIn1aVMHZAAAAygAAAFcz + iOtECzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwi + ZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGZpbmRpbmcifX0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdCJ9htsdEwAAAN8AAABXm4jztgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQi + OnsidGV4dCI6IiBhIGNyb3NzIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNE + RUZHSElKS0xNTk8ifdRb0/wAAAC5AAAAV436cVsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tE + ZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZl + bnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRl + eHQiOiJ3YWxrLiJ9fSwicCI6ImFiY2RlZiJ9dwn7ewAAAMMAAABXPpiJNQs6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmlu + Z0NvbnRlbnQiOnsidGV4dCI6IiBCdXQgd2FpdCwifX0sInAiOiJhYmNkZWZnaGlqayJ97sPHFgAA + AOcAAABXCtkA8Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUH + ABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5k + ZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBub3QgYWxsIn19LCJw + IjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWVyJ9k0Zf + uAAAANUAAABX0TjrFws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBzdHJlZXRzIn19 + LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERSJ9UBY5AgAAAPIAAABXotkYAws6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh + Ijp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBoYXZlIGNyb3NzIn19LCJwIjoiYWJjZGVm + Z2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0In2/QJeR + AAAA3QAAAFfhSKDWCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0Ijoid2Fsa3MsIn19LCJw + IjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk8ifTzTHw4AAADoAAAA + V4iJlyALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw + bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijow + LCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgZXNwZWNpYWxseSBpbiBsZXNz + In19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNIn0NwBC9AAAA + 8wAAAFefuTGzCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHVyYmFuIGFyZWFzLiJ9 + fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldY + WVowMTIzIn2nbYZtAAAAywAAAFcO6ML0CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0Ijoi + IFNvIEkgc2hvdWxkIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHEifREMNq4AAADBAAAAV0RY2lUL + OmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRp + b24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0 + YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgbWVudGlvbiBsb29raW5nIn19LCJwIjoi + YWJjIn2ZsijvAAAAzQAAAFeBqDdUCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGZv + ciJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QSJ96T6tEAAAAOgAAABXiImXIAs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh + Ijp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBhIGNyb3Nzd2FsayJ9fSwicCI6ImFiY2Rl + ZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1QifRVlAksAAADiAAAAV8I5 + j4ELOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGlj + YXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJk + ZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgb3IifX0sInAiOiJhYmNkZWZnaGlq + a2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXIn1hbza/AAAA4QAAAFeFmfVR + CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0 + aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVs + dGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHBlZGVzdHJpYW4gY3Jvc3NpbmcifX0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFIn3W5NyfAAAAxQAAAFex2HyVCzpl + dmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9u + L2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEi + OnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHNpZ25hbHMifX0sInAiOiJhYmNkZWZnaGlq + a2xtbm8iffqqiF4AAADtAAAAV0BpGFALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06 + Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNv + bnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIg + Zmlyc3QuXG5cbiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktM + TU5PUFFSU1RVVldYWVoifb/r1l4AAADkAAAAV015eiELOmV2ZW50LXR5cGUHABFjb250ZW50Qmxv + Y2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAF + ZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7 + InRleHQiOiJUaGVuLCBjaGVjayJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJD + REVGR0hJSktMTU5PUFEifZTpzS8AAADdAAAAV+FIoNYLOmV2ZW50LXR5cGUHABFjb250ZW50Qmxv + Y2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAF + ZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7 + InRleHQiOiIgZm9yIHRyYWZmaWMgbGlnaHRzIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBQiJ9reOjpAAAAOQAAABXTXl6IQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6 + Ii4gSWYifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BR + UlNUVVZXWCJ9jQ3CxQAAAL4AAABXP9qtSws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6 + IiB0aGVyZSdzIn19LCJwIjoiYWJjZGVmZ2gifZjb/rsAAADoAAAAV4iJlyALOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIgYSB0cmFmZmljIGxpZ2h0In19LCJwIjoiYWJjZGVmZ2hpamts + bW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QIn2TTVkcAAAAvQAAAFd4etebCzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVh + c29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiLCB3YWl0In19LCJwIjoiYWJjZGVmZ2hpIn0geXIRAAAA + 3wAAAFebiPO2CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGZvciB0aGUifX0sInAi + OiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OTyJ90yPCxQAAAOkAAABX + tem+kAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBs + aWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAs + ImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiB3YWxrIn19LCJwIjoiYWJjZGVm + Z2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxIn2M2R2NAAAA + 1gAAAFeWmJHHCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHNpZ25hbC4gQnV0In19 + LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQiJ9kKn5TQAAANUAAABX0TjrFws6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7 + InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBzb21ldGltZXMifX0sInAiOiJhYmNkZWZnaGlq + a2xtbm9wcXJzdHV2d3h5ekFCQyJ9Wi2WNgAAAPQAAABXLZntows6ZXZlbnQtdHlwZQcAEWNvbnRl + bnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5 + cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRl + bnQiOnsidGV4dCI6IiBwZW9wbGUgbWlnaHQgbm90In19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFy + c3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAifdzAJ8kAAADAAAAAV3k48+ULOmV2 + ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24v + anNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6 + eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIga25vdyB3aGF0In19LCJwIjoiYWJjZGVmZ2gi + fap4CEYAAADDAAAAVz6YiTULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVu + dC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRC + bG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgdGhlIHN5 + bWJvbHMifX0sInAiOiJhYmNkZWZnaGkifXv8WCMAAADiAAAAV8I5j4ELOmV2ZW50LXR5cGUHABFj + b250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2Fn + ZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdD + b250ZW50Ijp7InRleHQiOiIgbWVhbi4gTWF5YmUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5ekFCQ0RFRkdISUpLTE1OIn3j/DgJAAAAywAAAFcO6ML0CzpldmVudC10eXBlBwARY29u + dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut + dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29u + dGVudCI6eyJ0ZXh0IjoiIGV4cGxhaW4gdGhlIFwiIn19LCJwIjoiYWJjZGVmZ2hpamtsbW4ifcxB + PCIAAADdAAAAV+FIoNYLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10 + eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9j + a0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiJ3YWxrXCIifX0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OTyJ9nLYRMQAAAL0A + AABXeHrXmws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBh + cHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgi + OjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBhbmQgXCJkb24ifX0sInAi + OiJhYmNkZSJ9zz1UJAAAAN0AAABX4Uig1gs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6 + Iid0IHdhbGtcIiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktM + In39jowtAAAA7wAAAFc6qUswCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRl + bnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50 + QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHNpZ25h + bHMuIEFsc28ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + T1BRUlNUVVZXWFkifSIW1e8AAADRAAAAVyS4TdcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tE + ZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZl + bnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRl + eHQiOiIsIGluIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERSJ9pIre1QAA + ANAAAABXGdhkZws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUH + ABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5k + ZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBzb21lIHBsYWNlcyJ9 + fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXYifUbFeDYAAADlAAAAV3AZU5ELOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFz + b25pbmdDb250ZW50Ijp7InRleHQiOiIsIHRoZXJlIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFy + c3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWIn2EyM/zAAAA1gAAAFeWmJHHCzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVh + c29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGFyZSBjb3VudCJ9fSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDRCJ9erYhXwAAAPIAAABXotkYAws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQi + OnsidGV4dCI6ImRvd24gdGltIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNE + RUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3In2uDUosAAAAygAAAFcziOtECzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVh + c29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiZXJzIHdoaWNoIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5v + cHFycyJ9Ej245AAAAPEAAABX5Xli0ws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpj + b250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29u + dGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBj + YW4gaGVscC4ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + T1BRUlNUVVZXWFlaMDEyMzQifcE13Y0AAADUAAAAV+xYwqcLOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50 + Ijp7InRleHQiOiIgQnV0IHdoYXQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFC + QyJ9Ld6O9gAAAOMAAABX/1mmMQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBpZiB0 + aGVyZSdzIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9Q + In3keZ63AAAA0QAAAFckuE3XCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRl + bnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50 + QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIG5vIHRy + YWZmaWMifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3gifTTncDUAAADwAAAAV9gZS2ML + OmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRp + b24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0 + YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgbGlnaHQ/In19LCJwIjoiYWJjZGVmZ2hp + amtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTYifQg36UYA + AADSAAAAV2MYNwcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgVGhlbiB0aGV5IHNo + b3VsZCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnMifbygY9oAAADaAAAAV1NofMYLOmV2ZW50 + LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNv + bg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJy + ZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgbG9vayBib3RoIHdheXMifX0sInAiOiJhYmNkZWZn + aGlqa2xtbm9wcXJzdHV2d3h5ekFCQyJ9Y69EqQAAANsAAABXbghVdgs6ZXZlbnQtdHlwZQcAEWNv + bnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl + LXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0Nv + bnRlbnQiOnsidGV4dCI6IiBmb3IgY2Fycy4ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2 + d3h5ekFCQ0RFRkdISSJ9BqSvFwAAAMMAAABXPpiJNQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsi + dGV4dCI6IiBCdXQgaG93In19LCJwIjoiYWJjZGVmZ2hpamtsbSJ9S33y9gAAAOsAAABXzynt8As6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh + Ijp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBtYW55In19LCJwIjoiYWJjZGVmZ2hpamts + bW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjMifaaSSlwAAADrAAAA + V88p7fALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw + bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijow + LCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgdGltZXM/In19LCJwIjoiYWJj + ZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxIn2RIOBR + AAAA4wAAAFf/WaYxCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIFVzdWFsbHkifX0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlMifdCZOTYA + AAC4AAAAV7CaWOsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgbGVmdCJ9fSwicCI6 + ImFiY2RlIn1UykKhAAAA8QAAAFfleWLTCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0Ijoi + LXJpZ2h0In19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9Q + UVJTVFVWV1hZWjAxMjM0NTY3OCJ9/aupqgAAALgAAABXsJpY6ws6ZXZlbnQtdHlwZQcAEWNvbnRl + bnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5 + cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRl + bnQiOnsidGV4dCI6Ii1sZWZ0LCJ9fSwicCI6ImFiY2QifUzSvg8AAADnAAAAVwrZAPELOmV2ZW50 + LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNv + bg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJy + ZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgYnV0IG1heWJlIGNsYXJpZnkifX0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE0ifQT6oCkAAADjAAAAV/9ZpjELOmV2 + ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24v + anNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6 + eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgdGhhdC5cblxuIn19LCJwIjoiYWJjZGVmZ2hp + amtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUSJ9MuNgWgAAAPIAAABXotkYAws6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7 + InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IldhaXQsIGJ1dCJ9fSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU2In38Ibw1AAAA + 0wAAAFdeeB63CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGluIn19LCJwIjoiYWJj + ZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSCJ9r9TonQAAAOoAAABX8knEQAs6ZXZlbnQt + dHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29u + DTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJl + YXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBzb21lIGNvdW50cmllcywifX0sInAiOiJhYmNkZWZn + aGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUiJ958dPbAAAAMQAAABXjLhVJQs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh + Ijp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiB0cmFmZmljIGNvbWVzIn19LCJwIjoiYWJj + ZGVmZ2gifUivKjIAAADGAAAAV/Z4BkULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06 + Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNv + bnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIg + ZnJvbSB0aGUgb3Bwb3NpdGUifX0sInAiOiJhYmNkZWYifWfKP9IAAAD1AAAAVxD5xBMLOmV2ZW50 + LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNv + bg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJy + ZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgZGlyZWN0aW9uIn19LCJwIjoiYWJjZGVmZ2hpamts + bW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3OCJ9gCg/uwAA + AMwAAABXvMge5As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUH + ABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5k + ZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6Ii4gTGlrZSJ9fSwicCI6 + ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eCJ97qhEmQAAALoAAABXyloLiws6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmlu + Z0NvbnRlbnQiOnsidGV4dCI6IiBpbiJ9fSwicCI6ImFiY2RlZmdoaSJ9lqrL5QAAAMQAAABXjLhV + JQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRl + bHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiB0aGUgVUssIn19LCJwIjoiYWJjZGVm + Z2hpamtsbW4ifQTkL/MAAADJAAAAV3QokZQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQi + OiIgY2FycyJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXYifRCSMGgAAADnAAAAVwrZAPEL + OmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRp + b24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0 + YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgZHJpdmUgb24gdGhlIn19LCJwIjoiYWJj + ZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVIifQwhTE4AAADHAAAAV8sY + L/ULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGlj + YXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJk + ZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgbGVmdC4gU28ifX0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wIn3/z4/kAAAA7AAAAFd9CTHgCzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0 + ZXh0IjoiIG1heWJlIGFkZCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVG + R0hJSktMTU5PUFFSU1RVVldYWVoifUeYpYAAAADQAAAAVxnYZGcLOmV2ZW50LXR5cGUHABFjb250 + ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10 + eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250 + ZW50Ijp7InRleHQiOiIgYSBub3RlIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpB + In22xydmAAAA4gAAAFfCOY+BCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRl + bnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50 + QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGFib3V0 + IGJlaW5nIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTiJ9 + XEgpeQAAAOsAAABXzynt8As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50 + LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJs + b2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBhd2FyZSBv + ZiB0aGUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BR + UlNUVVYifQvDOWIAAADLAAAAVw7owvQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06 + Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNv + bnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIg + bG9jYWwgdHJhZmZpYyBkaXJlY3Rpb24ifX0sInAiOiJhYmNkZSJ9qyoJKgAAAOkAAABXtem+kAs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh + Ijp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6Ii4gQWxzbyJ9fSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowIn1hY9sAAAAA3QAAAFfh + SKDWCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwi + ZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiLCBkaXN0cmFjdGlvbnMifX0sInAi + OiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkcifdVswFYAAADqAAAAV/JJxEALOmV2 + ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24v + anNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6 + eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgbGlrZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIifQKDbcIAAADvAAAAVzqp + SzALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGlj + YXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJk + ZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgdXNpbmcgYSJ9fSwicCI6ImFiY2Rl + ZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNCJ98NdS + cAAAAPkAAABX1QkpEgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBwaG9uZSB3aGls + ZSBjcm9zc2luZyJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktM + TU5PUFFSU1RVVldYWVowMSJ9WLSrfgAAAOUAAABXcBlTkQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQi + OnsidGV4dCI6Ii4gRW0ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdI + SUpLTE1OT1BRUlNUVVZXWFkifRPUOj8AAADWAAAAV5aYkccLOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50 + Ijp7InRleHQiOiJwaGFzaXplIHRoZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6 + QUJDIn2447dDAAAA5wAAAFcK2QDxCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGlt + cG9ydGFuY2Ugb2Ygc3RheWluZyJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJD + REVGR0hJIn1rMsQzAAAA5QAAAFdwGVORCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0Ijoi + IGZvY3VzZWQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + T1BRUlNUVSJ9mpX8wAAAANMAAABXXngetws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6 + IiBhbmQgbm90In19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkMifVE02hIAAAC8 + AAAAV0Ua/isLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQ + YXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4 + IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgYmVpbmcifX0sInAiOiJh + YmNkZWZnaCJ9zv24YAAAAPYAAABXV1m+wws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6 + IiBkaXN0cmFjdGVkLlxuXG4ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RF + RkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMyJ9AA7gAQAAAN4AAABXpujaBgs6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmlu + Z0NvbnRlbnQiOnsidGV4dCI6IldoYXQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5 + ekFCQ0RFRkdISUpLTE1OT1BRUiJ9Ni1iTQAAANIAAABXYxg3Bws6ZXZlbnQtdHlwZQcAEWNvbnRl + bnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5 + cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRl + bnQiOnsidGV4dCI6IiBhYm91dCBjaGlsZHJlbiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dSJ9nyHypQAAAMgAAABXSUi4JAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBvciJ9 + fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3In3Yw3jMAAAA4QAAAFeFmfVRCzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVh + c29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHBlb3BsZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx + cnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSIn03u0oHAAAA3wAAAFebiPO2CzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29u + aW5nQ29udGVudCI6eyJ0ZXh0IjoiIHdpdGggZGlzYWJpbGl0aWVzPyJ9fSwicCI6ImFiY2RlZmdo + aWprbG1ub3BxcnN0dXZ3eHl6QUJDRCJ9sIvUggAAAOUAAABXcBlTkQs6ZXZlbnQtdHlwZQcAEWNv + bnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl + LXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0Nv + bnRlbnQiOnsidGV4dCI6IiBNYXliZSBtZW50aW9uIHVzaW5nIn19LCJwIjoiYWJjZGVmZ2hpamts + bW5vcHFyc3R1dnd4eXpBQkNERUZHSEkifc/5XFgAAADBAAAAV0RY2lULOmV2ZW50LXR5cGUHABFj + b250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2Fn + ZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdD + b250ZW50Ijp7InRleHQiOiIgcGVkZXN0cmlhbiJ9fSwicCI6ImFiY2RlZmdoIn0Emd0eAAAA5gAA + AFc3uSlBCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFw + cGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6 + MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGJyaWRnZXMifX0sInAiOiJh + YmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVYifQh+aMoAAADt + AAAAV0BpGFALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQ + YXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4 + IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgb3IgdHVubmVscyJ9fSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoi + fcIdYMAAAADpAAAAV7XpvpALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVu + dC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRC + bG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgaWYgYXZh + aWxhYmxlLiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5P + UFFSUyJ9B7KbdAAAAOUAAABXcBlTkQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpj + b250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29u + dGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBB + bHNvLCBtYWtpbmcifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpL + TE1OT1AifU5ZwWkAAADfAAAAV5uI87YLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06 + Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNv + bnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIg + ZXllIGNvbnRhY3Qgd2l0aCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVG + In3tTvuAAAAAvgAAAFc/2q1LCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRl + bnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50 + QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGRyaXZl + cnMgdG8ifX0sInAiOiJhYmNkZSJ9SZGYzwAAAMIAAABXA/ighQs6ZXZlbnQtdHlwZQcAEWNvbnRl + bnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5 + cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRl + bnQiOnsidGV4dCI6IiBlbnN1cmUgdGhleSBzZWUifX0sInAiOiJhYmNkIn0T2UymAAAA8QAAAFfl + eWLTCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwi + ZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHlvdS4gQnV0In19LCJwIjoiYWJj + ZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NSJ9 + b7fvfgAAAOIAAABXwjmPgQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50 + LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJs + b2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBub3QifX0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVYifTEJ + 9MIAAADaAAAAV1NofMYLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10 + eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9j + a0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgYWxsIGRyaXZl + cnMifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRiJ9uX9mmQAAAN4AAABX + pujaBgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBs + aWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAs + ImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBtaWdodCBtYWtlIn19LCJwIjoi + YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKSyJ9fJ6yvgAAAOAAAABXuPnc4Qs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh + Ijp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBleWUgY29udGFjdCwifX0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLIn3R0F+HAAAA3wAAAFebiPO2CzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsi + cmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHNvIG1heWJlIHRoYXQifX0sInAiOiJhYmNkZWZn + aGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISSJ9aYjZKQAAANUAAABX0TjrFws6ZXZlbnQtdHlw + ZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTpt + ZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNv + bmluZ0NvbnRlbnQiOnsidGV4dCI6IidzIG5vdCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QUJDREVGRyJ9g85FMwAAAN8AAABXm4jztgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsi + dGV4dCI6IiBhbHdheXMifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdI + SUpLTE1OT1AifXREBLsAAAC+AAAAVz/arUsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQi + OiIgcmVsaWFibGUuXG5cbiJ9fSwicCI6ImFiIn3AlEqaAAAA2AAAAFcpqC+mCzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29u + aW5nQ29udGVudCI6eyJ0ZXh0IjoiT2gifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5 + ekFCQ0RFRkdISUpLTE1OIn1oV3/4AAAA7wAAAFc6qUswCzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6 + eyJ0ZXh0IjoiLCBhbmQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdI + SUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1NjcifcNJYAIAAADyAAAAV6LZGAMLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIgamF5d2FsayJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU2NyJ9K0zSTgAAANMAAABXXnge + tws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRl + bHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6ImluZy4gU2hvdWxkIn19LCJwIjoiYWJj + ZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoifUNX9B0AAADcAAAAV9woiWYLOmV2ZW50LXR5cGUHABFj + b250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2Fn + ZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdD + b250ZW50Ijp7InRleHQiOiIgSSBtZW50aW9uIHRoYXQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9w + cXJzdHV2d3h5ekFCQ0RFIn0hOADUAAAA3wAAAFebiPO2CzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6 + eyJ0ZXh0IjoiIGl0J3MgaWxsZWdhbCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6 + QUJDREVGR0hJSiJ9G3veowAAAMcAAABXyxgv9Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4 + dCI6IiBpbiBzb21lIHBsYWNlcyJ9fSwicCI6ImFiY2RlZmdoaWoifV9HD34AAADpAAAAV7XpvpAL + OmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRp + b24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0 + YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgYW5kIHNhZmVyIn19LCJwIjoiYWJjZGVm + Z2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWVyJ9Y3plxQAAANsAAABX + bghVdgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBs + aWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAs + ImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiB0byB1c2UifX0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTCJ9Lv6e4AAAAMYAAABX9ngGRQs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7 + InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBjcm9zc3dhbGtzPyJ9fSwicCI6ImFiY2RlZmdo + aWprbCJ9mUlQPQAAANEAAABXJLhN1ws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpj + b250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29u + dGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBZ + ZXMifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFIn2HzL9hAAAAwQAAAFdE + WNpVCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwi + ZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiLCB0aGF0In19LCJwIjoiYWJjZGVm + Z2hpamtsbSJ9/6EOuwAAAOUAAABXcBlTkQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6 + IidzIGltcG9ydGFudCBmb3IifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RF + RkdISUpLTE0ifatDejYAAADEAAAAV4y4VSULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQi + OiIgbGVnYWwifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wIn02m2MZAAAA0gAAAFdjGDcHCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsi + cmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGFuZCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx + cnN0dXZ3eHl6QUJDREVGIn0kHSLHAAAA8AAAAFfYGUtjCzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6 + eyJ0ZXh0IjoiIHNhZmV0eSByZWFzb25zLiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3 + eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYIn1cPkKEAAAA9gAAAFdXWb7DCzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29u + aW5nQ29udGVudCI6eyJ0ZXh0IjoiIEFsc28sIGV2ZW4ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9w + cXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1Njc4In3FFMGvAAAAwAAA + AFd5OPPlCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFw + cGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6 + MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGlmIHRoZSB3YWxrIn19LCJw + IjoiYWJjZGVmIn2mye5iAAAAzQAAAFeBqDdUCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVs + dGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50 + eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0 + IjoiIHNpZ25hbCBpcyBvbiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxciJ9zBn3fgAAANYAAABX + lpiRxws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBs + aWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAs + ImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiwgY2hlY2sifX0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkcifd5XknkAAAD1AAAAVxD5xBMLOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFz + b25pbmdDb250ZW50Ijp7InRleHQiOiIgZm9yIHR1cm5pbmcifX0sInAiOiJhYmNkZWZnaGlqa2xt + bm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1NiJ9nCEOkgAAANAA + AABXGdhkZws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBh + cHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgi + OjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiB2ZWhpY2xlcyJ9fSwicCI6 + ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHkifRrG7tsAAADTAAAAV154HrcLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIgdGhhdCBtaWdodCBub3QifX0sInAiOiJhYmNkZWZnaGlqa2xt + bm9wcXJzdHV2In1KyZIMAAAA1wAAAFer+Lh3CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVs + dGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50 + eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0 + IjoiIHN0b3AifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUoifepR + NDIAAADdAAAAV+FIoNYLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10 + eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9j + a0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIuIEIifX0sInAi + OiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUiJ9x8G2NQAAAMwA + AABXvMge5As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBh + cHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgi + OjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6ImljeWNsZXMgYW5kIn19LCJw + IjoiYWJjZGVmZ2hpamtsbW5vcHFycyJ9YDKTswAAAPMAAABXn7kxsws6ZXZlbnQtdHlwZQcAEWNv + bnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl + LXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0Nv + bnRlbnQiOnsidGV4dCI6IiBtb3RvcmN5Y2xlcyJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNCJ9gkmI6gAAAM8AAABX+2hkNAs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh + Ijp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBjYW4gYmUifX0sInAiOiJhYmNkZWZnaGlq + a2xtbm9wcXJzdHV2d3h5eiJ9VdeX+gAAANIAAABXYxg3Bws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQi + OnsidGV4dCI6IiBxdWlldGVyIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQiJ9 + 1jeKqwAAAMYAAABX9ngGRQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50 + LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJs + b2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBhbmQifX0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdCJ9ItS9gQAAAPIAAABXotkYAws6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmlu + Z0NvbnRlbnQiOnsidGV4dCI6IiBoYXJkZXIgdG8gaGVhciJ9fSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowIn3TuvzZAAAAzQAAAFeBqDdU + CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0 + aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVs + dGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiLCBzbyByZW1pbmQifX0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdCJ93g0TfwAAAOwAAABXfQkx4As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQi + OnsidGV4dCI6IiB0byJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJ + SktMTU5PUFFSU1RVVldYWVowMTIzNDU2In2UKfXeAAAA6AAAAFeIiZcgCzpldmVudC10eXBlBwAR + Y29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3Nh + Z2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5n + Q29udGVudCI6eyJ0ZXh0IjoiIGxpc3RlbiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3 + eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWSJ9GqzNlQAAAMkAAABXdCiRlAs6ZXZlbnQtdHlw + ZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTpt + ZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNv + bmluZ0NvbnRlbnQiOnsidGV4dCI6IiBhcyJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3 + eCJ9ACDJqwAAAOgAAABXiImXIAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiB3ZWxs + IGFzIGxvb2sifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + T1BRUlMifVI+3FsAAADvAAAAVzqpSzALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06 + Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNv + bnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIu + XG5cbldhaXQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + T1BRUlNUVVZXWFlaMDEyMyJ9jQOB5AAAAPMAAABXn7kxsws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQi + OnsidGV4dCI6Iiwgd2hhdCBhYm91dCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6 + QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNCJ9NfbdswAAANAAAABXGdhkZws6ZXZlbnQt + dHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29u + DTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJl + YXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBhdCBuaWdodCJ9fSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHkifT4TVNAAAADaAAAAV1NofMYLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tE + ZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZl + bnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRl + eHQiOiI/IFcifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + TyJ9HlUKIgAAAPoAAABXkqlTwgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6ImVhcmlu + ZyByZWZsZWN0aXZlIGNsb3RoaW5nIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpB + QkNERUZHSElKS0xNTk9QUVJTVFVWV1giffPmSAUAAADbAAAAV24IVXYLOmV2ZW50LXR5cGUHABFj + b250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2Fn + ZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdD + b250ZW50Ijp7InRleHQiOiIgb3IgY2FycnlpbmcifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5ekFCQ0RFRkcifQ8LclkAAADoAAAAV4iJlyALOmV2ZW50LXR5cGUHABFjb250ZW50Qmxv + Y2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAF + ZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7 + InRleHQiOiIgYSBsaWdodCB0byJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJD + REVGR0hJSktMTU5PUFFSU1RVIn08yzWbAAAA6wAAAFfPKe3wCzpldmVudC10eXBlBwARY29udGVu + dEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlw + ZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVu + dCI6eyJ0ZXh0IjoiIGJlIG1vcmUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFC + Q0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMCJ9BpdpIgAAAMkAAABXdCiRlAs6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmlu + Z0NvbnRlbnQiOnsidGV4dCI6IiB2aXNpYmxlLiBUaGF0In19LCJwIjoiYWJjZGVmZ2hpamtsbSJ9 + EpNjEgAAALoAAABXyloLiws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50 + LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJs + b2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IidzIGEgZ29v + ZCJ9fSwicCI6ImFiYyJ9Cs8BTAAAAMUAAABXsdh8lQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsi + dGV4dCI6IiBwb2ludC4gQWxzbyJ9fSwicCI6ImFiY2RlZmdoaWprIn32XMTrAAAA7AAAAFd9CTHg + CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0 + aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVs + dGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiLCBjcm9zc2luZyJ9fSwicCI6ImFiY2Rl + ZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoifbmCmw0AAADO + AAAAV8YITYQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQ + YXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4 + IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgaW4gZ3JvdXBzIn19LCJw + IjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1diJ9dbw9awAAANQAAABX7FjCpws6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmlu + Z0NvbnRlbnQiOnsidGV4dCI6IiBpZiBwb3NzaWJsZSwifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9w + cXJzdHV2d3h5In11ExO8AAAAzQAAAFeBqDdUCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVs + dGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50 + eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0 + IjoiIGFzIG1vcmUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2dyJ9cgSBTwAAAMcAAABX + yxgv9Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBs + aWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAs + ImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBwZW9wbGUgYXJlIG1vcmUifX0s + InAiOiJhYmNkZWZnaGkifRhlc1QAAADzAAAAV5+5MbMLOmV2ZW50LXR5cGUHABFjb250ZW50Qmxv + Y2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAF + ZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7 + InRleHQiOiIgdmlzaWJsZS4ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RF + RkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1NjcifdqwdXUAAADpAAAAV7XpvpALOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFz + b25pbmdDb250ZW50Ijp7InRleHQiOiIgQnV0IGR1cmluZyJ9fSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVViJ92oLc9AAAANwAAABX3CiJZgs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7 + InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBDT1ZJRCJ9fSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU4ifZ9DVvUAAADlAAAAV3AZU5ELOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIsIG1heWJlIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWIn2Boy7VAAABBQAAAFeTkUfrCzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29u + aW5nQ29udGVudCI6eyJ0ZXh0IjoiIHNvY2lhbCBkaXN0YW5jaW5nIGFmZmVjdHMifX0sInAiOiJh + YmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1 + Njc4In3cFnthAAAA4gAAAFfCOY+BCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHRo + YXQ/IE5vdCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5P + UCJ9b3UCCwAAAOIAAABXwjmPgQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBzdXJl + IGlmIHRoYXQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE0i + fSWc/xAAAADIAAAAV0lIuCQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVu + dC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRC + bG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIncyBzdGls + bCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxciJ9c9q2LQAAAMAAAABXeTjz5Qs6ZXZlbnQtdHlw + ZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTpt + ZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNv + bmluZ0NvbnRlbnQiOnsidGV4dCI6IiBhIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcCJ90NMxvgAA + AO0AAABXQGkYUAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUH + ABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5k + ZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBjb25jZXJuLCJ9fSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVow + MSJ9iqaYkAAAAMQAAABXjLhVJQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBidXQg + bWF5YmUgc2tpcCJ9fSwicCI6ImFiY2RlZmcifa+YcOEAAADUAAAAV+xYwqcLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIgdGhhdCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3 + eHl6QUJDREVGRyJ9Gl4xkAAAAOMAAABX/1mmMQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4 + dCI6IiBwYXJ0IHVubGVzcyBuZWNlc3NhcnkifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2 + d3h5ekFCQ0RFIn3IVo3oAAAA3AAAAFfcKIlmCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVs + dGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50 + eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0 + IjoiLlxuXG5MZXQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpL + TCJ9qNCCGgAAAMsAAABXDujC9As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBtZSBv + dXRsaW5lIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyIn3dZEA0AAAAxgAAAFf2eAZFCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsi + cmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHRoZSBzdGVwczoifX0sInAiOiJhYmNkZWZnaGlq + a2xtIn2brGFbAAAA3AAAAFfcKIlmCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIDEu + In19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUSJ9md+Z + /wAAAM4AAABXxghNhAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBGaW5kIGEgc2Fm + ZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0In0sWj14AAAAxQAAAFex2HyVCzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVh + c29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGNyb3NzaW5nIn19LCJwIjoiYWJjZGVmZ2hpamtsbW4i + fWS+I2sAAAC8AAAAV0Ua/isLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVu + dC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRC + bG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgcG9pbnQu + ICJ9fSwicCI6ImFiY2RlZiJ9GCMjogAAAM4AAABXxghNhAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQi + OnsidGV4dCI6IjIuIE9ic2VydmUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2In3RCgP+ + AAAAzAAAAFe8yB7kCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHRyYWZmaWMgc2ln + bmFscy4ifX0sInAiOiJhYmNkZWZnaGlqa2xtIn1UtKNpAAAA5wAAAFcK2QDxCzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29u + aW5nQ29udGVudCI6eyJ0ZXh0IjoiIDMuIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4 + eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxIn0Grvq1AAAA2wAAAFduCFV2CzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVh + c29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIExvb2sgYm90aCB3YXlzIn19LCJwIjoiYWJjZGVmZ2hp + amtsbW5vcHFyc3R1dnd4eXpBQkNEIn2/5XzMAAAA6QAAAFe16b6QCzpldmVudC10eXBlBwARY29u + dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut + dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29u + dGVudCI6eyJ0ZXh0IjoiLiAifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RF + RkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQifcxZ61kAAADXAAAAV6v4uHcLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiI0LiBNYWtlIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBQkNERUZHSCJ9GlJzSwAAAMMAAABXPpiJNQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsi + dGV4dCI6IiBzdXJlIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcCJ9Cx5VYgAAAMgAAABXSUi4JAs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh + Ijp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBpdCJ9fSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3In3d1PVDAAAA1wAAAFer+Lh3CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVs + dGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50 + eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0 + IjoiJ3Mgc2FmZS4ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkcifSB0 + 85YAAADCAAAAVwP4oIULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10 + eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9j + a0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgNS4ifX0sInAi + OiJhYmNkZWZnaGlqa2xtbm9wcSJ9xyCfSAAAANgAAABXKagvpgs6ZXZlbnQtdHlwZQcAEWNvbnRl + bnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5 + cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRl + bnQiOnsidGV4dCI6IiBTdGF5IHZpc2libGUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2 + d3h5ekFCQyJ9DTOxvAAAALUAAABXSAqcWgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6 + Ii4gNiJ9fSwicCI6ImFiY2QifcEqgaMAAADHAAAAV8sYL/ULOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50 + Ijp7InRleHQiOiIuIFdhbGsifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzIn2oDrxYAAAAwQAA + AFdEWNpVCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFw + cGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6 + MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHN0cmFpZ2h0In19LCJwIjoi + YWJjZGVmZ2hpaiJ9fenSsQAAAMoAAABXM4jrRAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4 + dCI6IiBhY3Jvc3MuIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3QifdL/nvcAAADVAAAAV9E4 + 6xcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGlj + YXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJk + ZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgNyJ9fSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSksifb1icjoAAADxAAAAV+V5YtMLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIuIFN0YXkifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2 + d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1Njc4In3XEF4wAAAA4QAAAFeFmfVR + CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0 + aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVs + dGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGFsZXJ0LiBNYXliZSJ9fSwicCI6ImFi + Y2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMIn3jVBmXAAAA4wAAAFf/WaYxCzpl + dmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9u + L2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEi + OnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGFkZCB0aXBzIn19LCJwIjoiYWJjZGVmZ2hp + amtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVIifUFTvRUAAAC2AAAAVw+q5ooLOmV2 + ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24v + anNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6 + eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgZm9yIn19LCJwIjoiYWJjZCJ9kRkLeAAAAOgA + AABXiImXIAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBh + cHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgi + OjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBkaWZmZXJlbnQgc2l0dWF0 + aW9ucyJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSksifc6yCb4A + AADUAAAAV+xYwqcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgbGlrZSB1biJ9fSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDRCJ9DBPSjwAAAOEAAABXhZn1UQs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7 + InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6Im1hcmtlZCJ9fSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSUyJ9PxViNwAAANkAAABXFMgGFgs6ZXZlbnQt + dHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29u + DTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJl + YXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBjcm9zc2luZ3MsIGludGVyc2VjdGlvbnMifX0sInAi + OiJhYmNkZWZnaGlqa2xtbm9wcXIiffalLU8AAADlAAAAV3AZU5ELOmV2ZW50LXR5cGUHABFjb250 + ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10 + eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250 + ZW50Ijp7InRleHQiOiIgd2l0aCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJD + REVGR0hJSktMTU5PUFFSU1RVVldYIn1XUpEiAAAA8AAAAFfYGUtjCzpldmVudC10eXBlBwARY29u + dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut + dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29u + dGVudCI6eyJ0ZXh0IjoiIHNpZ25hbHMifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5 + ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1In1c01Y8AAAA4AAAAFe4+dzhCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsi + cmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiLCBldGMuXG5cbiJ9fSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU4ifYHC5vgAAADkAAAAV015eiELOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFz + b25pbmdDb250ZW50Ijp7InRleHQiOiJBbHNvIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1gifUKWWEcAAADdAAAAV+FIoNYLOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFz + b25pbmdDb250ZW50Ijp7InRleHQiOiIsIGluY2x1ZGUgc2FmZXR5In19LCJwIjoiYWJjZGVmZ2hp + amtsbW5vcHFyc3R1dnd4eXpBQkNERSJ9uHOggwAAANYAAABXlpiRxws6ZXZlbnQtdHlwZQcAEWNv + bnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl + LXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0Nv + bnRlbnQiOnsidGV4dCI6IiB0aXBzIGxpa2UgYXZvaWRpbmcifX0sInAiOiJhYmNkZWZnaGlqa2xt + bm9wcXJzdHUifQ23rAUAAADYAAAAVymoL6YLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQi + OiIgZGlzdHJhY3Rpb25zLCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUIifUdI + XBsAAADBAAAAV0RY2lULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10 + eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9j + a0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgd2F0Y2hpbmci + fX0sInAiOiJhYmNkZWZnaGlqIn0lVxXqAAAAxQAAAFex2HyVCzpldmVudC10eXBlBwARY29udGVu + dEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlw + ZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVu + dCI6eyJ0ZXh0IjoiIGZvciB0dXJuaW5nIn19LCJwIjoiYWJjZGVmZ2hpamsifftPfX4AAADUAAAA + V+xYwqcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw + bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijow + LCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgdmVoaWNsZXMsIGFuZCJ9fSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eCJ9G4en+gAAAOEAAABXhZn1UQs6ZXZlbnQtdHlw + ZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTpt + ZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNv + bmluZ0NvbnRlbnQiOnsidGV4dCI6IiBiZWluZyBjYXV0aW91cyJ9fSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSiJ9MQen8QAAALwAAABXRRr+Kws6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmlu + Z0NvbnRlbnQiOnsidGV4dCI6IiBhdCBuaWdodC4ifX0sInAiOiJhYmNkIn3H8rv6AAABAQAAAFdm + EeErCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwi + ZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIE1heWJlIG1lbnRpb24gcGVkZXN0 + cmlhbiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFS + U1RVVldYWVowMTIzNDUifbJKbQ0AAADeAAAAV6bo2gYLOmV2ZW50LXR5cGUHABFjb250ZW50Qmxv + Y2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAF + ZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7 + InRleHQiOiIgcmlnaHRzIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZH + SElKS0xNTk8ifZO2ir8AAAC6AAAAV8paC4sLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQi + OiIgYnV0In19LCJwIjoiYWJjZGVmZ2gifVsJiO0AAAC5AAAAV436cVsLOmV2ZW50LXR5cGUHABFj + b250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2Fn + ZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdD + b250ZW50Ijp7InRleHQiOiIgYWxzbyB0aGUifX0sInAiOiJhYiJ9cAKt7QAAAMcAAABXyxgv9Qs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh + Ijp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBuZWVkIn19LCJwIjoiYWJjZGVmZ2hpamts + bW5vcHFyc3QifRveHT0AAADpAAAAV7XpvpALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQi + OiIgdG8gYmUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + T1BRUlNUVVZXWFlaMCJ9gaSOZAAAAN0AAABX4Uig1gs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsi + dGV4dCI6IiBjYXV0aW91cyByZWdhcmRsZXNzIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBIn2GwrudAAAA5AAAAFdNeXohCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0Ijoi + LiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RV + VldYWVowIn3/wAcbAAAAvwAAAFcCuoT7CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0Ijoi + IFNob3VsZCJ9fSwicCI6ImFiY2RlZmdoaWoifeVV04sAAADbAAAAV24IVXYLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIgSSBtZW50aW9uIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFy + c3R1dnd4eXpBQkNERUZHSEkifTln190AAADYAAAAVymoL6YLOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50 + Ijp7InRleHQiOiIgdXNpbmcifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RF + RkdISUoiffmgYSAAAADQAAAAVxnYZGcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06 + Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNv + bnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIg + cGVkZXN0cmlhbiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3In0TQ5H3AAAAuQAAAFeN + +nFbCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwi + ZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGJyaWRnZXMifX0sInAiOiJhYmMi + fZ/vtisAAADdAAAAV+FIoNYLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVu + dC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRC + bG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgb3IgdW5k + ZXIifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTCJ9SjcQZQAA + AOEAAABXhZn1UQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUH + ABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5k + ZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6InBhc3NlcyBhcyJ9fSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUCJ9fc+WywAAAMAA + AABXeTjz5Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBh + cHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgi + OjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBhbHRlcm5hdGl2ZXM/In19 + LCJwIjoiYWJjZCJ9ce3hFgAAAPQAAABXLZntows6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4 + dCI6IiBZZXMsIHRoYXQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdI + SUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1NjcifRWIPnsAAADZAAAAVxTIBhYLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIncyBhIGdvb2QifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5ekFCQ0RFRkdIIn3r4BKeAAAAvgAAAFc/2q1LCzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6 + eyJ0ZXh0IjoiIGlkZWEuXG5cbiJ9fSwicCI6ImFiY2RlZiJ9Hd+xqgAAAMgAAABXSUi4JAs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7 + InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IldhaXQsIGJ1dCJ9fSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxIn19G6gEAAAA4AAAAFe4+dzhCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0Ijoi + IGluIHNvbWUgY291bnRyaWVzIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNE + RUYifffH7j0AAAC8AAAAV0Ua/isLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u + dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl + bnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIsIGV2 + ZW4ifX0sInAiOiJhYmNkZWZnaCJ9NZFj9gAAAMoAAABXM4jrRAs6ZXZlbnQtdHlwZQcAEWNvbnRl + bnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5 + cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRl + bnQiOnsidGV4dCI6IiBpZiB5b3UifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHUifZQFWGoA + AADkAAAAV015eiELOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgaGF2ZSB0aGUgcmln + aHQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE0ifd0tvAcA + AADnAAAAVwrZAPELOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgb2Ygd2F5LCJ9fSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVlcifaz/ + sisAAADsAAAAV30JMeALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10 + eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9j + a0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgZHJpdmVycyBt + aWdodCBub3QifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + T1BRUiJ9LvQwDQAAAOwAAABXfQkx4As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpj + b250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29u + dGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBz + dG9wLiJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFS + U1RVVldYWVowMTIzIn02wzaAAAAAuAAAAFewmljrCzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0 + ZXh0IjoiIFNvIGl0J3MifX0sInAiOiJhYiJ9BNKb7wAAAMUAAABXsdh8lQs6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmlu + Z0NvbnRlbnQiOnsidGV4dCI6IiBiZXR0ZXIgdG8gYWx3YXlzIn19LCJwIjoiYWJjZGVmIn12jfdD + AAAA3AAAAFfcKIlmCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGVuc3VyZSJ9fSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTSJ9GwsVTgAAANMAAABX + Xngetws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBs + aWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAs + ImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiB0aGUifX0sInAiOiJhYmNkZWZn + aGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkcifYwGslEAAADRAAAAVyS4TdcLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25p + bmdDb250ZW50Ijp7InRleHQiOiIgdmVoaWNsZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QSJ9YkYWnAAAAMwAAABXvMge5As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6 + IiBpcyBzdG9wcGluZyBiZWZvcmUifX0sInAiOiJhYmNkZWZnaGlqayJ9DR4IlAAAAPMAAABXn7kx + sws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRl + bHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBjcm9zc2luZy4gTWF5YmUifX0sInAi + OiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMCJ9 + VUn6sAAAAPEAAABX5Xli0ws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50 + LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJs + b2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBlbXBoYXNp + emUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNU + VVZXWFlaMDEyMzQifbQA5tkAAADoAAAAV4iJlyALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tE + ZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZl + bnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRl + eHQiOiIgdGhhdCBldmVuIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZH + SElKS0xNTk9QUVJTVFVWIn0XRMGXAAAAzgAAAFfGCE2ECzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6 + eyJ0ZXh0IjoiIGlmIHlvdSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHkife7nY2sA + AADUAAAAV+xYwqcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgaGF2ZSB0aGUgc2ln + bmFsIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1diJ9liQOwQAAAMoAAABXM4jrRAs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7 + InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiwgY2hlY2sifX0sInAiOiJhYmNkZWZnaGlqa2xt + bm9wcXJzdHUifSOQD/8AAADEAAAAV4y4VSULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQi + OiIgdGhhdCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxIn2c8InlAAAA8gAAAFei2RgDCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsi + cmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGNhcnMgYXJlIHN0b3BwaW5nIn19LCJwIjoiYWJj + ZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1gifY4gMiYAAADx + AAAAV+V5YtMLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQ + YXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4 + IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIuXG5cbkFsc28ifX0sInAi + OiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEy + MzQ1In1+jzAsAAAA4wAAAFf/WaYxCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiLCB0 + aGUgaW1wb3J0YW5jZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJ + SksifbkNUdkAAADhAAAAV4WZ9VELOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u + dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl + bnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgb2Yg + bm90In19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVIi + fV+jS+EAAAC+AAAAVz/arUsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVu + dC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRC + bG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgYXNzdW1p + bmcifX0sInAiOiJhYmNkZWZnIn2e9kTVAAAA4AAAAFe4+dzhCzpldmVudC10eXBlBwARY29udGVu + dEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlw + ZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVu + dCI6eyJ0ZXh0IjoiIHRoYXQgZHJpdmVycyBjYW4ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5ekFCQ0RFRkcifSzoLjoAAADPAAAAV/toZDQLOmV2ZW50LXR5cGUHABFjb250ZW50Qmxv + Y2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAF + ZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7 + InRleHQiOiIgc2VlIHlvdS4ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3gifUJE61EA + AADlAAAAV3AZU5ELOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgU29tZXRpbWVzIn19 + LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTIn2Ggn3b + AAAAvQAAAFd4etebCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGRyaXZlcnMifX0s + InAiOiJhYmNkZWZnIn0hFjWPAAAA7AAAAFd9CTHgCzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0 + ZXh0IjoiIGFyZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktM + TU5PUFFSU1RVVldYWVowMTIzNDUifWGtW/AAAADZAAAAVxTIBhYLOmV2ZW50LXR5cGUHABFjb250 + ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10 + eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250 + ZW50Ijp7InRleHQiOiIgZGlzdHJhY3RlZCB0b28ifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5ekFCIn0gkwSgAAAA5wAAAFcK2QDxCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVs + dGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50 + eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0 + IjoiLiBTbyJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5P + UFFSU1RVVldYWVowIn3D32TqAAAAvgAAAFc/2q1LCzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0 + ZXh0IjoiIGJlaW5nIn19LCJwIjoiYWJjZGVmZ2hpaiJ9xak6xgAAAO0AAABXQGkYUAs6ZXZlbnQt + dHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29u + DTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJl + YXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBwcm9hY3RpdmUgaW4ifX0sInAiOiJhYmNkZWZnaGlq + a2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWCJ94W5AzgAAAPcAAABXajmX + cws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRl + bHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBlbnN1cmluZyBzYWZldHkuXG5cbiJ9 + fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldY + WVoifRuYPhcAAAC+AAAAVz/arUsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u + dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl + bnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiJMZXQi + fX0sInAiOiJhYmNkZWZnaGlqa2xtIn03Av6BAAAA7wAAAFc6qUswCzpldmVudC10eXBlBwARY29u + dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut + dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29u + dGVudCI6eyJ0ZXh0IjoiIG1lIGNoZWNrIGlmIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAifaF+5RAAAADAAAAAV3k48+ULOmV2ZW50 + LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNv + bg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJy + ZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgSSBtaXNzZWQifX0sInAiOiJhYmNkZWZnaGkifW/V + aHMAAAD1AAAAVxD5xBMLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10 + eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9j + a0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgYW55dGhpbmcu + In19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVW + V1hZWjAxMjM0NTY3OCJ9IhIQpwAAAPQAAABXLZntows6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsi + dGV4dCI6IiBNYXliZSB0aGUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RF + RkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1Njcifbw3PygAAADAAAAAV3k48+ULOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFz + b25pbmdDb250ZW50Ijp7InRleHQiOiIgYmFzaWMifX0sInAiOiJhYmNkZWZnaGlqa2wifbCrSvYA + AADrAAAAV88p7fALOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgc3RlcHMgYXJlIn19 + LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZ + In0vU1FVAAAA7gAAAFcHyWKACzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRl + bnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50 + QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGNvdmVy + ZWQifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNU + VVZXWFlaMDEyMyJ9X/09mQAAAPMAAABXn7kxsws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4 + dCI6IiwgYnV0IGFkZGluZyJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVG + R0hJSktMTU5PUFFSU1RVVldYWVowMTIzNCJ9SC7/7QAAAOAAAABXuPnc4Qs6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmlu + Z0NvbnRlbnQiOnsidGV4dCI6IiB0aGUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5 + ekFCQ0RFRkdISUpLTE1OT1BRUlNUIn3VMT5uAAAAygAAAFcziOtECzpldmVudC10eXBlBwARY29u + dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut + dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29u + dGVudCI6eyJ0ZXh0IjoiIGV4dHJhIHRpcHMgbWFrZXMifX0sInAiOiJhYmNkZWZnaGlqayJ9VeEW + mQAAAPQAAABXLZntows6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBpdCBjb21wcmVo + ZW5zaXZlIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9Q + UVJTVFVWV1hZWjAifVCi/nMAAAC+AAAAVz/arUsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tE + ZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZl + bnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRl + eHQiOiIuIE9rYXkifX0sInAiOiJhYmNkZWZnaGlqIn2r30e3AAAA7wAAAFc6qUswCzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVh + c29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiLCBJIHRoaW5rIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5v + cHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjMifRQjBdkAAAC7AAAAV/c6 + IjsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGlj + YXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJk + ZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgdGhhdCdzIGEifX0sInAiOiJhYmNk + In3Xtlk/AAAA1wAAAFer+Lh3CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRl + bnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50 + QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHNvbGlk + IHN0cnVjdHVyZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHkifeHF1ycAAADZAAAA + VxTIBhYLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw + bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijow + LCJkZWx0YSI6eyJyZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIuIE5vdywifX0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLIn3lAuRKAAAA9gAAAFdXWb7DCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsi + cmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHByZXNlbnQgaXQgaW4ifX0sInAiOiJhYmNkZWZn + aGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1In0yLVKt + AAAA2QAAAFcUyAYWCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGEgY2xlYXIsIn19 + LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSCJ9NxJ6GAAAAPAAAABX2BlL + Yws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRl + bHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBzdGVwLWJ5LXN0ZXAifX0sInAiOiJh + YmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMCJ9dgDr + TwAAAPUAAABXEPnEEws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBtYW5uZXIgd2l0 + aCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RV + VldYWVowMTIzNDU2In3wrykoAAAAvQAAAFd4etebCzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0 + ZXh0IjoiIHNvbWUifX0sInAiOiJhYmNkZWZnaGlqIn2SsvmzAAAA4gAAAFfCOY+BCzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVh + c29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIGJ1bGxldCJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx + cnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSUyJ9nUKIGgAAAOoAAABX8knEQAs6ZXZlbnQtdHlw + ZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTpt + ZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNv + bmluZ0NvbnRlbnQiOnsidGV4dCI6IiBwb2ludHMgb3IifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9w + cXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWCJ9antzNwAAAN4AAABXpujaBgs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7 + InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBudW1iZXJlZCJ9fSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTSJ9pfkMVwAAALkAAABXjfpxWws6ZXZlbnQtdHlw + ZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTpt + ZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNv + bmluZ0NvbnRlbnQiOnsidGV4dCI6IiBsaXN0In19LCJwIjoiYWJjZGVmIn1uej2JAAAA0QAAAFck + uE3XCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwi + ZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiLiJ9fSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGR0giffb6tfQAAADdAAAAV+FIoNYLOmV2ZW50LXR5cGUHABFj + b250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2Fn + ZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFzb25pbmdD + b250ZW50Ijp7InRleHQiOiIgTWFrZSBzdXJlIHRoZSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx + cnN0dXZ3eHl6QUJDREVGRyJ9oPq6vwAAAOoAAABX8knEQAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQi + OnsidGV4dCI6IiBsYW5ndWFnZSBpcyBzaW1wbGUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5ekFCQ0RFRkdISUpLTE1OTyJ9rtz9fAAAAO4AAABXB8ligAs6ZXZlbnQtdHlwZQcAEWNv + bnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl + LXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0Nv + bnRlbnQiOnsidGV4dCI6IiBhbmQgZWFzeSB0byJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoifciYTqkAAADCAAAAVwP4oIULOmV2ZW50 + LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNv + bg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJy + ZWFzb25pbmdDb250ZW50Ijp7InRleHQiOiIgdW5kZXJzdGFuZCwifX0sInAiOiJhYmNkZWZnaCJ9 + LnVNcgAAAOkAAABXtem+kAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50 + LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJs + b2NrSW5kZXgiOjAsImRlbHRhIjp7InJlYXNvbmluZ0NvbnRlbnQiOnsidGV4dCI6IiBhdm9pZGlu + ZyBqYXJnb24uIn19LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xN + Tk9QIn1bRxglAAAAugAAAFfKWguLCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIEFs + In19LCJwIjoiYWJjZGVmZ2hpIn1Dsn5bAAAAyQAAAFd0KJGUCzpldmVudC10eXBlBwARY29udGVu + dEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlw + ZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVu + dCI6eyJ0ZXh0IjoicmlnaHQsIHRpbWUifX0sInAiOiJhYmNkZWZnaGlqa2xtbm9wIn3vJ6tMAAAA + 0QAAAFckuE3XCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MCwiZGVsdGEiOnsicmVhc29uaW5nQ29udGVudCI6eyJ0ZXh0IjoiIHRvIHB1dCBpdCJ9fSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHkifb94p7gAAADIAAAAV0lIuCQLOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJyZWFz + b25pbmdDb250ZW50Ijp7InRleHQiOiIgYWxsIHRvZ2V0aGVyLlxuIn19LCJwIjoiYWJjZGVmZ2hp + aiJ9ZP62hQAAAJQAAABWw6yqeAs6ZXZlbnQtdHlwZQcAEGNvbnRlbnRCbG9ja1N0b3ANOmNvbnRl + bnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50 + QmxvY2tJbmRleCI6MCwicCI6ImFiY2RlZmdoaWprbG1ub3AifcqwdEoAAACrAAAAV5fatbkLOmV2 + ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24v + anNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6 + eyJ0ZXh0IjoiXG5cbiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW4ifZZwJeAAAACyAAAAV/oqQEoLOmV2 + ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24v + anNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6 + eyJ0ZXh0IjoiQ3Jvc3NpbmcgdGhlIn0sInAiOiJhYmNkZWZnaGlqa2xtIn0S3ziDAAAAvwAAAFcC + uoT7CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwi + ZGVsdGEiOnsidGV4dCI6IiBzdHJlZXQgc2FmZWx5IGludm9sdmVzIn0sInAiOiJhYmNkZWZnaGlq + a2xtbm8ifd3NEVsAAADcAAAAV9woiWYLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06 + Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNv + bnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIGNhcmVmdWwifSwicCI6ImFiY2Rl + ZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU2In1C + vEkYAAAAyAAAAFdJSLgkCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQt + dHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50Qmxv + Y2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBvYnNlcnZhdGlvbiJ9LCJwIjoiYWJjZGVmZ2hp + amtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSEkifQlKGx8AAADbAAAAV24IVXYLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0Ijoi + IGFuZCBhd2FyZW5lc3MuIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdI + SUpLTE1OT1BRUlNUVVZXWFkifaNt+HgAAADQAAAAVxnYZGcLOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIEhlcmUncyJ9 + LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWIn2Z + TJn1AAAA2gAAAFdTaHzGCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQt + dHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50Qmxv + Y2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBhIHN0ZXAifSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDUifQ5mrlsAAADOAAAA + V8YITYQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw + bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijox + LCJkZWx0YSI6eyJ0ZXh0IjoiLWJ5LXN0ZXAgZ3VpZGUifSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx + cnN0dXZ3eHl6QUJDREVGR0hJSktMTSJ91JPnzAAAAKQAAABXFYoiaAs6ZXZlbnQtdHlwZQcAEWNv + bnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl + LXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiI6XG5c + biMjIyJ9LCJwIjoiYWJjIn30rTYbAAAAuQAAAFeN+nFbCzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiAqKiJ9LCJwIjoi + YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkMifa5MkDMAAADOAAAAV8YITYQLOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0 + IjoiQmFzaWMifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5P + UFFSU1RVViJ9V7OWogAAAK4AAABXXzo6yQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgU3RlcHMqKiJ9LCJwIjoiYWJj + ZGVmZ2hpamtsbSJ9pxiWqAAAALoAAABXyloLiws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgIFxuMS4ifSwicCI6ImFi + Y2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QSJ9V9JV5QAAANcAAABXq/i4dws6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIg + KipGaW5kIGEifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5P + UFFSU1RVVldYWVowIn0KajbMAAAA3gAAAFem6NoGCzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBTYWZlIFNwb3QifSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVow + MTIzNDU2In2SMyPmAAAAngAAAFf+G4JPCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IioqOiJ9LCJwIjoiYWIifZuELbcA + AADFAAAAV7HYfJULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiICBcbiAgIC0ifSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx + cnN0dXZ3eHl6QUJDREVGR0hJSiJ9Lmut7QAAAMMAAABXPpiJNQs6ZXZlbnQtdHlwZQcAEWNvbnRl + bnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5 + cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgVXNlIGEg + KioifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGRyJ93E10GAAAAOAAAABX + uPnc4Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBs + aWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEs + ImRlbHRhIjp7InRleHQiOiJjcm9zc3dhbGsqKiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3In16SHDIAAAAxwAAAFfLGC/1 + CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0 + aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVs + dGEiOnsidGV4dCI6IiBvciBwZWRlc3RyaWFuIHNpZ25hbCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5v + cHFyc3R1dnd4eSJ9HgvUlwAAAL8AAABXArqE+ws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgaWYgYXZhaWxhYmxlLiJ9 + LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4In1Ih0MYAAAAqwAAAFeX2rW5CzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4 + dCI6IiBBdm9pZCBqIn0sInAiOiJhYmNkZWZnaGlqIn3rz3ITAAAAswAAAFfHSmn6CzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4 + dCI6ImF5d2Fsa2luZyJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHEiffSvfqAAAACyAAAAV/oqQEoL + OmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRp + b24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0 + YSI6eyJ0ZXh0IjoiLCBhcyBpdCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyIn2d1i87AAAA3gAA + AFem6NoGCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFw + cGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6 + MSwiZGVsdGEiOnsidGV4dCI6IuKAmXMgaWxsZWdhbCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFy + c3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0In1RHiUHAAAAvwAAAFcCuoT7 + CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0 + aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVs + dGEiOnsidGV4dCI6IiBpbiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZH + SEkifc22aE4AAAC+AAAAVz/arUsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u + dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl + bnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIG1hbnkgYXJlYXMifSwicCI6ImFiY2Rl + ZmdoaWprbG1ub3BxcnN0dXZ3eHl6In3nJmSgAAAA3wAAAFebiPO2CzpldmVudC10eXBlBwARY29u + dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut + dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBhbmQg + bGVzcyJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJT + VFVWV1hZWjAxMjM0NTY3OCJ9IXJKLwAAAMsAAABXDujC9As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgc2FmZS4gIFxu + In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OIn1i14YbAAAA + twAAAFcyys86CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MSwiZGVsdGEiOnsidGV4dCI6IiAgIC0ifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3 + eHl6In2hIWyyAAAAsAAAAFeA6hMqCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBJZiBubyJ9LCJwIjoiYWJjZGVmZ2hp + amtsbW5vcHEifSU7ve8AAADhAAAAV4WZ9VELOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIGNyb3Nzd2FsayBleGlzdHMi + fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldY + WVowMTIifffR2ugAAAC8AAAAV0Ua/isLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06 + Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNv + bnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiLCBjaG9vc2UifSwicCI6ImFiY2Rl + ZmdoaWprbG1ub3BxcnN0dXZ3eHl6QSJ9U7KWQAAAALUAAABXSAqcWgs6ZXZlbnQtdHlwZQcAEWNv + bnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl + LXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgYSB3 + ZWxsIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHUifdvGQW8AAADTAAAAV154HrcLOmV2ZW50 + LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNv + bg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0 + ZXh0IjoiLWxpdCBhcmVhIHdpdGgifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJD + REVGR0hJSktMTU5PUFFSIn1zABHiAAAAtQAAAFdICpxaCzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBjbGVhciB2aXNp + YmlsaXR5IGluIn0sInAiOiJhYmNkZWZnaCJ9MxXBegAAAMYAAABX9ngGRQs6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIg + Ym90aCBkaXJlY3Rpb25zLlxuXG4ifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eCJ9+Ga2 + xQAAANAAAABXGdhkZws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIyLiAqKiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFy + c3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1gifUF+YLUAAADVAAAAV9E46xcLOmV2ZW50 + LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNv + bg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0 + ZXh0IjoiQ2hlY2sgVHJhZmZpYyBTaWduYWxzIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2 + d3h5ekFCQ0RFRkdISUpLTE0ifVWHrIQAAAC3AAAAVzLKzzoLOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiKio6ICBcbiAg + In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHUifVlGQL0AAACzAAAAV8dKafoLOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0 + IjoiIC0gV2FpdCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFycyJ9XUa9CgAAAKwAAABXJfppqQs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRh + Ijp7InRleHQiOiIgZm9yIHRoZSJ9LCJwIjoiYWJjZGVmZ2hpamsifdkpS84AAADNAAAAV4GoN1QL + OmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRp + b24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0 + YSI6eyJ0ZXh0IjoiIFwid2FsayJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNE + RUZHSElKS0xNTk9QUVJTIn1XYWCuAAAAuQAAAFeN+nFbCzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IlwiIHNpZ25hbCJ9 + LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dncifanzx1IAAADBAAAAV0RY2lULOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0 + IjoiIG9yIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLIn0dOlFB + AAAArgAAAFdfOjrJCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBncmVlbiBsaWdodCJ9LCJwIjoiYWJjZGVmZ2hpIn2+ + XRPVAAAAzQAAAFeBqDdUCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQt + dHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50Qmxv + Y2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBhdCBpbnRlcnNlY3Rpb25zIn0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISSJ9MuSbiwAAAKAAAABX4AqEqAs6ZXZlbnQt + dHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29u + DTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRl + eHQiOiIuICBcbiJ9LCJwIjoiYWIifRkJfg8AAADKAAAAVzOI60QLOmV2ZW50LXR5cGUHABFjb250 + ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10 + eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiICAgLSBX + YXRjaCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNIn1M7taH + AAAAuAAAAFewmljrCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBmb3IgY291bnQifSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dSJ9pZ9jgAAAAN4AAABXpujaBgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiJkb3duIHRpbWVycyJ9LCJwIjoi + YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0 + NSJ95bnyTgAAANQAAABX7FjCpws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgdG8gZW5zdXJlIHlvdSJ9LCJwIjoiYWJj + ZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTIn2u3jN1AAAA1gAAAFeW + mJHHCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwi + ZGVsdGEiOnsidGV4dCI6IiBoYXZlIGVub3VnaCB0aW1lIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9w + cXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUiJ9eMb8YgAAANMAAABXXngetws6ZXZlbnQtdHlw + ZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTpt + ZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQi + OiIgdG8gY3Jvc3MuXG5cbiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZH + SElKS0xNTk9QUVIifVIreXsAAADPAAAAV/toZDQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tE + ZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZl + bnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiMy4gKioifSwicCI6ImFi + Y2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVlcifVP0sDAAAACn + AAAAV1IqWLgLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQ + YXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4 + IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiTG9vayBCb3RoIn0sInAiOiJhYmNkZSJ9kH2gfAAAANAAAABX + GdhkZws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBs + aWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEs + ImRlbHRhIjp7InRleHQiOiIgV2F5cyoqOiAgXG4ifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QUJDREVGR0hJSktMTU5PUFEifYKS4s4AAACyAAAAV/oqQEoLOmV2ZW50LXR5cGUHABFj + b250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2Fn + ZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiICAg + LSAqKiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyIn1p9sy7AAAAsQAAAFe9ijqaCzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4 + dCI6IkxlZnQtUmlnaHQifSwicCI6ImFiY2RlZmdoaWprbG1uIn1IzsZWAAAApgAAAFdvSnEICzpl + dmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9u + L2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEi + OnsidGV4dCI6Ii1MZWZ0Kio6In0sInAiOiJhYmNkZSJ9HoYWugAAAL4AAABXP9qtSws6ZXZlbnQt + dHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29u + DTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRl + eHQiOiIgR2xhbmNlIGxlZnQifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHkifWVqmJMA + AACqAAAAV6q6nAkLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiLCByaWdodCJ9LCJwIjoiYWJjZGVmZ2hpaiJ9oJ8w3AAA + ANUAAABX0TjrFws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUH + ABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5k + ZXgiOjEsImRlbHRhIjp7InRleHQiOiIsIGFuZCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMiJ9gJYWYQAAALEAAABXvYo6mgs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7 + InRleHQiOiIgbGVmdCBhZ2FpbiAoIn0sInAiOiJhYmNkZWZnaGlqayJ9rw9KlgAAANgAAABXKagv + pgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRl + bHRhIjp7InRleHQiOiJvciByaWdodCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpB + QkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMiJ9Lnr5CwAAAKgAAABX0HrPaQs6ZXZlbnQtdHlw + ZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTpt + ZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQi + OiItbGVmdC1yaWdodCJ9LCJwIjoiYWJjZCJ9FH8ZQgAAALoAAABXyloLiws6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIg + aW4gY291bnRyaWVzIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdCJ9VacGFAAAAOQAAABXTXl6 + IQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRl + bHRhIjp7InRleHQiOiIgd2hlcmUgdHJhZmZpYyBkcml2ZXMifSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMSJ9CMzkoQAAALQAAABXdWq1 + 6gs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRl + bHRhIjp7InRleHQiOiIgb24gdGhlIGxlZnQifSwicCI6ImFiY2RlZmdoaWprbG1ubyJ9B/NSNAAA + AMcAAABXyxgv9Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUH + ABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5k + ZXgiOjEsImRlbHRhIjp7InRleHQiOiIsIGxpa2UgdGhlIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9w + cXJzdHV2d3h5ekFCQ0RFRkdISUoifX377ikAAACpAAAAV+0a5tkLOmV2ZW50LXR5cGUHABFjb250 + ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10 + eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIFVLKS4g + IFxuIn0sInAiOiJhYmNkZWZnIn00v7bZAAAA2QAAAFcUyAYWCzpldmVudC10eXBlBwARY29udGVu + dEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlw + ZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiAgIC0gTGlz + dGVuIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNU + VVZXWFlaMCJ92B4KIgAAAMsAAABXDujC9As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgZm9yIGFwcHJvYWNoaW5nIn0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdIIn1u7pLHAAAAugAAAFfKWguL + CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0 + aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVs + dGEiOnsidGV4dCI6IiB2ZWhpY2xlcywifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3In0O + +rRAAAAA5wAAAFcK2QDxCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQt + dHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50Qmxv + Y2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBlc3BlY2lhbGx5IGJpa2VzIn0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1Njc4 + In2NVYNwAAAApAAAAFcViiJoCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRl + bnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50 + QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBvciBxdWlldCJ9LCJwIjoiYWIife8s2uEA + AAC/AAAAVwK6hPsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIGVsZWN0cmljIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9w + cXJzdHV2d3h5ekFCQyJ9bnx5rgAAAM8AAABX+2hkNAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgY2Fycy5cblxuIn0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUiJ9PvgksQAA + ALYAAABXD6rmigs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUH + ABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5k + ZXgiOjEsImRlbHRhIjp7InRleHQiOiI0LiAqKiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4In17T+dkAAAAxgAAAFf2eAZFCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IkVuc3VyZSBBbGwifSwicCI6ImFiY2Rl + ZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJIn0R/Vc+AAAAswAAAFfHSmn6CzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4 + dCI6IiBUcmFmZmljIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXIifb7gho4AAACrAAAAV5fatbkL + OmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRp + b24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0 + YSI6eyJ0ZXh0IjoiIEhhcyJ9LCJwIjoiYWJjZGVmZ2hpamtsbW4ifUoqnzEAAACoAAAAV9B6z2kL + OmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRp + b24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0 + YSI6eyJ0ZXh0IjoiIFN0b3BwZWQqKjoifSwicCI6ImFiY2QifSpv9C4AAACqAAAAV6q6nAkLOmV2 + ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24v + anNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6 + eyJ0ZXh0IjoiICBcbiAgIn0sInAiOiJhYmNkZWZnaGlqayJ9DkO2lQAAAKoAAABXqrqcCQs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7 + InRleHQiOiIgLSBNYWtlIn0sInAiOiJhYmNkZWZnaGlqIn1//CRBAAAA4AAAAFe4+dzhCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsi + dGV4dCI6IiBleWUgY29udGFjdCB3aXRoIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5 + ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEifSpeiO4AAADoAAAAV4iJlyALOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0 + IjoiIGRyaXZlcnMgaWYgcG9zc2libGUifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6 + QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU2In2zxrgFAAAApQAAAFco6gvYCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsi + dGV4dCI6IiwifSwicCI6ImFiY2RlZmdoaWprIn264dm0AAAAywAAAFcO6ML0CzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6 + IiBhbmQifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFS + U1QifddJ1A0AAADSAAAAV2MYNwcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u + dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl + bnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIHdhaXQifSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoifXPViOMAAADLAAAAVw7o + wvQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGlj + YXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJk + ZWx0YSI6eyJ0ZXh0IjoiIGZvciB2ZWhpY2xlcyJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBQkNERUZHSElKSyJ9ins30wAAANUAAABX0TjrFws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgdG8gY29tZSJ9 + LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZ + WiJ9y6hJIAAAAKMAAABXp6r+eAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgdG8gYSJ9LCJwIjoiYWJjZGUifUYUV14A + AADmAAAAVze5KUELOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIGNvbXBsZXRlIHN0b3AgYmVmb3JlIn0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMyJ9W8kh + JQAAAMAAAABXeTjz5Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgc3RlcHBpbmcifSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDRCJ9HzF1CwAAAKcAAABXUipYuAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgaW50byJ9LCJw + IjoiYWJjZGVmZ2hpIn0rCPpBAAAApwAAAFdSKli4CzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiB0aGUgcm9hZCJ9LCJw + IjoiYWJjZGUifUNfI9IAAADVAAAAV9E46xcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiLiJ9LCJwIjoiYWJjZGVmZ2hp + amtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTYifZl574gA + AADMAAAAV7zIHuQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiICBcbiAgIC0ifSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx + cnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFEifde9ba0AAADHAAAAV8sYL/ULOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0Ijoi + IFdhdGNoIGZvciB0dXJuaW5nIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCIn37 + ylqNAAAAwwAAAFc+mIk1CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQt + dHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50Qmxv + Y2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiB2ZWhpY2xlcywifSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGIn09DBSBAAAAyAAAAFdJSLgkCzpldmVudC10eXBlBwARY29u + dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut + dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBldmVu + IGlmIHlvdSJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSEkifacyI5kA + AACtAAAAVxiaQBkLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIGhhdmUgdGhlIHJpZ2h0In0sInAiOiJhYmNkZSJ9JpOe + oQAAAL4AAABXP9qtSws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgb2YifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QUJDREVGR0gifbKKBZYAAADQAAAAVxnYZGcLOmV2ZW50LXR5cGUHABFjb250ZW50Qmxv + Y2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAF + ZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIHdheS5cblxuIn0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUIn2RnRbp + AAAAwQAAAFdEWNpVCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IjUuICoqIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5ekFCQ0RFRkdISSJ9gPxh7AAAAM4AAABXxghNhAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiJDcm9zcyBQcm9t + cHQifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PIn3keRBO + AAAAzgAAAFfGCE2ECzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MSwiZGVsdGEiOnsidGV4dCI6Imx5IGFuZCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFy + c3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFUifef2YhIAAAC+AAAAVz/arUsLOmV2ZW50LXR5 + cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06 + bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0 + IjoiIFNhZiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHIn1tku/OAAAA + zgAAAFfGCE2ECzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MSwiZGVsdGEiOnsidGV4dCI6ImVseSoqOiAgXG4ifSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx + cnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFEifTY9+V0AAADNAAAAV4GoN1QLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0Ijoi + ICAgLSBXYWxrIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + T1BRIn1uCkI4AAAA0AAAAFcZ2GRnCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBzdHJhaWdodCBhY3Jvc3MifSwicCI6 + ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTSJ9gVs2OQAAAMEAAABXRFja + VQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRl + bHRhIjp7InRleHQiOiLigJQifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVG + R0hJSksifX9jS68AAAC4AAAAV7CaWOsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06 + Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNv + bnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiZG9u4oCZdCJ9LCJwIjoiYWJjZGVm + Z2hpamtsbW5vcHFyc3R1dnd4In3BcW0eAAAA0wAAAFdeeB63CzpldmVudC10eXBlBwARY29udGVu + dEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlw + ZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBydW4gb3Ii + fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldY + WSJ92fIbnAAAALMAAABXx0pp+gs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgc3RvcCJ9LCJwIjoiYWJjZGVmZ2hpamts + bW5vcHFyc3R1In0q4uDsAAAAsgAAAFf6KkBKCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVs + dGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50 + eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBtaWR3YXkifSwicCI6ImFi + Y2RlZmdoaWprbG1ub3BxciJ9ogKTGQAAANoAAABXU2h8xgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIuICBcbiAgIn0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFla + MDEyMzQ1In2YF3jzAAAAtQAAAFdICpxaCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiAtIFN0YXkgaW5zaWRlIn0sInAi + OiJhYmNkZWZnaGlqa2xtbiJ90ahl1AAAAMAAAABXeTjz5Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgY3Jvc3MifSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGRyJ9Ji6+hgAAAKYAAABXb0pxCAs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRh + Ijp7InRleHQiOiJ3YWxrIn0sInAiOiJhYmNkZWZnaGkifV+az4EAAACmAAAAV29KcQgLOmV2ZW50 + LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNv + bg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0 + ZXh0IjoiIGxpbmVzIn0sInAiOiJhYmNkZWZnIn2/7zd5AAAAygAAAFcziOtECzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6 + IiBpZiB0aGV5In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1O + TyJ9hxAIoAAAAMkAAABXdCiRlAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiLigJlyZSBwcmVzZW50In0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISSJ9n+M4FQAAAMcAAABXyxgv9Qs6ZXZlbnQt + dHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29u + DTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRl + eHQiOiIuXG5cbjYifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktM + TU4ifY4qZc4AAADFAAAAV7HYfJULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u + dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl + bnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiLiAqKlN0YXkifSwicCI6ImFiY2RlZmdo + aWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSiJ9Rz0rSgAAALEAAABXvYo6mgs6ZXZlbnQtdHlw + ZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTpt + ZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQi + OiIgQWxlcnQgVW50aWwifSwicCI6ImFiY2RlZmdoaWprbCJ9FznXYgAAALkAAABXjfpxWws6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7 + InRleHQiOiIgWW91In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCIn24MB6IAAAA + xgAAAFf2eAZFCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MSwiZGVsdGEiOnsidGV4dCI6IiBSZWFjaCB0aGUgT3RoZXIifSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDIn338j1vAAAA3gAAAFem6NoGCzpldmVudC10eXBlBwARY29udGVu + dEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlw + ZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBTaWRlKio6 + ICBcbiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJT + VFVWV1hZWjAxMjM0In0dF07bAAAAxwAAAFfLGC/1CzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiAgIC0ifSwicCI6ImFi + Y2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUCJ9Am5r2gAAAMQAAABXjLhV + JQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRl + bHRhIjp7InRleHQiOiIgS2VlcCBzY2FubmluZyJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4eXpBQkMifcbX8ccAAACwAAAAV4DqEyoLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIGZvciB0cmFmZmljIGFzIn0s + InAiOiJhYmNkZWZnaCJ9e5aFbAAAANUAAABX0TjrFws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgeW91IGNyb3NzIn0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWCJ9 + /zAK0wAAALoAAABXyloLiws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50 + LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJs + b2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIuIEF2b2lkIn0sInAiOiJhYmNkZWZnaGlqa2xt + bm9wcXJzdHV2d3h5eiJ9eBlaFAAAAOcAAABXCtkA8Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgZGlzdHJhY3Rpb25z + IGxpa2UgcGhvbmVzIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpL + TE1OT1BRUlNUVVZXWFlaMCJ9axpwEAAAAMsAAABXDujC9As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgb3IgaGVhZHBo + b25lcy5cblxuIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFIn29O66RAAAA + vgAAAFc/2q1LCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MSwiZGVsdGEiOnsidGV4dCI6Ii0tLVxuXG4ifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QUJDRCJ9cUSyiwAAAOQAAABXTXl6IQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIjIyMgKipBZGRpdGlvbmFs + In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZX + WFlaMDEyMzQ1NiJ9Z9OJJwAAALUAAABXSAqcWgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgVGlwcyoqIn0sInAiOiJh + YmNkZWZnaGlqa2xtbm9wcXJzdHUifeMvWxYAAACjAAAAV6eq/ngLOmV2ZW50LXR5cGUHABFjb250 + ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10 + eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiICBcbi0g + KioifSwicCI6ImFiIn2gtBjDAAAAzwAAAFf7aGQ0CzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IkF0In0sInAiOiJhYmNk + ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaIn3qgc0dAAAA + xQAAAFex2HyVCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcA + EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRl + eCI6MSwiZGVsdGEiOnsidGV4dCI6IiBOaWdodCoqOiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFy + c3R1dnd4eXpBQkNERUZHSEkifaFIv0wAAADeAAAAV6bo2gYLOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIFdlYXIgcmVm + bGVjdGl2ZSJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9Q + UVJTVFVWV1hZWjAifam7ALwAAADFAAAAV7HYfJULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tE + ZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZl + bnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIGNsb3RoaW5nIG9yIGNh + cnJ5In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eiJ9fsi9PAAAAMYAAABX9ngGRQs6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRh + Ijp7InRleHQiOiIgYSBmbGFzaGxpZ2h0In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5 + ekFCQ0RFRiJ9eUZ9jQAAAMwAAABXvMge5As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgdG8gaW1wcm92ZSJ9LCJwIjoi + YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTiJ9JAPw3wAAAM0AAABXgag3 + VAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRl + bHRhIjp7InRleHQiOiIgdmlzaWJpbGl0eS4gIFxuIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5ekFCQ0RFRkdISUoifWm9y0AAAACmAAAAV29KcQgLOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiLSAqKldpdGgi + fSwicCI6ImFiY2RlIn12nPk7AAAAuQAAAFeN+nFbCzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBLaWRzIn0sInAiOiJh + YmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekEifbvPJckAAAC6AAAAV8paC4sLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0Ijoi + IG9yIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0QifT7Asj0AAADQAAAAVxnY + ZGcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGlj + YXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJk + ZWx0YSI6eyJ0ZXh0IjoiIERpc2FiaWxpdGllcyoqOiBIb2xkIn0sInAiOiJhYmNkZWZnaGlqa2xt + bm9wcXJzdHV2d3h5ekFCQ0RFRkdIIn2zf6F3AAAA2wAAAFduCFV2CzpldmVudC10eXBlBwARY29u + dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut + dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBoYW5k + cyB3aXRoIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BR + UlNUVVZXWFlaMDEyIn3F8vnVAAAA0wAAAFdeeB63CzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBjaGlsZHJlbiwifSwi + cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVViJ9Xt6E + +wAAALcAAABXMsrPOgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgYW5kIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5eiJ9uU0ouAAAAK8AAABXYloTeQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgdXNlIGFzc2lzdGl2ZSJ9LCJw + IjoiYWJjZGVmZ2gifSslP1YAAADMAAAAV7zIHuQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tE + ZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZl + bnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIGRldmljZXMifSwicCI6 + ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFEifcrsoFMAAADFAAAA + V7HYfJULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw + bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijox + LCJkZWx0YSI6eyJ0ZXh0IjoiIChlLmcifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6 + QUJDREVGR0hJSktMTSJ9nOWytgAAAMgAAABXSUi4JAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9j + a0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl + dmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIuLCB3aGl0ZSJ9LCJw + IjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNIn0HujirAAAA1AAAAFfs + WMKnCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwi + ZGVsdGEiOnsidGV4dCI6IiBjYW5lcykuIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5 + ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFkifXWVAY4AAACgAAAAV+AKhKgLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0Ijoi + IFNlZWsifSwicCI6ImFiIn1PUExjAAAAswAAAFfHSmn6CzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBwZWRlc3RyaWFu + IGJyaWRnZXMifSwicCI6ImFiY2RlZmcifcY0oMMAAACsAAAAVyX6aakLOmV2ZW50LXR5cGUHABFj + b250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2Fn + ZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIG9y + IHVuZGVyIn0sInAiOiJhYmNkZWZnaGlqIn3JxBMsAAAA0gAAAFdjGDcHCzpldmVudC10eXBlBwAR + Y29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3Nh + Z2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6InBh + c3NlcyBpZiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9Q + UVJTVFVWIn2Cl3H2AAAAxwAAAFfLGC/1CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN + OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj + b250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBhdmFpbGFibGUuICBcbiJ9LCJw + IjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERSJ9kcRIYAAAAMkAAABXdCiRlAs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7 + InRleHQiOiItICoqVW4ifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJ + SktMTU5PUCJ9XJqIlwAAANAAAABXGdhkZws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh + DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi + Y29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiJtYXJrZWQgUm9hZHMifSwicCI6 + ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFEifTHL6pYAAAC6AAAA + V8paC4sLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw + bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijox + LCJkZWx0YSI6eyJ0ZXh0IjoiKio6IENyb3NzIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2 + d3gifRuP2FMAAADDAAAAVz6YiTULOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u + dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl + bnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIHdoZXJlIn0sInAiOiJhYmNkZWZnaGlq + a2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUoifWDtrskAAADWAAAAV5aYkccLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0Ijoi + IHlvdSJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJT + VFVWV1hZWjAxMjM0In1kEsdAAAAArQAAAFcYmkAZCzpldmVudC10eXBlBwARY29udGVudEJsb2Nr + RGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2 + ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBjYW4gc2VlIG9uIn0s + InAiOiJhYmNkZWZnaGkifTVAemEAAADOAAAAV8YITYQLOmV2ZW50LXR5cGUHABFjb250ZW50Qmxv + Y2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAF + ZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiY29taW5nIHRyYWZm + aWMgY2xlYXJseSJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERSJ9X86ExwAA + AMIAAABXA/ighQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUH + ABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5k + ZXgiOjEsImRlbHRhIjp7InRleHQiOiIsIGFuZCBuZXZlciJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5v + cHFyc3R1dnd4eXpBQkNEIn1RlAtEAAAA0AAAAFcZ2GRnCzpldmVudC10eXBlBwARY29udGVudEJs + b2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcA + BWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBhc3N1bWUgZHJp + dmVycyBzZWUifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSiJ9o23F + pwAAANUAAABX0TjrFws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5 + cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2Nr + SW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgeW91LiAgXG4ifSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWSJ9OSrB8QAAANAAAABXGdhkZws6 + ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv + bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRh + Ijp7InRleHQiOiItICoqIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdI + SUpLTE1OT1BRUlNUVVZXWFkifaZgaiYAAADWAAAAV5aYkccLOmV2ZW50LXR5cGUHABFjb250ZW50 + QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBl + BwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiSW50ZXJuYXRp + b25hbCBUcmF2ZWwifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktM + TU5PIn0ylrrwAAAA0QAAAFckuE3XCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IioqOiBOb3RlIn0sInAiOiJhYmNkZWZn + aGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVYifTYkCvMAAADVAAAAV9E4 + 6xcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGlj + YXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJk + ZWx0YSI6eyJ0ZXh0IjoiIGxvY2FsIHRyYWZmaWMgcGF0dGVybnMifSwicCI6ImFiY2RlZmdoaWpr + bG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSksifSa9u2UAAADaAAAAV1NofMYLOmV2ZW50LXR5cGUH + ABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz + c2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0Ijoi + IChlLmcifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFS + U1RVVldYWVowMTIzNDU2NyJ93YsIyQAAALQAAABXdWq16gs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC + bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH + AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIuLCBsZWZ0In0s + InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdCJ9H9aKzgAAANkAAABXFMgGFgs6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIt + c2lkZSBkcml2aW5nKSJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElK + S0xNTk9QUVJTVFVWV1gife51UIUAAADLAAAAVw7owvQLOmV2ZW50LXR5cGUHABFjb250ZW50Qmxv + Y2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAF + ZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIGFuZCBwZWRlc3Ry + aWFuIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISSJ9MhOqFAAAALkA + AABXjfpxWws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBh + cHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgi + OjEsImRlbHRhIjp7InRleHQiOiIgY3VzdG9tcyJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1 + dnd4In2CR9mGAAAAsgAAAFf6KkBKCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6Ii5cblxuIyMjICoqIn0sInAiOiJhYmNk + ZWZnaGlqa2xtbiJ9RTbS6AAAAMQAAABXjLhVJQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl + bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu + dHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiJSZW1lbWJlcioqOiJ9LCJw + IjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUYifc7Jyr0AAADWAAAAV5aYkccLOmV2 + ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24v + anNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6 + eyJ0ZXh0IjoiICBcbi0gKioifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVG + R0hJSktMTU5PUFFSU1RVVldYWVowIn1CmxbsAAAAyAAAAFdJSLgkCzpldmVudC10eXBlBwARY29u + dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut + dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IlJpZ2h0 + In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1AifefXqWgA + AADUAAAAV+xYwqcLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIG9mIFdheSDiiaAifSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVViJ92TQhOQAAAKcAAABXUipYuAs6ZXZl + bnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9q + c29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7 + InRleHQiOiIgSW52In0sInAiOiJhYmNkZWZnaGlqIn3gDB8IAAAAqAAAAFfQes9pCzpldmVudC10 + eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24N + Om1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4 + dCI6ImluY2liaWxpdHkqKjoifSwicCI6ImFiIn3oglGjAAAA0gAAAFdjGDcHCzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6 + IiBFdmVuIHdpdGgifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktM + TU5PUFFSU1RVIn1nZQ9kAAAA1AAAAFfsWMKnCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVs + dGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50 + eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiBhIn0sInAiOiJhYmNkZWZn + aGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQifdLM8lgA + AADfAAAAV5uI87YLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu + ZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIHNpZ25hbCwgZG91YmxlIn0sInAiOiJhYmNkZWZnaGlq + a2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyIn03nKLmAAAA3wAA + AFebiPO2CzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFw + cGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6 + MSwiZGVsdGEiOnsidGV4dCI6Ii1jaGVjayB0aGF0In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJz + dHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaMDEyMzQ1NiJ9Z0JWmQAAAL0AAABXeHrX + mws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh + dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRl + bHRhIjp7InRleHQiOiIgY2FycyJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNE + RSJ9qkHI+QAAANkAAABXFMgGFgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgYXJlIHN0b3BwaW5nLiJ9LCJwIjoiYWJj + ZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1gifekEc/cAAAC9 + AAAAV3h615sLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQ + YXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4 + IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiICBcbi0gKioifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QUIifWdkfmEAAAC7AAAAV/c6IjsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0 + YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7 + ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiRGlzdHJhY3Rpb25zIEtpbGwi + fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxIn3si8izAAAAtwAAAFcyys86CzpldmVudC10eXBlBwAR + Y29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3Nh + Z2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6Iioq + OiBTdGF5In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2In2l3HMMAAAA2AAAAFcpqC+mCzpl + dmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9u + L2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEi + OnsidGV4dCI6IiBmb2N1c2Vk4oCUIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFC + Q0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaIn2s5ExlAAAAygAAAFcziOtECzpldmVudC10eXBlBwAR + Y29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3Nh + Z2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6Im5v + IHRleHRpbmcifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTSJ9 + fC+V/QAAANoAAABXU2h8xgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50 + LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJs + b2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIgb3Igc2Nyb2xsaW5nIHdoaWxlIn0sInAiOiJh + YmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUIn1/0W6bAAAA4AAA + AFe4+dzhCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFw + cGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6 + MSwiZGVsdGEiOnsidGV4dCI6IiBjcm9zc2luZy4gIFxuXG4ifSwicCI6ImFiY2RlZmdoaWprbG1u + b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIifVjBZl8AAACtAAAAVxia + QBkLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGlj + YXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJk + ZWx0YSI6eyJ0ZXh0IjoiQnkgZm9sbG93aW5nIHRoZXNlIn0sInAiOiJhYiJ9xhJvkgAAANIAAABX + Yxg3Bws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBs + aWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEs + ImRlbHRhIjp7InRleHQiOiIgc3RlcHMsIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5 + ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWCJ9sWhCqgAAAL4AAABXP9qtSws6ZXZlbnQtdHlwZQcA + EWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNz + YWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIg + eW914oCZIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0QifWwfK+sAAADPAAAA + V/toZDQLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw + bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijox + LCJkZWx0YSI6eyJ0ZXh0IjoibGwgbWluaW1pemUifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0 + dXZ3eHl6QUJDREVGR0hJSktMTU5PUFEifdacqjwAAADAAAAAV3k48+ULOmV2ZW50LXR5cGUHABFj + b250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2Fn + ZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0YSI6eyJ0ZXh0IjoiIHJp + c2tzIGFuZCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkMifQDfQ70AAACpAAAA + V+0a5tkLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw + bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijox + LCJkZWx0YSI6eyJ0ZXh0IjoiIGNyb3NzIn0sInAiOiJhYmNkZWZnaGlqIn1MeGeaAAAAvQAAAFd4 + etebCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxp + Y2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwi + ZGVsdGEiOnsidGV4dCI6IiBzYWZlbHkifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6 + QUJDIn1C4jxHAAAAqQAAAFftGubZCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNv + bnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250 + ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6IiEifSwicCI6ImFiY2RlZmdoaWprbG1u + byJ9ifQ4ZwAAAMsAAABXDujC9As6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250 + ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu + dEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRleHQiOiIg8J+atiJ9LCJwIjoiYWJjZGVmZ2hpamts + bW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTIn28koU8AAAAqwAAAFeX2rW5CzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsi + dGV4dCI6IuKZgiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vIn27RPkvAAAApQAAAFco6gvYCzpldmVu + dC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pz + b24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsi + dGV4dCI6Iu+4j/CfmqYifSwicCI6ImFiY2RlIn1fGGVkAAAAxAAAAFeMuFUlCzpldmVudC10eXBl + BwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1l + c3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidGV4dCI6 + IiJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUSJ90pHE + 9gAAAK8AAABWFV0j7ws6ZXZlbnQtdHlwZQcAEGNvbnRlbnRCbG9ja1N0b3ANOmNvbnRlbnQtdHlw + ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ + bmRleCI6MSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFEi + fSM3jgIAAACSAAAAUdKIynsLOmV2ZW50LXR5cGUHAAttZXNzYWdlU3RvcA06Y29udGVudC10eXBl + BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7InAiOiJhYmNkZWZnaGlq + a2xtbm9wcSIsInN0b3BSZWFzb24iOiJlbmRfdHVybiJ9lR4ZiwAAAOwAAABOGWKZIAs6ZXZlbnQt + dHlwZQcACG1ldGFkYXRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl + LXR5cGUHAAVldmVudHsibWV0cmljcyI6eyJsYXRlbmN5TXMiOjIyMTgwfSwicCI6ImFiY2RlZmdo + aWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMIiwidXNhZ2UiOnsiaW5wdXRUb2tlbnMiOjEy + LCJvdXRwdXRUb2tlbnMiOjEyMDEsInRvdGFsVG9rZW5zIjoxMjEzfX3uDo9k + headers: + connection: + - keep-alive + content-type: + - application/vnd.amazon.eventstream + transfer-encoding: + - chunked + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/test_bedrock.py b/tests/models/test_bedrock.py index a749b1001..20d3c2526 100644 --- a/tests/models/test_bedrock.py +++ b/tests/models/test_bedrock.py @@ -25,6 +25,7 @@ TextPart, TextPartDelta, ThinkingPart, + ThinkingPartDelta, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -626,3 +627,817 @@ async def test_bedrock_model_thinking_part(allow_model_requests: None, bedrock_p ), ] ) + + +async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, bedrock_provider: BedrockProvider): + m = BedrockConverseModel('us.deepseek.r1-v1:0', provider=bedrock_provider) + agent = Agent(m) + + event_parts: list[Any] = [] + async with agent.iter(user_prompt='How do I cross the street?') as agent_run: + async for node in agent_run: + if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node): + async with node.stream(agent_run.ctx) as request_stream: + async for event in request_stream: + event_parts.append(event) + + assert event_parts == snapshot( + [ + PartStartEvent(index=0, part=ThinkingPart(content='Okay')), + FinalResultEvent(tool_name=None, tool_call_id=None), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', so')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' user is')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' asking how to')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' cross the street')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. Let me')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' think')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' about how')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to approach')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' this. First')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', I need')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to make sure')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' I cover')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' all the basic')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' steps,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' but also')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' consider different')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' scenarios. Maybe')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' start with the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' obvious:')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' finding')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' a cross')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='walk.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' But wait,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' not all')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' streets')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' have cross')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='walks,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' especially in less')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' urban areas.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' So I should')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' mention looking')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' for')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' a crosswalk')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' or')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' pedestrian crossing')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' signals')), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ + first. + +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='Then, check')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' for traffic lights')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. If')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=" there's")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' a traffic light')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', wait')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' for the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' walk')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' signal. But')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' sometimes')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' people might not')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' know what')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the symbols')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' mean. Maybe')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' explain the "')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='walk"')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' and "don')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='\'t walk"')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' signals. Also')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', in')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' some places')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', there')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' are count')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='down tim')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='ers which')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' can help.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' But what')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=" if there's")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' no traffic')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' light?')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Then they should')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' look both ways')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' for cars.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' But how')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' many')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' times?')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Usually')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' left')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='-right')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='-left,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' but maybe clarify')), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ + that. + +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='Wait, but')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' in')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' some countries,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' traffic comes')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' from the opposite')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' direction')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. Like')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' in')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the UK,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' cars')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' drive on the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' left. So')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' maybe add')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' a note')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' about being')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' aware of the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' local traffic direction')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. Also')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', distractions')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' like')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' using a')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' phone while crossing')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. Em')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='phasize the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' importance of staying')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' focused')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' and not')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' being')), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ + distracted. + +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='What')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' about children')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' or')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' people')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' with disabilities?')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Maybe mention using')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' pedestrian')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' bridges')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' or tunnels')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' if available.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Also, making')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' eye contact with')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' drivers to')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' ensure they see')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' you. But')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' not')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' all drivers')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' might make')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' eye contact,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' so maybe that')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta="'s not")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' always')), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ + reliable. + +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='Oh')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', and')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' jaywalk')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='ing. Should')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' I mention that')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=" it's illegal")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' in some places')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' and safer')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to use')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' crosswalks?')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Yes')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', that')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta="'s important for")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' legal')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' and')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' safety reasons.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Also, even')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' if the walk')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' signal is on')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', check')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' for turning')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' vehicles')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' that might not')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' stop')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. B')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='icycles and')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' motorcycles')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' can be')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' quieter')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' and')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' harder to hear')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', so remind')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' listen')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' as')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' well as look')), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ +. + +Wait\ +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', what about')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' at night')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='? W')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='earing reflective clothing')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' or carrying')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' a light to')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' be more')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' visible. That')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta="'s a good")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' point. Also')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', crossing')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' in groups')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' if possible,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' as more')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' people are more')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' visible.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' But during')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' COVID')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', maybe')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' social distancing affects')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' that? Not')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' sure if that')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta="'s still")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' a')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' concern,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' but maybe skip')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' that')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' part unless necessary')), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ +. + +Let\ +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' me outline')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the steps:')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' 1.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Find a safe')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' crossing')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' point. ')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='2. Observe')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' traffic signals.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' 3.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Look both ways')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. ')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='4. Make')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' sure')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' it')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta="'s safe.")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' 5.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Stay visible')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. 6')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. Walk')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' straight')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' across.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' 7')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. Stay')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' alert. Maybe')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' add tips')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' for')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' different situations')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' like un')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='marked')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' crossings, intersections')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' with')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' signals')), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ +, etc. + +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='Also')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', include safety')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' tips like avoiding')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' distractions,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' watching')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' for turning')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' vehicles, and')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' being cautious')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' at night.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Maybe mention pedestrian')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' rights')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' but')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' also the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' need')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to be')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' cautious regardless')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Should')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' I mention')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' using')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' pedestrian')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' bridges')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' or under')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='passes as')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' alternatives?')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Yes, that')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta="'s a good")), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ + idea. + +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='Wait, but')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' in some countries')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', even')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' if you')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' have the right')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' of way,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' drivers might not')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' stop.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=" So it's")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' better to always')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' ensure')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' vehicle')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' is stopping before')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' crossing. Maybe')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' emphasize')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' that even')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' if you')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' have the signal')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', check')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' that')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' cars are stopping')), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ +. + +Also\ +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', the importance')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' of not')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' assuming')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' that drivers can')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' see you.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Sometimes')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' drivers')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' are')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' distracted too')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. So')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' being')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' proactive in')), + PartDeltaEvent( + index=0, + delta=ThinkingPartDelta( + content_delta="""\ + ensuring safety. + +""" + ), + ), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='Let')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' me check if')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' I missed')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' anything.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Maybe the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' basic')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' steps are')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' covered')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', but adding')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' extra tips makes')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' it comprehensive')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. Okay')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', I think')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=" that's a")), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' solid structure')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='. Now,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' present it in')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' a clear,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' step-by-step')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' manner with')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' some')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' bullet')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' points or')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' numbered')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' list')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Make sure the')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' language is simple')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' and easy to')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' understand,')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' avoiding jargon.')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Al')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='right, time')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to put it')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' all together.\n')), + PartStartEvent( + index=1, + part=TextPart( + content="""\ + + +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Crossing the')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' street safely involves')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' careful')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' observation')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' and awareness.')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=" Here's")), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' a step')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='-by-step guide')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ +: + +###\ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' **')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Basic')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Steps**')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + +1.\ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' **Find a')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Safe Spot')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='**:')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + + -\ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Use a **')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='crosswalk**')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' or pedestrian signal')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' if available.')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Avoid j')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='aywalking')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=', as it')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta="'s illegal")), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' in')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' many areas')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' and less')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' safe. \n')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' -')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' If no')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' crosswalk exists')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=', choose')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' a well')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='-lit area with')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' clear visibility in')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + both directions. + +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='2. **')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Check Traffic Signals')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ +**: + \ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Wait')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' for the')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' "walk')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='" signal')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' or')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' green light')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' at intersections')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='. \n')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Watch')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' for count')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='down timers')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' to ensure you')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' have enough time')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + to cross. + +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='3. **')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Look Both')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Ways**: \n')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - **')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Left-Right')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='-Left**:')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Glance left')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=', right')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=', and')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' left again (')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='or right')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='-left-right')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' in countries')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' where traffic drives')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' on the left')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=', like the')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' UK). \n')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Listen')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' for approaching')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' vehicles,')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' especially bikes')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' or quiet')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' electric')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + cars. + +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='4. **')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Ensure All')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Traffic')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Has')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Stopped**:')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + + \ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Make')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' eye contact with')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' drivers if possible')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=',')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' and')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' wait')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' for vehicles')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' to come')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' to a')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' complete stop before')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' stepping')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' into')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' the road')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='.')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + + -\ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Watch for turning')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' vehicles,')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' even if you')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' have the right')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' of')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + way. + +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='5. **')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Cross Prompt')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='ly and')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Saf')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='ely**: \n')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Walk')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' straight across')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='—')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta="don't")), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' run or')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' stop')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' midway')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ +. + \ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Stay inside')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' cross')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='walk')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' lines')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' if they')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta="'re present")), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ +. + +6\ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='. **Stay')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Alert Until')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' You')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Reach the Other')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Side**: \n')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' -')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Keep scanning')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' for traffic as')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' you cross')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='. Avoid')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' distractions like phones')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + or headphones. + +""" + ), + ), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ +--- + +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='### **Additional')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Tips**')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + +- **\ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='At')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Night**:')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Wear reflective')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' clothing or carry')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' a flashlight')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' to improve')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' visibility. \n')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='- **With')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Kids')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' or')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Disabilities**: Hold')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' hands with')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' children,')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' and')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' use assistive')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' devices')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' (e.g')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='., white')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' canes).')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Seek')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' pedestrian bridges')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' or under')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='passes if')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' available. \n')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='- **Un')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='marked Roads')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='**: Cross')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' where')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' you')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' can see on')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='coming traffic clearly')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=', and never')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' assume drivers see')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' you. \n')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='- **')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='International Travel')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='**: Note')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' local traffic patterns')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' (e.g')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='., left')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='-side driving)')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' and pedestrian')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' customs')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ +. + +### **\ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Remember**:')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + +- **\ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Right')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' of Way ≠')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Inv')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='incibility**:')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Even with')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' a')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' signal, double')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='-check that')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' cars')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' are stopping.')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + +- **\ +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Distractions Kill')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='**: Stay')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' focused—')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='no texting')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' or scrolling while')), + PartDeltaEvent( + index=1, + delta=TextPartDelta( + content_delta="""\ + crossing. + +""" + ), + ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='By following these')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' steps,')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=" you'")), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='ll minimize')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' risks and')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' cross')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' safely')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='!')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' 🚶')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='♂')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='️🚦')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='')), + ] + ) From 133abe31de3e1248da755a7621f1afe77453ecc5 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 17:18:02 +0200 Subject: [PATCH 18/27] Pass tests --- pydantic_ai_slim/pydantic_ai/_thinking_part.py | 2 ++ tests/models/test_deepseek.py | 10 ++-------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/_thinking_part.py b/pydantic_ai_slim/pydantic_ai/_thinking_part.py index bfabbba21..ff1adc34b 100644 --- a/pydantic_ai_slim/pydantic_ai/_thinking_part.py +++ b/pydantic_ai_slim/pydantic_ai/_thinking_part.py @@ -1,3 +1,5 @@ +from __future__ import annotations as _annotations + from pydantic_ai.messages import TextPart, ThinkingPart START_THINK_TAG = '' diff --git a/tests/models/test_deepseek.py b/tests/models/test_deepseek.py index f361ca3a5..648078329 100644 --- a/tests/models/test_deepseek.py +++ b/tests/models/test_deepseek.py @@ -1,23 +1,17 @@ from __future__ import annotations as _annotations -from typing import Union - import pytest from inline_snapshot import snapshot from pydantic_ai import Agent from pydantic_ai.messages import ModelRequest, ModelResponse, TextPart, ThinkingPart, UserPromptPart -from pydantic_ai.models.openai import OpenAIModel -from pydantic_ai.providers.deepseek import DeepSeekProvider from ..conftest import IsDatetime, IsStr, try_import with try_import() as imports_successful: - from openai.types import chat + from pydantic_ai.models.openai import OpenAIModel + from pydantic_ai.providers.deepseek import DeepSeekProvider - # note: we use Union here so that casting works with Python 3.9 - MockChatCompletion = Union[chat.ChatCompletion, Exception] - MockChatCompletionChunk = Union[chat.ChatCompletionChunk, Exception] pytestmark = [ pytest.mark.skipif(not imports_successful(), reason='openai not installed'), From 36008afea0009277a7e0ef5f72796f1cc625759b Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 17:33:28 +0200 Subject: [PATCH 19/27] fix test --- tests/models/test_bedrock.py | 154 +++++------------------------------ 1 file changed, 21 insertions(+), 133 deletions(-) diff --git a/tests/models/test_bedrock.py b/tests/models/test_bedrock.py index 20d3c2526..64b417b7d 100644 --- a/tests/models/test_bedrock.py +++ b/tests/models/test_bedrock.py @@ -865,7 +865,7 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' visible. That')), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta="'s a good")), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' point. Also')), - PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=', crossing')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=IsStr())), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' in groups')), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' if possible,')), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' as more')), @@ -897,7 +897,7 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the steps:')), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' 1.')), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Find a safe')), - PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' crossing')), + PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=IsStr())), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' point. ')), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='2. Observe')), PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' traffic signals.')), @@ -1086,30 +1086,14 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be """ ), ), - PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' **')), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Basic')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Steps**')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - -1.\ -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' **Find a')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Safe Spot')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='**:')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - - -\ -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Use a **')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='crosswalk**')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' or pedestrian signal')), @@ -1117,7 +1101,7 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Avoid j')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='aywalking')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=', as it')), - PartDeltaEvent(index=1, delta=TextPartDelta(content_delta="'s illegal")), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' in')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' many areas')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' and less')), @@ -1129,26 +1113,10 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' a well')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='-lit area with')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' clear visibility in')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - both directions. - -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='2. **')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Check Traffic Signals')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ -**: - \ -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Wait')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' for the')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' "walk')), @@ -1162,15 +1130,7 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='down timers')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' to ensure you')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' have enough time')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - to cross. - -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='3. **')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Look Both')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Ways**: \n')), @@ -1194,29 +1154,13 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' especially bikes')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' or quiet')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' electric')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - cars. - -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='4. **')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Ensure All')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Traffic')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Has')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Stopped**:')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - - \ -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Make')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' eye contact with')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' drivers if possible')), @@ -1231,29 +1175,13 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' into')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' the road')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='.')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - - -\ -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Watch for turning')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' vehicles,')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' even if you')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' have the right')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' of')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - way. - -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='5. **')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Cross Prompt')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='ly and')), @@ -1262,25 +1190,17 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Walk')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' straight across')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='—')), - PartDeltaEvent(index=1, delta=TextPartDelta(content_delta="don't")), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' run or')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' stop')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' midway')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ -. - \ -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' - Stay inside')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' cross')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='walk')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' lines')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' if they')), - PartDeltaEvent(index=1, delta=TextPartDelta(content_delta="'re present")), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent( index=1, delta=TextPartDelta( @@ -1322,15 +1242,7 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be ), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='### **Additional')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Tips**')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - -- **\ -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='At')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Night**:')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Wear reflective')), @@ -1385,15 +1297,7 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be ), ), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Remember**:')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - -- **\ -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Right')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' of Way ≠')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' Inv')), @@ -1404,32 +1308,16 @@ async def test_bedrock_model_thinking_part_stream(allow_model_requests: None, be PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='-check that')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' cars')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' are stopping.')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - -- **\ -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='Distractions Kill')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='**: Stay')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' focused—')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='no texting')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' or scrolling while')), - PartDeltaEvent( - index=1, - delta=TextPartDelta( - content_delta="""\ - crossing. - -""" - ), - ), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='By following these')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' steps,')), - PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=" you'")), + PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=IsStr())), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta='ll minimize')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' risks and')), PartDeltaEvent(index=1, delta=TextPartDelta(content_delta=' cross')), From 155c373231d2cfcf4206341119fbc1778eeb0a7a Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 22 Apr 2025 17:50:05 +0200 Subject: [PATCH 20/27] pass tests --- .pre-commit-config.yaml | 2 +- pydantic_ai_slim/pyproject.toml | 2 +- uv.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 80f99eb94..059fce23c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,6 +53,6 @@ repos: rev: v2.3.0 hooks: - id: codespell - args: ['--skip', 'tests/models/cassettes/*'] + args: ['--skip', 'tests/models/cassettes/*', '--skip', 'tests/models/test_cohere.py'] additional_dependencies: - tomli diff --git a/pydantic_ai_slim/pyproject.toml b/pydantic_ai_slim/pyproject.toml index 730184a1a..7b8002313 100644 --- a/pydantic_ai_slim/pyproject.toml +++ b/pydantic_ai_slim/pyproject.toml @@ -60,7 +60,7 @@ openai = ["openai>=1.75.0"] cohere = ["cohere>=5.13.11; platform_system != 'Emscripten'"] vertexai = ["google-auth>=2.36.0", "requests>=2.32.3"] anthropic = ["anthropic>=0.49.0"] -groq = ["groq>=0.15.0"] +groq = ["groq>=0.19.0"] mistral = ["mistralai>=1.2.5"] bedrock = ["boto3>=1.35.74"] # Tools diff --git a/uv.lock b/uv.lock index a9ca2a4fa..ace375a69 100644 --- a/uv.lock +++ b/uv.lock @@ -1214,7 +1214,7 @@ wheels = [ [[package]] name = "groq" -version = "0.18.0" +version = "0.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1224,9 +1224,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/8c/e72c164474a88dfed6c7327ad53cb87ff11566b74b3a76d41dc7b94fc51c/groq-0.18.0.tar.gz", hash = "sha256:8e2ccfea406d68b3525af4b7c0e321fcb3d2a73fc60bb70b4156e6cd88c72f03", size = 117322 } +sdist = { url = "https://files.pythonhosted.org/packages/fd/59/7e03f5b12c097b7af48d5fe847a5bd00c90ccfc04b801c2ae68a043ddf1e/groq-0.22.0.tar.gz", hash = "sha256:9d090fbe4a051655faff649890d18aaacb3121393ad9d55399171fe081f1057b", size = 122956 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/6c/5a53d632b44ef7655ac8d9b34432e13160917f9307c94b1467efd34e336e/groq-0.18.0-py3-none-any.whl", hash = "sha256:81d5ac00057a45d8ce559d23ab5d3b3893011d1f12c35187ab35a9182d826ea6", size = 121911 }, + { url = "https://files.pythonhosted.org/packages/70/77/e6b60636f648922cc53144c36e1e7b77c9670539aab9e8b84a5fd1a53880/groq-0.22.0-py3-none-any.whl", hash = "sha256:f53d3966dff713aaa635671c2d075ebb932b0d48e3c4031ede9b84a2a6694c79", size = 126685 }, ] [[package]] @@ -2976,7 +2976,7 @@ requires-dist = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "google-auth", marker = "extra == 'vertexai'", specifier = ">=2.36.0" }, { name = "griffe", specifier = ">=1.3.2" }, - { name = "groq", marker = "extra == 'groq'", specifier = ">=0.15.0" }, + { name = "groq", marker = "extra == 'groq'", specifier = ">=0.19.0" }, { name = "httpx", specifier = ">=0.27" }, { name = "logfire", marker = "extra == 'logfire'", specifier = ">=3.11.0" }, { name = "mcp", marker = "python_full_version >= '3.10' and extra == 'mcp'", specifier = ">=1.6.0" }, From 1a52cee0bdd18104621cfdd55ede11259da85dad Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 25 Apr 2025 09:45:54 +0200 Subject: [PATCH 21/27] bump boto3 --- pydantic_ai_slim/pyproject.toml | 2 +- uv.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pydantic_ai_slim/pyproject.toml b/pydantic_ai_slim/pyproject.toml index 7b8002313..0e677cc85 100644 --- a/pydantic_ai_slim/pyproject.toml +++ b/pydantic_ai_slim/pyproject.toml @@ -62,7 +62,7 @@ vertexai = ["google-auth>=2.36.0", "requests>=2.32.3"] anthropic = ["anthropic>=0.49.0"] groq = ["groq>=0.19.0"] mistral = ["mistralai>=1.2.5"] -bedrock = ["boto3>=1.35.74"] +bedrock = ["boto3>=1.37.24"] # Tools duckduckgo = ["duckduckgo-search>=7.0.0"] tavily = ["tavily-python>=0.5.0"] diff --git a/uv.lock b/uv.lock index ace375a69..c74546474 100644 --- a/uv.lock +++ b/uv.lock @@ -418,16 +418,16 @@ wheels = [ [[package]] name = "boto3" -version = "1.37.1" +version = "1.38.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/8c/c2af03daafaacea1db1823d23073facffa75818b61d376c3be77dd297ae8/boto3-1.37.1.tar.gz", hash = "sha256:96d18f7feb0c1fcb95f8837b74b6c8880e1b4e35ce5f8a8f8cb243a090c278ed", size = 111175 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6c/61664d66c05888fcb960e316a265a7f65ade54d9e5983cbded7d8945e0c2/boto3-1.38.2.tar.gz", hash = "sha256:53c8d44b231251fa9421dd13d968236d59fe2cf0421e077afedbf3821653fb3b", size = 111817 } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/ec/e722c53c9dc41e8df094587c32e19409bace8b43b5eb31fe3536ca57a38b/boto3-1.37.1-py3-none-any.whl", hash = "sha256:4320441f904435a1b85e6ecb81793192e522c737cc9ed6566014e29f0a11cb22", size = 139338 }, + { url = "https://files.pythonhosted.org/packages/ec/21/78e7b892153bd4a3cd94204ad554180b12f07a2ff8b182b6c35e2e643d36/boto3-1.38.2-py3-none-any.whl", hash = "sha256:ef3237b169cd906a44a32c03b3229833d923c9e9733355b329ded2151f91ec0b", size = 139898 }, ] [[package]] @@ -451,7 +451,7 @@ bedrock-runtime = [ [[package]] name = "botocore" -version = "1.37.1" +version = "1.38.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, @@ -459,9 +459,9 @@ dependencies = [ { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "urllib3", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/01/3083bff25fd91193162298920cb093b9095609408416526d52b2826965b7/botocore-1.37.1.tar.gz", hash = "sha256:b194db8fb2a0ffba53568c364ae26166e7eec0445496b2ac86a6e142f3dd982f", size = 13578835 } +sdist = { url = "https://files.pythonhosted.org/packages/06/3a/0576ba7922b295f57835469263357ad64e483676c37731c4b5c66457d2dc/botocore-1.38.2.tar.gz", hash = "sha256:b688a9bd17211a1eaae3a6c965ba9f3973e5435efaaa4fa201f499d3467830e1", size = 13848402 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/20/352b2bf99f93ba18986615841786cbd0d38f7856bd49d4e154a540f04afe/botocore-1.37.1-py3-none-any.whl", hash = "sha256:c1db1bfc5d8c6b3b6d1ca6794f605294b4264e82a7e727b88e0fef9c2b9fbb9c", size = 13359164 }, + { url = "https://files.pythonhosted.org/packages/21/40/28512e643df1674c8560fb2ea5676bd46413173695423b567eca041a901c/botocore-1.38.2-py3-none-any.whl", hash = "sha256:5d9cffedb1c759a058b43793d16647ed44ec87072f98a1bd6cd673ac0ae6b81d", size = 13506508 }, ] [[package]] @@ -2969,7 +2969,7 @@ dev = [ requires-dist = [ { name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=0.49.0" }, { name = "argcomplete", marker = "extra == 'cli'", specifier = ">=3.5.0" }, - { name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.35.74" }, + { name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.37.24" }, { name = "cohere", marker = "sys_platform != 'emscripten' and extra == 'cohere'", specifier = ">=5.13.11" }, { name = "duckduckgo-search", marker = "extra == 'duckduckgo'", specifier = ">=7.0.0" }, { name = "eval-type-backport", specifier = ">=0.2.0" }, @@ -3541,14 +3541,14 @@ wheels = [ [[package]] name = "s3transfer" -version = "0.11.2" +version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/45/2323b5928f86fd29f9afdcef4659f68fa73eaa5356912b774227f5cf46b5/s3transfer-0.11.2.tar.gz", hash = "sha256:3b39185cb72f5acc77db1a58b6e25b977f28d20496b6e58d6813d75f464d632f", size = 147885 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/9e/73b14aed38ee1f62cd30ab93cd0072dec7fb01f3033d116875ae3e7b8b44/s3transfer-0.12.0.tar.gz", hash = "sha256:8ac58bc1989a3fdb7c7f3ee0918a66b160d038a147c7b5db1500930a607e9a1c", size = 149178 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/ac/e7dc469e49048dc57f62e0c555d2ee3117fa30813d2a1a2962cce3a2a82a/s3transfer-0.11.2-py3-none-any.whl", hash = "sha256:be6ecb39fadd986ef1701097771f87e4d2f821f27f6071c872143884d2950fbc", size = 84151 }, + { url = "https://files.pythonhosted.org/packages/89/64/d2b49620039b82688aeebd510bd62ff4cdcdb86cbf650cc72ae42c5254a3/s3transfer-0.12.0-py3-none-any.whl", hash = "sha256:35b314d7d82865756edab59f7baebc6b477189e6ab4c53050e28c1de4d9cce18", size = 84773 }, ] [[package]] From 76a1d48365593a7125e13b4394559ea1e6642336 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 25 Apr 2025 10:31:41 +0200 Subject: [PATCH 22/27] Coverage on Bedrock --- .../test_bedrock_model_thinking_part.yaml | 260 +++++++++--------- tests/models/test_bedrock.py | 21 +- 2 files changed, 151 insertions(+), 130 deletions(-) diff --git a/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml b/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml index 08131b9ee..243b1a8fd 100644 --- a/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml +++ b/tests/models/cassettes/test_bedrock/test_bedrock_model_thinking_part.yaml @@ -5,7 +5,7 @@ interactions: headers: amz-sdk-invocation-id: - !!binary | - YTJmYWNmMWYtYjBhNy00MWZmLWFmZDEtY2RmYTdjZDgyZjAz + NWMyN2FhM2ItOTBmOC00NDc2LTkxNWUtYjg1NmFjODE4MTY4 amz-sdk-request: - !!binary | YXR0ZW1wdD0x @@ -21,177 +21,185 @@ interactions: connection: - keep-alive content-length: - - '4209' + - '4296' content-type: - application/json parsed_body: metrics: - latencyMs: 16152 + latencyMs: 24594 output: message: content: - - text: "\n\nCrossing the street safely involves following key steps to ensure your safety. Here's a clear guide:\n\n1. - **Find a Safe Spot**: \n - Use a **crosswalk** or pedestrian signal if available. Look for marked zebra crossings, - traffic lights, or overhead signs. \n - If none are nearby, choose a location with clear visibility in both - directions. Avoid curves, hills, or parked cars that block your view.\n\n2. **Check Traffic Signals**: \n - - Obey pedestrian signals (e.g., \"Walk\" or green man). Wait for the signal to indicate it's safe. \n - If - there are no signals, wait until traffic is fully stopped or there's a large enough gap to cross safely.\n\n3. - **Look Both Ways**: \n - **Left, right, then left again** (or right first in countries where traffic drives - on the left, like the UK or Japan). \n - Listen for approaching vehicles, bikes, or motorcycles. Remove headphones - and stay focused.\n\n4. **Make Eye Contact**: \n - Ensure drivers see you before stepping into the road. - Don't assume they'll stop, even at crosswalks.\n\n5. **Cross Carefully**: \n - Walk briskly, don't run. Stay - alert and keep looking for traffic as you cross. \n - If there's a median (middle island), treat it as two - separate crossings. Pause there if needed.\n\n6. **Stay Visible**: \n - At night, wear reflective clothing - or carry a light. Avoid crossing in poorly lit areas.\n\n7. **Special Situations**: \n - **Roundabouts**: - Cross one direction at a time, using splitter islands. \n - **Children**: Hold hands with adults and reinforce - the \"stop, look, listen\" rule. \n - **Turning vehicles**: Watch for cars turning into your path, even if - you have a \"Walk\" signal.\n\n**Safety Tips**: \n- Never assume a vehicle will stop. \n- Avoid distractions - like phones. \n- Follow local laws (e.g., jaywalking fines). \n\nBy staying alert and patient, you can cross - safely in any environment! \U0001F6B6♂️\U0001F6A6" + - text: "\n\nCrossing the street safely involves following key steps to ensure your safety. Here's a clear guide:\n\n### + **Steps to Cross Safely:**\n1. **Find a Safe Spot** \n - Use a **marked crosswalk** or pedestrian crossing + whenever possible. If unavailable, choose an intersection where drivers expect pedestrians.\n - Avoid crossing + between parked cars or mid-block, as drivers may not see you.\n\n2. **Check for Traffic Signals** \n - If + there's a traffic light or walk signal, wait for the \"walk\" symbol (or green light where pedestrian signals + aren't present). \n - **Don't start crossing on a flashing \"don't walk\"** or red light—finish crossing + if you've already started.\n\n3. **Look Both Ways** \n - **Left-Right-Left**: Look left, right, and left + again before stepping into the street. In countries with right-side driving, check for cars turning left. In + left-side driving countries (e.g., UK, Japan), check right first. \n - **Make eye contact** with drivers + to ensure they see you, even if you have the right of way.\n\n4. **Stay Alert While Crossing** \n - Keep + looking for traffic as you cross. Watch for turning vehicles, bikes, or scooters. \n - Walk at a steady pace—don't + run, but avoid lingering.\n\n5. **Use Medians if Available** \n - If the street has a median (center island), + treat it as a halfway point. Stop there and check traffic again before continuing.\n\n### **Additional Safety + Tips:**\n- **Avoid Distractions**: Put away phones, remove headphones, and stay focused. \n- **Visibility**: + At night, wear reflective clothing or carry a light. \n- **Children/Disabilities**: Hold children's hands. + Those with mobility aids should use designated crossings where possible. \n- **Right of Way**: Never assume + a driver will stop—wait for vehicles to halt before stepping into the road.\n\n### **Special Situations:**\n- + **Uncontrolled Crossings (no signals)**: Wait for a large gap in traffic. Cross perpendicular to the road (straight + line). \n- **Roundabouts**: Use crosswalks before entering the circle, and watch for multiple lanes of traffic. + \ \n- **Highways/Freeways**: Avoid crossing—seek an overpass or underpass instead.\n\nBy staying vigilant and + following these steps, you can cross the street safely! \U0001F6B8" - reasoningContent: reasoningText: - text: | - Okay, the user is asking how to cross the street. Let me think about the best way to explain this. First, I need to cover the basic steps clearly. They might be a child, a tourist unfamiliar with local traffic rules, or someone who needs a refresher. I should start with finding a safe place to cross. - - So, step one: look for a crosswalk or pedestrian crossing. If there's a traffic light, mention obeying the signals. Wait for the green light or the walk signal. But if there's no crosswalk, they should choose a spot with good visibility, where they can see oncoming traffic from both directions. - - Next, checking for traffic. They need to look left and right, and maybe even listen for cars. Remind them that in some countries, traffic drives on the left, so the direction to look first might vary. Also, watch out for bicycles or motorcycles that might be less visible. - - Making eye contact with drivers is important. Sometimes drivers might not stop, so it's safer to ensure they've been seen. Then, cross straight and stay alert. Don't run, but walk briskly. Avoid distractions like phones or headphones. - - If there's a median, mention using it as a halfway point. For areas without marked crossings, emphasize extra caution. Maybe add tips about crossing at night, like wearing reflective clothing. Also, remind them to follow local laws, like jaywalking rules. - - Wait, I should check if I missed anything. Oh, maybe mention looking both ways even if there's a signal, in case a car runs a red light. Also, teaching kids to hold an adult's hand. And note that in some places, turning vehicles might have a green light while pedestrians are crossing, so checking for turning cars is important. - - Hmm, should I structure this in numbered steps? That might be clearer. Let me organize the information step by step, making sure each point is concise. Avoid jargon, keep it simple. Maybe add some safety tips at the end, like not assuming vehicles will stop. Yeah, that makes sense. Let me put it all together now. + text: "Okay, the user is asking how to cross the street. Let me think about the best way to explain this. + First, I need to make sure they understand the basic steps but also stay safe. Maybe start by finding a + safe place to cross. Like a crosswalk or pedestrian crossing. If there's a traffic light, they should wait + for the walk signal. But if there's no signal, they need to look both ways. Wait until there's no traffic + coming, right?\n\nWait, what if there's a crosswalk but no lights? They should still check for cars. Maybe + mention making eye contact with drivers to ensure they're seen. Also, don't forget to keep looking while + crossing, in case a car appears suddenly. What about intersections without marked crosswalks? They should + still cross there rather than mid-block because drivers expect pedestrians at intersections. \n\nOh, and + distractions like phones or headphones. They should avoid those to stay alert. Teach them to walk, not run, + but keep a steady pace. If there's a median, maybe use it as a halfway point. Also, in some countries, traffic + drives on the left, so check the correct direction when looking. \n\nWhat about children or people with + disabilities? Maybe add something about holding hands with kids or using assistive devices. And if it's + dark, wearing reflective clothing could help. Are there any other safety tips? Maybe don't assume drivers + will stop, even if you have the right of way. Always double-check. \n\nLet me structure this step by step. + Start with finding a safe spot, check signals, look both ways, stay visible, keep monitoring traffic, and + avoid distractions. Maybe number the steps for clarity. Also, include additional tips for different scenarios. + Okay, that should cover the essentials without being too complicated.\n" role: assistant stopReason: end_turn usage: inputTokens: 12 - outputTokens: 890 - totalTokens: 902 + outputTokens: 882 + totalTokens: 894 status: code: 200 message: OK - request: body: '{"messages": [{"role": "user", "content": [{"text": "How do I cross the street?"}]}, {"role": "assistant", "content": - [{"text": "\n\nCrossing the street safely involves following key steps to ensure your safety. Here''s a clear guide:\n\n1. - **Find a Safe Spot**: \n - Use a **crosswalk** or pedestrian signal if available. Look for marked zebra crossings, - traffic lights, or overhead signs. \n - If none are nearby, choose a location with clear visibility in both directions. - Avoid curves, hills, or parked cars that block your view.\n\n2. **Check Traffic Signals**: \n - Obey pedestrian signals - (e.g., \"Walk\" or green man). Wait for the signal to indicate it\u2019s safe. \n - If there are no signals, wait - until traffic is fully stopped or there\u2019s a large enough gap to cross safely.\n\n3. **Look Both Ways**: \n - - **Left, right, then left again** (or right first in countries where traffic drives on the left, like the UK or Japan). \n - - Listen for approaching vehicles, bikes, or motorcycles. Remove headphones and stay focused.\n\n4. **Make Eye Contact**: \n - - Ensure drivers see you before stepping into the road. Don\u2019t assume they\u2019ll stop, even at crosswalks.\n\n5. - **Cross Carefully**: \n - Walk briskly, don\u2019t run. Stay alert and keep looking for traffic as you cross. \n - - If there\u2019s a median (middle island), treat it as two separate crossings. Pause there if needed.\n\n6. **Stay Visible**: \n - - At night, wear reflective clothing or carry a light. Avoid crossing in poorly lit areas.\n\n7. **Special Situations**: \n - - **Roundabouts**: Cross one direction at a time, using splitter islands. \n - **Children**: Hold hands with adults - and reinforce the \"stop, look, listen\" rule. \n - **Turning vehicles**: Watch for cars turning into your path, - even if you have a \"Walk\" signal.\n\n**Safety Tips**: \n- Never assume a vehicle will stop. \n- Avoid distractions - like phones. \n- Follow local laws (e.g., jaywalking fines). \n\nBy staying alert and patient, you can cross safely - in any environment! \ud83d\udeb6\u2642\ufe0f\ud83d\udea6"}]}, {"role": "user", "content": [{"text": "Considering the - way to cross the street, analogously, how do I cross the river?"}]}], "system": [], "inferenceConfig": {}}' + [{"text": "\n\nCrossing the street safely involves following key steps to ensure your safety. Here''s a clear guide:\n\n### + **Steps to Cross Safely:**\n1. **Find a Safe Spot** \n - Use a **marked crosswalk** or pedestrian crossing whenever + possible. If unavailable, choose an intersection where drivers expect pedestrians.\n - Avoid crossing between parked + cars or mid-block, as drivers may not see you.\n\n2. **Check for Traffic Signals** \n - If there\u2019s a traffic + light or walk signal, wait for the \"walk\" symbol (or green light where pedestrian signals aren\u2019t present). \n - + **Don\u2019t start crossing on a flashing \"don\u2019t walk\"** or red light\u2014finish crossing if you\u2019ve already + started.\n\n3. **Look Both Ways** \n - **Left-Right-Left**: Look left, right, and left again before stepping into + the street. In countries with right-side driving, check for cars turning left. In left-side driving countries (e.g., + UK, Japan), check right first. \n - **Make eye contact** with drivers to ensure they see you, even if you have the + right of way.\n\n4. **Stay Alert While Crossing** \n - Keep looking for traffic as you cross. Watch for turning vehicles, + bikes, or scooters. \n - Walk at a steady pace\u2014don\u2019t run, but avoid lingering.\n\n5. **Use Medians if Available** \n - + If the street has a median (center island), treat it as a halfway point. Stop there and check traffic again before continuing.\n\n### + **Additional Safety Tips:**\n- **Avoid Distractions**: Put away phones, remove headphones, and stay focused. \n- **Visibility**: + At night, wear reflective clothing or carry a light. \n- **Children/Disabilities**: Hold children\u2019s hands. Those + with mobility aids should use designated crossings where possible. \n- **Right of Way**: Never assume a driver will + stop\u2014wait for vehicles to halt before stepping into the road.\n\n### **Special Situations:**\n- **Uncontrolled + Crossings (no signals)**: Wait for a large gap in traffic. Cross perpendicular to the road (straight line). \n- **Roundabouts**: + Use crosswalks before entering the circle, and watch for multiple lanes of traffic. \n- **Highways/Freeways**: Avoid + crossing\u2014seek an overpass or underpass instead.\n\nBy staying vigilant and following these steps, you can cross + the street safely! \ud83d\udeb8"}]}, {"role": "user", "content": [{"text": "Considering the way to cross the street, + analogously, how do I cross the river?"}]}], "system": [], "inferenceConfig": {}, "additionalModelRequestFields": {"thinking": + {"type": "enabled", "budget_tokens": 1024}}}' headers: amz-sdk-invocation-id: - !!binary | - YWI5ZDlhOGEtNTRhOC00MWUwLThlNmEtYjkwZmM5ZDZiNjRi + ODFkYmUyODUtN2EzMy00NjE2LTk3NWQtMDU3YjI5MjA0ZmNh amz-sdk-request: - !!binary | YXR0ZW1wdD0x content-length: - - '2284' + - '2720' content-type: - !!binary | YXBwbGljYXRpb24vanNvbg== method: POST - uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.deepseek.r1-v1%3A0/converse + uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.anthropic.claude-3-7-sonnet-20250219-v1%3A0/converse response: headers: connection: - keep-alive content-length: - - '6938' + - '3598' content-type: - application/json parsed_body: metrics: - latencyMs: 20753 + latencyMs: 16324 output: message: content: - - text: "\n\nCrossing a river, like crossing a street, requires careful planning, awareness of hazards, and methodical - execution. Here's an analogous guide to river crossing, prioritizing safety and strategy:\n\n---\n\n### **1. - Assess the Situation** (Analogous to *Finding a Safe Spot*) \n - **Research the river**: Understand its depth, - current strength, and seasonal behavior (e.g., snowmelt can increase flow). \n - **Look for hazards**: Rapids, - submerged rocks, logs (\"strainers\"), deep pools, or unstable banks. \n - **Choose a crossing point**: \n - \ - **Wider sections** often mean slower currents. \n - **Shallow, calm areas** with visible gravel - or sand bottoms. \n - Avoid bends where currents are stronger. \n\n---\n\n### **2. Prepare Yourself** - (Analogous to *Checking Traffic Signals*) \n - **Wear proper footwear**: Sturdy shoes with grip for slippery - rocks. \n - **Unbuckle backpack straps**: In case you fall, you can shed weight quickly. \n - **Use a - walking stick or pole**: For stability against the current. \n - **Form a team**: Cross with others for support - (link arms or form a human chain). \n\n---\n\n### **3. Test the Current** (Analogous to *Looking Both Ways*) - \ \n - **Enter slowly**: Wade in upstream of your target exit point (the current will push you downstream). - \ \n - **Face upstream**: Lean into the current, planting your feet firmly. \n - **Probe depth**: Use your - stick to test the riverbed ahead. If water reaches your thighs, reconsider crossing. \n\n---\n\n### **4. Cross - Strategically** (Analogous to *Crossing Carefully*) \n - **Move diagonally downstream**: Angle your path - to work with the current. \n - **Shuffle sideways**: Don't lift your feet high; slide them to maintain balance. - \ \n - **Stay low**: Bend your knees to lower your center of gravity. \n\n---\n\n### **5. Watch for Hidden - Dangers** (Analogous to *Making Eye Contact*) \n - **Avoid \"strainers\"**: Branches or logs that water flows - through—these can trap you. \n - **Beware of slippery rocks**: Algae-covered surfaces are treacherous. \n - \ - **Cold water risk**: Hypothermia can set in quickly—move fast in icy rivers. \n\n---\n\n### **6. Exit - Safely** (Analogous to *Staying Visible*) \n - **Climb out downstream**: Currents are weaker near banks. - Use vegetation or rocks for support. \n - **Dry off and reassess**: Change clothes if wet to avoid hypothermia. - \ \n\n---\n\n### **Special Situations**: \n - **Flooded rivers**: Never attempt—even shallow floodwater can - have deadly force. \n - **Swiftwater**: Use a rope system or turn back if unmanageable. \n - **Wildlife**: - In some regions, be aware of animals (e.g., snakes, hippos, crocodiles). \n\n---\n\n### **Safety Tips**: \n - \ - **Never cross alone** if avoidable. \n - **If you fall**: Float on your back, feet downstream, and paddle - to shore. \n - **Know when to quit**: If uncertain, find another route or wait for conditions to improve. - \ \n\n---\n\n**Analogy Recap**: \nJust as you'd prioritize crosswalks and traffic signals on a street, rivers - demand respect for natural forces. Preparation, patience, and adaptability are key to safely navigating both! - \U0001F30A\U0001F6B6♀️" - reasoningContent: reasoningText: - text: "Okay, the user is asking how to cross a river, using the analogy of crossing a street. Let me start - by understanding the original question and the previous answer. The user wanted to know how to cross a street - safely, and I provided a detailed step-by-step guide. Now, they want a similar approach for crossing a river.\n\nFirst, - I need to figure out the key elements of crossing a street and map them to river crossing. The street guide - involved safety, checking surroundings, using proper signals, etc. For a river, safety is even more critical - because rivers have currents, variable depths, and hidden hazards. \n\nI should start by breaking down the - process into steps similar to the street crossing: preparation, assessing the situation, choosing the right - spot, checking for dangers, using proper techniques, and post-crossing actions. \n\nPreparation would involve - researching the river, checking weather, and having the right gear. Assessing the river's conditions is - like checking traffic signals; you need to know the depth, current speed, and hazards. Choosing the crossing - point is analogous to finding a safe spot on the street. \n\nDangers in a river include strong currents, - slippery rocks, and cold water. Techniques like facing upstream, using a pole for stability, and moving - diagonally could be similar to looking both ways and making eye contact with drivers. Team crossing strategies - would mirror group safety in street crossing. \n\nI need to mention special situations like cold water risks, - flood conditions, and wildlife. Safety tips should emphasize never crossing alone, avoiding flooded rivers, - and knowing when to turn back. \n\nWait, the user might be thinking metaphorically, but the previous answer - was literal. Should I address both literal and metaphorical aspects? The original question was literal, - so maybe stick to the literal approach here. \n\nAlso, consider the user's possible scenarios: are they - hiking, in an emergency, or just curious? Since the street answer was practical, the river answer should - be too. Provide actionable steps with clear analogies to each part of the street guide. \n\nMake sure to - highlight the importance of preparation and caution, as rivers can be unpredictable. Mention tools like - a walking stick, proper footwear, and checking for alternative routes. Compare each step to the street crossing - steps to maintain the analogy. \n\nCheck for any missing elements. In street crossing, we had visibility, - signals, looking both ways. For rivers, it's depth, current, obstacles. Also, post-crossing actions like - drying off and reassessing. \n\nI should structure the answer similarly: numbered steps with key points, - then safety tips. Use analogies in each section. Make sure the language is clear and easy to follow, avoiding - technical jargon but still informative. \n\nDouble-check for accuracy. For example, facing upstream when - crossing to brace against the current, using a pole for stability, moving diagonally. Yes, those are standard - river crossing techniques. Also, mention the importance of unbuckling backpacks in case you need to shed - weight quickly. \n\nInclude warnings about hypothermia from cold water, the danger of flooded rivers even - if they seem calm. Emphasize that turning back is a valid option if it's too risky. \n\nAlright, I think - that covers the main points. Structure it step-by-step, mirror the street crossing guide, and ensure safety - is the top priority throughout.\n" + signature: ErcBCkgIAhABGAIiQMuiyDObz/Z/ryneAVaQDk4iH6JqSNKJmJTwpQ1RqPz07UFTEffhkJW76u0WVKZaYykZAHmZl/IbQOPDLGU0nhQSDDuHLg82YIApYmWyfhoMe8vxT1/WGTJwyCeOIjC5OfF0+c6JOAvXvv9ElFXHo3yS3am1V0KpTiFj4YCy/bqfxv1wFGBw0KOMsTgq7ugqHeuOpzNM91a/RgtYHUdrcAKm9iCRu24jIOCjr5+h + text: |- + This is an interesting analogy! The person is asking me to use the street-crossing principles I just outlined and apply them to crossing a river. I'll create an analogous guide for river crossing, keeping the same safety-focused structure but adapting it to the water environment. + + I should include: + 1. Finding safe crossing spots (like bridges, ferries, or shallow fords) + 2. Checking conditions (water level, current speed, weather) + 3. Looking for hazards (similar to looking both ways) + 4. Crossing techniques + 5. Staying alert during crossing + 6. Special situations + + I'll make sure to emphasize safety throughout, just as I did with street crossing. + - text: |- + # How to Cross a River Safely + + Just as with street crossing, river crossing requires careful assessment and proper technique. Here's how to approach it safely: + + ### **Steps to Cross a River:** + + 1. **Find a Safe Crossing Point** + - Use **established bridges** or **ferry services** whenever possible + - If you must ford the river, look for wide, shallow sections with slower current + - Avoid narrow, deep sections where water moves quickly + + 2. **Assess River Conditions** + - **Check water levels and flow rate** before attempting to cross + - Look for debris or floating hazards (like logs) coming downstream + - Consider recent weather—rain upstream can cause sudden changes in water level + + 3. **Evaluate Before Entering** + - **Upstream-Downstream-Upstream**: Look for hazards upstream that might flow toward you, downstream for where you might drift if swept away, then upstream again + - **Test depth** with a walking stick before committing to cross + + 4. **Cross Safely** + - Face **upstream at an angle** (diagonal crossing) to avoid being knocked over + - Use a **walking stick or trekking pole** for a third point of contact + - Unbuckle backpack waist straps so you can shed gear quickly if you fall + + 5. **Stay Alert While Crossing** + - Move deliberately with careful foot placement + - Continue monitoring upstream for approaching hazards + - Maintain at least two points of contact with the riverbed at all times + + ### **Additional Safety Tips:** + - **Depth Check**: Water above knee-height becomes significantly more dangerous + - **Footwear**: Keep shoes on to protect feet from rocks and debris + - **Group Crossing**: Link arms in a line facing upstream for stability in moderate currents + - **Emergency Plan**: Know what to do if swept away (float on back, feet downstream) + + ### **Special Situations:** + - **Deep Rivers**: Seek proper water craft rather than swimming across + - **Winter Crossings**: Be extremely cautious with ice—thickness may vary + - **Flash Flood Areas**: If water is rising or muddy/turbulent, wait it out instead of crossing + - **Wildlife Concerns**: Check for crocodiles, hippos, or other dangerous animals in relevant regions + + Remember: When in doubt, don't cross! Unlike streets, rivers can change conditions rapidly and unpredictably. role: assistant stopReason: end_turn usage: - inputTokens: 488 - outputTokens: 1427 - totalTokens: 1915 + cacheReadInputTokenCount: 0 + cacheReadInputTokens: 0 + cacheWriteInputTokenCount: 0 + cacheWriteInputTokens: 0 + inputTokens: 636 + outputTokens: 690 + totalTokens: 1326 status: code: 200 message: OK diff --git a/tests/models/test_bedrock.py b/tests/models/test_bedrock.py index 64b417b7d..654c00efd 100644 --- a/tests/models/test_bedrock.py +++ b/tests/models/test_bedrock.py @@ -585,8 +585,8 @@ async def test_bedrock_multiple_documents_in_history( async def test_bedrock_model_thinking_part(allow_model_requests: None, bedrock_provider: BedrockProvider): - m = BedrockConverseModel('us.deepseek.r1-v1:0', provider=bedrock_provider) - agent = Agent(m) + deepseek_model = BedrockConverseModel('us.deepseek.r1-v1:0', provider=bedrock_provider) + agent = Agent(deepseek_model) result = await agent.run('How do I cross the street?') assert result.all_messages() == snapshot( @@ -600,8 +600,15 @@ async def test_bedrock_model_thinking_part(allow_model_requests: None, bedrock_p ] ) + anthropic_model = BedrockConverseModel('us.anthropic.claude-3-7-sonnet-20250219-v1:0', provider=bedrock_provider) result = await agent.run( 'Considering the way to cross the street, analogously, how do I cross the river?', + model=anthropic_model, + model_settings=BedrockModelSettings( + bedrock_additional_model_requests_fields={ + 'thinking': {'type': 'enabled', 'budget_tokens': 1024}, + } + ), message_history=result.all_messages(), ) assert result.all_messages() == snapshot( @@ -621,8 +628,14 @@ async def test_bedrock_model_thinking_part(allow_model_requests: None, bedrock_p ] ), ModelResponse( - parts=[IsInstance(TextPart), IsInstance(ThinkingPart)], - model_name='us.deepseek.r1-v1:0', + parts=[ + ThinkingPart( + content=IsStr(), + signature='ErcBCkgIAhABGAIiQMuiyDObz/Z/ryneAVaQDk4iH6JqSNKJmJTwpQ1RqPz07UFTEffhkJW76u0WVKZaYykZAHmZl/IbQOPDLGU0nhQSDDuHLg82YIApYmWyfhoMe8vxT1/WGTJwyCeOIjC5OfF0+c6JOAvXvv9ElFXHo3yS3am1V0KpTiFj4YCy/bqfxv1wFGBw0KOMsTgq7ugqHeuOpzNM91a/RgtYHUdrcAKm9iCRu24jIOCjr5+h', + ), + IsInstance(TextPart), + ], + model_name='us.anthropic.claude-3-7-sonnet-20250219-v1:0', timestamp=IsDatetime(), ), ] From 7b56087d5f5856e3bba8a39c2413fb9984715dda Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 25 Apr 2025 10:34:11 +0200 Subject: [PATCH 23/27] Bump openai --- pydantic_ai_slim/pydantic_ai/models/openai.py | 4 ++-- pydantic_ai_slim/pyproject.toml | 2 +- uv.lock | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 07650d506..d6316516f 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -655,10 +655,10 @@ def _get_reasoning(self, model_settings: OpenAIResponsesModelSettings) -> Reason reasoning_summary = model_settings.get('openai_reasoning_summary', None) reasoning_generate_summary = model_settings.get('openai_reasoning_generate_summary', None) - if reasoning_summary and reasoning_generate_summary: + if reasoning_summary and reasoning_generate_summary: # pragma: no cover raise ValueError('`openai_reasoning_summary` and `openai_reasoning_generate_summary` cannot both be set.') - if reasoning_generate_summary is not None: + if reasoning_generate_summary is not None: # pragma: no cover warnings.warn( '`openai_reasoning_generate_summary` is deprecated, use `openai_reasoning_summary` instead', DeprecationWarning, diff --git a/pydantic_ai_slim/pyproject.toml b/pydantic_ai_slim/pyproject.toml index 0e677cc85..d237cc8a9 100644 --- a/pydantic_ai_slim/pyproject.toml +++ b/pydantic_ai_slim/pyproject.toml @@ -56,7 +56,7 @@ dependencies = [ # WARNING if you add optional groups, please update docs/install.md logfire = ["logfire>=3.11.0"] # Models -openai = ["openai>=1.75.0"] +openai = ["openai>=1.76.0"] cohere = ["cohere>=5.13.11; platform_system != 'Emscripten'"] vertexai = ["google-auth>=2.36.0", "requests>=2.32.3"] anthropic = ["anthropic>=0.49.0"] diff --git a/uv.lock b/uv.lock index c74546474..84732764f 100644 --- a/uv.lock +++ b/uv.lock @@ -2156,7 +2156,7 @@ wheels = [ [[package]] name = "openai" -version = "1.75.0" +version = "1.76.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2168,9 +2168,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/b1/318f5d4c482f19c5fcbcde190801bfaaaec23413cda0b88a29f6897448ff/openai-1.75.0.tar.gz", hash = "sha256:fb3ea907efbdb1bcfd0c44507ad9c961afd7dce3147292b54505ecfd17be8fd1", size = 429492 } +sdist = { url = "https://files.pythonhosted.org/packages/84/51/817969ec969b73d8ddad085670ecd8a45ef1af1811d8c3b8a177ca4d1309/openai-1.76.0.tar.gz", hash = "sha256:fd2bfaf4608f48102d6b74f9e11c5ecaa058b60dad9c36e409c12477dfd91fb2", size = 434660 } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/9a/f34f163294345f123673ed03e77c33dee2534f3ac1f9d18120384457304d/openai-1.75.0-py3-none-any.whl", hash = "sha256:fe6f932d2ded3b429ff67cc9ad118c71327db32eb9d32dd723de3acfca337125", size = 646972 }, + { url = "https://files.pythonhosted.org/packages/59/aa/84e02ab500ca871eb8f62784426963a1c7c17a72fea3c7f268af4bbaafa5/openai-1.76.0-py3-none-any.whl", hash = "sha256:a712b50e78cf78e6d7b2a8f69c4978243517c2c36999756673e07a14ce37dc0a", size = 661201 }, ] [[package]] @@ -2981,7 +2981,7 @@ requires-dist = [ { name = "logfire", marker = "extra == 'logfire'", specifier = ">=3.11.0" }, { name = "mcp", marker = "python_full_version >= '3.10' and extra == 'mcp'", specifier = ">=1.6.0" }, { name = "mistralai", marker = "extra == 'mistral'", specifier = ">=1.2.5" }, - { name = "openai", marker = "extra == 'openai'", specifier = ">=1.75.0" }, + { name = "openai", marker = "extra == 'openai'", specifier = ">=1.76.0" }, { name = "opentelemetry-api", specifier = ">=1.28.0" }, { name = "prompt-toolkit", marker = "extra == 'cli'", specifier = ">=3" }, { name = "pydantic", specifier = ">=2.10" }, From e5e901f9e2f734a481b8f1fcde5c557691232dad Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 25 Apr 2025 10:47:15 +0200 Subject: [PATCH 24/27] Fix openai provider streaming --- .../pydantic_ai/models/anthropic.py | 2 +- pydantic_ai_slim/pydantic_ai/models/openai.py | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index 20f54c50c..e567cc3b3 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -264,7 +264,7 @@ def _process_response(self, response: AnthropicMessage) -> ModelResponse: for item in response.content: if isinstance(item, TextBlock): items.append(TextPart(content=item.text)) - elif isinstance(item, RedactedThinkingBlock): + elif isinstance(item, RedactedThinkingBlock): # pragma: no cover warnings.warn( 'PydanticAI currently does not handle redacted thinking blocks. ' 'If you have a suggestion on how we should handle them, please open an issue.', diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index d6316516f..215c3ae50 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -881,11 +881,6 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: if isinstance(chunk, responses.ResponseCompletedEvent): self._usage += _map_usage(chunk.response) - elif isinstance(chunk, responses.ResponseAudioDeltaEvent): - # TODO(Marcelo): The reasoning events are getting this type. - # See https://github.com/openai/openai-python/issues/2311 for more details. - pass - elif isinstance(chunk, responses.ResponseContentPartAddedEvent): pass # there's nothing we need to do here @@ -937,6 +932,22 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: # NOTE: We only need this if the tool call deltas don't include the final info. pass + elif isinstance(chunk, responses.ResponseReasoningSummaryPartAddedEvent): + pass # there's nothing we need to do here + + elif isinstance(chunk, responses.ResponseReasoningSummaryPartDoneEvent): + pass # there's nothing we need to do here + + elif isinstance(chunk, responses.ResponseReasoningSummaryTextDoneEvent): + pass # there's nothing we need to do here + + elif isinstance(chunk, responses.ResponseReasoningSummaryTextDeltaEvent): + yield self._parts_manager.handle_thinking_delta( + vendor_part_id=chunk.item_id, + content=chunk.delta, + signature=chunk.item_id, + ) + elif isinstance(chunk, responses.ResponseTextDeltaEvent): yield self._parts_manager.handle_text_delta(vendor_part_id=chunk.content_index, content=chunk.delta) From bf03ecdd950ba08584622b7815cc836e90970f38 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 25 Apr 2025 11:01:52 +0200 Subject: [PATCH 25/27] Add basic documentation --- docs/thinking.md | 3 +++ mkdocs.yml | 1 + 2 files changed, 4 insertions(+) create mode 100644 docs/thinking.md diff --git a/docs/thinking.md b/docs/thinking.md new file mode 100644 index 000000000..fac3d02b9 --- /dev/null +++ b/docs/thinking.md @@ -0,0 +1,3 @@ +# Thinking + +Also known as reasoning, "thinking" is the process of using a model's capabilities to reason about a task. diff --git a/mkdocs.yml b/mkdocs.yml index 02cf3e703..55a1a7e06 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,6 +38,7 @@ nav: - graph.md - evals.md - input.md + - thinking.md - MCP: - mcp/index.md - mcp/client.md From 006d17a567e2411a8a458383ae131fb13e4f4f6c Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 25 Apr 2025 20:33:08 +0200 Subject: [PATCH 26/27] Add more coverage --- .../pydantic_ai/_thinking_part.py | 1 + pydantic_ai_slim/pydantic_ai/models/openai.py | 1 + pydantic_ai_slim/pydantic_ai/models/test.py | 19 ++++++------- tests/models/test_model_test.py | 2 +- tests/test_thinking_part.py | 28 +++++++++++++++++++ 5 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 tests/test_thinking_part.py diff --git a/pydantic_ai_slim/pydantic_ai/_thinking_part.py b/pydantic_ai_slim/pydantic_ai/_thinking_part.py index ff1adc34b..5e24cbc3f 100644 --- a/pydantic_ai_slim/pydantic_ai/_thinking_part.py +++ b/pydantic_ai_slim/pydantic_ai/_thinking_part.py @@ -24,6 +24,7 @@ def split_content_into_text_and_thinking(content: str) -> list[ThinkingPart | Te think_content, content = content.split(END_THINK_TAG, 1) parts.append(ThinkingPart(content=think_content)) else: + # We lose the `` tag, but it shouldn't matter. parts.append(TextPart(content=content)) content = '' if content: diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 215c3ae50..0e47c109f 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -303,6 +303,7 @@ def _process_response(self, response: chat.ChatCompletion) -> ModelResponse: timestamp = datetime.fromtimestamp(response.created, tz=timezone.utc) choice = response.choices[0] items: list[ModelResponsePart] = [] + # The `reasoning_content` is only present in DeepSeek models. if reasoning_content := getattr(choice.message, 'reasoning_content', None): items.append(ThinkingPart(content=reasoning_content)) if choice.message.content is not None: diff --git a/pydantic_ai_slim/pydantic_ai/models/test.py b/pydantic_ai_slim/pydantic_ai/models/test.py index 68b095cdf..e53cdc12a 100644 --- a/pydantic_ai_slim/pydantic_ai/models/test.py +++ b/pydantic_ai_slim/pydantic_ai/models/test.py @@ -242,7 +242,7 @@ def __post_init__(self, _messages: Iterable[ModelMessage]): async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: for i, part in enumerate(self._structured_response.parts): - if isinstance(part, (TextPart, ThinkingPart)): + if isinstance(part, TextPart): text = part.content *words, last_word = text.split(' ') words = [f'{word} ' for word in words] @@ -251,20 +251,17 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: mid = len(text) // 2 words = [text[:mid], text[mid:]] self._usage += _get_string_usage('') - if part.part_kind == 'text': - yield self._parts_manager.handle_text_delta(vendor_part_id=i, content='') - for word in words: - self._usage += _get_string_usage(word) - yield self._parts_manager.handle_text_delta(vendor_part_id=i, content=word) - else: - yield self._parts_manager.handle_thinking_delta(vendor_part_id=i, content='') - for word in words: - self._usage += _get_string_usage(word) - yield self._parts_manager.handle_thinking_delta(vendor_part_id=i, content=word) + yield self._parts_manager.handle_text_delta(vendor_part_id=i, content='') + for word in words: + self._usage += _get_string_usage(word) + yield self._parts_manager.handle_text_delta(vendor_part_id=i, content=word) elif isinstance(part, ToolCallPart): yield self._parts_manager.handle_tool_call_part( vendor_part_id=i, tool_name=part.tool_name, args=part.args, tool_call_id=part.tool_call_id ) + elif isinstance(part, ThinkingPart): + # NOTE: There's no way to reach this part of the code, since we don't generate ThinkingPart on TestModel. + pass # pragma: no cover else: assert_never(part) diff --git a/tests/models/test_model_test.py b/tests/models/test_model_test.py index 40e573146..02242dc74 100644 --- a/tests/models/test_model_test.py +++ b/tests/models/test_model_test.py @@ -300,6 +300,6 @@ def test_max_items(): ) def test_different_content_input(content: AudioUrl | VideoUrl | ImageUrl | BinaryContent): agent = Agent() - result = agent.run_sync('x', model=TestModel(custom_output_text='custom')) + result = agent.run_sync(['x', content], model=TestModel(custom_output_text='custom')) assert result.output == snapshot('custom') assert result.usage() == snapshot(Usage(requests=1, request_tokens=51, response_tokens=1, total_tokens=52)) diff --git a/tests/test_thinking_part.py b/tests/test_thinking_part.py new file mode 100644 index 000000000..0ee54b4fa --- /dev/null +++ b/tests/test_thinking_part.py @@ -0,0 +1,28 @@ +from __future__ import annotations as _annotations + +import pytest + +from pydantic_ai._thinking_part import split_content_into_text_and_thinking +from pydantic_ai.messages import ModelResponsePart, TextPart, ThinkingPart + + +@pytest.mark.parametrize( + ('content', 'parts'), + [ + ('foo bar', [TextPart(content='foo bar')]), + ( + 'foo barthinking', + [TextPart(content='foo bar'), ThinkingPart(content='thinking')], + ), + ( + 'foo barthinkingbaz', + [TextPart(content='foo bar'), ThinkingPart(content='thinking'), TextPart(content='baz')], + ), + ( + 'foo barthinking', + [TextPart(content='foo bar'), TextPart(content='thinking')], + ), + ], +) +def test_split_content_into_text_and_thinking(content: str, parts: list[ModelResponsePart]): + assert split_content_into_text_and_thinking(content) == parts From 19c275e37abea358989105a1765f2faec5bd7365 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Mon, 28 Apr 2025 09:27:50 +0200 Subject: [PATCH 27/27] Apply changes --- .../pydantic_ai/_parts_manager.py | 2 +- pydantic_ai_slim/pydantic_ai/models/openai.py | 42 +++++++++++++------ pydantic_ai_slim/pydantic_ai/models/test.py | 2 +- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/_parts_manager.py b/pydantic_ai_slim/pydantic_ai/_parts_manager.py index 8ecfc6387..d010c7eff 100644 --- a/pydantic_ai_slim/pydantic_ai/_parts_manager.py +++ b/pydantic_ai_slim/pydantic_ai/_parts_manager.py @@ -171,7 +171,7 @@ def handle_thinking_delta( if content is not None: # There is no existing thinking part that should be updated, so create a new one new_part_index = len(self._parts) - part = ThinkingPart(content=content) + part = ThinkingPart(content=content, signature=signature) if vendor_part_id is not None: self._vendor_id_to_part_index[vendor_part_id] = new_part_index self._parts.append(part) diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 8d67aaca4..6c8f19f33 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -685,7 +685,7 @@ def _map_tool_definition(f: ToolDefinition) -> responses.FunctionToolParam: 'strict': f.strict or False, } - async def _map_messages( + async def _map_messages( # noqa: C901 self, messages: list[ModelMessage] ) -> tuple[str | NotGiven, list[responses.ResponseInputItemParam]]: """Just maps a `pydantic_ai.Message` to a `openai.types.responses.ResponseInputParam`.""" @@ -722,24 +722,33 @@ async def _map_messages( else: assert_never(part) elif isinstance(message, ModelResponse): - thinking_parts: list[ThinkingPart] = [] + last_thinking_part_idx: int | None = None for item in message.parts: if isinstance(item, TextPart): openai_messages.append(responses.EasyInputMessageParam(role='assistant', content=item.content)) elif isinstance(item, ToolCallPart): openai_messages.append(self._map_tool_call(item)) elif isinstance(item, ThinkingPart): - thinking_parts.append(item) + assert item.signature is not None, 'If this is triggered, please create an issue.' + if last_thinking_part_idx is not None: + reasoning_item = cast(responses.ResponseReasoningItemParam, openai_messages[last_thinking_part_idx]) # fmt: skip + if item.signature == reasoning_item['id']: + assert isinstance(reasoning_item['summary'], list) + reasoning_item['summary'].append(Summary(text=item.content, type='summary_text')) + continue + last_thinking_part_idx = len(openai_messages) + openai_messages.append( + responses.ResponseReasoningItemParam( + id=item.signature, + summary=[Summary(text=item.content, type='summary_text')], + type='reasoning', + ) + ) else: assert_never(item) - if thinking_parts: - openai_messages.append( - responses.ResponseReasoningItemParam( - id=thinking_parts[0].signature or '', - summary=[Summary(text=item.content, type='summary_text') for item in thinking_parts], - type='reasoning', - ) - ) + from rich.pretty import pprint + + pprint(openai_messages) else: assert_never(message) instructions = self._get_instructions(messages) or NOT_GIVEN @@ -877,6 +886,9 @@ class OpenAIResponsesStreamedResponse(StreamedResponse): async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: # noqa: C901 async for chunk in self._response: + from rich.pretty import pprint + + pprint(chunk) if isinstance(chunk, responses.ResponseCompletedEvent): self._usage += _map_usage(chunk.response) @@ -917,11 +929,15 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: vendor_part_id=chunk.item.id, tool_name=chunk.item.name, args=chunk.item.arguments, - tool_call_id=chunk.item.id, + tool_call_id=chunk.item.call_id, ) elif isinstance(chunk.item, responses.ResponseReasoningItem): content = chunk.item.summary[0].text if chunk.item.summary else '' - yield self._parts_manager.handle_thinking_delta(vendor_part_id=chunk.item.id, content=content) + yield self._parts_manager.handle_thinking_delta( + vendor_part_id=chunk.item.id, + content=content, + signature=chunk.item.id, + ) elif isinstance(chunk.item, responses.ResponseOutputMessage): pass else: diff --git a/pydantic_ai_slim/pydantic_ai/models/test.py b/pydantic_ai_slim/pydantic_ai/models/test.py index e53cdc12a..cdac5bf28 100644 --- a/pydantic_ai_slim/pydantic_ai/models/test.py +++ b/pydantic_ai_slim/pydantic_ai/models/test.py @@ -259,7 +259,7 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: yield self._parts_manager.handle_tool_call_part( vendor_part_id=i, tool_name=part.tool_name, args=part.args, tool_call_id=part.tool_call_id ) - elif isinstance(part, ThinkingPart): + elif isinstance(part, ThinkingPart): # pragma: no cover # NOTE: There's no way to reach this part of the code, since we don't generate ThinkingPart on TestModel. pass # pragma: no cover else: