From b78671ed403561a47249c6c1d4f3fd6b4bc50944 Mon Sep 17 00:00:00 2001 From: Lucas Gomide Date: Thu, 29 May 2025 15:34:34 -0300 Subject: [PATCH] feat: log usage tools when called by LLM (#2916) * feat: log usage tools when called by LLM * feat: print llm tool usage in console --- src/crewai/llm.py | 36 +- src/crewai/utilities/events/event_listener.py | 44 +- .../utilities/events/tool_usage_events.py | 6 +- .../events/utils/console_formatter.py | 46 +++ ...andle_streaming_tool_calls_with_error.yaml | 143 +++++++ .../test_llm_callback_replacement.yaml | 388 ++---------------- tests/llm_test.py | 50 ++- 7 files changed, 335 insertions(+), 378 deletions(-) create mode 100644 tests/cassettes/test_handle_streaming_tool_calls_with_error.yaml diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 1e7c705a2..5aa7192e3 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -5,7 +5,7 @@ import sys import threading import warnings from collections import defaultdict -from contextlib import contextmanager, redirect_stderr, redirect_stdout +from contextlib import contextmanager from typing import ( Any, DefaultDict, @@ -18,7 +18,7 @@ from typing import ( Union, cast, ) - +from datetime import datetime from dotenv import load_dotenv from litellm.types.utils import ChatCompletionDeltaToolCall from pydantic import BaseModel, Field @@ -30,6 +30,11 @@ from crewai.utilities.events.llm_events import ( LLMCallType, LLMStreamChunkEvent, ) +from crewai.utilities.events.tool_usage_events import ( + ToolUsageStartedEvent, + ToolUsageFinishedEvent, + ToolUsageErrorEvent, +) with warnings.catch_warnings(): warnings.simplefilter("ignore", UserWarning) @@ -833,7 +838,26 @@ class LLM(BaseLLM): fn = available_functions[function_name] # --- 3.2) Execute function + assert hasattr(crewai_event_bus, "emit") + started_at = datetime.now() + crewai_event_bus.emit( + self, + event=ToolUsageStartedEvent( + tool_name=function_name, + tool_args=function_args, + ), + ) result = fn(**function_args) + crewai_event_bus.emit( + self, + event=ToolUsageFinishedEvent( + output=result, + tool_name=function_name, + tool_args=function_args, + started_at=started_at, + finished_at=datetime.now(), + ), + ) # --- 3.3) Emit success event self._handle_emit_call_events(result, LLMCallType.TOOL_CALL) @@ -849,6 +873,14 @@ class LLM(BaseLLM): self, event=LLMCallFailedEvent(error=f"Tool execution error: {str(e)}"), ) + crewai_event_bus.emit( + self, + event=ToolUsageErrorEvent( + tool_name=function_name, + tool_args=function_args, + error=f"Tool execution error: {str(e)}" + ), + ) return None def call( diff --git a/src/crewai/utilities/events/event_listener.py b/src/crewai/utilities/events/event_listener.py index b660cd67a..240e685c9 100644 --- a/src/crewai/utilities/events/event_listener.py +++ b/src/crewai/utilities/events/event_listener.py @@ -2,7 +2,7 @@ from io import StringIO from typing import Any, Dict from pydantic import Field, PrivateAttr - +from crewai.llm import LLM from crewai.task import Task from crewai.telemetry.telemetry import Telemetry from crewai.utilities import Logger @@ -286,27 +286,43 @@ class EventListener(BaseEventListener): @crewai_event_bus.on(ToolUsageStartedEvent) def on_tool_usage_started(source, event: ToolUsageStartedEvent): - self.formatter.handle_tool_usage_started( - self.formatter.current_agent_branch, - event.tool_name, + if isinstance(source, LLM): + self.formatter.handle_llm_tool_usage_started( + event.tool_name, + ) + else: + self.formatter.handle_tool_usage_started( + self.formatter.current_agent_branch, + event.tool_name, self.formatter.current_crew_tree, ) @crewai_event_bus.on(ToolUsageFinishedEvent) def on_tool_usage_finished(source, event: ToolUsageFinishedEvent): - self.formatter.handle_tool_usage_finished( - self.formatter.current_tool_branch, - event.tool_name, - self.formatter.current_crew_tree, - ) + if isinstance(source, LLM): + self.formatter.handle_llm_tool_usage_finished( + event.tool_name, + ) + else: + self.formatter.handle_tool_usage_finished( + self.formatter.current_tool_branch, + event.tool_name, + self.formatter.current_crew_tree, + ) @crewai_event_bus.on(ToolUsageErrorEvent) def on_tool_usage_error(source, event: ToolUsageErrorEvent): - self.formatter.handle_tool_usage_error( - self.formatter.current_tool_branch, - event.tool_name, - event.error, - self.formatter.current_crew_tree, + if isinstance(source, LLM): + self.formatter.handle_llm_tool_usage_error( + event.tool_name, + event.error, + ) + else: + self.formatter.handle_tool_usage_error( + self.formatter.current_tool_branch, + event.tool_name, + event.error, + self.formatter.current_crew_tree, ) # ----------- LLM EVENTS ----------- diff --git a/src/crewai/utilities/events/tool_usage_events.py b/src/crewai/utilities/events/tool_usage_events.py index 8ab22f667..13de0d519 100644 --- a/src/crewai/utilities/events/tool_usage_events.py +++ b/src/crewai/utilities/events/tool_usage_events.py @@ -7,11 +7,11 @@ from .base_events import BaseEvent class ToolUsageEvent(BaseEvent): """Base event for tool usage tracking""" - agent_key: str - agent_role: str + agent_key: Optional[str] = None + agent_role: Optional[str] = None tool_name: str tool_args: Dict[str, Any] | str - tool_class: str + tool_class: Optional[str] = None run_attempts: int | None = None delegations: int | None = None agent: Optional[Any] = None diff --git a/src/crewai/utilities/events/utils/console_formatter.py b/src/crewai/utilities/events/utils/console_formatter.py index 8d427a495..f8d85b3f7 100644 --- a/src/crewai/utilities/events/utils/console_formatter.py +++ b/src/crewai/utilities/events/utils/console_formatter.py @@ -26,6 +26,7 @@ class ConsoleFormatter: _spinner_thread: Optional[threading.Thread] = None _stop_spinner_event: Optional[threading.Event] = None _spinner_running: bool = False + current_llm_tool_tree: Optional[Tree] = None def __init__(self, verbose: bool = False): self.console = Console(width=None) @@ -469,6 +470,51 @@ class ConsoleFormatter: self.print() return method_branch + def get_llm_tree(self, tool_name: str): + text = Text() + text.append(f"🔧 Using {tool_name} from LLM available_function", style="yellow") + + tree = self.current_flow_tree or self.current_crew_tree + + if tree: + tree.add(text) + + return tree or Tree(text) + + def handle_llm_tool_usage_started( + self, + tool_name: str, + ): + tree = self.get_llm_tree(tool_name) + self.add_tree_node(tree, "🔄 Tool Usage Started", "green") + self.print(tree) + self.print() + return tree + + def handle_llm_tool_usage_finished( + self, + tool_name: str, + ): + tree = self.get_llm_tree(tool_name) + self.add_tree_node(tree, "✅ Tool Usage Completed", "green") + self.print(tree) + self.print() + + def handle_llm_tool_usage_error( + self, + tool_name: str, + error: str, + ): + tree = self.get_llm_tree(tool_name) + self.add_tree_node(tree, "❌ Tool Usage Failed", "red") + self.print(tree) + self.print() + + error_content = self.create_status_content( + "Tool Usage Failed", tool_name, "red", Error=error + ) + self.print_panel(error_content, "Tool Error", "red") + def handle_tool_usage_started( self, agent_branch: Optional[Tree], diff --git a/tests/cassettes/test_handle_streaming_tool_calls_with_error.yaml b/tests/cassettes/test_handle_streaming_tool_calls_with_error.yaml new file mode 100644 index 000000000..82c93d658 --- /dev/null +++ b/tests/cassettes/test_handle_streaming_tool_calls_with_error.yaml @@ -0,0 +1,143 @@ +interactions: +- request: + body: '{"messages": [{"role": "user", "content": "What is the weather in New York?"}], + "model": "gpt-4o", "stop": [], "stream": true, "stream_options": {"include_usage": + true}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": + "Get the current weather in a given location", "parameters": {"type": "object", + "properties": {"location": {"type": "string", "description": "The city and state, + e.g. San Francisco, CA"}}, "required": ["location"]}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '470' + content-type: + - application/json + cookie: + - _cfuvid=3UeEmz_rnmsoZxrVUv32u35gJOi766GDWNe5_RTjiPk-1736537376739-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.68.2 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.68.2 + x-stainless-raw-response: + - 'true' + x-stainless-read-timeout: + - '600.0' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: 'data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_UkMsNK0RTJ1nlT19WqgLJYV9","type":"function","function":{"name":"get_weather","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"location"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"New"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" + York"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":","}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" + NY"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],"usage":null} + + + data: {"id":"chatcmpl-BcY6NFDeu4HFOAIarpwSNAUEMuPTg","object":"chat.completion.chunk","created":1748527251,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_07871e2ad8","choices":[],"usage":{"prompt_tokens":68,"completion_tokens":17,"total_tokens":85,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}} + + + data: [DONE] + + + ' + headers: + CF-RAY: + - 947685373af8a435-GRU + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Thu, 29 May 2025 14:00:51 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=fFoq7oCHLgmljA4hsHWxTGHMEWJ.0t1XTuDptZPPkOc-1748527251-1.0.1.1-PP3Hd7XzA4AQFn0JQWjuQdhFwey0Pj9maUWKfFG16Bkl69Uk65A8XKN73UbsvO327TruwxameKb_m_HDePCR.YN0TZlE8Pu45WsA9shDwKY; + path=/; expires=Thu, 29-May-25 14:30:51 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=ut1CVX5GOYnv03fiV2Dsv7cm5soJmwgSutkPAEuVXWg-1748527251565-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '332' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '334' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999989' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_1dc91fc964a8d23ee023693400e5c181 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_llm_callback_replacement.yaml b/tests/cassettes/test_llm_callback_replacement.yaml index 064a505c4..fb1c19642 100644 --- a/tests/cassettes/test_llm_callback_replacement.yaml +++ b/tests/cassettes/test_llm_callback_replacement.yaml @@ -204,361 +204,35 @@ interactions: status_code: 200 - request: body: !!binary | - CtmdAQokCiIKDHNlcnZpY2UubmFtZRISChBjcmV3QUktdGVsZW1ldHJ5Eq+dAQoSChBjcmV3YWku - dGVsZW1ldHJ5EmMKEKLfuECJGAc0Sv0pC0269lwSCChYwUI1JksUKg1GbG93IENyZWF0aW9uMAE5 - kMTMfCVUQxhBWPfMfCVUQxhKGAoJZmxvd19uYW1lEgsKCVN0YXRlRmxvd3oCGAGFAQABAAASigEK - EBu2HhgIdB6HsR59V4wnLgkSCBxhw2Rk0ArJKg5GbG93IEV4ZWN1dGlvbjABOTgH53wlVEMYQbBw - 53wlVEMYShgKCWZsb3dfbmFtZRILCglTdGF0ZUZsb3dKJAoKbm9kZV9uYW1lcxIWChRbInN0ZXBf - MSIsICJzdGVwXzIiXXoCGAGFAQABAAASbAoQot+4QIkYBzRK/SkLTbr2XBIIKFjBQjUmSxQqDUZs - b3cgQ3JlYXRpb24wATkYyCJ9JVRDGEHI/iJ9JVRDGEohCglmbG93X25hbWUSFAoSVVVJRFN0cnVj - dHVyZWRGbG93egIYAYUBAAEAABKgAQoQG7YeGAh0HoexHn1XjCcuCRIIHGHDZGTQCskqDkZsb3cg - RXhlY3V0aW9uMAE5EI0zfSVUQxhBqMczfSVUQxhKIQoJZmxvd19uYW1lEhQKElVVSURTdHJ1Y3R1 - cmVkRmxvd0oxCgpub2RlX25hbWVzEiMKIVsiZmlyc3RfbWV0aG9kIiwgInNlY29uZF9tZXRob2Qi - XXoCGAGFAQABAAASaQoQot+4QIkYBzRK/SkLTbr2XBIIKFjBQjUmSxQqDUZsb3cgQ3JlYXRpb24w - ATlwqmp9JVRDGEGA0Wp9JVRDGEoeCglmbG93X25hbWUSEQoPUmVzdGFydGFibGVGbG93egIYAYUB - AAEAABKQAQoQG7YeGAh0HoexHn1XjCcuCRIIHGHDZGTQCskqDkZsb3cgRXhlY3V0aW9uMAE56Cp8 - fSVUQxhB8Hx8fSVUQxhKHgoJZmxvd19uYW1lEhEKD1Jlc3RhcnRhYmxlRmxvd0okCgpub2RlX25h - bWVzEhYKFFsic3RlcF8xIiwgInN0ZXBfMiJdegIYAYUBAAEAABKQAQoQdFTCtCIDudZwchZzoNOX - RxIIhkf3XlJigkQqDkZsb3cgRXhlY3V0aW9uMAE5IKSJfSVUQxhBGM+JfSVUQxhKHgoJZmxvd19u - YW1lEhEKD1Jlc3RhcnRhYmxlRmxvd0okCgpub2RlX25hbWVzEhYKFFsic3RlcF8xIiwgInN0ZXBf - MiJdegIYAYUBAAEAABJnChCi37hAiRgHNEr9KQtNuvZcEggoWMFCNSZLFCoNRmxvdyBDcmVhdGlv - bjABObi/o30lVEMYQSjXo30lVEMYShwKCWZsb3dfbmFtZRIPCg1TdGF0ZWxlc3NGbG93egIYAYUB - AAEAABKNAQoQG7YeGAh0HoexHn1XjCcuCRIIHGHDZGTQCskqDkZsb3cgRXhlY3V0aW9uMAE5+Iqx - fSVUQxhB8LWxfSVUQxhKHAoJZmxvd19uYW1lEg8KDVN0YXRlbGVzc0Zsb3dKIwoKbm9kZV9uYW1l - cxIVChNbImluaXQiLCAicHJvY2VzcyJdegIYAYUBAAEAABJjChCi37hAiRgHNEr9KQtNuvZcEggo - WMFCNSZLFCoNRmxvdyBDcmVhdGlvbjABOVDa0n0lVEMYQZD50n0lVEMYShgKCWZsb3dfbmFtZRIL - CglBc3luY0Zsb3d6AhgBhQEAAQAAEooBChAbth4YCHQeh7EefVeMJy4JEggcYcNkZNAKySoORmxv - dyBFeGVjdXRpb24wATkQW+J9JVRDGEGoleJ9JVRDGEoYCglmbG93X25hbWUSCwoJQXN5bmNGbG93 - SiQKCm5vZGVfbmFtZXMSFgoUWyJzdGVwXzEiLCAic3RlcF8yIl16AhgBhQEAAQAAEmQKEKLfuECJ - GAc0Sv0pC0269lwSCChYwUI1JksUKg1GbG93IENyZWF0aW9uMAE56DtAiiVUQxhBeKFAiiVUQxhK - GQoJZmxvd19uYW1lEgwKClNpbXBsZUZsb3d6AhgBhQEAAQAAEosBChAbth4YCHQeh7EefVeMJy4J - EggcYcNkZNAKySoORmxvdyBFeGVjdXRpb24wATnI5WqKJVRDGEHAjWuKJVRDGEoZCglmbG93X25h - bWUSDAoKU2ltcGxlRmxvd0okCgpub2RlX25hbWVzEhYKFFsic3RlcF8xIiwgInN0ZXBfMiJdegIY - AYUBAAEAABJoChCi37hAiRgHNEr9KQtNuvZcEggoWMFCNSZLFCoNRmxvdyBDcmVhdGlvbjABOQDN - NoslVEMYQSjwNoslVEMYSh0KCWZsb3dfbmFtZRIQCg5NdWx0aVN0YXJ0Rmxvd3oCGAGFAQABAAAS - owEKEBu2HhgIdB6HsR59V4wnLgkSCBxhw2Rk0ArJKg5GbG93IEV4ZWN1dGlvbjABOTgoSYslVEMY - QaBqSYslVEMYSh0KCWZsb3dfbmFtZRIQCg5NdWx0aVN0YXJ0Rmxvd0o4Cgpub2RlX25hbWVzEioK - KFsic3RlcF9hIiwgInN0ZXBfYiIsICJzdGVwX2MiLCAic3RlcF9kIl16AhgBhQEAAQAAEmkKEKLf - uECJGAc0Sv0pC0269lwSCChYwUI1JksUKg1GbG93IENyZWF0aW9uMAE5aL9tiyVUQxhBkOJtiyVU - QxhKHgoJZmxvd19uYW1lEhEKD09yQ29uZGl0aW9uRmxvd3oCGAGFAQABAAASmgEKEBu2HhgIdB6H - sR59V4wnLgkSCBxhw2Rk0ArJKg5GbG93IEV4ZWN1dGlvbjABObiTgIslVEMYQXjxgIslVEMYSh4K - CWZsb3dfbmFtZRIRCg9PckNvbmRpdGlvbkZsb3dKLgoKbm9kZV9uYW1lcxIgCh5bInN0ZXBfYSIs - ICJzdGVwX2IiLCAic3RlcF9jIl16AhgBhQEAAQAAEmQKEKLfuECJGAc0Sv0pC0269lwSCChYwUI1 - JksUKg1GbG93IENyZWF0aW9uMAE5CGa9iyVUQxhBMIm9iyVUQxhKGQoJZmxvd19uYW1lEgwKCkN5 - Y2xpY0Zsb3d6AhgBhQEAAQAAEpUBChAbth4YCHQeh7EefVeMJy4JEggcYcNkZNAKySoORmxvdyBF - eGVjdXRpb24wATkgZ9GLJVRDGEFwrdGLJVRDGEoZCglmbG93X25hbWUSDAoKQ3ljbGljRmxvd0ou - Cgpub2RlX25hbWVzEiAKHlsic3RlcF8xIiwgInN0ZXBfMiIsICJzdGVwXzMiXXoCGAGFAQABAAAS - YgoQot+4QIkYBzRK/SkLTbr2XBIIKFjBQjUmSxQqDUZsb3cgQ3JlYXRpb24wATnoYQSMJVRDGEHI - kASMJVRDGEoXCglmbG93X25hbWUSCgoIUG9lbUZsb3d6AhgBhQEAAQAAEtgBChAbth4YCHQeh7Ee - fVeMJy4JEggcYcNkZNAKySoORmxvdyBFeGVjdXRpb24wATlQGhuMJVRDGEGgYBuMJVRDGEoXCglm - bG93X25hbWUSCgoIUG9lbUZsb3dKcwoKbm9kZV9uYW1lcxJlCmNbImZpbmlzaF9wb2VtIiwgInBy - ZXBhcmVfY29sb3IiLCAicHJlcGFyZV9mbG93ZXIiLCAic2F2ZV9wb2VtX3RvX2RhdGFiYXNlIiwg - IndyaXRlX2ZpcnN0X3NlbnRlbmNlIl16AhgBhQEAAQAAEmsKEKLfuECJGAc0Sv0pC0269lwSCChY - wUI1JksUKg1GbG93IENyZWF0aW9uMAE5mC5pjCVUQxhBqFVpjCVUQxhKIAoJZmxvd19uYW1lEhMK - EUNvbXBsZXhSb3V0ZXJGbG93egIYAYUBAAEAABLtAQoQG7YeGAh0HoexHn1XjCcuCRIIHGHDZGTQ - CskqDkZsb3cgRXhlY3V0aW9uMAE5eGJ9jCVUQxhByKh9jCVUQxhKIAoJZmxvd19uYW1lEhMKEUNv - bXBsZXhSb3V0ZXJGbG93Sn8KCm5vZGVfbmFtZXMScQpvWyJicmFuY2hfMl9zdGVwIiwgImhhbmRs - ZV9uZXh0X3N0ZXBfb3JfZXZlbnQiLCAibG9nX2ZpbmFsX3N0ZXAiLCAicm91dGVyX2FuZCIsICJy - b3V0ZXJfb3IiLCAic3RlcF9hIiwgInN0ZXBfYiJdegIYAYUBAAEAABJuChCi37hAiRgHNEr9KQtN - uvZcEggoWMFCNSZLFCoNRmxvdyBDcmVhdGlvbjABOeCip4wlVEMYQdjNp4wlVEMYSiMKCWZsb3df - bmFtZRIWChRVVUlEVW5zdHJ1Y3R1cmVkRmxvd3oCGAGFAQABAAASogEKEBu2HhgIdB6HsR59V4wn - LgkSCBxhw2Rk0ArJKg5GbG93IEV4ZWN1dGlvbjABOdAPuYwlVEMYQYBGuYwlVEMYSiMKCWZsb3df - bmFtZRIWChRVVUlEVW5zdHJ1Y3R1cmVkRmxvd0oxCgpub2RlX25hbWVzEiMKIVsiZmlyc3RfbWV0 - aG9kIiwgInNlY29uZF9tZXRob2QiXXoCGAGFAQABAAASZAoQot+4QIkYBzRK/SkLTbr2XBIIKFjB - QjUmSxQqDUZsb3cgQ3JlYXRpb24wATkoHeWMJVRDGEGQX+WMJVRDGEoZCglmbG93X25hbWUSDAoK - Um91dGVyRmxvd3oCGAGFAQABAAASpAEKEBu2HhgIdB6HsR59V4wnLgkSCBxhw2Rk0ArJKg5GbG93 - IEV4ZWN1dGlvbjABOVA5+4wlVEMYQYiD+4wlVEMYShkKCWZsb3dfbmFtZRIMCgpSb3V0ZXJGbG93 - Sj0KCm5vZGVfbmFtZXMSLwotWyJmYWxzeSIsICJyb3V0ZXIiLCAic3RhcnRfbWV0aG9kIiwgInRy - dXRoeSJdegIYAYUBAAEAABJnChCi37hAiRgHNEr9KQtNuvZcEggoWMFCNSZLFCoNRmxvdyBDcmVh - dGlvbjABOSg4H40lVEMYQQhnH40lVEMYShwKCWZsb3dfbmFtZRIPCg1FeGNlcHRpb25GbG93egIY - AYUBAAEAABKOAQoQG7YeGAh0HoexHn1XjCcuCRIIHGHDZGTQCskqDkZsb3cgRXhlY3V0aW9uMAE5 - AKkwjSVUQxhBaOswjSVUQxhKHAoJZmxvd19uYW1lEg8KDUV4Y2VwdGlvbkZsb3dKJAoKbm9kZV9u - YW1lcxIWChRbInN0ZXBfMSIsICJzdGVwXzIiXXoCGAGFAQABAAASagoQot+4QIkYBzRK/SkLTbr2 - XBIIKFjBQjUmSxQqDUZsb3cgQ3JlYXRpb24wATm46FGNJVRDGEHgC1KNJVRDGEofCglmbG93X25h - bWUSEgoQQW5kQ29uZGl0aW9uRmxvd3oCGAGFAQABAAASmwEKEBu2HhgIdB6HsR59V4wnLgkSCBxh - w2Rk0ArJKg5GbG93IEV4ZWN1dGlvbjABOVBAYo0lVEMYQdB+Yo0lVEMYSh8KCWZsb3dfbmFtZRIS - ChBBbmRDb25kaXRpb25GbG93Si4KCm5vZGVfbmFtZXMSIAoeWyJzdGVwXzEiLCAic3RlcF8yIiwg - InN0ZXBfMyJdegIYAYUBAAEAABJpChCi37hAiRgHNEr9KQtNuvZcEggoWMFCNSZLFCoNRmxvdyBD - cmVhdGlvbjABOVhykY0lVEMYQWiZkY0lVEMYSh4KCWZsb3dfbmFtZRIRCg9NdWx0aVJvdXRlckZs - b3d6AhgBhQEAAQAAEqICChAbth4YCHQeh7EefVeMJy4JEggcYcNkZNAKySoORmxvdyBFeGVjdXRp - b24wATnYx6KNJVRDGEEQEqONJVRDGEoeCglmbG93X25hbWUSEQoPTXVsdGlSb3V0ZXJGbG93SrUB - Cgpub2RlX25hbWVzEqYBCqMBWyJhbmVtaWFfYW5hbHlzaXMiLCAiYW5lbWlhX3JvdXRlciIsICJk - aWFiZXRlc19hbmFseXNpcyIsICJkaWFiZXRlc19yb3V0ZXIiLCAiZGlhZ25vc2VfY29uZGl0aW9u - cyIsICJoeXBlcnRlbnNpb25fYW5hbHlzaXMiLCAiaHlwZXJ0ZW5zaW9uX3JvdXRlciIsICJzY2Fu - X21lZGljYWwiXXoCGAGFAQABAAASZwoQot+4QIkYBzRK/SkLTbr2XBIIKFjBQjUmSxQqDUZsb3cg - Q3JlYXRpb24wATkAZs2NJVRDGEHglM2NJVRDGEocCglmbG93X25hbWUSDwoNU3RhdGVsZXNzRmxv - d3oCGAGFAQABAAASjQEKEBu2HhgIdB6HsR59V4wnLgkSCBxhw2Rk0ArJKg5GbG93IEV4ZWN1dGlv - bjABOfBJ4I0lVEMYQbh84I0lVEMYShwKCWZsb3dfbmFtZRIPCg1TdGF0ZWxlc3NGbG93SiMKCm5v - ZGVfbmFtZXMSFQoTWyJpbml0IiwgInByb2Nlc3MiXXoCGAGFAQABAAASaAoQot+4QIkYBzRK/SkL - Tbr2XBIIKFjBQjUmSxQqDUZsb3cgQ3JlYXRpb24wATnQUaKOJVRDGEEQcaKOJVRDGEodCglmbG93 - X25hbWUSEAoOT25ib2FyZGluZ0Zsb3d6AhgBhQEAAQAAEqQBChAbth4YCHQeh7EefVeMJy4JEggc - YcNkZNAKySoORmxvdyBFeGVjdXRpb24wATloMrGOJVRDGEEwZbGOJVRDGEodCglmbG93X25hbWUS - EAoOT25ib2FyZGluZ0Zsb3dKOQoKbm9kZV9uYW1lcxIrCilbInNlbmRfd2VsY29tZV9tZXNzYWdl - IiwgInVzZXJfc2lnbnNfdXAiXXoCGAGFAQABAAASpAEKEHRUwrQiA7nWcHIWc6DTl0cSCIZH915S - YoJEKg5GbG93IEV4ZWN1dGlvbjABOSBhwI4lVEMYQWjSwI4lVEMYSh0KCWZsb3dfbmFtZRIQCg5P - bmJvYXJkaW5nRmxvd0o5Cgpub2RlX25hbWVzEisKKVsic2VuZF93ZWxjb21lX21lc3NhZ2UiLCAi - dXNlcl9zaWduc191cCJdegIYAYUBAAEAABJiChCi37hAiRgHNEr9KQtNuvZcEggoWMFCNSZLFCoN - RmxvdyBDcmVhdGlvbjABOYiD744lVEMYQcii744lVEMYShcKCWZsb3dfbmFtZRIKCghQb2VtRmxv - d3oCGAGFAQABAAASiwEKEBu2HhgIdB6HsR59V4wnLgkSCBxhw2Rk0ArJKg5GbG93IEV4ZWN1dGlv - bjABORDG/I4lVEMYQcD8/I4lVEMYShcKCWZsb3dfbmFtZRIKCghQb2VtRmxvd0omCgpub2RlX25h - bWVzEhgKFlsic2V0X3NlbnRlbmNlX2NvdW50Il16AhgBhQEAAQAAEmIKEHRUwrQiA7nWcHIWc6DT - l0cSCIZH915SYoJEKg1GbG93IENyZWF0aW9uMAE5YCMOjyVUQxhB6DYOjyVUQxhKFwoJZmxvd19u - YW1lEgoKCFBvZW1GbG93egIYAYUBAAEAABKLAQoQ+IJI5Szv23yAr2JkgMzajhIIiLWekQ9/opYq - DkZsb3cgRXhlY3V0aW9uMAE5aBUejyVUQxhBYEAejyVUQxhKFwoJZmxvd19uYW1lEgoKCFBvZW1G - bG93SiYKCm5vZGVfbmFtZXMSGAoWWyJzZXRfc2VudGVuY2VfY291bnQiXXoCGAGFAQABAAASYgoQ - ORKxisuZI8VOlFqqkbOP8RII4xCjMFEzEPAqDUZsb3cgQ3JlYXRpb24wATnIaTePJVRDGEFQfTeP - JVRDGEoXCglmbG93X25hbWUSCgoIUG9lbUZsb3d6AhgBhQEAAQAAEosBChBGNO93ef4eaK09E79N - 4aSAEgimMY60twg0WSoORmxvdyBFeGVjdXRpb24wATnIjEaPJVRDGEHAt0aPJVRDGEoXCglmbG93 - X25hbWUSCgoIUG9lbUZsb3dKJgoKbm9kZV9uYW1lcxIYChZbInNldF9zZW50ZW5jZV9jb3VudCJd - egIYAYUBAAEAABJiChBlZYci9JilJqJsVsHp1G6EEgit0w4tLscveyoNRmxvdyBDcmVhdGlvbjAB - OYhmXI8lVEMYQfh9XI8lVEMYShcKCWZsb3dfbmFtZRIKCghQb2VtRmxvd3oCGAGFAQABAAASiwEK - ENVcATReHOdubt4FxYacl6kSCGS2sAp886WiKg5GbG93IEV4ZWN1dGlvbjABOQALaI8lVEMYQSgu - aI8lVEMYShcKCWZsb3dfbmFtZRIKCghQb2VtRmxvd0omCgpub2RlX25hbWVzEhgKFlsic2V0X3Nl - bnRlbmNlX2NvdW50Il16AhgBhQEAAQAAEmsKEKLfuECJGAc0Sv0pC0269lwSCChYwUI1JksUKg1G - bG93IENyZWF0aW9uMAE54LSSjyVUQxhBaMiSjyVUQxhKIAoJZmxvd19uYW1lEhMKEU11bHRpU3Rl - cFBvZW1GbG93egIYAYUBAAEAABKxAQoQG7YeGAh0HoexHn1XjCcuCRIIHGHDZGTQCskqDkZsb3cg - RXhlY3V0aW9uMAE5sHSejyVUQxhBkKOejyVUQxhKIAoJZmxvd19uYW1lEhMKEU11bHRpU3RlcFBv - ZW1GbG93SkMKCm5vZGVfbmFtZXMSNQozWyJmaW5pc2hlZCIsICJzZXRfcG9lbV90eXBlIiwgInNl - dF9zZW50ZW5jZV9jb3VudCJdegIYAYUBAAEAABJrChB0VMK0IgO51nByFnOg05dHEgiGR/deUmKC - RCoNRmxvdyBDcmVhdGlvbjABOXBlxY8lVEMYQeB8xY8lVEMYSiAKCWZsb3dfbmFtZRITChFNdWx0 - aVN0ZXBQb2VtRmxvd3oCGAGFAQABAAASsQEKEPiCSOUs79t8gK9iZIDM2o4SCIi1npEPf6KWKg5G - bG93IEV4ZWN1dGlvbjABOZgc148lVEMYQZBH148lVEMYSiAKCWZsb3dfbmFtZRITChFNdWx0aVN0 - ZXBQb2VtRmxvd0pDCgpub2RlX25hbWVzEjUKM1siZmluaXNoZWQiLCAic2V0X3BvZW1fdHlwZSIs - ICJzZXRfc2VudGVuY2VfY291bnQiXXoCGAGFAQABAAASawoQORKxisuZI8VOlFqqkbOP8RII4xCj - MFEzEPAqDUZsb3cgQ3JlYXRpb24wATn4mf6PJVRDGEGArf6PJVRDGEogCglmbG93X25hbWUSEwoR - TXVsdGlTdGVwUG9lbUZsb3d6AhgBhQEAAQAAErEBChBGNO93ef4eaK09E79N4aSAEgimMY60twg0 - WSoORmxvdyBFeGVjdXRpb24wATnw5w2QJVRDGEEADw6QJVRDGEogCglmbG93X25hbWUSEwoRTXVs - dGlTdGVwUG9lbUZsb3dKQwoKbm9kZV9uYW1lcxI1CjNbImZpbmlzaGVkIiwgInNldF9wb2VtX3R5 - cGUiLCAic2V0X3NlbnRlbmNlX2NvdW50Il16AhgBhQEAAQAAEq8NChCi37hAiRgHNEr9KQtNuvZc - EggoWMFCNSZLFCoMQ3JldyBDcmVhdGVkMAE5QDbLkyVUQxhB6OTTkyVUQxhKGwoOY3Jld2FpX3Zl - cnNpb24SCQoHMC4xMjEuMEoaCg5weXRob25fdmVyc2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkS - IgogNmJhOTEyZjkxMjlkNjg0OWEwYWM0OWNmYmQzMjFkYWRKMQoHY3Jld19pZBImCiQxOTM3YzRh - MC1hNTZjLTQ1MWEtOWU5YS0zNGM3MTBkMTM3ZTRKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRp - YWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3 - X251bWJlcl9vZl9hZ2VudHMSAhgCSjoKEGNyZXdfZmluZ2VycHJpbnQSJgokMDAxZDVhNGEtMzlj - NC00MWQ3LTljMDEtZjcwNjI2MGEyMjc3SjsKG2NyZXdfZmluZ2VycHJpbnRfY3JlYXRlZF9hdBIc - ChoyMDI1LTA1LTI3VDAxOjEzOjIwLjcxNzYzOUrPBQoLY3Jld19hZ2VudHMSvwUKvAVbeyJrZXki - OiAiNzNjMzQ5YzkzYzE2M2I1ZDRkZjk4YTY0ZmFjMWM0MzAiLCAiaWQiOiAiNDJjNzg4ZDEtMjJl - NS00NmE2LTk1OWEtYmIwMGZiMzViOWQ1IiwgInJvbGUiOiAie3RvcGljfSBTZW5pb3IgRGF0YSBS - ZXNlYXJjaGVyXG4iLCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAyNSwgIm1heF9ycG0i - OiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAib3BlbmFpL21vZGVsX25hbWUiLCAibGxt - IjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2Nv - ZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVz - IjogW119LCB7ImtleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1MyIsICJpZCI6 - ICJlNGMzNmIwYi02NTcyLTQ2ZmMtOTNkZC0xMTUzNWM0NjcxZjQiLCAicm9sZSI6ICJ7dG9waWN9 - IFJlcG9ydGluZyBBbmFseXN0XG4iLCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAyNSwg - Im1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAib25saW5lX2xsbSIsICJs - bG0iOiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3df - Y29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFt - ZXMiOiBbXX1dSpMECgpjcmV3X3Rhc2tzEoQECoEEW3sia2V5IjogIjAwMTc5N2UzZjYyZDMzY2Qx - ZDYzNWViNmZkZDViNDUzIiwgImlkIjogImQ4NTE0MzM4LTgzOGItNDUzMC1iNDllLTI2YWYzZjc3 - YTljZiIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwg - ImFnZW50X3JvbGUiOiAie3RvcGljfSBTZW5pb3IgRGF0YSBSZXNlYXJjaGVyXG4iLCAiYWdlbnRf - a2V5IjogIjczYzM0OWM5M2MxNjNiNWQ0ZGY5OGE2NGZhYzFjNDMwIiwgInRvb2xzX25hbWVzIjog - W119LCB7ImtleSI6ICJiMTdiMTg4ZGJmMTRmOTNhOThlNWI5NWFhZDM2NzU3NyIsICJpZCI6ICI5 - Y2NmNWZhZS1kZTZkLTQwOTMtYTA0Ni04MGQ0ZDIxMzJkODciLCAiYXN5bmNfZXhlY3V0aW9uPyI6 - IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0b3BpY30gUmVw - b3J0aW5nIEFuYWx5c3RcbiIsICJhZ2VudF9rZXkiOiAiMTA0ZmUwNjU5ZTEwYjQyNmNmODhmMDI0 - ZmI1NzE1NTMiLCAidG9vbHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKABAoQG7YeGAh0HoexHn1X - jCcuCRIIHGHDZGTQCskqDFRhc2sgQ3JlYXRlZDABOdCx8ZMlVEMYQXC78pMlVEMYSi4KCGNyZXdf - a2V5EiIKIDZiYTkxMmY5MTI5ZDY4NDlhMGFjNDljZmJkMzIxZGFkSjEKB2NyZXdfaWQSJgokMTkz - N2M0YTAtYTU2Yy00NTFhLTllOWEtMzRjNzEwZDEzN2U0Si4KCHRhc2tfa2V5EiIKIDAwMTc5N2Uz - ZjYyZDMzY2QxZDYzNWViNmZkZDViNDUzSjEKB3Rhc2tfaWQSJgokZDg1MTQzMzgtODM4Yi00NTMw - LWI0OWUtMjZhZjNmNzdhOWNmSjoKEGNyZXdfZmluZ2VycHJpbnQSJgokMDAxZDVhNGEtMzljNC00 - MWQ3LTljMDEtZjcwNjI2MGEyMjc3SjoKEHRhc2tfZmluZ2VycHJpbnQSJgokMjFmZjExMDktZjQy - Zi00MzQ3LWJjMDctMDgyMWNiMWZkMGUySjsKG3Rhc2tfZmluZ2VycHJpbnRfY3JlYXRlZF9hdBIc - ChoyMDI1LTA1LTI3VDAxOjEzOjIwLjcxNzUwNEo7ChFhZ2VudF9maW5nZXJwcmludBImCiQxZjkx - ZWFjYy04MWE5LTQzNTItOTJmOC00MjI0ZWQxY2QxNTZ6AhgBhQEAAQAAEoAEChD4gkjlLO/bfICv - YmSAzNqOEgiItZ6RD3+ilioMVGFzayBDcmVhdGVkMAE52AatlCVUQxhByNmtlCVUQxhKLgoIY3Jl - d19rZXkSIgogNmJhOTEyZjkxMjlkNjg0OWEwYWM0OWNmYmQzMjFkYWRKMQoHY3Jld19pZBImCiQx - OTM3YzRhMC1hNTZjLTQ1MWEtOWU5YS0zNGM3MTBkMTM3ZTRKLgoIdGFza19rZXkSIgogMDAxNzk3 - ZTNmNjJkMzNjZDFkNjM1ZWI2ZmRkNWI0NTNKMQoHdGFza19pZBImCiRkODUxNDMzOC04MzhiLTQ1 - MzAtYjQ5ZS0yNmFmM2Y3N2E5Y2ZKOgoQY3Jld19maW5nZXJwcmludBImCiQwMDFkNWE0YS0zOWM0 - LTQxZDctOWMwMS1mNzA2MjYwYTIyNzdKOgoQdGFza19maW5nZXJwcmludBImCiQyMWZmMTEwOS1m - NDJmLTQzNDctYmMwNy0wODIxY2IxZmQwZTJKOwobdGFza19maW5nZXJwcmludF9jcmVhdGVkX2F0 - EhwKGjIwMjUtMDUtMjdUMDE6MTM6MjAuNzE3NTA0SjsKEWFnZW50X2ZpbmdlcnByaW50EiYKJDFm - OTFlYWNjLTgxYTktNDM1Mi05MmY4LTQyMjRlZDFjZDE1NnoCGAGFAQABAAASgAQKEEY073d5/h5o - rT0Tv03hpIASCKYxjrS3CDRZKgxUYXNrIENyZWF0ZWQwATnYr3aVJVRDGEGIY3eVJVRDGEouCghj - cmV3X2tleRIiCiA2YmE5MTJmOTEyOWQ2ODQ5YTBhYzQ5Y2ZiZDMyMWRhZEoxCgdjcmV3X2lkEiYK - JDE5MzdjNGEwLWE1NmMtNDUxYS05ZTlhLTM0YzcxMGQxMzdlNEouCgh0YXNrX2tleRIiCiBiMTdi - MTg4ZGJmMTRmOTNhOThlNWI5NWFhZDM2NzU3N0oxCgd0YXNrX2lkEiYKJDljY2Y1ZmFlLWRlNmQt - NDA5My1hMDQ2LTgwZDRkMjEzMmQ4N0o6ChBjcmV3X2ZpbmdlcnByaW50EiYKJDAwMWQ1YTRhLTM5 - YzQtNDFkNy05YzAxLWY3MDYyNjBhMjI3N0o6ChB0YXNrX2ZpbmdlcnByaW50EiYKJDAwZTljMDgx - LWU1ZWYtNDhjNS1iMTc0LWRlZWI2Zjk1OGE5OUo7Cht0YXNrX2ZpbmdlcnByaW50X2NyZWF0ZWRf - YXQSHAoaMjAyNS0wNS0yN1QwMToxMzoyMC43MTc1ODhKOwoRYWdlbnRfZmluZ2VycHJpbnQSJgok - ODFiN2U4MTEtYmZmMi00OGY0LWExMzQtN2RmODAxMWQwYTE3egIYAYUBAAEAABKvDQoQot+4QIkY - BzRK/SkLTbr2XBIIKFjBQjUmSxQqDENyZXcgQ3JlYXRlZDABOThCVpYlVEMYQZh/XpYlVEMYShsK - DmNyZXdhaV92ZXJzaW9uEgkKBzAuMTIxLjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoGMy4xMS43Si4K - CGNyZXdfa2V5EiIKIDZiYTkxMmY5MTI5ZDY4NDlhMGFjNDljZmJkMzIxZGFkSjEKB2NyZXdfaWQS - JgokMWExMmMyMGUtMWJiNy00OWRiLTk2MTAtMzcxMmU3YTRiNDU1ShwKDGNyZXdfcHJvY2VzcxIM - CgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9vZl90YXNrcxIC - GAJKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAko6ChBjcmV3X2ZpbmdlcnByaW50EiYKJGJj - ZDE5Nzc3LWZmNjAtNDQyNy05NzkwLTc4ODJkZmMxNjE5NEo7ChtjcmV3X2ZpbmdlcnByaW50X2Ny - ZWF0ZWRfYXQSHAoaMjAyNS0wNS0yN1QwMToxMzoyMC43NjExMDhKzwUKC2NyZXdfYWdlbnRzEr8F - CrwFW3sia2V5IjogIjczYzM0OWM5M2MxNjNiNWQ0ZGY5OGE2NGZhYzFjNDMwIiwgImlkIjogIjAy - MzI1NmU1LTRkZDctNGIxOC04ODQyLTMxYTM2MjcwNjQyYiIsICJyb2xlIjogInt0b3BpY30gU2Vu - aW9yIERhdGEgUmVzZWFyY2hlclxuIiwgInZlcmJvc2U/IjogdHJ1ZSwgIm1heF9pdGVyIjogMjUs - ICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIm9wZW5haS9tb2RlbF9u - YW1lIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2Us - ICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0 - b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiMTA0ZmUwNjU5ZTEwYjQyNmNmODhmMDI0ZmI1NzE1 - NTMiLCAiaWQiOiAiNjJiZWZmMzMtNzkyOC00ZTlkLTkxYWMtMWUzNTNmZjRhNzhkIiwgInJvbGUi - OiAie3RvcGljfSBSZXBvcnRpbmcgQW5hbHlzdFxuIiwgInZlcmJvc2U/IjogdHJ1ZSwgIm1heF9p - dGVyIjogMjUsICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIm9ubGlu - ZV9sbG0iLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxz - ZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwg - InRvb2xzX25hbWVzIjogW119XUqTBAoKY3Jld190YXNrcxKEBAqBBFt7ImtleSI6ICIwMDE3OTdl - M2Y2MmQzM2NkMWQ2MzVlYjZmZGQ1YjQ1MyIsICJpZCI6ICI4ZGM3MDM1Ni1mMDljLTQxMDYtOWZi - ZC0wNTNmYWNlYzIwOWQiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/ - IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0b3BpY30gU2VuaW9yIERhdGEgUmVzZWFyY2hlclxu - IiwgImFnZW50X2tleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIsICJ0b29s - c19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiYjE3YjE4OGRiZjE0ZjkzYTk4ZTViOTVhYWQzNjc1Nzci - LCAiaWQiOiAiOTVkODFmYjMtYTVkOC00ZDUxLThkZmMtYWIwODRmNTU5Y2RmIiwgImFzeW5jX2V4 - ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJ7 - dG9waWN9IFJlcG9ydGluZyBBbmFseXN0XG4iLCAiYWdlbnRfa2V5IjogIjEwNGZlMDY1OWUxMGI0 - MjZjZjg4ZjAyNGZiNTcxNTUzIiwgInRvb2xzX25hbWVzIjogW119XXoCGAGFAQABAAASgAQKEBu2 - HhgIdB6HsR59V4wnLgkSCBxhw2Rk0ArJKgxUYXNrIENyZWF0ZWQwATlYR3eWJVRDGEE483eWJVRD - GEouCghjcmV3X2tleRIiCiA2YmE5MTJmOTEyOWQ2ODQ5YTBhYzQ5Y2ZiZDMyMWRhZEoxCgdjcmV3 - X2lkEiYKJDFhMTJjMjBlLTFiYjctNDlkYi05NjEwLTM3MTJlN2E0YjQ1NUouCgh0YXNrX2tleRIi - CiAwMDE3OTdlM2Y2MmQzM2NkMWQ2MzVlYjZmZGQ1YjQ1M0oxCgd0YXNrX2lkEiYKJDhkYzcwMzU2 - LWYwOWMtNDEwNi05ZmJkLTA1M2ZhY2VjMjA5ZEo6ChBjcmV3X2ZpbmdlcnByaW50EiYKJGJjZDE5 - Nzc3LWZmNjAtNDQyNy05NzkwLTc4ODJkZmMxNjE5NEo6ChB0YXNrX2ZpbmdlcnByaW50EiYKJDU4 - NWVjNWI1LTY3MzUtNDU0Ny1hOGY2LWY2ZTRiMzgzMjJlNko7Cht0YXNrX2ZpbmdlcnByaW50X2Ny - ZWF0ZWRfYXQSHAoaMjAyNS0wNS0yN1QwMToxMzoyMC43NjA5OTRKOwoRYWdlbnRfZmluZ2VycHJp - bnQSJgokZjczM2I4NzQtZmIxOC00MzUxLWE0YjEtYTFjY2E5ZDgzZGM1egIYAYUBAAEAABKABAoQ - +IJI5Szv23yAr2JkgMzajhIIiLWekQ9/opYqDFRhc2sgQ3JlYXRlZDABOUhTKpclVEMYQfAxK5cl - VEMYSi4KCGNyZXdfa2V5EiIKIDZiYTkxMmY5MTI5ZDY4NDlhMGFjNDljZmJkMzIxZGFkSjEKB2Ny - ZXdfaWQSJgokMWExMmMyMGUtMWJiNy00OWRiLTk2MTAtMzcxMmU3YTRiNDU1Si4KCHRhc2tfa2V5 - EiIKIDAwMTc5N2UzZjYyZDMzY2QxZDYzNWViNmZkZDViNDUzSjEKB3Rhc2tfaWQSJgokOGRjNzAz - NTYtZjA5Yy00MTA2LTlmYmQtMDUzZmFjZWMyMDlkSjoKEGNyZXdfZmluZ2VycHJpbnQSJgokYmNk - MTk3NzctZmY2MC00NDI3LTk3OTAtNzg4MmRmYzE2MTk0SjoKEHRhc2tfZmluZ2VycHJpbnQSJgok - NTg1ZWM1YjUtNjczNS00NTQ3LWE4ZjYtZjZlNGIzODMyMmU2SjsKG3Rhc2tfZmluZ2VycHJpbnRf - Y3JlYXRlZF9hdBIcChoyMDI1LTA1LTI3VDAxOjEzOjIwLjc2MDk5NEo7ChFhZ2VudF9maW5nZXJw - cmludBImCiRmNzMzYjg3NC1mYjE4LTQzNTEtYTRiMS1hMWNjYTlkODNkYzV6AhgBhQEAAQAAEoAE - ChBGNO93ef4eaK09E79N4aSAEgimMY60twg0WSoMVGFzayBDcmVhdGVkMAE5OI7qlyVUQxhBQF3r - lyVUQxhKLgoIY3Jld19rZXkSIgogNmJhOTEyZjkxMjlkNjg0OWEwYWM0OWNmYmQzMjFkYWRKMQoH - Y3Jld19pZBImCiQxYTEyYzIwZS0xYmI3LTQ5ZGItOTYxMC0zNzEyZTdhNGI0NTVKLgoIdGFza19r - ZXkSIgogYjE3YjE4OGRiZjE0ZjkzYTk4ZTViOTVhYWQzNjc1NzdKMQoHdGFza19pZBImCiQ5NWQ4 - MWZiMy1hNWQ4LTRkNTEtOGRmYy1hYjA4NGY1NTljZGZKOgoQY3Jld19maW5nZXJwcmludBImCiRi - Y2QxOTc3Ny1mZjYwLTQ0MjctOTc5MC03ODgyZGZjMTYxOTRKOgoQdGFza19maW5nZXJwcmludBIm - CiRkZjNlNTYwNi03YjRjLTQzYTgtYTljOS0yZjRhMjEwYjI5NmRKOwobdGFza19maW5nZXJwcmlu - dF9jcmVhdGVkX2F0EhwKGjIwMjUtMDUtMjdUMDE6MTM6MjAuNzYxMDUzSjsKEWFnZW50X2Zpbmdl - cnByaW50EiYKJDU2YTAxNzA0LTJlODctNDcxMS05MjEwLWQ1MmUxNmRmZDEyNnoCGAGFAQABAAAS - rw0KEKLfuECJGAc0Sv0pC0269lwSCChYwUI1JksUKgxDcmV3IENyZWF0ZWQwATkoPACZJVRDGEGw - EwqZJVRDGEobCg5jcmV3YWlfdmVyc2lvbhIJCgcwLjEyMS4wShoKDnB5dGhvbl92ZXJzaW9uEggK - BjMuMTEuN0ouCghjcmV3X2tleRIiCiA2YmE5MTJmOTEyOWQ2ODQ5YTBhYzQ5Y2ZiZDMyMWRhZEox - CgdjcmV3X2lkEiYKJDM4NGU2ZTcwLTNlNDYtNDdjNC04NGM2LWVjOTgwYjk2OTE3MUocCgxjcmV3 - X3Byb2Nlc3MSDAoKc2VxdWVudGlhbEoRCgtjcmV3X21lbW9yeRICEABKGgoUY3Jld19udW1iZXJf - b2ZfdGFza3MSAhgCShsKFWNyZXdfbnVtYmVyX29mX2FnZW50cxICGAJKOgoQY3Jld19maW5nZXJw - cmludBImCiQxM2VjZDE5NC1mMWNkLTQ3ZGUtOWQ1Yy00M2NhZTE0YTMzNWRKOwobY3Jld19maW5n - ZXJwcmludF9jcmVhdGVkX2F0EhwKGjIwMjUtMDUtMjdUMDE6MTM6MjAuODA1ODgxSs8FCgtjcmV3 - X2FnZW50cxK/BQq8BVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs - ICJpZCI6ICI0Njg3ZmI3Zi03ZGI1LTQ4ZWUtOWIxOC1jMjJkZjBjYzY2YjAiLCAicm9sZSI6ICJ7 - dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhf - aXRlciI6IDI1LCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICJvcGVu - YWkvbW9kZWxfbmFtZSIsICJsbG0iOiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdhdGlvbl9lbmFibGVk - PyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGlt - aXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsia2V5IjogIjEwNGZlMDY1OWUxMGI0MjZjZjg4 - ZjAyNGZiNTcxNTUzIiwgImlkIjogIjAzYzM1ZGIzLWUwOWItNGJjNC04ZjZlLTI4ZjhhZWQxMmQ2 - NyIsICJyb2xlIjogInt0b3BpY30gUmVwb3J0aW5nIEFuYWx5c3RcbiIsICJ2ZXJib3NlPyI6IHRy - dWUsICJtYXhfaXRlciI6IDI1LCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xs - bSI6ICJvbmxpbmVfbGxtIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJs - ZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9s - aW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1KkwQKCmNyZXdfdGFza3MShAQKgQRbeyJrZXki - OiAiMDAxNzk3ZTNmNjJkMzNjZDFkNjM1ZWI2ZmRkNWI0NTMiLCAiaWQiOiAiNGExNGMxMTAtYTIy - Zi00Mjg4LTk0N2QtNWNkNmEwMzk2OTg3IiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1 - bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJ7dG9waWN9IFNlbmlvciBEYXRhIFJl - c2VhcmNoZXJcbiIsICJhZ2VudF9rZXkiOiAiNzNjMzQ5YzkzYzE2M2I1ZDRkZjk4YTY0ZmFjMWM0 - MzAiLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsia2V5IjogImIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1 - YWFkMzY3NTc3IiwgImlkIjogImUyZTU2OTE3LWU3OGYtNDJlMC04MGY2LTgxOTQ0YTRmZmYwNiIs - ICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50 - X3JvbGUiOiAie3RvcGljfSBSZXBvcnRpbmcgQW5hbHlzdFxuIiwgImFnZW50X2tleSI6ICIxMDRm - ZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1MyIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEA - AQAAEoAEChAbth4YCHQeh7EefVeMJy4JEggcYcNkZNAKySoMVGFzayBDcmVhdGVkMAE56KknmSVU - QxhBQDArmSVUQxhKLgoIY3Jld19rZXkSIgogNmJhOTEyZjkxMjlkNjg0OWEwYWM0OWNmYmQzMjFk - YWRKMQoHY3Jld19pZBImCiQzODRlNmU3MC0zZTQ2LTQ3YzQtODRjNi1lYzk4MGI5NjkxNzFKLgoI - dGFza19rZXkSIgogMDAxNzk3ZTNmNjJkMzNjZDFkNjM1ZWI2ZmRkNWI0NTNKMQoHdGFza19pZBIm - CiQ0YTE0YzExMC1hMjJmLTQyODgtOTQ3ZC01Y2Q2YTAzOTY5ODdKOgoQY3Jld19maW5nZXJwcmlu - dBImCiQxM2VjZDE5NC1mMWNkLTQ3ZGUtOWQ1Yy00M2NhZTE0YTMzNWRKOgoQdGFza19maW5nZXJw - cmludBImCiQ2OWYwN2MyMy0wYzgxLTRlNGYtYjNmOC1kNzJiYjE2NjA1NzFKOwobdGFza19maW5n - ZXJwcmludF9jcmVhdGVkX2F0EhwKGjIwMjUtMDUtMjdUMDE6MTM6MjAuODA1Nzc0SjsKEWFnZW50 - X2ZpbmdlcnByaW50EiYKJGIxNWUzMGQzLThmYzEtNGRlYy1hZWZiLTVhMzhiMTViMDE0ZHoCGAGF - AQABAAASgAQKEPiCSOUs79t8gK9iZIDM2o4SCIi1npEPf6KWKgxUYXNrIENyZWF0ZWQwATnQG+aZ - JVRDGEFg/uaZJVRDGEouCghjcmV3X2tleRIiCiA2YmE5MTJmOTEyOWQ2ODQ5YTBhYzQ5Y2ZiZDMy - MWRhZEoxCgdjcmV3X2lkEiYKJDM4NGU2ZTcwLTNlNDYtNDdjNC04NGM2LWVjOTgwYjk2OTE3MUou - Cgh0YXNrX2tleRIiCiAwMDE3OTdlM2Y2MmQzM2NkMWQ2MzVlYjZmZGQ1YjQ1M0oxCgd0YXNrX2lk - EiYKJDRhMTRjMTEwLWEyMmYtNDI4OC05NDdkLTVjZDZhMDM5Njk4N0o6ChBjcmV3X2ZpbmdlcnBy - aW50EiYKJDEzZWNkMTk0LWYxY2QtNDdkZS05ZDVjLTQzY2FlMTRhMzM1ZEo6ChB0YXNrX2Zpbmdl - cnByaW50EiYKJDY5ZjA3YzIzLTBjODEtNGU0Zi1iM2Y4LWQ3MmJiMTY2MDU3MUo7Cht0YXNrX2Zp - bmdlcnByaW50X2NyZWF0ZWRfYXQSHAoaMjAyNS0wNS0yN1QwMToxMzoyMC44MDU3NzRKOwoRYWdl - bnRfZmluZ2VycHJpbnQSJgokYjE1ZTMwZDMtOGZjMS00ZGVjLWFlZmItNWEzOGIxNWIwMTRkegIY - AYUBAAEAABKABAoQRjTvd3n+HmitPRO/TeGkgBIIpjGOtLcINFkqDFRhc2sgQ3JlYXRlZDABOVhP - sZolVEMYQZAWspolVEMYSi4KCGNyZXdfa2V5EiIKIDZiYTkxMmY5MTI5ZDY4NDlhMGFjNDljZmJk - MzIxZGFkSjEKB2NyZXdfaWQSJgokMzg0ZTZlNzAtM2U0Ni00N2M0LTg0YzYtZWM5ODBiOTY5MTcx - Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf - aWQSJgokZTJlNTY5MTctZTc4Zi00MmUwLTgwZjYtODE5NDRhNGZmZjA2SjoKEGNyZXdfZmluZ2Vy - cHJpbnQSJgokMTNlY2QxOTQtZjFjZC00N2RlLTlkNWMtNDNjYWUxNGEzMzVkSjoKEHRhc2tfZmlu - Z2VycHJpbnQSJgokZDRmMGE3NzItMzUwOC00OGI1LWI5OWEtZWU2ZmEzMjE5ZWMwSjsKG3Rhc2tf - ZmluZ2VycHJpbnRfY3JlYXRlZF9hdBIcChoyMDI1LTA1LTI3VDAxOjEzOjIwLjgwNTgzNEo7ChFh - Z2VudF9maW5nZXJwcmludBImCiQ2NzhjOTkxNC01NWU2LTQ0YjYtODRlMS01ZjBhODA3OWIzNzl6 - AhgBhQEAAQAAEqcNChCi37hAiRgHNEr9KQtNuvZcEggoWMFCNSZLFCoMQ3JldyBDcmVhdGVkMAE5 - UMbImyVUQxhB4PvQmyVUQxhKGwoOY3Jld2FpX3ZlcnNpb24SCQoHMC4xMjEuMEoaCg5weXRob25f - dmVyc2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogNmJhOTEyZjkxMjlkNjg0OWEwYWM0OWNm - YmQzMjFkYWRKMQoHY3Jld19pZBImCiQyNWZlM2JkNS00MmRmLTRmYmYtYTQzYi00MDlmZDUyZGMz - YjNKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNy - ZXdfbnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSjoKEGNy - ZXdfZmluZ2VycHJpbnQSJgokN2FiOGQ2NzctYjczYi00MmFkLTljOGEtNjEyYTg1N2MwZGIzSjsK - G2NyZXdfZmluZ2VycHJpbnRfY3JlYXRlZF9hdBIcChoyMDI1LTA1LTI3VDAxOjEzOjIwLjg1MjQ5 - MkrHBQoLY3Jld19hZ2VudHMStwUKtAVbeyJrZXkiOiAiNzNjMzQ5YzkzYzE2M2I1ZDRkZjk4YTY0 - ZmFjMWM0MzAiLCAiaWQiOiAiMTExNDdkNDgtMzlkMS00MzRjLTk1MmYtNzk4NmJhN2M0OGVjIiwg - InJvbGUiOiAie3RvcGljfSBTZW5pb3IgRGF0YSBSZXNlYXJjaGVyXG4iLCAidmVyYm9zZT8iOiB0 - cnVlLCAibWF4X2l0ZXIiOiAyNSwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19s - bG0iOiAibG9jYWxfbGxtIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJs - ZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9s - aW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiMTA0ZmUwNjU5ZTEwYjQyNmNm - ODhmMDI0ZmI1NzE1NTMiLCAiaWQiOiAiNmJlMTZjNWUtMDc4OS00MTQ1LWE5MmUtYWQzNzE2Mjdl - M2VhIiwgInJvbGUiOiAie3RvcGljfSBSZXBvcnRpbmcgQW5hbHlzdFxuIiwgInZlcmJvc2U/Ijog - dHJ1ZSwgIm1heF9pdGVyIjogMjUsICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdf - bGxtIjogIm9ubGluZV9sbG0iLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5h - YmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5 - X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUqTBAoKY3Jld190YXNrcxKEBAqBBFt7Imtl - eSI6ICIwMDE3OTdlM2Y2MmQzM2NkMWQ2MzVlYjZmZGQ1YjQ1MyIsICJpZCI6ICI3YjBjNjAwYy1j - MTcwLTQ1MmYtYTE1Mi05MzJiY2E0NzljNzciLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAi - aHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0b3BpY30gU2VuaW9yIERhdGEg - UmVzZWFyY2hlclxuIiwgImFnZW50X2tleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMx - YzQzMCIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiYjE3YjE4OGRiZjE0ZjkzYTk4ZTVi - OTVhYWQzNjc1NzciLCAiaWQiOiAiZTdmMWRmNzAtNjhmMS00N2FiLWI0M2QtNWRjOGVhNGNiZjM3 - IiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdl - bnRfcm9sZSI6ICJ7dG9waWN9IFJlcG9ydGluZyBBbmFseXN0XG4iLCAiYWdlbnRfa2V5IjogIjEw - NGZlMDY1OWUxMGI0MjZjZjg4ZjAyNGZiNTcxNTUzIiwgInRvb2xzX25hbWVzIjogW119XXoCGAGF - AQABAAASgAQKEBu2HhgIdB6HsR59V4wnLgkSCBxhw2Rk0ArJKgxUYXNrIENyZWF0ZWQwATmo8e+b - JVRDGEG4lfCbJVRDGEouCghjcmV3X2tleRIiCiA2YmE5MTJmOTEyOWQ2ODQ5YTBhYzQ5Y2ZiZDMy - MWRhZEoxCgdjcmV3X2lkEiYKJDI1ZmUzYmQ1LTQyZGYtNGZiZi1hNDNiLTQwOWZkNTJkYzNiM0ou - Cgh0YXNrX2tleRIiCiAwMDE3OTdlM2Y2MmQzM2NkMWQ2MzVlYjZmZGQ1YjQ1M0oxCgd0YXNrX2lk - EiYKJDdiMGM2MDBjLWMxNzAtNDUyZi1hMTUyLTkzMmJjYTQ3OWM3N0o6ChBjcmV3X2ZpbmdlcnBy - aW50EiYKJDdhYjhkNjc3LWI3M2ItNDJhZC05YzhhLTYxMmE4NTdjMGRiM0o6ChB0YXNrX2Zpbmdl - cnByaW50EiYKJDhjY2IyZWVhLThhNTMtNGY0MS05MDkxLTRiODJiZjk5NTM1MUo7Cht0YXNrX2Zp - bmdlcnByaW50X2NyZWF0ZWRfYXQSHAoaMjAyNS0wNS0yN1QwMToxMzoyMC44NTIzODhKOwoRYWdl - bnRfZmluZ2VycHJpbnQSJgokOWMxMTJkM2UtM2U2Yy00YTY0LTk5YTEtZTVlZDM4ZjZkY2EyegIY - AYUBAAEAABKABAoQ+IJI5Szv23yAr2JkgMzajhIIiLWekQ9/opYqDFRhc2sgQ3JlYXRlZDABOQjU - mJwlVEMYQaCLmZwlVEMYSi4KCGNyZXdfa2V5EiIKIDZiYTkxMmY5MTI5ZDY4NDlhMGFjNDljZmJk - MzIxZGFkSjEKB2NyZXdfaWQSJgokMjVmZTNiZDUtNDJkZi00ZmJmLWE0M2ItNDA5ZmQ1MmRjM2Iz - Si4KCHRhc2tfa2V5EiIKIDAwMTc5N2UzZjYyZDMzY2QxZDYzNWViNmZkZDViNDUzSjEKB3Rhc2tf - aWQSJgokN2IwYzYwMGMtYzE3MC00NTJmLWExNTItOTMyYmNhNDc5Yzc3SjoKEGNyZXdfZmluZ2Vy - cHJpbnQSJgokN2FiOGQ2NzctYjczYi00MmFkLTljOGEtNjEyYTg1N2MwZGIzSjoKEHRhc2tfZmlu - Z2VycHJpbnQSJgokOGNjYjJlZWEtOGE1My00ZjQxLTkwOTEtNGI4MmJmOTk1MzUxSjsKG3Rhc2tf - ZmluZ2VycHJpbnRfY3JlYXRlZF9hdBIcChoyMDI1LTA1LTI3VDAxOjEzOjIwLjg1MjM4OEo7ChFh - Z2VudF9maW5nZXJwcmludBImCiQ5YzExMmQzZS0zZTZjLTRhNjQtOTlhMS1lNWVkMzhmNmRjYTJ6 - AhgBhQEAAQAAEoAEChBGNO93ef4eaK09E79N4aSAEgimMY60twg0WSoMVGFzayBDcmVhdGVkMAE5 - iBtTnSVUQxhB8NpTnSVUQxhKLgoIY3Jld19rZXkSIgogNmJhOTEyZjkxMjlkNjg0OWEwYWM0OWNm - YmQzMjFkYWRKMQoHY3Jld19pZBImCiQyNWZlM2JkNS00MmRmLTRmYmYtYTQzYi00MDlmZDUyZGMz - YjNKLgoIdGFza19rZXkSIgogYjE3YjE4OGRiZjE0ZjkzYTk4ZTViOTVhYWQzNjc1NzdKMQoHdGFz - a19pZBImCiRlN2YxZGY3MC02OGYxLTQ3YWItYjQzZC01ZGM4ZWE0Y2JmMzdKOgoQY3Jld19maW5n - ZXJwcmludBImCiQ3YWI4ZDY3Ny1iNzNiLTQyYWQtOWM4YS02MTJhODU3YzBkYjNKOgoQdGFza19m - aW5nZXJwcmludBImCiQ3YzY4NjdiYi1hMzEwLTQ2ZDUtOTM4Mi0zMGIyZDhmN2ZmMmZKOwobdGFz - a19maW5nZXJwcmludF9jcmVhdGVkX2F0EhwKGjIwMjUtMDUtMjdUMDE6MTM6MjAuODUyNDQ1SjsK - EWFnZW50X2ZpbmdlcnByaW50EiYKJGM0YzRiNjdjLTgxYzktNDFjNS1iYzVkLTRiNjcyNDQxY2Mw - N3oCGAGFAQABAAA= + CtcMCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSrgwKEgoQY3Jld2FpLnRl + bGVtZXRyeRKUCAoQu3w5ZNCcMWutYN9ACENEihIIIWUtKzKLQXoqDENyZXcgQ3JlYXRlZDABOcjc + jv4SBEQYQWg/lv4SBEQYShsKDmNyZXdhaV92ZXJzaW9uEgkKBzAuMTIwLjFKGgoOcHl0aG9uX3Zl + cnNpb24SCAoGMy4xMi45Si4KCGNyZXdfa2V5EiIKIDY5NDY1NGEzMThmNzE5ODgzYzA2ZjhlNmQ5 + YTc1NDlmSjEKB2NyZXdfaWQSJgokMjI4NzU3NTAtYjIwMC00MTI4LWJmYjUtYTFmNTFjNDhlNDk5 + ShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3 + X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUo6ChBjcmV3 + X2ZpbmdlcnByaW50EiYKJDBhZGQxM2U2LTBhYWQtNDUyNS1iYTE0LWZhMDUzZGM2ZjE0ZUo7Chtj + cmV3X2ZpbmdlcnByaW50X2NyZWF0ZWRfYXQSHAoaMjAyNS0wNS0yOVQxMDo1NzoxNC45NTE4MTlK + zAIKC2NyZXdfYWdlbnRzErwCCrkCW3sia2V5IjogIjU1ODY5YmNiMTYzMjNlNzEyOWQyNTIzNjJj + ODU1ZGE2IiwgImlkIjogIjJiY2UyZTE0LWIyN2UtNDM1MC1iZmIyLWE1YTNkMTRmYTJhMCIsICJy + b2xlIjogIlNheSBIaSIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyNSwgIm1heF9y + cG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJ0ZXN0LW1vZGVs + IiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6 + IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUr7AQoKY3Jl + d190YXNrcxLsAQrpAVt7ImtleSI6ICJkZTI5NDBmMDZhZDhhNDE2YzI4Y2MwZjI2MTBmMTgwYiIs + ICJpZCI6ICJiM2MyMzNkZC1kNDk2LTQ1YjQtYWFkMy1kYzYyZGI3ZjJiZWEiLCAiYXN5bmNfZXhl + Y3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogIlNh + eSBIaSIsICJhZ2VudF9rZXkiOiAiNTU4NjliY2IxNjMyM2U3MTI5ZDI1MjM2MmM4NTVkYTYiLCAi + dG9vbHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKABAoQaW1V2ASOUN5hjxpKH5WT+BIIe6lsRrYF + 84MqDFRhc2sgQ3JlYXRlZDABOfA/rv4SBEQYQSC1rv4SBEQYSi4KCGNyZXdfa2V5EiIKIDY5NDY1 + NGEzMThmNzE5ODgzYzA2ZjhlNmQ5YTc1NDlmSjEKB2NyZXdfaWQSJgokMjI4NzU3NTAtYjIwMC00 + MTI4LWJmYjUtYTFmNTFjNDhlNDk5Si4KCHRhc2tfa2V5EiIKIGRlMjk0MGYwNmFkOGE0MTZjMjhj + YzBmMjYxMGYxODBiSjEKB3Rhc2tfaWQSJgokYjNjMjMzZGQtZDQ5Ni00NWI0LWFhZDMtZGM2MmRi + N2YyYmVhSjoKEGNyZXdfZmluZ2VycHJpbnQSJgokMGFkZDEzZTYtMGFhZC00NTI1LWJhMTQtZmEw + NTNkYzZmMTRlSjoKEHRhc2tfZmluZ2VycHJpbnQSJgokZGVlNDA1YjgtMTkxNC00N2NkLTlkMTgt + ZTdmZDA0NjFkOGE4SjsKG3Rhc2tfZmluZ2VycHJpbnRfY3JlYXRlZF9hdBIcChoyMDI1LTA1LTI5 + VDEwOjU3OjE0Ljk1MTc4M0o7ChFhZ2VudF9maW5nZXJwcmludBImCiRiNWQ0NGNlMS00NGRjLTQ0 + YzYtYTU1YS0xODZhM2QxZmU2YjJ6AhgBhQEAAQAA headers: Accept: - '*/*' @@ -567,7 +241,7 @@ interactions: Connection: - keep-alive Content-Length: - - '20189' + - '1626' Content-Type: - application/x-protobuf User-Agent: @@ -583,7 +257,7 @@ interactions: Content-Type: - application/x-protobuf Date: - - Tue, 27 May 2025 08:13:25 GMT + - Thu, 29 May 2025 13:57:17 GMT status: code: 200 message: OK diff --git a/tests/llm_test.py b/tests/llm_test.py index f80637c60..36fc2dfcb 100644 --- a/tests/llm_test.py +++ b/tests/llm_test.py @@ -2,7 +2,6 @@ import os from time import sleep from unittest.mock import MagicMock, patch -import litellm import pytest from pydantic import BaseModel @@ -11,7 +10,11 @@ from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, LLM from crewai.utilities.events import ( LLMCallCompletedEvent, LLMStreamChunkEvent, + ToolUsageStartedEvent, + ToolUsageFinishedEvent, + ToolUsageErrorEvent, ) + from crewai.utilities.token_counter_callback import TokenCalcHandler @@ -222,7 +225,7 @@ def test_get_custom_llm_provider_gemini(): def test_get_custom_llm_provider_openai(): llm = LLM(model="gpt-4") - assert llm._get_custom_llm_provider() == None + assert llm._get_custom_llm_provider() is None def test_validate_call_params_supported(): @@ -511,12 +514,18 @@ def assert_event_count( expected_completed_tool_call: int = 0, expected_stream_chunk: int = 0, expected_completed_llm_call: int = 0, + expected_tool_usage_started: int = 0, + expected_tool_usage_finished: int = 0, + expected_tool_usage_error: int = 0, expected_final_chunk_result: str = "", ): event_count = { "completed_tool_call": 0, "stream_chunk": 0, "completed_llm_call": 0, + "tool_usage_started": 0, + "tool_usage_finished": 0, + "tool_usage_error": 0, } final_chunk_result = "" for _call in mock_emit.call_args_list: @@ -535,12 +544,21 @@ def assert_event_count( and event.call_type.value == "llm_call" ): event_count["completed_llm_call"] += 1 + elif isinstance(event, ToolUsageStartedEvent): + event_count["tool_usage_started"] += 1 + elif isinstance(event, ToolUsageFinishedEvent): + event_count["tool_usage_finished"] += 1 + elif isinstance(event, ToolUsageErrorEvent): + event_count["tool_usage_error"] += 1 else: continue assert event_count["completed_tool_call"] == expected_completed_tool_call assert event_count["stream_chunk"] == expected_stream_chunk assert event_count["completed_llm_call"] == expected_completed_llm_call + assert event_count["tool_usage_started"] == expected_tool_usage_started + assert event_count["tool_usage_finished"] == expected_tool_usage_finished + assert event_count["tool_usage_error"] == expected_tool_usage_error assert final_chunk_result == expected_final_chunk_result @@ -574,6 +592,34 @@ def test_handle_streaming_tool_calls(get_weather_tool_schema, mock_emit): expected_completed_tool_call=1, expected_stream_chunk=10, expected_completed_llm_call=1, + expected_tool_usage_started=1, + expected_tool_usage_finished=1, + expected_final_chunk_result=expected_final_chunk_result, + ) + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_handle_streaming_tool_calls_with_error(get_weather_tool_schema, mock_emit): + def get_weather_error(location): + raise Exception("Error") + + llm = LLM(model="openai/gpt-4o", stream=True) + response = llm.call( + messages=[ + {"role": "user", "content": "What is the weather in New York?"}, + ], + tools=[get_weather_tool_schema], + available_functions={ + "get_weather": get_weather_error + }, + ) + assert response == "" + expected_final_chunk_result = '{"location":"New York, NY"}' + assert_event_count( + mock_emit=mock_emit, + expected_stream_chunk=9, + expected_completed_llm_call=1, + expected_tool_usage_started=1, + expected_tool_usage_error=1, expected_final_chunk_result=expected_final_chunk_result, )