feat: log usage tools when called by LLM (#2916)

* feat: log usage tools when called by LLM

* feat: print llm tool usage in console
This commit is contained in:
Lucas Gomide
2025-05-29 15:34:34 -03:00
committed by João Moura
parent 4a13f2f7b1
commit b78671ed40
7 changed files with 335 additions and 378 deletions

View File

@@ -5,7 +5,7 @@ import sys
import threading import threading
import warnings import warnings
from collections import defaultdict from collections import defaultdict
from contextlib import contextmanager, redirect_stderr, redirect_stdout from contextlib import contextmanager
from typing import ( from typing import (
Any, Any,
DefaultDict, DefaultDict,
@@ -18,7 +18,7 @@ from typing import (
Union, Union,
cast, cast,
) )
from datetime import datetime
from dotenv import load_dotenv from dotenv import load_dotenv
from litellm.types.utils import ChatCompletionDeltaToolCall from litellm.types.utils import ChatCompletionDeltaToolCall
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -30,6 +30,11 @@ from crewai.utilities.events.llm_events import (
LLMCallType, LLMCallType,
LLMStreamChunkEvent, LLMStreamChunkEvent,
) )
from crewai.utilities.events.tool_usage_events import (
ToolUsageStartedEvent,
ToolUsageFinishedEvent,
ToolUsageErrorEvent,
)
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning) warnings.simplefilter("ignore", UserWarning)
@@ -833,7 +838,26 @@ class LLM(BaseLLM):
fn = available_functions[function_name] fn = available_functions[function_name]
# --- 3.2) Execute function # --- 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) 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 # --- 3.3) Emit success event
self._handle_emit_call_events(result, LLMCallType.TOOL_CALL) self._handle_emit_call_events(result, LLMCallType.TOOL_CALL)
@@ -849,6 +873,14 @@ class LLM(BaseLLM):
self, self,
event=LLMCallFailedEvent(error=f"Tool execution error: {str(e)}"), 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 return None
def call( def call(

View File

@@ -2,7 +2,7 @@ from io import StringIO
from typing import Any, Dict from typing import Any, Dict
from pydantic import Field, PrivateAttr from pydantic import Field, PrivateAttr
from crewai.llm import LLM
from crewai.task import Task from crewai.task import Task
from crewai.telemetry.telemetry import Telemetry from crewai.telemetry.telemetry import Telemetry
from crewai.utilities import Logger from crewai.utilities import Logger
@@ -286,27 +286,43 @@ class EventListener(BaseEventListener):
@crewai_event_bus.on(ToolUsageStartedEvent) @crewai_event_bus.on(ToolUsageStartedEvent)
def on_tool_usage_started(source, event: ToolUsageStartedEvent): def on_tool_usage_started(source, event: ToolUsageStartedEvent):
self.formatter.handle_tool_usage_started( if isinstance(source, LLM):
self.formatter.current_agent_branch, self.formatter.handle_llm_tool_usage_started(
event.tool_name, event.tool_name,
)
else:
self.formatter.handle_tool_usage_started(
self.formatter.current_agent_branch,
event.tool_name,
self.formatter.current_crew_tree, self.formatter.current_crew_tree,
) )
@crewai_event_bus.on(ToolUsageFinishedEvent) @crewai_event_bus.on(ToolUsageFinishedEvent)
def on_tool_usage_finished(source, event: ToolUsageFinishedEvent): def on_tool_usage_finished(source, event: ToolUsageFinishedEvent):
self.formatter.handle_tool_usage_finished( if isinstance(source, LLM):
self.formatter.current_tool_branch, self.formatter.handle_llm_tool_usage_finished(
event.tool_name, event.tool_name,
self.formatter.current_crew_tree, )
) 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) @crewai_event_bus.on(ToolUsageErrorEvent)
def on_tool_usage_error(source, event: ToolUsageErrorEvent): def on_tool_usage_error(source, event: ToolUsageErrorEvent):
self.formatter.handle_tool_usage_error( if isinstance(source, LLM):
self.formatter.current_tool_branch, self.formatter.handle_llm_tool_usage_error(
event.tool_name, event.tool_name,
event.error, event.error,
self.formatter.current_crew_tree, )
else:
self.formatter.handle_tool_usage_error(
self.formatter.current_tool_branch,
event.tool_name,
event.error,
self.formatter.current_crew_tree,
) )
# ----------- LLM EVENTS ----------- # ----------- LLM EVENTS -----------

View File

@@ -7,11 +7,11 @@ from .base_events import BaseEvent
class ToolUsageEvent(BaseEvent): class ToolUsageEvent(BaseEvent):
"""Base event for tool usage tracking""" """Base event for tool usage tracking"""
agent_key: str agent_key: Optional[str] = None
agent_role: str agent_role: Optional[str] = None
tool_name: str tool_name: str
tool_args: Dict[str, Any] | str tool_args: Dict[str, Any] | str
tool_class: str tool_class: Optional[str] = None
run_attempts: int | None = None run_attempts: int | None = None
delegations: int | None = None delegations: int | None = None
agent: Optional[Any] = None agent: Optional[Any] = None

View File

@@ -26,6 +26,7 @@ class ConsoleFormatter:
_spinner_thread: Optional[threading.Thread] = None _spinner_thread: Optional[threading.Thread] = None
_stop_spinner_event: Optional[threading.Event] = None _stop_spinner_event: Optional[threading.Event] = None
_spinner_running: bool = False _spinner_running: bool = False
current_llm_tool_tree: Optional[Tree] = None
def __init__(self, verbose: bool = False): def __init__(self, verbose: bool = False):
self.console = Console(width=None) self.console = Console(width=None)
@@ -469,6 +470,51 @@ class ConsoleFormatter:
self.print() self.print()
return method_branch 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( def handle_tool_usage_started(
self, self,
agent_branch: Optional[Tree], agent_branch: Optional[Tree],

View File

@@ -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

View File

@@ -204,361 +204,35 @@ interactions:
status_code: 200 status_code: 200
- request: - request:
body: !!binary | body: !!binary |
CtmdAQokCiIKDHNlcnZpY2UubmFtZRISChBjcmV3QUktdGVsZW1ldHJ5Eq+dAQoSChBjcmV3YWku CtcMCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSrgwKEgoQY3Jld2FpLnRl
dGVsZW1ldHJ5EmMKEKLfuECJGAc0Sv0pC0269lwSCChYwUI1JksUKg1GbG93IENyZWF0aW9uMAE5 bGVtZXRyeRKUCAoQu3w5ZNCcMWutYN9ACENEihIIIWUtKzKLQXoqDENyZXcgQ3JlYXRlZDABOcjc
kMTMfCVUQxhBWPfMfCVUQxhKGAoJZmxvd19uYW1lEgsKCVN0YXRlRmxvd3oCGAGFAQABAAASigEK jv4SBEQYQWg/lv4SBEQYShsKDmNyZXdhaV92ZXJzaW9uEgkKBzAuMTIwLjFKGgoOcHl0aG9uX3Zl
EBu2HhgIdB6HsR59V4wnLgkSCBxhw2Rk0ArJKg5GbG93IEV4ZWN1dGlvbjABOTgH53wlVEMYQbBw cnNpb24SCAoGMy4xMi45Si4KCGNyZXdfa2V5EiIKIDY5NDY1NGEzMThmNzE5ODgzYzA2ZjhlNmQ5
53wlVEMYShgKCWZsb3dfbmFtZRILCglTdGF0ZUZsb3dKJAoKbm9kZV9uYW1lcxIWChRbInN0ZXBf YTc1NDlmSjEKB2NyZXdfaWQSJgokMjI4NzU3NTAtYjIwMC00MTI4LWJmYjUtYTFmNTFjNDhlNDk5
MSIsICJzdGVwXzIiXXoCGAGFAQABAAASbAoQot+4QIkYBzRK/SkLTbr2XBIIKFjBQjUmSxQqDUZs ShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3
b3cgQ3JlYXRpb24wATkYyCJ9JVRDGEHI/iJ9JVRDGEohCglmbG93X25hbWUSFAoSVVVJRFN0cnVj X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUo6ChBjcmV3
dHVyZWRGbG93egIYAYUBAAEAABKgAQoQG7YeGAh0HoexHn1XjCcuCRIIHGHDZGTQCskqDkZsb3cg X2ZpbmdlcnByaW50EiYKJDBhZGQxM2U2LTBhYWQtNDUyNS1iYTE0LWZhMDUzZGM2ZjE0ZUo7Chtj
RXhlY3V0aW9uMAE5EI0zfSVUQxhBqMczfSVUQxhKIQoJZmxvd19uYW1lEhQKElVVSURTdHJ1Y3R1 cmV3X2ZpbmdlcnByaW50X2NyZWF0ZWRfYXQSHAoaMjAyNS0wNS0yOVQxMDo1NzoxNC45NTE4MTlK
cmVkRmxvd0oxCgpub2RlX25hbWVzEiMKIVsiZmlyc3RfbWV0aG9kIiwgInNlY29uZF9tZXRob2Qi zAIKC2NyZXdfYWdlbnRzErwCCrkCW3sia2V5IjogIjU1ODY5YmNiMTYzMjNlNzEyOWQyNTIzNjJj
XXoCGAGFAQABAAASaQoQot+4QIkYBzRK/SkLTbr2XBIIKFjBQjUmSxQqDUZsb3cgQ3JlYXRpb24w ODU1ZGE2IiwgImlkIjogIjJiY2UyZTE0LWIyN2UtNDM1MC1iZmIyLWE1YTNkMTRmYTJhMCIsICJy
ATlwqmp9JVRDGEGA0Wp9JVRDGEoeCglmbG93X25hbWUSEQoPUmVzdGFydGFibGVGbG93egIYAYUB b2xlIjogIlNheSBIaSIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyNSwgIm1heF9y
AAEAABKQAQoQG7YeGAh0HoexHn1XjCcuCRIIHGHDZGTQCskqDkZsb3cgRXhlY3V0aW9uMAE56Cp8 cG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJ0ZXN0LW1vZGVs
fSVUQxhB8Hx8fSVUQxhKHgoJZmxvd19uYW1lEhEKD1Jlc3RhcnRhYmxlRmxvd0okCgpub2RlX25h IiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6
bWVzEhYKFFsic3RlcF8xIiwgInN0ZXBfMiJdegIYAYUBAAEAABKQAQoQdFTCtCIDudZwchZzoNOX IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUr7AQoKY3Jl
RxIIhkf3XlJigkQqDkZsb3cgRXhlY3V0aW9uMAE5IKSJfSVUQxhBGM+JfSVUQxhKHgoJZmxvd19u d190YXNrcxLsAQrpAVt7ImtleSI6ICJkZTI5NDBmMDZhZDhhNDE2YzI4Y2MwZjI2MTBmMTgwYiIs
YW1lEhEKD1Jlc3RhcnRhYmxlRmxvd0okCgpub2RlX25hbWVzEhYKFFsic3RlcF8xIiwgInN0ZXBf ICJpZCI6ICJiM2MyMzNkZC1kNDk2LTQ1YjQtYWFkMy1kYzYyZGI3ZjJiZWEiLCAiYXN5bmNfZXhl
MiJdegIYAYUBAAEAABJnChCi37hAiRgHNEr9KQtNuvZcEggoWMFCNSZLFCoNRmxvdyBDcmVhdGlv Y3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogIlNh
bjABObi/o30lVEMYQSjXo30lVEMYShwKCWZsb3dfbmFtZRIPCg1TdGF0ZWxlc3NGbG93egIYAYUB eSBIaSIsICJhZ2VudF9rZXkiOiAiNTU4NjliY2IxNjMyM2U3MTI5ZDI1MjM2MmM4NTVkYTYiLCAi
AAEAABKNAQoQG7YeGAh0HoexHn1XjCcuCRIIHGHDZGTQCskqDkZsb3cgRXhlY3V0aW9uMAE5+Iqx dG9vbHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKABAoQaW1V2ASOUN5hjxpKH5WT+BIIe6lsRrYF
fSVUQxhB8LWxfSVUQxhKHAoJZmxvd19uYW1lEg8KDVN0YXRlbGVzc0Zsb3dKIwoKbm9kZV9uYW1l 84MqDFRhc2sgQ3JlYXRlZDABOfA/rv4SBEQYQSC1rv4SBEQYSi4KCGNyZXdfa2V5EiIKIDY5NDY1
cxIVChNbImluaXQiLCAicHJvY2VzcyJdegIYAYUBAAEAABJjChCi37hAiRgHNEr9KQtNuvZcEggo NGEzMThmNzE5ODgzYzA2ZjhlNmQ5YTc1NDlmSjEKB2NyZXdfaWQSJgokMjI4NzU3NTAtYjIwMC00
WMFCNSZLFCoNRmxvdyBDcmVhdGlvbjABOVDa0n0lVEMYQZD50n0lVEMYShgKCWZsb3dfbmFtZRIL MTI4LWJmYjUtYTFmNTFjNDhlNDk5Si4KCHRhc2tfa2V5EiIKIGRlMjk0MGYwNmFkOGE0MTZjMjhj
CglBc3luY0Zsb3d6AhgBhQEAAQAAEooBChAbth4YCHQeh7EefVeMJy4JEggcYcNkZNAKySoORmxv YzBmMjYxMGYxODBiSjEKB3Rhc2tfaWQSJgokYjNjMjMzZGQtZDQ5Ni00NWI0LWFhZDMtZGM2MmRi
dyBFeGVjdXRpb24wATkQW+J9JVRDGEGoleJ9JVRDGEoYCglmbG93X25hbWUSCwoJQXN5bmNGbG93 N2YyYmVhSjoKEGNyZXdfZmluZ2VycHJpbnQSJgokMGFkZDEzZTYtMGFhZC00NTI1LWJhMTQtZmEw
SiQKCm5vZGVfbmFtZXMSFgoUWyJzdGVwXzEiLCAic3RlcF8yIl16AhgBhQEAAQAAEmQKEKLfuECJ NTNkYzZmMTRlSjoKEHRhc2tfZmluZ2VycHJpbnQSJgokZGVlNDA1YjgtMTkxNC00N2NkLTlkMTgt
GAc0Sv0pC0269lwSCChYwUI1JksUKg1GbG93IENyZWF0aW9uMAE56DtAiiVUQxhBeKFAiiVUQxhK ZTdmZDA0NjFkOGE4SjsKG3Rhc2tfZmluZ2VycHJpbnRfY3JlYXRlZF9hdBIcChoyMDI1LTA1LTI5
GQoJZmxvd19uYW1lEgwKClNpbXBsZUZsb3d6AhgBhQEAAQAAEosBChAbth4YCHQeh7EefVeMJy4J VDEwOjU3OjE0Ljk1MTc4M0o7ChFhZ2VudF9maW5nZXJwcmludBImCiRiNWQ0NGNlMS00NGRjLTQ0
EggcYcNkZNAKySoORmxvdyBFeGVjdXRpb24wATnI5WqKJVRDGEHAjWuKJVRDGEoZCglmbG93X25h YzYtYTU1YS0xODZhM2QxZmU2YjJ6AhgBhQEAAQAA
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=
headers: headers:
Accept: Accept:
- '*/*' - '*/*'
@@ -567,7 +241,7 @@ interactions:
Connection: Connection:
- keep-alive - keep-alive
Content-Length: Content-Length:
- '20189' - '1626'
Content-Type: Content-Type:
- application/x-protobuf - application/x-protobuf
User-Agent: User-Agent:
@@ -583,7 +257,7 @@ interactions:
Content-Type: Content-Type:
- application/x-protobuf - application/x-protobuf
Date: Date:
- Tue, 27 May 2025 08:13:25 GMT - Thu, 29 May 2025 13:57:17 GMT
status: status:
code: 200 code: 200
message: OK message: OK

View File

@@ -2,7 +2,6 @@ import os
from time import sleep from time import sleep
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import litellm
import pytest import pytest
from pydantic import BaseModel from pydantic import BaseModel
@@ -11,7 +10,11 @@ from crewai.llm import CONTEXT_WINDOW_USAGE_RATIO, LLM
from crewai.utilities.events import ( from crewai.utilities.events import (
LLMCallCompletedEvent, LLMCallCompletedEvent,
LLMStreamChunkEvent, LLMStreamChunkEvent,
ToolUsageStartedEvent,
ToolUsageFinishedEvent,
ToolUsageErrorEvent,
) )
from crewai.utilities.token_counter_callback import TokenCalcHandler 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(): def test_get_custom_llm_provider_openai():
llm = LLM(model="gpt-4") 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(): def test_validate_call_params_supported():
@@ -511,12 +514,18 @@ def assert_event_count(
expected_completed_tool_call: int = 0, expected_completed_tool_call: int = 0,
expected_stream_chunk: int = 0, expected_stream_chunk: int = 0,
expected_completed_llm_call: 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 = "", expected_final_chunk_result: str = "",
): ):
event_count = { event_count = {
"completed_tool_call": 0, "completed_tool_call": 0,
"stream_chunk": 0, "stream_chunk": 0,
"completed_llm_call": 0, "completed_llm_call": 0,
"tool_usage_started": 0,
"tool_usage_finished": 0,
"tool_usage_error": 0,
} }
final_chunk_result = "" final_chunk_result = ""
for _call in mock_emit.call_args_list: for _call in mock_emit.call_args_list:
@@ -535,12 +544,21 @@ def assert_event_count(
and event.call_type.value == "llm_call" and event.call_type.value == "llm_call"
): ):
event_count["completed_llm_call"] += 1 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: else:
continue continue
assert event_count["completed_tool_call"] == expected_completed_tool_call assert event_count["completed_tool_call"] == expected_completed_tool_call
assert event_count["stream_chunk"] == expected_stream_chunk assert event_count["stream_chunk"] == expected_stream_chunk
assert event_count["completed_llm_call"] == expected_completed_llm_call 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 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_completed_tool_call=1,
expected_stream_chunk=10, expected_stream_chunk=10,
expected_completed_llm_call=1, 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, expected_final_chunk_result=expected_final_chunk_result,
) )