From 738fa46b204d6134ca2182a590184df37363bdcf Mon Sep 17 00:00:00 2001 From: Georgiy Tarasov Date: Tue, 17 Dec 2024 17:27:57 +0100 Subject: [PATCH] feat(product-assistant): a human in the loop for the taxonomy agent (#26767) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Michael Matloka --- ee/api/conversation.py | 69 ++++ ee/api/test/test_conversation.py | 157 +++++++++ ee/hogai/assistant.py | 228 ++++++++----- ee/hogai/django_checkpoint/__init__.py | 0 ee/hogai/django_checkpoint/checkpointer.py | 309 +++++++++++++++++ .../test/test_checkpointer.py | 274 +++++++++++++++ .../eval/tests/test_eval_funnel_generator.py | 13 +- .../eval/tests/test_eval_funnel_planner.py | 9 +- ee/hogai/eval/tests/test_eval_router.py | 11 +- .../eval/tests/test_eval_trends_generator.py | 13 +- .../eval/tests/test_eval_trends_planner.py | 9 +- ee/hogai/eval/utils.py | 14 +- ee/hogai/funnels/nodes.py | 8 +- ee/hogai/funnels/prompts.py | 2 + ee/hogai/funnels/test/test_nodes.py | 15 +- ee/hogai/funnels/toolkit.py | 2 +- ee/hogai/graph.py | 19 +- ee/hogai/router/nodes.py | 12 +- ee/hogai/router/test/test_nodes.py | 30 +- ee/hogai/schema_generator/nodes.py | 154 +++++---- ee/hogai/schema_generator/test/test_nodes.py | 315 ++++++++++-------- ee/hogai/summarizer/nodes.py | 27 +- ee/hogai/summarizer/test/test_nodes.py | 130 ++++---- ee/hogai/taxonomy_agent/nodes.py | 96 ++++-- ee/hogai/taxonomy_agent/prompts.py | 9 + ee/hogai/taxonomy_agent/test/test_nodes.py | 164 +++++---- ee/hogai/taxonomy_agent/toolkit.py | 11 + ee/hogai/test/test_assistant.py | 230 +++++++++++-- ee/hogai/test/test_utils.py | 74 ++-- ee/hogai/trends/nodes.py | 8 +- ee/hogai/trends/prompts.py | 2 + ee/hogai/trends/test/test_nodes.py | 18 +- ee/hogai/trends/toolkit.py | 6 +- ee/hogai/utils.py | 117 ------- ee/hogai/utils/__init__.py | 0 ee/hogai/utils/helpers.py | 79 +++++ ee/hogai/utils/nodes.py | 18 + ee/hogai/utils/state.py | 70 ++++ ee/hogai/utils/types.py | 52 +++ ...rsation_conversationcheckpoint_and_more.py | 147 ++++++++ ee/migrations/max_migration.txt | 2 +- ee/models/__init__.py | 5 + ee/models/assistant.py | 83 +++++ ee/urls.py | 18 +- ...app-max-ai--empty-thread-loading--dark.png | Bin 12608 -> 11803 bytes ...pp-max-ai--empty-thread-loading--light.png | Bin 12744 -> 11738 bytes ...ax-ai--generation-failure-thread--dark.png | Bin 16382 -> 16703 bytes ...x-ai--generation-failure-thread--light.png | Bin 16210 -> 16411 bytes .../scenes-app-max-ai--thread--dark.png | Bin 15943 -> 16224 bytes .../scenes-app-max-ai--thread--light.png | Bin 16015 -> 16240 bytes ...p-max-ai--thread-with-rate-limit--dark.png | Bin 13009 -> 13299 bytes ...-max-ai--thread-with-rate-limit--light.png | Bin 13106 -> 13345 bytes ...-ai--welcome-loading-suggestions--dark.png | Bin 19765 -> 20141 bytes ...ai--welcome-loading-suggestions--light.png | Bin 20071 -> 20284 bytes frontend/src/lib/api.ts | 16 +- frontend/src/queries/schema.json | 53 +-- frontend/src/queries/schema.ts | 30 +- frontend/src/scenes/max/Intro.tsx | 7 +- frontend/src/scenes/max/Max.stories.tsx | 59 ++-- frontend/src/scenes/max/Max.tsx | 6 +- frontend/src/scenes/max/Thread.tsx | 8 +- .../max/__mocks__/chatResponse.mocks.ts | 18 +- .../scenes/max/__mocks__/failureMessage.json | 2 +- .../scenes/max/__mocks__/summaryMessage.json | 2 +- .../max/__mocks__/visualizationMessage.json | 2 +- frontend/src/scenes/max/maxLogic.ts | 53 ++- frontend/src/types.ts | 7 + posthog/api/query.py | 23 +- .../api/test/__snapshots__/test_api_docs.ambr | 1 - posthog/schema.py | 28 +- requirements-dev.txt | 8 +- requirements.in | 11 +- requirements.txt | 20 +- 73 files changed, 2506 insertions(+), 877 deletions(-) create mode 100644 ee/api/conversation.py create mode 100644 ee/api/test/test_conversation.py create mode 100644 ee/hogai/django_checkpoint/__init__.py create mode 100644 ee/hogai/django_checkpoint/checkpointer.py create mode 100644 ee/hogai/django_checkpoint/test/test_checkpointer.py delete mode 100644 ee/hogai/utils.py create mode 100644 ee/hogai/utils/__init__.py create mode 100644 ee/hogai/utils/helpers.py create mode 100644 ee/hogai/utils/nodes.py create mode 100644 ee/hogai/utils/state.py create mode 100644 ee/hogai/utils/types.py create mode 100644 ee/migrations/0018_conversation_conversationcheckpoint_and_more.py create mode 100644 ee/models/assistant.py diff --git a/ee/api/conversation.py b/ee/api/conversation.py new file mode 100644 index 0000000000000..70e314b94039f --- /dev/null +++ b/ee/api/conversation.py @@ -0,0 +1,69 @@ +from typing import cast + +from django.http import StreamingHttpResponse +from pydantic import ValidationError +from rest_framework import serializers +from rest_framework.renderers import BaseRenderer +from rest_framework.request import Request +from rest_framework.viewsets import GenericViewSet + +from ee.hogai.assistant import Assistant +from ee.models.assistant import Conversation +from posthog.api.routing import TeamAndOrgViewSetMixin +from posthog.models.user import User +from posthog.rate_limit import AIBurstRateThrottle, AISustainedRateThrottle +from posthog.schema import HumanMessage + + +class MessageSerializer(serializers.Serializer): + content = serializers.CharField(required=True, max_length=1000) + conversation = serializers.UUIDField(required=False) + + def validate(self, data): + try: + message = HumanMessage(content=data["content"]) + data["message"] = message + except ValidationError: + raise serializers.ValidationError("Invalid message content.") + return data + + +class ServerSentEventRenderer(BaseRenderer): + media_type = "text/event-stream" + format = "txt" + + def render(self, data, accepted_media_type=None, renderer_context=None): + return data + + +class ConversationViewSet(TeamAndOrgViewSetMixin, GenericViewSet): + scope_object = "INTERNAL" + serializer_class = MessageSerializer + renderer_classes = [ServerSentEventRenderer] + queryset = Conversation.objects.all() + lookup_url_kwarg = "conversation" + + def safely_get_queryset(self, queryset): + # Only allow access to conversations created by the current user + return queryset.filter(user=self.request.user) + + def get_throttles(self): + return [AIBurstRateThrottle(), AISustainedRateThrottle()] + + def create(self, request: Request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + conversation_id = serializer.validated_data.get("conversation") + if conversation_id: + self.kwargs[self.lookup_url_kwarg] = conversation_id + conversation = self.get_object() + else: + conversation = self.get_queryset().create(user=request.user, team=self.team) + assistant = Assistant( + self.team, + conversation, + serializer.validated_data["message"], + user=cast(User, request.user), + is_new_conversation=not conversation_id, + ) + return StreamingHttpResponse(assistant.stream(), content_type=ServerSentEventRenderer.media_type) diff --git a/ee/api/test/test_conversation.py b/ee/api/test/test_conversation.py new file mode 100644 index 0000000000000..6eb466876dc01 --- /dev/null +++ b/ee/api/test/test_conversation.py @@ -0,0 +1,157 @@ +from unittest.mock import patch + +from rest_framework import status + +from ee.hogai.assistant import Assistant +from ee.models.assistant import Conversation +from posthog.models.team.team import Team +from posthog.models.user import User +from posthog.test.base import APIBaseTest + + +class TestConversation(APIBaseTest): + def setUp(self): + super().setUp() + self.other_team = Team.objects.create(organization=self.organization, name="other team") + self.other_user = User.objects.create_and_join( + organization=self.organization, + email="other@posthog.com", + password="password", + first_name="Other", + ) + + def _get_streaming_content(self, response): + return b"".join(response.streaming_content) + + def test_create_conversation(self): + with patch.object(Assistant, "_stream", return_value=["test response"]) as stream_mock: + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + {"content": "test query"}, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self._get_streaming_content(response), b"test response") + self.assertEqual(Conversation.objects.count(), 1) + conversation: Conversation = Conversation.objects.first() + self.assertEqual(conversation.user, self.user) + self.assertEqual(conversation.team, self.team) + stream_mock.assert_called_once() + + def test_add_message_to_existing_conversation(self): + with patch.object(Assistant, "_stream", return_value=["test response"]) as stream_mock: + conversation = Conversation.objects.create(user=self.user, team=self.team) + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + { + "conversation": str(conversation.id), + "content": "test query", + }, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self._get_streaming_content(response), b"test response") + self.assertEqual(Conversation.objects.count(), 1) + stream_mock.assert_called_once() + + def test_cant_access_other_users_conversation(self): + conversation = Conversation.objects.create(user=self.other_user, team=self.team) + + self.client.force_login(self.user) + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + {"conversation": conversation.id, "content": "test query"}, + ) + + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_cant_access_other_teams_conversation(self): + conversation = Conversation.objects.create(user=self.user, team=self.other_team) + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + {"conversation": conversation.id, "content": "test query"}, + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_invalid_message_format(self): + response = self.client.post("/api/environments/@current/conversations/") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_rate_limit_burst(self): + # Create multiple requests to trigger burst rate limit + with patch.object(Assistant, "_stream", return_value=["test response"]): + for _ in range(11): # Assuming burst limit is less than this + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + {"content": "test query"}, + ) + self.assertEqual(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS) + + def test_empty_content(self): + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + {"content": ""}, + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_content_too_long(self): + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + {"content": "x" * 1001}, # Very long message + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_invalid_conversation_id(self): + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + { + "conversation": "not-a-valid-uuid", + "content": "test query", + }, + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_nonexistent_conversation(self): + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + { + "conversation": "12345678-1234-5678-1234-567812345678", + "content": "test query", + }, + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_deleted_conversation(self): + # Create and then delete a conversation + conversation = Conversation.objects.create(user=self.user, team=self.team) + conversation_id = conversation.id + conversation.delete() + + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + { + "conversation": str(conversation_id), + "content": "test query", + }, + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_unauthenticated_request(self): + self.client.logout() + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + {"content": "test query"}, + ) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_streaming_error_handling(self): + def raise_error(): + yield "some content" + raise Exception("Streaming error") + + with patch.object(Assistant, "_stream", side_effect=raise_error): + response = self.client.post( + f"/api/environments/{self.team.id}/conversations/", + {"content": "test query"}, + ) + with self.assertRaises(Exception) as context: + b"".join(response.streaming_content) + self.assertTrue("Streaming error" in str(context.exception)) diff --git a/ee/hogai/assistant.py b/ee/hogai/assistant.py index 77b1c2c050008..3a296ba9ce7d6 100644 --- a/ee/hogai/assistant.py +++ b/ee/hogai/assistant.py @@ -1,9 +1,12 @@ +import json from collections.abc import AsyncGenerator, Generator, Iterator from functools import partial -from typing import Any, Literal, Optional, TypedDict, TypeGuard, Union +from typing import Any, Optional +from uuid import uuid4 from asgiref.sync import sync_to_async from langchain_core.messages import AIMessageChunk +from langchain_core.runnables.config import RunnableConfig from langfuse.callback import CallbackHandler from langgraph.graph.state import CompiledStateGraph from pydantic import BaseModel @@ -17,7 +20,19 @@ from ee.hogai.trends.nodes import ( TrendsGeneratorNode, ) -from ee.hogai.utils import AssistantNodeName, AssistantState, Conversation +from ee.hogai.utils.state import ( + GraphMessageUpdateTuple, + GraphTaskStartedUpdateTuple, + GraphValueUpdateTuple, + is_message_update, + is_state_update, + is_task_started_update, + is_value_update, + validate_state_update, + validate_value_update, +) +from ee.hogai.utils.types import AssistantNodeName, AssistantState, PartialAssistantState +from ee.models import Conversation from posthog.event_usage import report_user_action from posthog.models import Team, User from posthog.schema import ( @@ -40,42 +55,6 @@ langfuse_handler = None -def is_value_update(update: list[Any]) -> TypeGuard[tuple[Literal["values"], dict[AssistantNodeName, AssistantState]]]: - """ - Transition between nodes. - """ - return len(update) == 2 and update[0] == "updates" - - -class LangGraphState(TypedDict): - langgraph_node: AssistantNodeName - - -def is_message_update( - update: list[Any], -) -> TypeGuard[tuple[Literal["messages"], tuple[Union[AIMessageChunk, Any], LangGraphState]]]: - """ - Streaming of messages. Returns a partial state. - """ - return len(update) == 2 and update[0] == "messages" - - -def is_state_update(update: list[Any]) -> TypeGuard[tuple[Literal["updates"], AssistantState]]: - """ - Update of the state. - """ - return len(update) == 2 and update[0] == "values" - - -def is_task_started_update( - update: list[Any], -) -> TypeGuard[tuple[Literal["messages"], tuple[Union[AIMessageChunk, Any], LangGraphState]]]: - """ - Streaming of messages. Returns a partial state. - """ - return len(update) == 2 and update[0] == "debug" and update[1]["type"] == "task" - - VISUALIZATION_NODES: dict[AssistantNodeName, type[SchemaGeneratorNode]] = { AssistantNodeName.TRENDS_GENERATOR: TrendsGeneratorNode, AssistantNodeName.FUNNEL_GENERATOR: FunnelGeneratorNode, @@ -87,13 +66,25 @@ class Assistant: _graph: CompiledStateGraph _user: Optional[User] _conversation: Conversation + _latest_message: HumanMessage + _state: Optional[AssistantState] - def __init__(self, team: Team, conversation: Conversation, user: Optional[User] = None): + def __init__( + self, + team: Team, + conversation: Conversation, + new_message: HumanMessage, + user: Optional[User] = None, + is_new_conversation: bool = False, + ): self._team = team self._user = user self._conversation = conversation + self._latest_message = new_message.model_copy(deep=True, update={"id": str(uuid4())}) + self._is_new_conversation = is_new_conversation self._graph = AssistantGraph(team).compile_full_graph() self._chunks = AIMessageChunk(content="") + self._state = None def stream(self): if SERVER_GATEWAY_INTERFACE == "ASGI": @@ -110,15 +101,19 @@ async def _astream(self) -> AsyncGenerator[str, None]: break def _stream(self) -> Generator[str, None, None]: - callbacks = [langfuse_handler] if langfuse_handler else [] + state = self._init_or_update_state() + config = self._get_config() + generator: Iterator[Any] = self._graph.stream( - self._initial_state, - config={"recursion_limit": 24, "callbacks": callbacks}, - stream_mode=["messages", "values", "updates", "debug"], + state, config=config, stream_mode=["messages", "values", "updates", "debug"] ) - # Send a chunk to establish the connection avoiding the worker's timeout. - yield self._serialize_message(AssistantGenerationStatusEvent(type=AssistantGenerationStatusType.ACK)) + # Assign the conversation id to the client. + if self._is_new_conversation: + yield self._serialize_conversation() + + # Send the last message with the initialized id. + yield self._serialize_message(self._latest_message) try: last_viz_message = None @@ -127,7 +122,15 @@ def _stream(self) -> Generator[str, None, None]: if isinstance(message, VisualizationMessage): last_viz_message = message yield self._serialize_message(message) - self._report_conversation(last_viz_message) + + # Check if the assistant has requested help. + state = self._graph.get_state(config) + if state.next: + yield self._serialize_message( + AssistantMessage(content=state.tasks[0].interrupts[0].value, id=str(uuid4())) + ) + else: + self._report_conversation_state(last_viz_message) except: # This is an unhandled error, so we just stop further generation at this point yield self._serialize_message(FailureMessage()) @@ -135,8 +138,34 @@ def _stream(self) -> Generator[str, None, None]: @property def _initial_state(self) -> AssistantState: - messages = [message.root for message in self._conversation.messages] - return {"messages": messages, "intermediate_steps": None, "plan": None} + return AssistantState(messages=[self._latest_message], start_id=self._latest_message.id) + + def _get_config(self) -> RunnableConfig: + callbacks = [langfuse_handler] if langfuse_handler else [] + config: RunnableConfig = { + "recursion_limit": 24, + "callbacks": callbacks, + "configurable": {"thread_id": self._conversation.id}, + } + return config + + def _init_or_update_state(self): + config = self._get_config() + snapshot = self._graph.get_state(config) + if snapshot.next: + saved_state = validate_state_update(snapshot.values) + self._state = saved_state + if saved_state.intermediate_steps: + intermediate_steps = saved_state.intermediate_steps.copy() + intermediate_steps[-1] = (intermediate_steps[-1][0], self._latest_message.content) + self._graph.update_state( + config, + PartialAssistantState(messages=[self._latest_message], intermediate_steps=intermediate_steps), + ) + return None + initial_state = self._initial_state + self._state = initial_state + return initial_state def _node_to_reasoning_message( self, node_name: AssistantNodeName, input: AssistantState @@ -152,7 +181,7 @@ def _node_to_reasoning_message( ): substeps: list[str] = [] if input: - if intermediate_steps := input.get("intermediate_steps"): + if intermediate_steps := input.intermediate_steps: for action, _ in intermediate_steps: match action.tool: case "retrieve_event_properties": @@ -178,42 +207,65 @@ def _node_to_reasoning_message( return None def _process_update(self, update: Any) -> BaseModel | None: - if is_value_update(update): - _, state_update = update + if is_state_update(update): + _, new_state = update + self._state = validate_state_update(new_state) + elif is_value_update(update) and (new_message := self._process_value_update(update)): + return new_message + elif is_message_update(update) and (new_message := self._process_message_update(update)): + return new_message + elif is_task_started_update(update) and (new_message := self._process_task_started_update(update)): + return new_message + return None - if AssistantNodeName.ROUTER in state_update and "messages" in state_update[AssistantNodeName.ROUTER]: - return state_update[AssistantNodeName.ROUTER]["messages"][0] - elif intersected_nodes := state_update.keys() & VISUALIZATION_NODES.keys(): - # Reset chunks when schema validation fails. - self._chunks = AIMessageChunk(content="") + def _process_value_update(self, update: GraphValueUpdateTuple) -> BaseModel | None: + _, maybe_state_update = update + state_update = validate_value_update(maybe_state_update) + + if node_val := state_update.get(AssistantNodeName.ROUTER): + if isinstance(node_val, PartialAssistantState) and node_val.messages: + return node_val.messages[0] + elif intersected_nodes := state_update.keys() & VISUALIZATION_NODES.keys(): + # Reset chunks when schema validation fails. + self._chunks = AIMessageChunk(content="") - node_name = intersected_nodes.pop() - if "messages" in state_update[node_name]: - return state_update[node_name]["messages"][0] - elif state_update[node_name].get("intermediate_steps", []): - return AssistantGenerationStatusEvent(type=AssistantGenerationStatusType.GENERATION_ERROR) - elif AssistantNodeName.SUMMARIZER in state_update: + node_name = intersected_nodes.pop() + node_val = state_update[node_name] + if not isinstance(node_val, PartialAssistantState): + return None + if node_val.messages: + return node_val.messages[0] + elif node_val.intermediate_steps: + return AssistantGenerationStatusEvent(type=AssistantGenerationStatusType.GENERATION_ERROR) + elif node_val := state_update.get(AssistantNodeName.SUMMARIZER): + if isinstance(node_val, PartialAssistantState) and node_val.messages: self._chunks = AIMessageChunk(content="") - return state_update[AssistantNodeName.SUMMARIZER]["messages"][0] - elif is_message_update(update): - langchain_message, langgraph_state = update[1] - if isinstance(langchain_message, AIMessageChunk): - if langgraph_state["langgraph_node"] in VISUALIZATION_NODES.keys(): - self._chunks += langchain_message # type: ignore - parsed_message = VISUALIZATION_NODES[langgraph_state["langgraph_node"]].parse_output( - self._chunks.tool_calls[0]["args"] - ) - if parsed_message: - return VisualizationMessage(answer=parsed_message.query) - elif langgraph_state["langgraph_node"] == AssistantNodeName.SUMMARIZER: - self._chunks += langchain_message # type: ignore - return AssistantMessage(content=self._chunks.content) - elif is_task_started_update(update): - _, task_update = update - node_name = task_update["payload"]["name"] # type: ignore - node_input = task_update["payload"]["input"] # type: ignore - if reasoning_message := self._node_to_reasoning_message(node_name, node_input): - return reasoning_message + return node_val.messages[0] + + return None + + def _process_message_update(self, update: GraphMessageUpdateTuple) -> BaseModel | None: + langchain_message, langgraph_state = update[1] + if isinstance(langchain_message, AIMessageChunk): + if langgraph_state["langgraph_node"] in VISUALIZATION_NODES.keys(): + self._chunks += langchain_message # type: ignore + parsed_message = VISUALIZATION_NODES[langgraph_state["langgraph_node"]].parse_output( + self._chunks.tool_calls[0]["args"] + ) + if parsed_message: + initiator_id = self._state.start_id if self._state is not None else None + return VisualizationMessage(answer=parsed_message.query, initiator=initiator_id) + elif langgraph_state["langgraph_node"] == AssistantNodeName.SUMMARIZER: + self._chunks += langchain_message # type: ignore + return AssistantMessage(content=self._chunks.content) + return None + + def _process_task_started_update(self, update: GraphTaskStartedUpdateTuple) -> BaseModel | None: + _, task_update = update + node_name = task_update["payload"]["name"] # type: ignore + node_input = task_update["payload"]["input"] # type: ignore + if reasoning_message := self._node_to_reasoning_message(node_name, node_input): + return reasoning_message return None def _serialize_message(self, message: BaseModel) -> str: @@ -224,9 +276,15 @@ def _serialize_message(self, message: BaseModel) -> str: output += f"event: {AssistantEventType.MESSAGE}\n" return output + f"data: {message.model_dump_json(exclude_none=True)}\n\n" - def _report_conversation(self, message: Optional[VisualizationMessage]): - human_message = self._conversation.messages[-1].root - if self._user and message and isinstance(human_message, HumanMessage): + def _serialize_conversation(self) -> str: + output = f"event: {AssistantEventType.CONVERSATION}\n" + json_conversation = json.dumps({"id": str(self._conversation.id)}) + output += f"data: {json_conversation}\n\n" + return output + + def _report_conversation_state(self, message: Optional[VisualizationMessage]): + human_message = self._latest_message + if self._user and message: report_user_action( self._user, "chat with ai", diff --git a/ee/hogai/django_checkpoint/__init__.py b/ee/hogai/django_checkpoint/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/ee/hogai/django_checkpoint/checkpointer.py b/ee/hogai/django_checkpoint/checkpointer.py new file mode 100644 index 0000000000000..78817dca9df76 --- /dev/null +++ b/ee/hogai/django_checkpoint/checkpointer.py @@ -0,0 +1,309 @@ +import json +import random +import threading +from collections.abc import Iterable, Iterator, Sequence +from typing import Any, Optional, cast + +from django.db import transaction +from django.db.models import Q +from langchain_core.runnables import RunnableConfig +from langgraph.checkpoint.base import ( + WRITES_IDX_MAP, + BaseCheckpointSaver, + ChannelVersions, + Checkpoint, + CheckpointMetadata, + CheckpointTuple, + PendingWrite, + get_checkpoint_id, +) +from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer +from langgraph.checkpoint.serde.types import ChannelProtocol + +from ee.models.assistant import ConversationCheckpoint, ConversationCheckpointBlob, ConversationCheckpointWrite + + +class DjangoCheckpointer(BaseCheckpointSaver[str]): + jsonplus_serde = JsonPlusSerializer() + _lock: threading.Lock + + def __init__(self, *args): + super().__init__(*args) + self._lock = threading.Lock() + + def _load_writes(self, writes: Sequence[ConversationCheckpointWrite]) -> list[PendingWrite]: + return ( + [ + ( + str(checkpoint_write.task_id), + checkpoint_write.channel, + self.serde.loads_typed((checkpoint_write.type, checkpoint_write.blob)), + ) + for checkpoint_write in writes + if checkpoint_write.type is not None and checkpoint_write.blob is not None + ] + if writes + else [] + ) + + def _load_json(self, obj: Any): + return self.jsonplus_serde.loads(self.jsonplus_serde.dumps(obj)) + + def _dump_json(self, obj: Any) -> dict[str, Any]: + serialized_metadata = self.jsonplus_serde.dumps(obj) + # NOTE: we're using JSON serializer (not msgpack), so we need to remove null characters before writing + nulls_removed = serialized_metadata.decode().replace("\\u0000", "") + return json.loads(nulls_removed) + + def _get_checkpoint_qs( + self, + config: Optional[RunnableConfig], + filter: Optional[dict[str, Any]], + before: Optional[RunnableConfig], + ): + query = Q() + + # construct predicate for config filter + if config and "configurable" in config: + thread_id = config["configurable"].get("thread_id") + query &= Q(thread_id=thread_id) + checkpoint_ns = config["configurable"].get("checkpoint_ns") + if checkpoint_ns is not None: + query &= Q(checkpoint_ns=checkpoint_ns) + if checkpoint_id := get_checkpoint_id(config): + query &= Q(id=checkpoint_id) + + # construct predicate for metadata filter + if filter: + query &= Q(metadata__contains=filter) + + # construct predicate for `before` + if before is not None: + query &= Q(id__lt=get_checkpoint_id(before)) + + return ConversationCheckpoint.objects.filter(query).order_by("-id") + + def _get_checkpoint_channel_values( + self, checkpoint: ConversationCheckpoint + ) -> Iterable[ConversationCheckpointBlob]: + if not checkpoint.checkpoint: + return [] + loaded_checkpoint = self._load_json(checkpoint.checkpoint) + if "channel_versions" not in loaded_checkpoint: + return [] + query = Q() + for channel, version in loaded_checkpoint["channel_versions"].items(): + query |= Q(channel=channel, version=version) + return checkpoint.blobs.filter(query) + + def list( + self, + config: Optional[RunnableConfig], + *, + filter: Optional[dict[str, Any]] = None, + before: Optional[RunnableConfig] = None, + limit: Optional[int] = None, + ) -> Iterator[CheckpointTuple]: + """List checkpoints from the database. + + This method retrieves a list of checkpoint tuples from the Postgres database based + on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first). + + Args: + config (RunnableConfig): The config to use for listing the checkpoints. + filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata. Defaults to None. + before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None. + limit (Optional[int]): The maximum number of checkpoints to return. Defaults to None. + + Yields: + Iterator[CheckpointTuple]: An iterator of checkpoint tuples. + """ + qs = self._get_checkpoint_qs(config, filter, before) + if limit: + qs = qs[:limit] + + for checkpoint in qs: + channel_values = self._get_checkpoint_channel_values(checkpoint) + loaded_checkpoint: Checkpoint = self._load_json(checkpoint.checkpoint) + + checkpoint_dict: Checkpoint = { + **loaded_checkpoint, + "pending_sends": [ + self.serde.loads_typed((checkpoint_write.type, checkpoint_write.blob)) + for checkpoint_write in checkpoint.pending_sends + ], + "channel_values": { + checkpoint_blob.channel: self.serde.loads_typed((checkpoint_blob.type, checkpoint_blob.blob)) + for checkpoint_blob in channel_values + if checkpoint_blob.type is not None + and checkpoint_blob.type != "empty" + and checkpoint_blob.blob is not None + }, + } + + yield CheckpointTuple( + { + "configurable": { + "thread_id": checkpoint.thread_id, + "checkpoint_ns": checkpoint.checkpoint_ns, + "checkpoint_id": checkpoint.id, + } + }, + checkpoint_dict, + self._load_json(checkpoint.metadata), + ( + { + "configurable": { + "thread_id": checkpoint.thread_id, + "checkpoint_ns": checkpoint.checkpoint_ns, + "checkpoint_id": checkpoint.parent_checkpoint_id, + } + } + if checkpoint.parent_checkpoint + else None + ), + self._load_writes(checkpoint.pending_writes), + ) + + def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: + """Get a checkpoint tuple from the database. + + This method retrieves a checkpoint tuple from the Postgres database based on the + provided config. If the config contains a "checkpoint_id" key, the checkpoint with + the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint + for the given thread ID is retrieved. + + Args: + config (RunnableConfig): The config to use for retrieving the checkpoint. + + Returns: + Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found. + """ + return next(self.list(config), None) + + def put( + self, + config: RunnableConfig, + checkpoint: Checkpoint, + metadata: CheckpointMetadata, + new_versions: ChannelVersions, + ) -> RunnableConfig: + """Save a checkpoint to the database. + + This method saves a checkpoint to the Postgres database. The checkpoint is associated + with the provided config and its parent config (if any). + + Args: + config (RunnableConfig): The config to associate with the checkpoint. + checkpoint (Checkpoint): The checkpoint to save. + metadata (CheckpointMetadata): Additional metadata to save with the checkpoint. + new_versions (ChannelVersions): New channel versions as of this write. + + Returns: + RunnableConfig: Updated configuration after storing the checkpoint. + """ + configurable = config["configurable"] + thread_id: str = configurable["thread_id"] + checkpoint_id = get_checkpoint_id(config) + checkpoint_ns: str | None = configurable.get("checkpoint_ns") or "" + + checkpoint_copy = cast(dict[str, Any], checkpoint.copy()) + channel_values = checkpoint_copy.pop("channel_values", {}) + + next_config: RunnableConfig = { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": checkpoint["id"], + } + } + + with self._lock, transaction.atomic(): + updated_checkpoint, _ = ConversationCheckpoint.objects.update_or_create( + id=checkpoint["id"], + thread_id=thread_id, + checkpoint_ns=checkpoint_ns, + defaults={ + "parent_checkpoint_id": checkpoint_id, + "checkpoint": self._dump_json({**checkpoint_copy, "pending_sends": []}), + "metadata": self._dump_json(metadata), + }, + ) + + blobs = [] + for channel, version in new_versions.items(): + type, blob = ( + self.serde.dumps_typed(channel_values[channel]) if channel in channel_values else ("empty", None) + ) + blobs.append( + ConversationCheckpointBlob( + checkpoint=updated_checkpoint, + channel=channel, + version=str(version), + type=type, + blob=blob, + ) + ) + + ConversationCheckpointBlob.objects.bulk_create(blobs, ignore_conflicts=True) + return next_config + + def put_writes( + self, + config: RunnableConfig, + writes: Sequence[tuple[str, Any]], + task_id: str, + ) -> None: + """Store intermediate writes linked to a checkpoint. + + This method saves intermediate writes associated with a checkpoint to the Postgres database. + + Args: + config (RunnableConfig): Configuration of the related checkpoint. + writes (List[Tuple[str, Any]]): List of writes to store. + task_id (str): Identifier for the task creating the writes. + """ + configurable = config["configurable"] + thread_id: str = configurable["thread_id"] + checkpoint_id = get_checkpoint_id(config) + checkpoint_ns: str | None = configurable.get("checkpoint_ns") or "" + + with self._lock, transaction.atomic(): + # `put_writes` and `put` are concurrently called without guaranteeing the call order + # so we need to ensure the checkpoint is created before creating writes. + # Thread.lock() will prevent race conditions though to the same checkpoints within a single pod. + checkpoint, _ = ConversationCheckpoint.objects.get_or_create( + id=checkpoint_id, thread_id=thread_id, checkpoint_ns=checkpoint_ns + ) + + writes_to_create = [] + for idx, (channel, value) in enumerate(writes): + type, blob = self.serde.dumps_typed(value) + writes_to_create.append( + ConversationCheckpointWrite( + checkpoint=checkpoint, + task_id=task_id, + idx=idx, + channel=channel, + type=type, + blob=blob, + ) + ) + + ConversationCheckpointWrite.objects.bulk_create( + writes_to_create, + update_conflicts=all(w[0] in WRITES_IDX_MAP for w in writes), + unique_fields=["checkpoint", "task_id", "idx"], + update_fields=["channel", "type", "blob"], + ) + + def get_next_version(self, current: Optional[str | int], channel: ChannelProtocol) -> str: + if current is None: + current_v = 0 + elif isinstance(current, int): + current_v = current + else: + current_v = int(current.split(".")[0]) + next_v = current_v + 1 + next_h = random.random() + return f"{next_v:032}.{next_h:016}" diff --git a/ee/hogai/django_checkpoint/test/test_checkpointer.py b/ee/hogai/django_checkpoint/test/test_checkpointer.py new file mode 100644 index 0000000000000..2f8fd7f4a60ed --- /dev/null +++ b/ee/hogai/django_checkpoint/test/test_checkpointer.py @@ -0,0 +1,274 @@ +# type: ignore + +from typing import Any, TypedDict + +from langchain_core.runnables import RunnableConfig +from langgraph.checkpoint.base import ( + Checkpoint, + CheckpointMetadata, + create_checkpoint, + empty_checkpoint, +) +from langgraph.checkpoint.base.id import uuid6 +from langgraph.errors import NodeInterrupt +from langgraph.graph import END, START +from langgraph.graph.state import CompiledStateGraph, StateGraph + +from ee.hogai.django_checkpoint.checkpointer import DjangoCheckpointer +from ee.models.assistant import ( + Conversation, + ConversationCheckpoint, + ConversationCheckpointBlob, + ConversationCheckpointWrite, +) +from posthog.test.base import NonAtomicBaseTest + + +class TestDjangoCheckpointer(NonAtomicBaseTest): + CLASS_DATA_LEVEL_SETUP = False + + def _build_graph(self, checkpointer: DjangoCheckpointer): + class State(TypedDict): + val: int + + graph = StateGraph(State) + + def handle_node1(state: State) -> State: + if state["val"] == 1: + raise NodeInterrupt("test") + return {"val": state["val"] + 1} + + graph.add_node("node1", handle_node1) + graph.add_node("node2", lambda state: state) + + graph.add_edge(START, "node1") + graph.add_edge("node1", "node2") + graph.add_edge("node2", END) + + return graph.compile(checkpointer=checkpointer) + + def test_saver(self): + thread1 = Conversation.objects.create(user=self.user, team=self.team) + thread2 = Conversation.objects.create(user=self.user, team=self.team) + + config_1: RunnableConfig = { + "configurable": { + "thread_id": thread1.id, + "checkpoint_ns": "", + } + } + chkpnt_1: Checkpoint = empty_checkpoint() + + config_2: RunnableConfig = { + "configurable": { + "thread_id": thread2.id, + "checkpoint_ns": "", + } + } + chkpnt_2: Checkpoint = create_checkpoint(chkpnt_1, {}, 1) + + config_3: RunnableConfig = { + "configurable": { + "thread_id": thread2.id, + "checkpoint_id": chkpnt_2["id"], + "checkpoint_ns": "inner", + } + } + chkpnt_3: Checkpoint = empty_checkpoint() + + metadata_1: CheckpointMetadata = { + "source": "input", + "step": 2, + "writes": {}, + "score": 1, + } + metadata_2: CheckpointMetadata = { + "source": "loop", + "step": 1, + "writes": {"foo": "bar"}, + "score": None, + } + metadata_3: CheckpointMetadata = {} + + test_data = { + "configs": [config_1, config_2, config_3], + "checkpoints": [chkpnt_1, chkpnt_2, chkpnt_3], + "metadata": [metadata_1, metadata_2, metadata_3], + } + + saver = DjangoCheckpointer() + + configs = test_data["configs"] + checkpoints = test_data["checkpoints"] + metadata = test_data["metadata"] + + saver.put(configs[0], checkpoints[0], metadata[0], {}) + saver.put(configs[1], checkpoints[1], metadata[1], {}) + saver.put(configs[2], checkpoints[2], metadata[2], {}) + + # call method / assertions + query_1 = {"source": "input"} # search by 1 key + query_2 = { + "step": 1, + "writes": {"foo": "bar"}, + } # search by multiple keys + query_3: dict[str, Any] = {} # search by no keys, return all checkpoints + query_4 = {"source": "update", "step": 1} # no match + + search_results_1 = list(saver.list(None, filter=query_1)) + assert len(search_results_1) == 1 + assert search_results_1[0].metadata == metadata[0] + + search_results_2 = list(saver.list(None, filter=query_2)) + assert len(search_results_2) == 1 + assert search_results_2[0].metadata == metadata[1] + + search_results_3 = list(saver.list(None, filter=query_3)) + assert len(search_results_3) == 3 + + search_results_4 = list(saver.list(None, filter=query_4)) + assert len(search_results_4) == 0 + + # search by config (defaults to checkpoints across all namespaces) + search_results_5 = list(saver.list({"configurable": {"thread_id": thread2.id}})) + assert len(search_results_5) == 2 + assert { + search_results_5[0].config["configurable"]["checkpoint_ns"], + search_results_5[1].config["configurable"]["checkpoint_ns"], + } == {"", "inner"} + + def test_channel_versions(self): + thread1 = Conversation.objects.create(user=self.user, team=self.team) + + chkpnt = { + "v": 1, + "ts": "2024-07-31T20:14:19.804150+00:00", + "id": str(uuid6(clock_seq=-2)), + "channel_values": { + "post": "hog", + "node": "node", + }, + "channel_versions": { + "__start__": 2, + "my_key": 3, + "start:node": 3, + "node": 3, + }, + "versions_seen": { + "__input__": {}, + "__start__": {"__start__": 1}, + "node": {"start:node": 2}, + }, + "pending_sends": [], + } + metadata = {"meta": "key"} + + write_config = {"configurable": {"thread_id": thread1.id, "checkpoint_ns": ""}} + read_config = {"configurable": {"thread_id": thread1.id}} + + saver = DjangoCheckpointer() + saver.put(write_config, chkpnt, metadata, {}) + + checkpoint = ConversationCheckpoint.objects.first() + self.assertIsNotNone(checkpoint) + self.assertEqual(checkpoint.thread, thread1) + self.assertEqual(checkpoint.checkpoint_ns, "") + self.assertEqual(str(checkpoint.id), chkpnt["id"]) + self.assertIsNone(checkpoint.parent_checkpoint) + chkpnt.pop("channel_values") + self.assertEqual(checkpoint.checkpoint, chkpnt) + self.assertEqual(checkpoint.metadata, metadata) + + checkpoints = list(saver.list(read_config)) + self.assertEqual(len(checkpoints), 1) + + checkpoint = saver.get(read_config) + self.assertEqual(checkpoint, checkpoints[0].checkpoint) + + def test_put_copies_checkpoint(self): + thread1 = Conversation.objects.create(user=self.user, team=self.team) + chkpnt = { + "v": 1, + "ts": "2024-07-31T20:14:19.804150+00:00", + "id": str(uuid6(clock_seq=-2)), + "channel_values": { + "post": "hog", + "node": "node", + }, + "channel_versions": { + "__start__": 2, + "my_key": 3, + "start:node": 3, + "node": 3, + }, + "versions_seen": { + "__input__": {}, + "__start__": {"__start__": 1}, + "node": {"start:node": 2}, + }, + "pending_sends": [], + } + metadata = {"meta": "key"} + write_config = {"configurable": {"thread_id": thread1.id, "checkpoint_ns": ""}} + saver = DjangoCheckpointer() + saver.put(write_config, chkpnt, metadata, {}) + self.assertIn("channel_values", chkpnt) + + def test_concurrent_puts_and_put_writes(self): + graph: CompiledStateGraph = self._build_graph(DjangoCheckpointer()) + thread = Conversation.objects.create(user=self.user, team=self.team) + config = {"configurable": {"thread_id": str(thread.id)}} + graph.invoke( + {"val": 0}, + config=config, + ) + self.assertEqual(len(ConversationCheckpoint.objects.all()), 4) + self.assertEqual(len(ConversationCheckpointBlob.objects.all()), 10) + self.assertEqual(len(ConversationCheckpointWrite.objects.all()), 6) + + def test_resuming(self): + checkpointer = DjangoCheckpointer() + graph: CompiledStateGraph = self._build_graph(checkpointer) + thread = Conversation.objects.create(user=self.user, team=self.team) + config = {"configurable": {"thread_id": str(thread.id)}} + + graph.invoke( + {"val": 1}, + config=config, + ) + snapshot = graph.get_state(config) + self.assertIsNotNone(snapshot.next) + self.assertEqual(snapshot.tasks[0].interrupts[0].value, "test") + + self.assertEqual(len(ConversationCheckpoint.objects.all()), 2) + self.assertEqual(len(ConversationCheckpointBlob.objects.all()), 4) + self.assertEqual(len(ConversationCheckpointWrite.objects.all()), 3) + self.assertEqual(len(list(checkpointer.list(config))), 2) + + latest_checkpoint = ConversationCheckpoint.objects.last() + latest_write = ConversationCheckpointWrite.objects.filter(checkpoint=latest_checkpoint).first() + actual_checkpoint = checkpointer.get_tuple(config) + self.assertIsNotNone(actual_checkpoint) + self.assertIsNotNone(latest_write) + self.assertEqual(len(latest_checkpoint.writes.all()), 1) + blobs = list(latest_checkpoint.blobs.all()) + self.assertEqual(len(blobs), 3) + self.assertEqual(actual_checkpoint.checkpoint["id"], str(latest_checkpoint.id)) + self.assertEqual(len(actual_checkpoint.pending_writes), 1) + self.assertEqual(actual_checkpoint.pending_writes[0][0], str(latest_write.task_id)) + + graph.update_state(config, {"val": 2}) + # add the value update checkpoint + self.assertEqual(len(ConversationCheckpoint.objects.all()), 3) + self.assertEqual(len(ConversationCheckpointBlob.objects.all()), 6) + self.assertEqual(len(ConversationCheckpointWrite.objects.all()), 5) + self.assertEqual(len(list(checkpointer.list(config))), 3) + + res = graph.invoke(None, config=config) + self.assertEqual(len(ConversationCheckpoint.objects.all()), 5) + self.assertEqual(len(ConversationCheckpointBlob.objects.all()), 12) + self.assertEqual(len(ConversationCheckpointWrite.objects.all()), 9) + self.assertEqual(len(list(checkpointer.list(config))), 5) + self.assertEqual(res, {"val": 3}) + snapshot = graph.get_state(config) + self.assertFalse(snapshot.next) diff --git a/ee/hogai/eval/tests/test_eval_funnel_generator.py b/ee/hogai/eval/tests/test_eval_funnel_generator.py index cd7e93b260ae9..4d7876ca6f73c 100644 --- a/ee/hogai/eval/tests/test_eval_funnel_generator.py +++ b/ee/hogai/eval/tests/test_eval_funnel_generator.py @@ -1,9 +1,11 @@ +from typing import cast + from langgraph.graph.state import CompiledStateGraph from ee.hogai.assistant import AssistantGraph from ee.hogai.eval.utils import EvalBaseTest -from ee.hogai.utils import AssistantNodeName -from posthog.schema import AssistantFunnelsQuery, HumanMessage +from ee.hogai.utils.types import AssistantNodeName, AssistantState +from posthog.schema import AssistantFunnelsQuery, HumanMessage, VisualizationMessage class TestEvalFunnelGenerator(EvalBaseTest): @@ -14,8 +16,11 @@ def _call_node(self, query: str, plan: str) -> AssistantFunnelsQuery: .add_funnel_generator(AssistantNodeName.END) .compile() ) - state = graph.invoke({"messages": [HumanMessage(content=query)], "plan": plan}) - return state["messages"][-1].answer + state = graph.invoke( + AssistantState(messages=[HumanMessage(content=query)], plan=plan), + self._get_config(), + ) + return cast(VisualizationMessage, AssistantState.model_validate(state).messages[-1]).answer def test_node_replaces_equals_with_contains(self): query = "what is the conversion rate from a page view to sign up for users with name John?" diff --git a/ee/hogai/eval/tests/test_eval_funnel_planner.py b/ee/hogai/eval/tests/test_eval_funnel_planner.py index 3760961f9bb03..9adbd75e77c6c 100644 --- a/ee/hogai/eval/tests/test_eval_funnel_planner.py +++ b/ee/hogai/eval/tests/test_eval_funnel_planner.py @@ -5,7 +5,7 @@ from ee.hogai.assistant import AssistantGraph from ee.hogai.eval.utils import EvalBaseTest -from ee.hogai.utils import AssistantNodeName +from ee.hogai.utils.types import AssistantNodeName, AssistantState from posthog.schema import HumanMessage @@ -40,8 +40,11 @@ def _call_node(self, query): .add_funnel_planner(AssistantNodeName.END) .compile() ) - state = graph.invoke({"messages": [HumanMessage(content=query)]}) - return state["plan"] + state = graph.invoke( + AssistantState(messages=[HumanMessage(content=query)]), + self._get_config(), + ) + return AssistantState.model_validate(state).plan or "" def test_basic_funnel(self): query = "what was the conversion from a page view to sign up?" diff --git a/ee/hogai/eval/tests/test_eval_router.py b/ee/hogai/eval/tests/test_eval_router.py index 25a84769dbfc8..c1307e9d40f00 100644 --- a/ee/hogai/eval/tests/test_eval_router.py +++ b/ee/hogai/eval/tests/test_eval_router.py @@ -1,8 +1,10 @@ +from typing import cast + from langgraph.graph.state import CompiledStateGraph from ee.hogai.assistant import AssistantGraph from ee.hogai.eval.utils import EvalBaseTest -from ee.hogai.utils import AssistantNodeName +from ee.hogai.utils.types import AssistantNodeName, AssistantState from posthog.schema import HumanMessage, RouterMessage @@ -15,8 +17,11 @@ def _call_node(self, query: str | list): .compile() ) messages = [HumanMessage(content=query)] if isinstance(query, str) else query - state = graph.invoke({"messages": messages}) - return state["messages"][-1].content + state = graph.invoke( + AssistantState(messages=messages), + self._get_config(), + ) + return cast(RouterMessage, AssistantState.model_validate(state).messages[-1]).content def test_outputs_basic_trends_insight(self): query = "Show the $pageview trend" diff --git a/ee/hogai/eval/tests/test_eval_trends_generator.py b/ee/hogai/eval/tests/test_eval_trends_generator.py index c5341584ca2f7..496bbf0100b51 100644 --- a/ee/hogai/eval/tests/test_eval_trends_generator.py +++ b/ee/hogai/eval/tests/test_eval_trends_generator.py @@ -1,9 +1,11 @@ +from typing import cast + from langgraph.graph.state import CompiledStateGraph from ee.hogai.assistant import AssistantGraph from ee.hogai.eval.utils import EvalBaseTest -from ee.hogai.utils import AssistantNodeName -from posthog.schema import AssistantTrendsQuery, HumanMessage +from ee.hogai.utils.types import AssistantNodeName, AssistantState +from posthog.schema import AssistantTrendsQuery, HumanMessage, VisualizationMessage class TestEvalTrendsGenerator(EvalBaseTest): @@ -14,8 +16,11 @@ def _call_node(self, query: str, plan: str) -> AssistantTrendsQuery: .add_trends_generator(AssistantNodeName.END) .compile() ) - state = graph.invoke({"messages": [HumanMessage(content=query)], "plan": plan}) - return state["messages"][-1].answer + state = graph.invoke( + AssistantState(messages=[HumanMessage(content=query)], plan=plan), + self._get_config(), + ) + return cast(VisualizationMessage, AssistantState.model_validate(state).messages[-1]).answer def test_node_replaces_equals_with_contains(self): query = "what is pageview trend for users with name John?" diff --git a/ee/hogai/eval/tests/test_eval_trends_planner.py b/ee/hogai/eval/tests/test_eval_trends_planner.py index e7ea741d03687..d4fbff456a91c 100644 --- a/ee/hogai/eval/tests/test_eval_trends_planner.py +++ b/ee/hogai/eval/tests/test_eval_trends_planner.py @@ -5,7 +5,7 @@ from ee.hogai.assistant import AssistantGraph from ee.hogai.eval.utils import EvalBaseTest -from ee.hogai.utils import AssistantNodeName +from ee.hogai.utils.types import AssistantNodeName, AssistantState from posthog.schema import HumanMessage @@ -40,8 +40,11 @@ def _call_node(self, query): .add_trends_planner(AssistantNodeName.END) .compile() ) - state = graph.invoke({"messages": [HumanMessage(content=query)]}) - return state["plan"] + state = graph.invoke( + AssistantState(messages=[HumanMessage(content=query)]), + self._get_config(), + ) + return AssistantState.model_validate(state).plan or "" def test_no_excessive_property_filters(self): query = "Show the $pageview trend" diff --git a/ee/hogai/eval/utils.py b/ee/hogai/eval/utils.py index 1e50a75daefa2..6e03c4cfafa9f 100644 --- a/ee/hogai/eval/utils.py +++ b/ee/hogai/eval/utils.py @@ -3,15 +3,25 @@ import pytest from django.test import override_settings from flaky import flaky +from langchain_core.runnables import RunnableConfig +from ee.models.assistant import Conversation from posthog.demo.matrix.manager import MatrixManager from posthog.tasks.demo_create_data import HedgeboxMatrix -from posthog.test.base import BaseTest +from posthog.test.base import NonAtomicBaseTest @pytest.mark.skipif(os.environ.get("DEEPEVAL") != "YES", reason="Only runs for the assistant evaluation") @flaky(max_runs=3, min_passes=1) -class EvalBaseTest(BaseTest): +class EvalBaseTest(NonAtomicBaseTest): + def _get_config(self) -> RunnableConfig: + conversation = Conversation.objects.create(team=self.team, user=self.user) + return { + "configurable": { + "thread_id": conversation.id, + } + } + @classmethod def setUpTestData(cls): super().setUpTestData() diff --git a/ee/hogai/funnels/nodes.py b/ee/hogai/funnels/nodes.py index a55bc223847f2..6f71305e0b796 100644 --- a/ee/hogai/funnels/nodes.py +++ b/ee/hogai/funnels/nodes.py @@ -6,12 +6,12 @@ from ee.hogai.schema_generator.nodes import SchemaGeneratorNode, SchemaGeneratorToolsNode from ee.hogai.schema_generator.utils import SchemaGeneratorOutput from ee.hogai.taxonomy_agent.nodes import TaxonomyAgentPlannerNode, TaxonomyAgentPlannerToolsNode -from ee.hogai.utils import AssistantState +from ee.hogai.utils.types import AssistantState, PartialAssistantState from posthog.schema import AssistantFunnelsQuery class FunnelPlannerNode(TaxonomyAgentPlannerNode): - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: toolkit = FunnelsTaxonomyAgentToolkit(self._team) prompt = ChatPromptTemplate.from_messages( [ @@ -23,7 +23,7 @@ def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: class FunnelPlannerToolsNode(TaxonomyAgentPlannerToolsNode): - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: toolkit = FunnelsTaxonomyAgentToolkit(self._team) return super()._run_with_toolkit(state, toolkit, config=config) @@ -36,7 +36,7 @@ class FunnelGeneratorNode(SchemaGeneratorNode[AssistantFunnelsQuery]): OUTPUT_MODEL = FunnelsSchemaGeneratorOutput OUTPUT_SCHEMA = FUNNEL_SCHEMA - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: prompt = ChatPromptTemplate.from_messages( [ ("system", FUNNEL_SYSTEM_PROMPT), diff --git a/ee/hogai/funnels/prompts.py b/ee/hogai/funnels/prompts.py index b2deec894a070..3808809c173a7 100644 --- a/ee/hogai/funnels/prompts.py +++ b/ee/hogai/funnels/prompts.py @@ -12,6 +12,8 @@ {{react_format}} +{{react_human_in_the_loop}} + Below you will find information on how to correctly discover the taxonomy of the user's data. diff --git a/ee/hogai/funnels/test/test_nodes.py b/ee/hogai/funnels/test/test_nodes.py index 5c65b14110599..4f4e9fca0e5d4 100644 --- a/ee/hogai/funnels/test/test_nodes.py +++ b/ee/hogai/funnels/test/test_nodes.py @@ -4,6 +4,7 @@ from langchain_core.runnables import RunnableLambda from ee.hogai.funnels.nodes import FunnelGeneratorNode, FunnelsSchemaGeneratorOutput +from ee.hogai.utils.types import AssistantState, PartialAssistantState from posthog.schema import ( AssistantFunnelsQuery, HumanMessage, @@ -15,6 +16,7 @@ @override_settings(IN_UNIT_TESTING=True) class TestFunnelsGeneratorNode(ClickhouseTestMixin, APIBaseTest): def setUp(self): + super().setUp() self.schema = AssistantFunnelsQuery(series=[]) def test_node_runs(self): @@ -24,16 +26,13 @@ def test_node_runs(self): lambda _: FunnelsSchemaGeneratorOutput(query=self.schema).model_dump() ) new_state = node.run( - { - "messages": [HumanMessage(content="Text")], - "plan": "Plan", - }, + AssistantState(messages=[HumanMessage(content="Text")], plan="Plan"), {}, ) self.assertEqual( new_state, - { - "messages": [VisualizationMessage(answer=self.schema, plan="Plan", done=True)], - "intermediate_steps": None, - }, + PartialAssistantState( + messages=[VisualizationMessage(answer=self.schema, plan="Plan", id=new_state.messages[0].id)], + intermediate_steps=None, + ), ) diff --git a/ee/hogai/funnels/toolkit.py b/ee/hogai/funnels/toolkit.py index 8d6407027aac1..ae603519cc331 100644 --- a/ee/hogai/funnels/toolkit.py +++ b/ee/hogai/funnels/toolkit.py @@ -1,5 +1,5 @@ from ee.hogai.taxonomy_agent.toolkit import TaxonomyAgentToolkit, ToolkitTool -from ee.hogai.utils import dereference_schema +from ee.hogai.utils.helpers import dereference_schema from posthog.schema import AssistantFunnelsQuery diff --git a/ee/hogai/graph.py b/ee/hogai/graph.py index 79e5f914097ce..bf961d6bb9aa8 100644 --- a/ee/hogai/graph.py +++ b/ee/hogai/graph.py @@ -1,10 +1,10 @@ from collections.abc import Hashable from typing import Optional, cast -from langfuse.callback import CallbackHandler +from langchain_core.runnables.base import RunnableLike from langgraph.graph.state import StateGraph -from ee import settings +from ee.hogai.django_checkpoint.checkpointer import DjangoCheckpointer from ee.hogai.funnels.nodes import ( FunnelGeneratorNode, FunnelGeneratorToolsNode, @@ -19,15 +19,10 @@ TrendsPlannerNode, TrendsPlannerToolsNode, ) -from ee.hogai.utils import AssistantNodeName, AssistantState +from ee.hogai.utils.types import AssistantNodeName, AssistantState from posthog.models.team.team import Team -if settings.LANGFUSE_PUBLIC_KEY: - langfuse_handler = CallbackHandler( - public_key=settings.LANGFUSE_PUBLIC_KEY, secret_key=settings.LANGFUSE_SECRET_KEY, host=settings.LANGFUSE_HOST - ) -else: - langfuse_handler = None +checkpointer = DjangoCheckpointer() class AssistantGraph: @@ -45,10 +40,14 @@ def add_edge(self, from_node: AssistantNodeName, to_node: AssistantNodeName): self._graph.add_edge(from_node, to_node) return self + def add_node(self, node: AssistantNodeName, action: RunnableLike): + self._graph.add_node(node, action) + return self + def compile(self): if not self._has_start_node: raise ValueError("Start node not added to the graph") - return self._graph.compile() + return self._graph.compile(checkpointer=checkpointer) def add_start(self): return self.add_edge(AssistantNodeName.START, AssistantNodeName.ROUTER) diff --git a/ee/hogai/router/nodes.py b/ee/hogai/router/nodes.py index c9151faaabc29..f6aeacdebbe6b 100644 --- a/ee/hogai/router/nodes.py +++ b/ee/hogai/router/nodes.py @@ -1,4 +1,5 @@ from typing import Literal, cast +from uuid import uuid4 from langchain_core.messages import AIMessage as LangchainAIMessage, BaseMessage from langchain_core.prompts import ChatPromptTemplate @@ -11,7 +12,8 @@ ROUTER_SYSTEM_PROMPT, ROUTER_USER_PROMPT, ) -from ee.hogai.utils import AssistantState, AssistantNode +from ee.hogai.utils.nodes import AssistantNode +from ee.hogai.utils.types import AssistantState, PartialAssistantState from posthog.schema import HumanMessage, RouterMessage RouteName = Literal["trends", "funnel"] @@ -22,7 +24,7 @@ class RouterOutput(BaseModel): class RouterNode(AssistantNode): - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: prompt = ChatPromptTemplate.from_messages( [ ("system", ROUTER_SYSTEM_PROMPT), @@ -31,10 +33,10 @@ def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: ) + self._construct_messages(state) chain = prompt | self._model output: RouterOutput = chain.invoke({}, config) - return {"messages": [RouterMessage(content=output.visualization_type)]} + return PartialAssistantState(messages=[RouterMessage(content=output.visualization_type, id=str(uuid4()))]) def router(self, state: AssistantState) -> RouteName: - last_message = state["messages"][-1] + last_message = state.messages[-1] if isinstance(last_message, RouterMessage): return cast(RouteName, last_message.content) raise ValueError("Invalid route.") @@ -47,7 +49,7 @@ def _model(self): def _construct_messages(self, state: AssistantState): history: list[BaseMessage] = [] - for message in state["messages"]: + for message in state.messages: if isinstance(message, HumanMessage): history += ChatPromptTemplate.from_messages( [("user", ROUTER_USER_PROMPT.strip())], template_format="mustache" diff --git a/ee/hogai/router/test/test_nodes.py b/ee/hogai/router/test/test_nodes.py index 06014fb0b9f59..53074a381b804 100644 --- a/ee/hogai/router/test/test_nodes.py +++ b/ee/hogai/router/test/test_nodes.py @@ -2,11 +2,11 @@ from unittest.mock import patch from django.test import override_settings -from langchain_core.messages import AIMessage as LangchainAIMessage -from langchain_core.messages import HumanMessage as LangchainHumanMessage +from langchain_core.messages import AIMessage as LangchainAIMessage, HumanMessage as LangchainHumanMessage from langchain_core.runnables import RunnableLambda from ee.hogai.router.nodes import RouterNode, RouterOutput +from ee.hogai.utils.types import AssistantState, PartialAssistantState from posthog.schema import ( HumanMessage, RouterMessage, @@ -19,7 +19,7 @@ class TestRouterNode(ClickhouseTestMixin, APIBaseTest): def test_router(self): node = RouterNode(self.team) - state: Any = {"messages": [RouterMessage(content="trends")]} + state: Any = AssistantState(messages=[RouterMessage(content="trends")]) self.assertEqual(node.router(state), "trends") def test_node_runs(self): @@ -28,28 +28,36 @@ def test_node_runs(self): return_value=RunnableLambda(lambda _: RouterOutput(visualization_type="funnel")), ): node = RouterNode(self.team) - state: Any = {"messages": [HumanMessage(content="generate trends")]} - self.assertEqual(node.run(state, {}), {"messages": [RouterMessage(content="funnel")]}) + state: Any = AssistantState(messages=[HumanMessage(content="generate trends")]) + next_state = node.run(state, {}) + self.assertEqual( + next_state, + PartialAssistantState(messages=[RouterMessage(content="funnel", id=next_state.messages[0].id)]), + ) with patch( "ee.hogai.router.nodes.RouterNode._model", return_value=RunnableLambda(lambda _: RouterOutput(visualization_type="trends")), ): node = RouterNode(self.team) - state: Any = {"messages": [HumanMessage(content="generate trends")]} - self.assertEqual(node.run(state, {}), {"messages": [RouterMessage(content="trends")]}) + state: Any = AssistantState(messages=[HumanMessage(content="generate trends")]) + next_state = node.run(state, {}) + self.assertEqual( + next_state, + PartialAssistantState(messages=[RouterMessage(content="trends", id=next_state.messages[0].id)]), + ) def test_node_reconstructs_conversation(self): node = RouterNode(self.team) - state: Any = {"messages": [HumanMessage(content="generate trends")]} + state: Any = AssistantState(messages=[HumanMessage(content="generate trends")]) self.assertEqual(node._construct_messages(state), [LangchainHumanMessage(content="Question: generate trends")]) - state = { - "messages": [ + state = AssistantState( + messages=[ HumanMessage(content="generate trends"), RouterMessage(content="trends"), VisualizationMessage(), ] - } + ) self.assertEqual( node._construct_messages(state), [LangchainHumanMessage(content="Question: generate trends"), LangchainAIMessage(content="trends")], diff --git a/ee/hogai/schema_generator/nodes.py b/ee/hogai/schema_generator/nodes.py index f2d383d5c1e30..4bed02fd462cc 100644 --- a/ee/hogai/schema_generator/nodes.py +++ b/ee/hogai/schema_generator/nodes.py @@ -1,10 +1,16 @@ -import itertools import xml.etree.ElementTree as ET +from collections.abc import Sequence from functools import cached_property from typing import Generic, Optional, TypeVar +from uuid import uuid4 from langchain_core.agents import AgentAction -from langchain_core.messages import AIMessage as LangchainAssistantMessage, BaseMessage, merge_message_runs +from langchain_core.messages import ( + AIMessage as LangchainAssistantMessage, + BaseMessage, + HumanMessage as LangchainHumanMessage, + merge_message_runs, +) from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate from langchain_core.runnables import RunnableConfig from langchain_openai import ChatOpenAI @@ -23,10 +29,14 @@ QUESTION_PROMPT, ) from ee.hogai.schema_generator.utils import SchemaGeneratorOutput -from ee.hogai.utils import AssistantNode, AssistantState, filter_visualization_conversation +from ee.hogai.utils.helpers import find_last_message_of_type, slice_messages_to_conversation_start +from ee.hogai.utils.nodes import AssistantNode +from ee.hogai.utils.types import AssistantMessageUnion, AssistantState, PartialAssistantState from posthog.models.group_type_mapping import GroupTypeMapping from posthog.schema import ( + AssistantMessage, FailureMessage, + HumanMessage, VisualizationMessage, ) @@ -63,9 +73,10 @@ def _run_with_prompt( state: AssistantState, prompt: ChatPromptTemplate, config: Optional[RunnableConfig] = None, - ) -> AssistantState: - generated_plan = state.get("plan", "") - intermediate_steps = state.get("intermediate_steps") or [] + ) -> PartialAssistantState: + start_id = state.start_id + generated_plan = state.plan or "" + intermediate_steps = state.intermediate_steps or [] validation_error_message = intermediate_steps[-1][1] if intermediate_steps else None generation_prompt = prompt + self._construct_messages(state, validation_error_message=validation_error_message) @@ -79,35 +90,36 @@ def _run_with_prompt( except PydanticOutputParserException as e: # Generation step is expensive. After a second unsuccessful attempt, it's better to send a failure message. if len(intermediate_steps) >= 2: - return { - "messages": [ + return PartialAssistantState( + messages=[ FailureMessage( content=f"Oops! It looks like I’m having trouble generating this {self.INSIGHT_NAME} insight. Could you please try again?" ) ], - "intermediate_steps": None, - } + intermediate_steps=None, + ) - return { - "intermediate_steps": [ + return PartialAssistantState( + intermediate_steps=[ *intermediate_steps, (AgentAction("handle_incorrect_response", e.llm_output, e.validation_message), None), ], - } + ) - return { - "messages": [ + return PartialAssistantState( + messages=[ VisualizationMessage( plan=generated_plan, answer=message.query, - done=True, + initiator=start_id, + id=str(uuid4()), ) ], - "intermediate_steps": None, - } + intermediate_steps=None, + ) def router(self, state: AssistantState): - if state.get("intermediate_steps") is not None: + if state.intermediate_steps: return "tools" return "next" @@ -123,15 +135,25 @@ def _group_mapping_prompt(self) -> str: ) return ET.tostring(root, encoding="unicode") + def _get_human_viz_message_mapping(self, messages: Sequence[AssistantMessageUnion]) -> dict[str, int]: + mapping: dict[str, int] = {} + for idx, msg in enumerate(messages): + if isinstance(msg, VisualizationMessage) and msg.initiator is not None: + mapping[msg.initiator] = idx + return mapping + def _construct_messages( self, state: AssistantState, validation_error_message: Optional[str] = None ) -> list[BaseMessage]: """ Reconstruct the conversation for the generation. Take all previously generated questions, plans, and schemas, and return the history. """ - messages = state.get("messages", []) - generated_plan = state.get("plan", "") + messages = state.messages + generated_plan = state.plan + start_id = state.start_id + if start_id is not None: + messages = slice_messages_to_conversation_start(messages, start_id) if len(messages) == 0: return [] @@ -141,43 +163,61 @@ def _construct_messages( ) ] - human_messages, visualization_messages = filter_visualization_conversation(messages) - first_ai_message = True + msg_mapping = self._get_human_viz_message_mapping(messages) + initiator_message = messages[-1] + last_viz_message = find_last_message_of_type(messages, VisualizationMessage) + + for message in messages: + # The initial human message and the new plan are added to the end of the conversation. + if message == initiator_message: + continue + if isinstance(message, HumanMessage): + if message.id and (viz_message_idx := msg_mapping.get(message.id)): + # Plans go first. + viz_message = messages[viz_message_idx] + if isinstance(viz_message, VisualizationMessage): + conversation.append( + HumanMessagePromptTemplate.from_template(PLAN_PROMPT, template_format="mustache").format( + plan=viz_message.plan or "" + ) + ) - for idx, (human_message, ai_message) in enumerate( - itertools.zip_longest(human_messages, visualization_messages) - ): - # Plans go first - if ai_message: - conversation.append( - HumanMessagePromptTemplate.from_template( - PLAN_PROMPT if first_ai_message else NEW_PLAN_PROMPT, - template_format="mustache", - ).format(plan=ai_message.plan or "") - ) - first_ai_message = False - elif generated_plan: - conversation.append( - HumanMessagePromptTemplate.from_template( - PLAN_PROMPT if first_ai_message else NEW_PLAN_PROMPT, - template_format="mustache", - ).format(plan=generated_plan) + # Augment with the prompt previous initiator messages. + conversation.append( + HumanMessagePromptTemplate.from_template(QUESTION_PROMPT, template_format="mustache").format( + question=message.content + ) + ) + # Otherwise, just append the human message. + else: + conversation.append(LangchainHumanMessage(content=message.content)) + # Summary, human-in-the-loop messages. + elif isinstance(message, AssistantMessage): + conversation.append(LangchainAssistantMessage(content=message.content)) + + # Include only last generated schema because it doesn't need more context. + if last_viz_message: + conversation.append( + LangchainAssistantMessage( + content=last_viz_message.answer.model_dump_json() if last_viz_message.answer else "" ) - - # Then questions - if human_message: + ) + # Add the initiator message and the generated plan to the end, so instructions are clear. + if isinstance(initiator_message, HumanMessage): + if generated_plan: + plan_prompt = PLAN_PROMPT if messages[0] == initiator_message else NEW_PLAN_PROMPT conversation.append( - HumanMessagePromptTemplate.from_template(QUESTION_PROMPT, template_format="mustache").format( - question=human_message.content + HumanMessagePromptTemplate.from_template(plan_prompt, template_format="mustache").format( + plan=generated_plan or "" ) ) - - # Then schemas, but include only last generated schema because it doesn't need more context. - if ai_message and idx + 1 == len(visualization_messages): - conversation.append( - LangchainAssistantMessage(content=ai_message.answer.model_dump_json() if ai_message.answer else "") + conversation.append( + HumanMessagePromptTemplate.from_template(QUESTION_PROMPT, template_format="mustache").format( + question=initiator_message.content ) + ) + # Retries must be added to the end of the conversation. if validation_error_message: conversation.append( HumanMessagePromptTemplate.from_template(FAILOVER_PROMPT, template_format="mustache").format( @@ -193,10 +233,10 @@ class SchemaGeneratorToolsNode(AssistantNode): Used for failover from generation errors. """ - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: - intermediate_steps = state.get("intermediate_steps", []) + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: + intermediate_steps = state.intermediate_steps or [] if not intermediate_steps: - return state + return PartialAssistantState() action, _ = intermediate_steps[-1] prompt = ( @@ -205,9 +245,9 @@ def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: .content ) - return { - "intermediate_steps": [ + return PartialAssistantState( + intermediate_steps=[ *intermediate_steps[:-1], (action, str(prompt)), ] - } + ) diff --git a/ee/hogai/schema_generator/test/test_nodes.py b/ee/hogai/schema_generator/test/test_nodes.py index 795045af50b56..b44154b93b927 100644 --- a/ee/hogai/schema_generator/test/test_nodes.py +++ b/ee/hogai/schema_generator/test/test_nodes.py @@ -4,10 +4,11 @@ from django.test import override_settings from langchain_core.agents import AgentAction from langchain_core.prompts import ChatPromptTemplate -from langchain_core.runnables import RunnableLambda +from langchain_core.runnables import RunnableConfig, RunnableLambda from ee.hogai.schema_generator.nodes import SchemaGeneratorNode, SchemaGeneratorToolsNode from ee.hogai.schema_generator.utils import SchemaGeneratorOutput +from ee.hogai.utils.types import AssistantState, PartialAssistantState from posthog.schema import ( AssistantMessage, AssistantTrendsQuery, @@ -16,7 +17,7 @@ RouterMessage, VisualizationMessage, ) -from posthog.test.base import APIBaseTest, ClickhouseTestMixin +from posthog.test.base import BaseTest TestSchema = SchemaGeneratorOutput[AssistantTrendsQuery] @@ -26,7 +27,7 @@ class DummyGeneratorNode(SchemaGeneratorNode[AssistantTrendsQuery]): OUTPUT_MODEL = SchemaGeneratorOutput[AssistantTrendsQuery] OUTPUT_SCHEMA = {} - def run(self, state, config): + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: prompt = ChatPromptTemplate.from_messages( [ ("system", "system_prompt"), @@ -36,8 +37,9 @@ def run(self, state, config): @override_settings(IN_UNIT_TESTING=True) -class TestSchemaGeneratorNode(ClickhouseTestMixin, APIBaseTest): +class TestSchemaGeneratorNode(BaseTest): def setUp(self): + super().setUp() self.schema = AssistantTrendsQuery(series=[]) def test_node_runs(self): @@ -45,23 +47,23 @@ def test_node_runs(self): with patch.object(DummyGeneratorNode, "_model") as generator_model_mock: generator_model_mock.return_value = RunnableLambda(lambda _: TestSchema(query=self.schema).model_dump()) new_state = node.run( - { - "messages": [HumanMessage(content="Text")], - "plan": "Plan", - }, + AssistantState( + messages=[HumanMessage(content="Text", id="0")], + plan="Plan", + start_id="0", + ), {}, ) - self.assertEqual( - new_state, - { - "messages": [VisualizationMessage(answer=self.schema, plan="Plan", done=True)], - "intermediate_steps": None, - }, - ) + self.assertIsNone(new_state.intermediate_steps) + self.assertEqual(len(new_state.messages), 1) + self.assertEqual(new_state.messages[0].type, "ai/viz") + self.assertEqual(new_state.messages[0].answer, self.schema) - def test_agent_reconstructs_conversation(self): + def test_agent_reconstructs_conversation_and_does_not_add_an_empty_plan(self): node = DummyGeneratorNode(self.team) - history = node._construct_messages({"messages": [HumanMessage(content="Text")]}) + history = node._construct_messages( + AssistantState(messages=[HumanMessage(content="Text", id="0")], start_id="0") + ) self.assertEqual(len(history), 2) self.assertEqual(history[0].type, "human") self.assertIn("mapping", history[0].content) @@ -69,7 +71,11 @@ def test_agent_reconstructs_conversation(self): self.assertIn("Answer to this question:", history[1].content) self.assertNotIn("{{question}}", history[1].content) - history = node._construct_messages({"messages": [HumanMessage(content="Text")], "plan": "randomplan"}) + def test_agent_reconstructs_conversation_adds_plan(self): + node = DummyGeneratorNode(self.team) + history = node._construct_messages( + AssistantState(messages=[HumanMessage(content="Text", id="0")], plan="randomplan", start_id="0") + ) self.assertEqual(len(history), 3) self.assertEqual(history[0].type, "human") self.assertIn("mapping", history[0].content) @@ -82,16 +88,18 @@ def test_agent_reconstructs_conversation(self): self.assertNotIn("{{question}}", history[2].content) self.assertIn("Text", history[2].content) + def test_agent_reconstructs_conversation_can_handle_follow_ups(self): node = DummyGeneratorNode(self.team) history = node._construct_messages( - { - "messages": [ - HumanMessage(content="Text"), - VisualizationMessage(answer=self.schema, plan="randomplan"), - HumanMessage(content="Follow Up"), + AssistantState( + messages=[ + HumanMessage(content="Text", id="0"), + VisualizationMessage(answer=self.schema, plan="randomplan", id="1", initiator="0"), + HumanMessage(content="Follow Up", id="2"), ], - "plan": "newrandomplan", - } + plan="newrandomplan", + start_id="2", + ) ) self.assertEqual(len(history), 6) @@ -116,13 +124,41 @@ def test_agent_reconstructs_conversation(self): self.assertNotIn("{{question}}", history[5].content) self.assertIn("Follow Up", history[5].content) - def test_agent_reconstructs_conversation_and_merges_messages(self): + def test_agent_reconstructs_conversation_and_does_not_merge_messages(self): + node = DummyGeneratorNode(self.team) + history = node._construct_messages( + AssistantState( + messages=[HumanMessage(content="Te", id="0"), HumanMessage(content="xt", id="1")], + plan="randomplan", + start_id="1", + ) + ) + self.assertEqual(len(history), 4) + self.assertEqual(history[0].type, "human") + self.assertIn("mapping", history[0].content) + self.assertIn("Te", history[1].content) + self.assertEqual(history[2].type, "human") + self.assertNotIn("{{plan}}", history[2].content) + self.assertIn("randomplan", history[2].content) + self.assertEqual(history[3].type, "human") + self.assertIn("Answer to this question:", history[3].content) + self.assertNotIn("{{question}}", history[3].content) + self.assertEqual(history[3].type, "human") + self.assertIn("xt", history[3].content) + + def test_filters_out_human_in_the_loop_after_initiator(self): node = DummyGeneratorNode(self.team) history = node._construct_messages( - { - "messages": [HumanMessage(content="Te"), HumanMessage(content="xt")], - "plan": "randomplan", - } + AssistantState( + messages=[ + HumanMessage(content="Text", id="0"), + VisualizationMessage(answer=self.schema, plan="randomplan", initiator="0", id="1"), + HumanMessage(content="Follow", id="2"), + HumanMessage(content="Up", id="3"), + ], + plan="newrandomplan", + start_id="0", + ) ) self.assertEqual(len(history), 3) self.assertEqual(history[0].type, "human") @@ -134,104 +170,114 @@ def test_agent_reconstructs_conversation_and_merges_messages(self): self.assertEqual(history[2].type, "human") self.assertIn("Answer to this question:", history[2].content) self.assertNotIn("{{question}}", history[2].content) - self.assertIn("Te\nxt", history[2].content) + self.assertIn("Text", history[2].content) + def test_preserves_human_in_the_loop_before_initiator(self): node = DummyGeneratorNode(self.team) history = node._construct_messages( - { - "messages": [ - HumanMessage(content="Text"), - VisualizationMessage(answer=self.schema, plan="randomplan"), - HumanMessage(content="Follow"), - HumanMessage(content="Up"), + AssistantState( + messages=[ + HumanMessage(content="Question 1", id="0"), + AssistantMessage(content="Loop", id="1"), + HumanMessage(content="Answer", id="2"), + VisualizationMessage(answer=self.schema, plan="randomplan", initiator="0", id="3"), + HumanMessage(content="Question 2", id="4"), ], - "plan": "newrandomplan", - } + plan="newrandomplan", + start_id="4", + ) ) - - self.assertEqual(len(history), 6) + self.assertEqual(len(history), 8) self.assertEqual(history[0].type, "human") self.assertIn("mapping", history[0].content) self.assertEqual(history[1].type, "human") self.assertIn("the plan", history[1].content) self.assertNotIn("{{plan}}", history[1].content) self.assertIn("randomplan", history[1].content) - self.assertEqual(history[2].type, "human") - self.assertIn("Answer to this question:", history[2].content) self.assertNotIn("{{question}}", history[2].content) - self.assertIn("Text", history[2].content) + self.assertIn("Question 1", history[2].content) self.assertEqual(history[3].type, "ai") - self.assertEqual(history[3].content, self.schema.model_dump_json()) + self.assertEqual("Loop", history[3].content) self.assertEqual(history[4].type, "human") - self.assertIn("the new plan", history[4].content) - self.assertNotIn("{{plan}}", history[4].content) - self.assertIn("newrandomplan", history[4].content) - self.assertEqual(history[5].type, "human") - self.assertIn("Answer to this question:", history[5].content) - self.assertNotIn("{{question}}", history[5].content) - self.assertIn("Follow\nUp", history[5].content) + self.assertEqual("Answer", history[4].content) + self.assertEqual(history[5].type, "ai") + self.assertEqual(history[6].type, "human") + self.assertIn("the new plan", history[6].content) + self.assertIn("newrandomplan", history[6].content) + self.assertEqual(history[7].type, "human") + self.assertNotIn("{{question}}", history[7].content) + self.assertIn("Question 2", history[7].content) def test_agent_reconstructs_typical_conversation(self): node = DummyGeneratorNode(self.team) history = node._construct_messages( - { - "messages": [ - HumanMessage(content="Question 1"), - RouterMessage(content="trends"), - VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 1"), - AssistantMessage(content="Summary 1"), - HumanMessage(content="Question 2"), - RouterMessage(content="funnel"), - VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 2"), - AssistantMessage(content="Summary 2"), - HumanMessage(content="Question 3"), - RouterMessage(content="funnel"), + AssistantState( + messages=[ + HumanMessage(content="Question 1", id="0"), + RouterMessage(content="trends", id="1"), + VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 1", initiator="0", id="2"), + AssistantMessage(content="Summary 1", id="3"), + HumanMessage(content="Question 2", id="4"), + RouterMessage(content="funnel", id="5"), + VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 2", initiator="4", id="6"), + AssistantMessage(content="Summary 2", id="7"), + HumanMessage(content="Question 3", id="8"), + RouterMessage(content="funnel", id="9"), ], - "plan": "Plan 3", - } + plan="Plan 3", + start_id="8", + ) ) - self.assertEqual(len(history), 8) + + self.assertEqual(len(history), 10) self.assertEqual(history[0].type, "human") self.assertIn("mapping", history[0].content) self.assertEqual(history[1].type, "human") self.assertIn("Plan 1", history[1].content) self.assertEqual(history[2].type, "human") self.assertIn("Question 1", history[2].content) - self.assertEqual(history[3].type, "human") - self.assertIn("Plan 2", history[3].content) + self.assertEqual(history[3].type, "ai") + self.assertEqual(history[3].content, "Summary 1") self.assertEqual(history[4].type, "human") - self.assertIn("Question 2", history[4].content) - self.assertEqual(history[5].type, "ai") - self.assertEqual(history[6].type, "human") - self.assertIn("Plan 3", history[6].content) - self.assertEqual(history[7].type, "human") - self.assertIn("Question 3", history[7].content) - - def test_prompt(self): + self.assertIn("Plan 2", history[4].content) + self.assertEqual(history[5].type, "human") + self.assertIn("Question 2", history[5].content) + self.assertEqual(history[6].type, "ai") + self.assertEqual(history[6].content, "Summary 2") + self.assertEqual(history[7].type, "ai") + self.assertEqual(history[8].type, "human") + self.assertIn("Plan 3", history[8].content) + self.assertEqual(history[9].type, "human") + self.assertIn("Question 3", history[9].content) + + def test_prompt_messages_merged(self): node = DummyGeneratorNode(self.team) - state = { - "messages": [ - HumanMessage(content="Question 1"), - RouterMessage(content="trends"), - VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 1"), - AssistantMessage(content="Summary 1"), - HumanMessage(content="Question 2"), - RouterMessage(content="funnel"), - VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 2"), - AssistantMessage(content="Summary 2"), - HumanMessage(content="Question 3"), - RouterMessage(content="funnel"), + state = AssistantState( + messages=[ + HumanMessage(content="Question 1", id="0"), + RouterMessage(content="trends", id="1"), + VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 1", initiator="0", id="2"), + AssistantMessage(content="Summary 1", id="3"), + HumanMessage(content="Question 2", id="4"), + RouterMessage(content="funnel", id="5"), + VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 2", initiator="4", id="6"), + AssistantMessage(content="Summary 2", id="7"), + HumanMessage(content="Question 3", id="8"), + RouterMessage(content="funnel", id="9"), ], - "plan": "Plan 3", - } + plan="Plan 3", + start_id="8", + ) with patch.object(DummyGeneratorNode, "_model") as generator_model_mock: def assert_prompt(prompt): - self.assertEqual(len(prompt), 4) + self.assertEqual(len(prompt), 6) self.assertEqual(prompt[0].type, "system") self.assertEqual(prompt[1].type, "human") self.assertEqual(prompt[2].type, "ai") self.assertEqual(prompt[3].type, "human") + self.assertEqual(prompt[4].type, "ai") + self.assertEqual(prompt[5].type, "human") generator_model_mock.return_value = RunnableLambda(assert_prompt) node.run(state, {}) @@ -244,19 +290,17 @@ def test_failover_with_incorrect_schema(self): schema["query"] = [] generator_model_mock.return_value = RunnableLambda(lambda _: json.dumps(schema)) - new_state = node.run({"messages": [HumanMessage(content="Text")]}, {}) - self.assertIn("intermediate_steps", new_state) - self.assertEqual(len(new_state["intermediate_steps"]), 1) + new_state = node.run(AssistantState(messages=[HumanMessage(content="Text")]), {}) + self.assertEqual(len(new_state.intermediate_steps), 1) new_state = node.run( - { - "messages": [HumanMessage(content="Text")], - "intermediate_steps": [(AgentAction(tool="", tool_input="", log="exception"), "exception")], - }, + AssistantState( + messages=[HumanMessage(content="Text")], + intermediate_steps=[(AgentAction(tool="", tool_input="", log="exception"), "exception")], + ), {}, ) - self.assertIn("intermediate_steps", new_state) - self.assertEqual(len(new_state["intermediate_steps"]), 2) + self.assertEqual(len(new_state.intermediate_steps), 2) def test_node_leaves_failover(self): node = DummyGeneratorNode(self.team) @@ -266,25 +310,25 @@ def test_node_leaves_failover(self): return_value=RunnableLambda(lambda _: TestSchema(query=self.schema).model_dump()), ): new_state = node.run( - { - "messages": [HumanMessage(content="Text")], - "intermediate_steps": [(AgentAction(tool="", tool_input="", log="exception"), "exception")], - }, + AssistantState( + messages=[HumanMessage(content="Text")], + intermediate_steps=[(AgentAction(tool="", tool_input="", log="exception"), "exception")], + ), {}, ) - self.assertIsNone(new_state["intermediate_steps"]) + self.assertIsNone(new_state.intermediate_steps) new_state = node.run( - { - "messages": [HumanMessage(content="Text")], - "intermediate_steps": [ + AssistantState( + messages=[HumanMessage(content="Text")], + intermediate_steps=[ (AgentAction(tool="", tool_input="", log="exception"), "exception"), (AgentAction(tool="", tool_input="", log="exception"), "exception"), ], - }, + ), {}, ) - self.assertIsNone(new_state["intermediate_steps"]) + self.assertIsNone(new_state.intermediate_steps) def test_node_leaves_failover_after_second_unsuccessful_attempt(self): node = DummyGeneratorNode(self.team) @@ -295,29 +339,30 @@ def test_node_leaves_failover_after_second_unsuccessful_attempt(self): generator_model_mock.return_value = RunnableLambda(lambda _: json.dumps(schema)) new_state = node.run( - { - "messages": [HumanMessage(content="Text")], - "intermediate_steps": [ + AssistantState( + messages=[HumanMessage(content="Text")], + intermediate_steps=[ (AgentAction(tool="", tool_input="", log="exception"), "exception"), (AgentAction(tool="", tool_input="", log="exception"), "exception"), ], - }, + ), {}, ) - self.assertIsNone(new_state["intermediate_steps"]) - self.assertEqual(len(new_state["messages"]), 1) - self.assertIsInstance(new_state["messages"][0], FailureMessage) + self.assertIsNone(new_state.intermediate_steps) + self.assertEqual(len(new_state.messages), 1) + self.assertIsInstance(new_state.messages[0], FailureMessage) def test_agent_reconstructs_conversation_with_failover(self): action = AgentAction(tool="fix", tool_input="validation error", log="exception") node = DummyGeneratorNode(self.team) history = node._construct_messages( - { - "messages": [HumanMessage(content="Text")], - "plan": "randomplan", - "intermediate_steps": [(action, "uniqexception")], - }, - "uniqexception", + AssistantState( + messages=[HumanMessage(content="Text", id="0")], + plan="randomplan", + intermediate_steps=[(action, "uniqexception")], + start_id="0", + ), + validation_error_message="uniqexception", ) self.assertEqual(len(history), 4) self.assertEqual(history[0].type, "human") @@ -337,14 +382,14 @@ def test_agent_reconstructs_conversation_with_failover(self): def test_agent_reconstructs_conversation_with_failed_messages(self): node = DummyGeneratorNode(self.team) history = node._construct_messages( - { - "messages": [ + AssistantState( + messages=[ HumanMessage(content="Text"), FailureMessage(content="Error"), HumanMessage(content="Text"), ], - "plan": "randomplan", - }, + plan="randomplan", + ), ) self.assertEqual(len(history), 3) self.assertEqual(history[0].type, "human") @@ -360,19 +405,19 @@ def test_agent_reconstructs_conversation_with_failed_messages(self): def test_router(self): node = DummyGeneratorNode(self.team) - state = node.router({"messages": [], "intermediate_steps": None}) + state = node.router(AssistantState(messages=[], intermediate_steps=None)) self.assertEqual(state, "next") state = node.router( - {"messages": [], "intermediate_steps": [(AgentAction(tool="", tool_input="", log=""), None)]} + AssistantState(messages=[], intermediate_steps=[(AgentAction(tool="", tool_input="", log=""), None)]) ) self.assertEqual(state, "tools") -class TestSchemaGeneratorToolsNode(ClickhouseTestMixin, APIBaseTest): +class TestSchemaGeneratorToolsNode(BaseTest): def test_tools_node(self): node = SchemaGeneratorToolsNode(self.team) action = AgentAction(tool="fix", tool_input="validationerror", log="pydanticexception") - state = node.run({"messages": [], "intermediate_steps": [(action, None)]}, {}) - self.assertIsNotNone("validationerror", state["intermediate_steps"][0][1]) - self.assertIn("validationerror", state["intermediate_steps"][0][1]) - self.assertIn("pydanticexception", state["intermediate_steps"][0][1]) + state = node.run(AssistantState(messages=[], intermediate_steps=[(action, None)]), {}) + self.assertIsNotNone("validationerror", state.intermediate_steps[0][1]) + self.assertIn("validationerror", state.intermediate_steps[0][1]) + self.assertIn("pydanticexception", state.intermediate_steps[0][1]) diff --git a/ee/hogai/summarizer/nodes.py b/ee/hogai/summarizer/nodes.py index 8d5e8a406f45e..513246bcc1238 100644 --- a/ee/hogai/summarizer/nodes.py +++ b/ee/hogai/summarizer/nodes.py @@ -1,15 +1,18 @@ import json from time import sleep +from uuid import uuid4 + from django.conf import settings +from django.core.serializers.json import DjangoJSONEncoder from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnableConfig from langchain_openai import ChatOpenAI -from django.core.serializers.json import DjangoJSONEncoder from rest_framework.exceptions import APIException from sentry_sdk import capture_exception -from ee.hogai.summarizer.prompts import SUMMARIZER_SYSTEM_PROMPT, SUMMARIZER_INSTRUCTION_PROMPT -from ee.hogai.utils import AssistantNode, AssistantNodeName, AssistantState +from ee.hogai.summarizer.prompts import SUMMARIZER_INSTRUCTION_PROMPT, SUMMARIZER_SYSTEM_PROMPT +from ee.hogai.utils.nodes import AssistantNode +from ee.hogai.utils.types import AssistantNodeName, AssistantState, PartialAssistantState from posthog.api.services.query import process_query_dict from posthog.clickhouse.client.execute_async import get_query_status from posthog.errors import ExposedCHQueryError @@ -21,8 +24,8 @@ class SummarizerNode(AssistantNode): name = AssistantNodeName.SUMMARIZER - def run(self, state: AssistantState, config: RunnableConfig): - viz_message = state["messages"][-1] + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: + viz_message = state.messages[-1] if not isinstance(viz_message, VisualizationMessage): raise ValueError("Can only run summarization with a visualization message as the last one in the state") if viz_message.answer is None: @@ -58,10 +61,16 @@ def run(self, state: AssistantState, config: RunnableConfig): err_message = ", ".join(f"{key}: {value}" for key, value in err.detail.items()) elif isinstance(err.detail, list): err_message = ", ".join(map(str, err.detail)) - return {"messages": [FailureMessage(content=f"There was an error running this query: {err_message}")]} + return PartialAssistantState( + messages=[ + FailureMessage(content=f"There was an error running this query: {err_message}", id=str(uuid4())) + ] + ) except Exception as err: capture_exception(err) - return {"messages": [FailureMessage(content="There was an unknown error running this query.")]} + return PartialAssistantState( + messages=[FailureMessage(content="There was an unknown error running this query.", id=str(uuid4()))] + ) summarization_prompt = ChatPromptTemplate(self._construct_messages(state), template_format="mustache") @@ -76,7 +85,7 @@ def run(self, state: AssistantState, config: RunnableConfig): config, ) - return {"messages": [AssistantMessage(content=str(message.content), done=True)]} + return PartialAssistantState(messages=[AssistantMessage(content=str(message.content), id=str(uuid4()))]) @property def _model(self): @@ -85,7 +94,7 @@ def _model(self): def _construct_messages(self, state: AssistantState) -> list[tuple[str, str]]: conversation: list[tuple[str, str]] = [("system", SUMMARIZER_SYSTEM_PROMPT)] - for message in state.get("messages", []): + for message in state.messages: if isinstance(message, HumanMessage): conversation.append(("human", message.content)) elif isinstance(message, AssistantMessage): diff --git a/ee/hogai/summarizer/test/test_nodes.py b/ee/hogai/summarizer/test/test_nodes.py index b38d88275aa19..9c54517717b5f 100644 --- a/ee/hogai/summarizer/test/test_nodes.py +++ b/ee/hogai/summarizer/test/test_nodes.py @@ -1,23 +1,23 @@ from unittest.mock import patch from django.test import override_settings -from langchain_core.runnables import RunnableLambda from langchain_core.messages import ( HumanMessage as LangchainHumanMessage, ) +from langchain_core.runnables import RunnableLambda +from rest_framework.exceptions import ValidationError + from ee.hogai.summarizer.nodes import SummarizerNode from ee.hogai.summarizer.prompts import SUMMARIZER_INSTRUCTION_PROMPT, SUMMARIZER_SYSTEM_PROMPT +from ee.hogai.utils.types import AssistantState +from posthog.api.services.query import process_query_dict from posthog.schema import ( - AssistantMessage, AssistantTrendsEventsNode, AssistantTrendsQuery, - FailureMessage, HumanMessage, VisualizationMessage, ) -from rest_framework.exceptions import ValidationError from posthog.test.base import APIBaseTest, ClickhouseTestMixin -from posthog.api.services.query import process_query_dict @override_settings(IN_UNIT_TESTING=True) @@ -32,28 +32,26 @@ def test_node_runs(self, mock_process_query_dict): lambda _: LangchainHumanMessage(content="The results indicate foobar.") ) new_state = node.run( - { - "messages": [ - HumanMessage(content="Text"), + AssistantState( + messages=[ + HumanMessage(content="Text", id="test"), VisualizationMessage( answer=AssistantTrendsQuery(series=[AssistantTrendsEventsNode()]), plan="Plan", - done=True, + id="test2", + initiator="test", ), ], - "plan": "Plan", - }, + plan="Plan", + start_id="test", + ), {}, ) mock_process_query_dict.assert_called_once() # Query processing started - self.assertEqual( - new_state, - { - "messages": [ - AssistantMessage(content="The results indicate foobar.", done=True), - ], - }, - ) + msg = new_state.messages[0] + self.assertEqual(msg.content, "The results indicate foobar.") + self.assertEqual(msg.type, "ai") + self.assertIsNotNone(msg.id) @patch( "ee.hogai.summarizer.nodes.process_query_dict", @@ -66,28 +64,26 @@ def test_node_handles_internal_error(self, mock_process_query_dict): lambda _: LangchainHumanMessage(content="The results indicate foobar.") ) new_state = node.run( - { - "messages": [ - HumanMessage(content="Text"), + AssistantState( + messages=[ + HumanMessage(content="Text", id="test"), VisualizationMessage( answer=AssistantTrendsQuery(series=[AssistantTrendsEventsNode()]), plan="Plan", - done=True, + id="test2", + initiator="test", ), ], - "plan": "Plan", - }, + plan="Plan", + start_id="test", + ), {}, ) mock_process_query_dict.assert_called_once() # Query processing started - self.assertEqual( - new_state, - { - "messages": [ - FailureMessage(content="There was an unknown error running this query."), - ], - }, - ) + msg = new_state.messages[0] + self.assertEqual(msg.content, "There was an unknown error running this query.") + self.assertEqual(msg.type, "ai/failure") + self.assertIsNotNone(msg.id) @patch( "ee.hogai.summarizer.nodes.process_query_dict", @@ -102,33 +98,29 @@ def test_node_handles_exposed_error(self, mock_process_query_dict): lambda _: LangchainHumanMessage(content="The results indicate foobar.") ) new_state = node.run( - { - "messages": [ - HumanMessage(content="Text"), + AssistantState( + messages=[ + HumanMessage(content="Text", id="test"), VisualizationMessage( answer=AssistantTrendsQuery(series=[AssistantTrendsEventsNode()]), plan="Plan", - done=True, + id="test2", + initiator="test", ), ], - "plan": "Plan", - }, + plan="Plan", + start_id="test", + ), {}, ) mock_process_query_dict.assert_called_once() # Query processing started + msg = new_state.messages[0] self.assertEqual( - new_state, - { - "messages": [ - FailureMessage( - content=( - "There was an error running this query: This query exceeds the capabilities of our picolator. " - "Try de-brolling its flim-flam." - ) - ), - ], - }, + msg.content, + "There was an error running this query: This query exceeds the capabilities of our picolator. Try de-brolling its flim-flam.", ) + self.assertEqual(msg.type, "ai/failure") + self.assertIsNotNone(msg.id) def test_node_requires_a_viz_message_in_state(self): node = SummarizerNode(self.team) @@ -137,12 +129,13 @@ def test_node_requires_a_viz_message_in_state(self): ValueError, "Can only run summarization with a visualization message as the last one in the state" ): node.run( - { - "messages": [ + AssistantState( + messages=[ HumanMessage(content="Text"), ], - "plan": "Plan", - }, + plan="Plan", + start_id="test", + ), {}, ) @@ -151,16 +144,13 @@ def test_node_requires_viz_message_in_state_to_have_query(self): with self.assertRaisesMessage(ValueError, "Did not found query in the visualization message"): node.run( - { - "messages": [ - VisualizationMessage( - answer=None, - plan="Plan", - done=True, - ), + AssistantState( + messages=[ + VisualizationMessage(answer=None, plan="Plan", id="test"), ], - "plan": "Plan", - }, + plan="Plan", + start_id="test", + ), {}, ) @@ -170,16 +160,18 @@ def test_agent_reconstructs_conversation(self): node = SummarizerNode(self.team) history = node._construct_messages( - { - "messages": [ - HumanMessage(content="What's the trends in signups?"), + AssistantState( + messages=[ + HumanMessage(content="What's the trends in signups?", id="test"), VisualizationMessage( answer=AssistantTrendsQuery(series=[AssistantTrendsEventsNode()]), plan="Plan", - done=True, + id="test2", + initiator="test", ), - ] - } + ], + start_id="test", + ) ) self.assertEqual( history, diff --git a/ee/hogai/taxonomy_agent/nodes.py b/ee/hogai/taxonomy_agent/nodes.py index 025058a51eec1..bd26a7a93918f 100644 --- a/ee/hogai/taxonomy_agent/nodes.py +++ b/ee/hogai/taxonomy_agent/nodes.py @@ -1,4 +1,3 @@ -import itertools import xml.etree.ElementTree as ET from abc import ABC from functools import cached_property @@ -7,10 +6,16 @@ from git import Optional from langchain.agents.format_scratchpad import format_log_to_str from langchain_core.agents import AgentAction -from langchain_core.messages import AIMessage as LangchainAssistantMessage, BaseMessage, merge_message_runs +from langchain_core.messages import ( + AIMessage as LangchainAssistantMessage, + BaseMessage, + HumanMessage as LangchainHumanMessage, + merge_message_runs, +) from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate from langchain_core.runnables import RunnableConfig from langchain_openai import ChatOpenAI +from langgraph.errors import NodeInterrupt from pydantic import ValidationError from ee.hogai.taxonomy import CORE_FILTER_DEFINITIONS_BY_GROUP @@ -24,6 +29,7 @@ REACT_FOLLOW_UP_PROMPT, REACT_FORMAT_PROMPT, REACT_FORMAT_REMINDER_PROMPT, + REACT_HUMAN_IN_THE_LOOP_PROMPT, REACT_MALFORMED_JSON_PROMPT, REACT_MISSING_ACTION_CORRECTION_PROMPT, REACT_MISSING_ACTION_PROMPT, @@ -33,13 +39,18 @@ REACT_USER_PROMPT, ) from ee.hogai.taxonomy_agent.toolkit import TaxonomyAgentTool, TaxonomyAgentToolkit -from ee.hogai.utils import AssistantNode, AssistantState, filter_visualization_conversation, remove_line_breaks +from ee.hogai.utils.helpers import filter_messages, remove_line_breaks, slice_messages_to_conversation_start +from ee.hogai.utils.nodes import AssistantNode +from ee.hogai.utils.types import AssistantState, PartialAssistantState from posthog.hogql_queries.ai.team_taxonomy_query_runner import TeamTaxonomyQueryRunner from posthog.hogql_queries.query_runner import ExecutionMode from posthog.models.group_type_mapping import GroupTypeMapping from posthog.schema import ( + AssistantMessage, CachedTeamTaxonomyQueryResponse, + HumanMessage, TeamTaxonomyQuery, + VisualizationMessage, ) @@ -50,8 +61,8 @@ def _run_with_prompt_and_toolkit( prompt: ChatPromptTemplate, toolkit: TaxonomyAgentToolkit, config: Optional[RunnableConfig] = None, - ) -> AssistantState: - intermediate_steps = state.get("intermediate_steps") or [] + ) -> PartialAssistantState: + intermediate_steps = state.intermediate_steps or [] conversation = ( prompt + ChatPromptTemplate.from_messages( @@ -79,6 +90,7 @@ def _run_with_prompt_and_toolkit( "react_format": self._get_react_format_prompt(toolkit), "react_format_reminder": REACT_FORMAT_REMINDER_PROMPT, "react_property_filters": self._get_react_property_filters_prompt(), + "react_human_in_the_loop": REACT_HUMAN_IN_THE_LOOP_PROMPT, "product_description": self._team.project.product_description, "groups": self._team_group_types, "events": self._events_prompt, @@ -108,12 +120,12 @@ def _run_with_prompt_and_toolkit( e.llm_output, ) - return { - "intermediate_steps": [*intermediate_steps, (result, None)], - } + return PartialAssistantState( + intermediate_steps=[*intermediate_steps, (result, None)], + ) def router(self, state: AssistantState): - if state.get("intermediate_steps", []): + if state.intermediate_steps: return "tools" raise ValueError("Invalid state.") @@ -188,33 +200,34 @@ def _construct_messages(self, state: AssistantState) -> list[BaseMessage]: """ Reconstruct the conversation for the agent. On this step we only care about previously asked questions and generated plans. All other messages are filtered out. """ - human_messages, visualization_messages = filter_visualization_conversation(state.get("messages", [])) - - if not human_messages: - return [] - + start_id = state.start_id + filtered_messages = filter_messages(slice_messages_to_conversation_start(state.messages, start_id)) conversation = [] - for idx, messages in enumerate(itertools.zip_longest(human_messages, visualization_messages)): - human_message, viz_message = messages - - if human_message: + for idx, message in enumerate(filtered_messages): + if isinstance(message, HumanMessage): + # Add initial instructions. if idx == 0: conversation.append( HumanMessagePromptTemplate.from_template(REACT_USER_PROMPT, template_format="mustache").format( - question=human_message.content + question=message.content ) ) - else: + # Add follow-up instructions only for the human message that initiated a generation. + elif message.id == start_id: conversation.append( HumanMessagePromptTemplate.from_template( REACT_FOLLOW_UP_PROMPT, template_format="mustache", - ).format(feedback=human_message.content) + ).format(feedback=message.content) ) - - if viz_message: - conversation.append(LangchainAssistantMessage(content=viz_message.plan or "")) + # Everything else leave as is. + else: + conversation.append(LangchainHumanMessage(content=message.content)) + elif isinstance(message, VisualizationMessage): + conversation.append(LangchainAssistantMessage(content=message.plan or "")) + elif isinstance(message, AssistantMessage): + conversation.append(LangchainAssistantMessage(content=message.content)) return conversation @@ -230,26 +243,37 @@ def _get_agent_scratchpad(self, scratchpad: list[tuple[AgentAction, str | None]] class TaxonomyAgentPlannerToolsNode(AssistantNode, ABC): def _run_with_toolkit( self, state: AssistantState, toolkit: TaxonomyAgentToolkit, config: Optional[RunnableConfig] = None - ) -> AssistantState: - intermediate_steps = state.get("intermediate_steps") or [] - action, _ = intermediate_steps[-1] + ) -> PartialAssistantState: + intermediate_steps = state.intermediate_steps or [] + action, observation = intermediate_steps[-1] try: input = TaxonomyAgentTool.model_validate({"name": action.tool, "arguments": action.tool_input}).root except ValidationError as e: - observation = ( + observation = str( ChatPromptTemplate.from_template(REACT_PYDANTIC_VALIDATION_EXCEPTION_PROMPT, template_format="mustache") .format_messages(exception=e.errors(include_url=False))[0] .content ) - return {"intermediate_steps": [*intermediate_steps[:-1], (action, str(observation))]} + return PartialAssistantState( + intermediate_steps=[*intermediate_steps[:-1], (action, str(observation))], + ) # The plan has been found. Move to the generation. if input.name == "final_answer": - return { - "plan": input.arguments, - "intermediate_steps": None, - } + return PartialAssistantState( + plan=input.arguments, + intermediate_steps=[], + ) + if input.name == "ask_user_for_help": + # The agent has requested help, so we interrupt the graph. + if not observation: + raise NodeInterrupt(input.arguments) + + # Feedback was provided. + return PartialAssistantState( + intermediate_steps=[*intermediate_steps[:-1], (action, observation)], + ) output = "" if input.name == "retrieve_event_properties": @@ -263,9 +287,11 @@ def _run_with_toolkit( else: output = toolkit.handle_incorrect_response(input.arguments) - return {"intermediate_steps": [*intermediate_steps[:-1], (action, output)]} + return PartialAssistantState( + intermediate_steps=[*intermediate_steps[:-1], (action, output)], + ) def router(self, state: AssistantState): - if state.get("plan") is not None: + if state.plan is not None: return "plan_found" return "continue" diff --git a/ee/hogai/taxonomy_agent/prompts.py b/ee/hogai/taxonomy_agent/prompts.py index f63a7dfe15455..c9d409bcdf103 100644 --- a/ee/hogai/taxonomy_agent/prompts.py +++ b/ee/hogai/taxonomy_agent/prompts.py @@ -81,6 +81,15 @@ """.strip() +REACT_HUMAN_IN_THE_LOOP_PROMPT = """ + +Ask the user for clarification if: +- The user's question is ambiguous. +- You can't find matching events or properties. +- You're unable to build a plan that effectively answers the user's question. + +""".strip() + REACT_FORMAT_REMINDER_PROMPT = """ Begin! Reminder that you must ALWAYS respond with a valid JSON blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:```$JSON_BLOB``` then Observation. """.strip() diff --git a/ee/hogai/taxonomy_agent/test/test_nodes.py b/ee/hogai/taxonomy_agent/test/test_nodes.py index 40127c19370b6..cb25331664331 100644 --- a/ee/hogai/taxonomy_agent/test/test_nodes.py +++ b/ee/hogai/taxonomy_agent/test/test_nodes.py @@ -11,7 +11,7 @@ TaxonomyAgentPlannerToolsNode, ) from ee.hogai.taxonomy_agent.toolkit import TaxonomyAgentToolkit, ToolkitTool -from ee.hogai.utils import AssistantState +from ee.hogai.utils.types import AssistantState, PartialAssistantState from posthog.models import GroupTypeMapping from posthog.schema import ( AssistantMessage, @@ -37,7 +37,7 @@ def setUp(self): def _get_node(self): class Node(TaxonomyAgentPlannerNode): - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: prompt: ChatPromptTemplate = ChatPromptTemplate.from_messages([("user", "test")]) toolkit = DummyToolkit(self._team) return super()._run_with_prompt_and_toolkit(state, prompt, toolkit, config=config) @@ -46,19 +46,20 @@ def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: def test_agent_reconstructs_conversation(self): node = self._get_node() - history = node._construct_messages({"messages": [HumanMessage(content="Text")]}) + history = node._construct_messages(AssistantState(messages=[HumanMessage(content="Text")])) self.assertEqual(len(history), 1) self.assertEqual(history[0].type, "human") self.assertIn("Text", history[0].content) self.assertNotIn(f"{{question}}", history[0].content) history = node._construct_messages( - { - "messages": [ - HumanMessage(content="Text"), - VisualizationMessage(answer=self.schema, plan="randomplan"), - ] - } + AssistantState( + messages=[ + HumanMessage(content="Text", id="0"), + VisualizationMessage(answer=self.schema, plan="randomplan", id="1", initiator="0"), + ], + start_id="1", + ) ) self.assertEqual(len(history), 2) self.assertEqual(history[0].type, "human") @@ -68,13 +69,14 @@ def test_agent_reconstructs_conversation(self): self.assertEqual(history[1].content, "randomplan") history = node._construct_messages( - { - "messages": [ - HumanMessage(content="Text"), - VisualizationMessage(answer=self.schema, plan="randomplan"), - HumanMessage(content="Text"), - ] - } + AssistantState( + messages=[ + HumanMessage(content="Text", id="0"), + VisualizationMessage(answer=self.schema, plan="randomplan", id="1", initiator="0"), + HumanMessage(content="Text", id="2"), + ], + start_id="2", + ) ) self.assertEqual(len(history), 3) self.assertEqual(history[0].type, "human") @@ -89,12 +91,14 @@ def test_agent_reconstructs_conversation(self): def test_agent_reconstructs_conversation_and_omits_unknown_messages(self): node = self._get_node() history = node._construct_messages( - { - "messages": [ - HumanMessage(content="Text"), - AssistantMessage(content="test"), - ] - } + AssistantState( + messages=[ + HumanMessage(content="Text", id="0"), + RouterMessage(content="trends", id="1"), + AssistantMessage(content="test", id="2"), + ], + start_id="0", + ) ) self.assertEqual(len(history), 1) self.assertEqual(history[0].type, "human") @@ -104,13 +108,13 @@ def test_agent_reconstructs_conversation_and_omits_unknown_messages(self): def test_agent_reconstructs_conversation_with_failures(self): node = self._get_node() history = node._construct_messages( - { - "messages": [ + AssistantState( + messages=[ HumanMessage(content="Text"), FailureMessage(content="Error"), HumanMessage(content="Text"), - ] - } + ], + ) ) self.assertEqual(len(history), 1) self.assertEqual(history[0].type, "human") @@ -120,32 +124,60 @@ def test_agent_reconstructs_conversation_with_failures(self): def test_agent_reconstructs_typical_conversation(self): node = self._get_node() history = node._construct_messages( - { - "messages": [ - HumanMessage(content="Question 1"), - RouterMessage(content="trends"), - VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 1"), - AssistantMessage(content="Summary 1"), - HumanMessage(content="Question 2"), - RouterMessage(content="funnel"), - VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 2"), - AssistantMessage(content="Summary 2"), - HumanMessage(content="Question 3"), - RouterMessage(content="funnel"), - ] - } + AssistantState( + messages=[ + HumanMessage(content="Question 1", id="0"), + RouterMessage(content="trends", id="1"), + VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 1", id="2", initiator="0"), + AssistantMessage(content="Summary 1", id="3"), + HumanMessage(content="Question 2", id="4"), + RouterMessage(content="funnel", id="5"), + AssistantMessage(content="Loop 1", id="6"), + HumanMessage(content="Loop Answer 1", id="7"), + VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 2", id="8", initiator="4"), + AssistantMessage(content="Summary 2", id="9"), + HumanMessage(content="Question 3", id="10"), + RouterMessage(content="funnel", id="11"), + ], + start_id="10", + ) ) - self.assertEqual(len(history), 5) + self.assertEqual(len(history), 9) self.assertEqual(history[0].type, "human") self.assertIn("Question 1", history[0].content) self.assertEqual(history[1].type, "ai") self.assertEqual(history[1].content, "Plan 1") - self.assertEqual(history[2].type, "human") - self.assertIn("Question 2", history[2].content) - self.assertEqual(history[3].type, "ai") - self.assertEqual(history[3].content, "Plan 2") - self.assertEqual(history[4].type, "human") - self.assertIn("Question 3", history[4].content) + self.assertEqual(history[2].type, "ai") + self.assertEqual(history[2].content, "Summary 1") + self.assertEqual(history[3].type, "human") + self.assertIn("Question 2", history[3].content) + self.assertEqual(history[4].type, "ai") + self.assertEqual(history[4].content, "Loop 1") + self.assertEqual(history[5].type, "human") + self.assertEqual(history[5].content, "Loop Answer 1") + self.assertEqual(history[6].content, "Plan 2") + self.assertEqual(history[6].type, "ai") + self.assertEqual(history[7].type, "ai") + self.assertEqual(history[7].content, "Summary 2") + self.assertEqual(history[8].type, "human") + self.assertIn("Question 3", history[8].content) + + def test_agent_reconstructs_conversation_without_messages_after_parent(self): + node = self._get_node() + history = node._construct_messages( + AssistantState( + messages=[ + HumanMessage(content="Question 1", id="0"), + RouterMessage(content="trends", id="1"), + AssistantMessage(content="Loop 1", id="2"), + HumanMessage(content="Loop Answer 1", id="3"), + ], + start_id="0", + ) + ) + self.assertEqual(len(history), 1) + self.assertEqual(history[0].type, "human") + self.assertIn("Question 1", history[0].content) def test_agent_filters_out_low_count_events(self): _create_person(distinct_ids=["test"], team=self.team) @@ -182,9 +214,9 @@ def test_agent_handles_output_without_action_block(self): return_value=RunnableLambda(lambda _: LangchainAIMessage(content="I don't want to output an action.")), ): node = self._get_node() - state_update = node.run({"messages": [HumanMessage(content="Question")]}, {}) - self.assertEqual(len(state_update["intermediate_steps"]), 1) - action, obs = state_update["intermediate_steps"][0] + state_update = node.run(AssistantState(messages=[HumanMessage(content="Question")]), {}) + self.assertEqual(len(state_update.intermediate_steps), 1) + action, obs = state_update.intermediate_steps[0] self.assertIsNone(obs) self.assertIn("I don't want to output an action.", action.log) self.assertIn("Action:", action.log) @@ -196,9 +228,9 @@ def test_agent_handles_output_with_malformed_json(self): return_value=RunnableLambda(lambda _: LangchainAIMessage(content="Thought.\nAction: abc")), ): node = self._get_node() - state_update = node.run({"messages": [HumanMessage(content="Question")]}, {}) - self.assertEqual(len(state_update["intermediate_steps"]), 1) - action, obs = state_update["intermediate_steps"][0] + state_update = node.run(AssistantState(messages=[HumanMessage(content="Question")]), {}) + self.assertEqual(len(state_update.intermediate_steps), 1) + action, obs = state_update.intermediate_steps[0] self.assertIsNone(obs) self.assertIn("Thought.\nAction: abc", action.log) self.assertIn("action", action.tool_input) @@ -232,34 +264,34 @@ def test_property_filters_prompt(self): class TestTaxonomyAgentPlannerToolsNode(ClickhouseTestMixin, APIBaseTest): def _get_node(self): class Node(TaxonomyAgentPlannerToolsNode): - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: toolkit = DummyToolkit(self._team) return super()._run_with_toolkit(state, toolkit, config=config) return Node(self.team) def test_node_handles_action_name_validation_error(self): - state = { - "intermediate_steps": [(AgentAction(tool="does not exist", tool_input="input", log="log"), "test")], - "messages": [], - } + state = AssistantState( + intermediate_steps=[(AgentAction(tool="does not exist", tool_input="input", log="log"), "test")], + messages=[], + ) node = self._get_node() state_update = node.run(state, {}) - self.assertEqual(len(state_update["intermediate_steps"]), 1) - action, observation = state_update["intermediate_steps"][0] + self.assertEqual(len(state_update.intermediate_steps), 1) + action, observation = state_update.intermediate_steps[0] self.assertIsNotNone(observation) self.assertIn("", observation) def test_node_handles_action_input_validation_error(self): - state = { - "intermediate_steps": [ + state = AssistantState( + intermediate_steps=[ (AgentAction(tool="retrieve_entity_property_values", tool_input="input", log="log"), "test") ], - "messages": [], - } + messages=[], + ) node = self._get_node() state_update = node.run(state, {}) - self.assertEqual(len(state_update["intermediate_steps"]), 1) - action, observation = state_update["intermediate_steps"][0] + self.assertEqual(len(state_update.intermediate_steps), 1) + action, observation = state_update.intermediate_steps[0] self.assertIsNotNone(observation) self.assertIn("", observation) diff --git a/ee/hogai/taxonomy_agent/toolkit.py b/ee/hogai/taxonomy_agent/toolkit.py index dc8a0e092c2e6..d05b6f0c933ef 100644 --- a/ee/hogai/taxonomy_agent/toolkit.py +++ b/ee/hogai/taxonomy_agent/toolkit.py @@ -55,6 +55,7 @@ class SingleArgumentTaxonomyAgentTool(BaseModel): "retrieve_event_properties", "final_answer", "handle_incorrect_response", + "ask_user_for_help", ] arguments: str @@ -145,6 +146,16 @@ def _default_tools(self) -> list[ToolkitTool]: property_name: The name of the property that you want to retrieve values for. """, }, + { + "name": "ask_user_for_help", + "signature": "(question: str)", + "description": """ + Use this tool to ask a question to the user. Your question must be concise and clear. + + Args: + question: The question you want to ask. + """, + }, ] def render_text_description(self) -> str: diff --git a/ee/hogai/test/test_assistant.py b/ee/hogai/test/test_assistant.py index b6cd65bd4ec12..6d0bb8807d629 100644 --- a/ee/hogai/test/test_assistant.py +++ b/ee/hogai/test/test_assistant.py @@ -1,31 +1,63 @@ import json -from typing import Any +from typing import Any, Optional, cast from unittest.mock import patch -from uuid import uuid4 -from ee.hogai.utils import Conversation -from posthog.schema import AssistantMessage, HumanMessage -from ..assistant import Assistant + +from langchain_core import messages +from langchain_core.agents import AgentAction +from langchain_core.runnables import RunnableConfig, RunnableLambda from langgraph.graph.state import CompiledStateGraph +from langgraph.types import StateSnapshot +from pydantic import BaseModel + +from ee.models.assistant import Conversation +from posthog.schema import AssistantMessage, HumanMessage, ReasoningMessage +from posthog.test.base import NonAtomicBaseTest + +from ..assistant import Assistant from ..graph import AssistantGraph, AssistantNodeName -from posthog.test.base import BaseTest -from langchain_core.agents import AgentAction -class TestAssistant(BaseTest): - def _run_assistant_graph(self, test_graph: CompiledStateGraph) -> list[tuple[str, Any]]: +class TestAssistant(NonAtomicBaseTest): + CLASS_DATA_LEVEL_SETUP = False + + def setUp(self): + super().setUp() + self.conversation = Conversation.objects.create(team=self.team, user=self.user) + + def _run_assistant_graph( + self, + test_graph: Optional[CompiledStateGraph] = None, + message: Optional[str] = "Hello", + conversation: Optional[Conversation] = None, + is_new_conversation: bool = False, + ) -> list[tuple[str, Any]]: # Create assistant instance with our test graph assistant = Assistant( - team=self.team, - conversation=Conversation(messages=[HumanMessage(content="Hello")], session_id=str(uuid4())), + self.team, + conversation or self.conversation, + HumanMessage(content=message), + self.user, + is_new_conversation=is_new_conversation, ) - assistant._graph = test_graph + if test_graph: + assistant._graph = test_graph # Capture and parse output of assistant.stream() output: list[tuple[str, Any]] = [] for message in assistant.stream(): - event_line, data_line, *_ = message.split("\n") + event_line, data_line, *_ = cast(str, message).split("\n") output.append((event_line.removeprefix("event: "), json.loads(data_line.removeprefix("data: ")))) return output + def assertConversationEqual(self, output: list[tuple[str, Any]], expected_output: list[tuple[str, Any]]): + for i, ((output_msg_type, output_msg), (expected_msg_type, expected_msg)) in enumerate( + zip(output, expected_output) + ): + self.assertEqual(output_msg_type, expected_msg_type, f"Message type mismatch at index {i}") + msg_dict = ( + expected_msg.model_dump(exclude_none=True) if isinstance(expected_msg, BaseModel) else expected_msg + ) + self.assertDictContainsSubset(msg_dict, output_msg, f"Message content mismatch at index {i}") + @patch( "ee.hogai.trends.nodes.TrendsPlannerNode.run", return_value={"intermediate_steps": [(AgentAction(tool="final_answer", tool_input="", log=""), None)]}, @@ -39,19 +71,22 @@ def test_reasoning_messages_added(self, _mock_summarizer_run, _mock_funnel_plann .add_edge(AssistantNodeName.START, AssistantNodeName.TRENDS_PLANNER) .add_trends_planner(AssistantNodeName.SUMMARIZER) .add_summarizer(AssistantNodeName.END) - .compile() + .compile(), + conversation=self.conversation, ) # Assert that ReasoningMessages are added - assert output == [ - ("status", {"type": "ack"}), + expected_output = [ + ( + "message", + HumanMessage(content="Hello").model_dump(exclude_none=True), + ), ( "message", { "type": "ai/reasoning", "content": "Picking relevant events and properties", # For TrendsPlannerNode "substeps": [], - "done": True, }, ), ( @@ -60,7 +95,6 @@ def test_reasoning_messages_added(self, _mock_summarizer_run, _mock_funnel_plann "type": "ai/reasoning", "content": "Picking relevant events and properties", # For TrendsPlannerToolsNode "substeps": [], - "done": True, }, ), ( @@ -71,6 +105,7 @@ def test_reasoning_messages_added(self, _mock_summarizer_run, _mock_funnel_plann }, ), ] + self.assertConversationEqual(output, expected_output) @patch( "ee.hogai.trends.nodes.TrendsPlannerNode.run", @@ -105,19 +140,22 @@ def test_reasoning_messages_with_substeps_added(self, _mock_funnel_planner_run): AssistantGraph(self.team) .add_edge(AssistantNodeName.START, AssistantNodeName.TRENDS_PLANNER) .add_trends_planner(AssistantNodeName.END) - .compile() + .compile(), + conversation=self.conversation, ) # Assert that ReasoningMessages are added - assert output == [ - ("status", {"type": "ack"}), + expected_output = [ + ( + "message", + HumanMessage(content="Hello").model_dump(exclude_none=True), + ), ( "message", { "type": "ai/reasoning", "content": "Picking relevant events and properties", # For TrendsPlannerNode "substeps": [], - "done": True, }, ), ( @@ -131,7 +169,153 @@ def test_reasoning_messages_with_substeps_added(self, _mock_funnel_planner_run): "Analyzing `currency` event's property `purchase`", "Analyzing person property `country_of_birth`", ], - "done": True, }, ), ] + self.assertConversationEqual(output, expected_output) + + def _test_human_in_the_loop(self, graph: CompiledStateGraph): + with patch("ee.hogai.taxonomy_agent.nodes.TaxonomyAgentPlannerNode._model") as mock: + config: RunnableConfig = { + "configurable": { + "thread_id": self.conversation.id, + } + } + + # Interrupt the graph + message = """ + Thought: Let's ask for help. + Action: + ``` + { + "action": "ask_user_for_help", + "action_input": "Need help with this query" + } + ``` + """ + mock.return_value = RunnableLambda(lambda _: messages.AIMessage(content=message)) + output = self._run_assistant_graph(graph, conversation=self.conversation) + expected_output = [ + ("message", HumanMessage(content="Hello")), + ("message", ReasoningMessage(content="Picking relevant events and properties", substeps=[])), + ("message", ReasoningMessage(content="Picking relevant events and properties", substeps=[])), + ("message", AssistantMessage(content="Need help with this query")), + ] + self.assertConversationEqual(output, expected_output) + snapshot: StateSnapshot = graph.get_state(config) + self.assertTrue(snapshot.next) + self.assertIn("intermediate_steps", snapshot.values) + + # Resume the graph from the interruption point. + message = """ + Thought: Finish. + Action: + ``` + { + "action": "final_answer", + "action_input": "Plan" + } + ``` + """ + mock.return_value = RunnableLambda(lambda _: messages.AIMessage(content=message)) + output = self._run_assistant_graph(graph, conversation=self.conversation, message="It's straightforward") + expected_output = [ + ("message", HumanMessage(content="It's straightforward")), + ("message", ReasoningMessage(content="Picking relevant events and properties", substeps=[])), + ("message", ReasoningMessage(content="Picking relevant events and properties", substeps=[])), + ] + self.assertConversationEqual(output, expected_output) + snapshot: StateSnapshot = graph.get_state(config) + self.assertFalse(snapshot.next) + self.assertEqual(snapshot.values.get("intermediate_steps"), []) + self.assertEqual(snapshot.values["plan"], "Plan") + + def test_trends_interrupt_when_asking_for_help(self): + graph = ( + AssistantGraph(self.team) + .add_edge(AssistantNodeName.START, AssistantNodeName.TRENDS_PLANNER) + .add_trends_planner(AssistantNodeName.END) + .compile() + ) + self._test_human_in_the_loop(graph) + + def test_funnels_interrupt_when_asking_for_help(self): + graph = ( + AssistantGraph(self.team) + .add_edge(AssistantNodeName.START, AssistantNodeName.FUNNEL_PLANNER) + .add_funnel_planner(AssistantNodeName.END) + .compile() + ) + self._test_human_in_the_loop(graph) + + def test_intermediate_steps_are_updated_after_feedback(self): + with patch("ee.hogai.taxonomy_agent.nodes.TaxonomyAgentPlannerNode._model") as mock: + graph = ( + AssistantGraph(self.team) + .add_edge(AssistantNodeName.START, AssistantNodeName.TRENDS_PLANNER) + .add_trends_planner(AssistantNodeName.END) + .compile() + ) + config: RunnableConfig = { + "configurable": { + "thread_id": self.conversation.id, + } + } + + # Interrupt the graph + message = """ + Thought: Let's ask for help. + Action: + ``` + { + "action": "ask_user_for_help", + "action_input": "Need help with this query" + } + ``` + """ + mock.return_value = RunnableLambda(lambda _: messages.AIMessage(content=message)) + self._run_assistant_graph(graph, conversation=self.conversation) + snapshot: StateSnapshot = graph.get_state(config) + self.assertTrue(snapshot.next) + self.assertIn("intermediate_steps", snapshot.values) + self.assertEqual(len(snapshot.values["intermediate_steps"]), 1) + action, observation = snapshot.values["intermediate_steps"][0] + self.assertEqual(action.tool, "ask_user_for_help") + self.assertIsNone(observation) + + self._run_assistant_graph(graph, conversation=self.conversation, message="It's straightforward") + snapshot: StateSnapshot = graph.get_state(config) + self.assertTrue(snapshot.next) + self.assertIn("intermediate_steps", snapshot.values) + self.assertEqual(len(snapshot.values["intermediate_steps"]), 2) + action, observation = snapshot.values["intermediate_steps"][0] + self.assertEqual(action.tool, "ask_user_for_help") + self.assertEqual(observation, "It's straightforward") + action, observation = snapshot.values["intermediate_steps"][1] + self.assertEqual(action.tool, "ask_user_for_help") + self.assertIsNone(observation) + + def test_new_conversation_handles_serialized_conversation(self): + graph = ( + AssistantGraph(self.team) + .add_node(AssistantNodeName.ROUTER, lambda _: {"messages": [AssistantMessage(content="Hello")]}) + .add_edge(AssistantNodeName.START, AssistantNodeName.ROUTER) + .add_edge(AssistantNodeName.ROUTER, AssistantNodeName.END) + .compile() + ) + output = self._run_assistant_graph( + graph, + conversation=self.conversation, + is_new_conversation=True, + ) + expected_output = [ + ("conversation", {"id": str(self.conversation.id)}), + ] + self.assertConversationEqual(output[:1], expected_output) + + output = self._run_assistant_graph( + graph, + conversation=self.conversation, + is_new_conversation=False, + ) + self.assertNotEqual(output[0][0], "conversation") diff --git a/ee/hogai/test/test_utils.py b/ee/hogai/test/test_utils.py index 42e54d058c556..8c32471c88508 100644 --- a/ee/hogai/test/test_utils.py +++ b/ee/hogai/test/test_utils.py @@ -1,6 +1,4 @@ -from langchain_core.messages import HumanMessage as LangchainHumanMessage - -from ee.hogai.utils import filter_visualization_conversation, merge_human_messages +from ee.hogai.utils.helpers import filter_messages from posthog.schema import ( AssistantMessage, AssistantTrendsQuery, @@ -13,40 +11,29 @@ class TestTrendsUtils(BaseTest): - def test_merge_human_messages(self): - res = merge_human_messages( - [ - LangchainHumanMessage(content="Text"), - LangchainHumanMessage(content="Text"), - LangchainHumanMessage(content="Te"), - LangchainHumanMessage(content="xt"), - ] - ) - self.assertEqual(len(res), 1) - self.assertEqual(res, [LangchainHumanMessage(content="Text\nTe\nxt")]) - - def test_filter_trends_conversation(self): - human_messages, visualization_messages = filter_visualization_conversation( + def test_filters_and_merges_human_messages(self): + conversation = [ + HumanMessage(content="Text"), + FailureMessage(content="Error"), + HumanMessage(content="Text"), + VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="plan"), + HumanMessage(content="Text2"), + VisualizationMessage(answer=None, plan="plan"), + ] + messages = filter_messages(conversation) + self.assertEqual(len(messages), 4) + self.assertEqual( [ - HumanMessage(content="Text"), - FailureMessage(content="Error"), - HumanMessage(content="Text"), + HumanMessage(content="Text\nText"), VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="plan"), HumanMessage(content="Text2"), VisualizationMessage(answer=None, plan="plan"), - ] - ) - self.assertEqual(len(human_messages), 2) - self.assertEqual(len(visualization_messages), 1) - self.assertEqual( - human_messages, [LangchainHumanMessage(content="Text"), LangchainHumanMessage(content="Text2")] - ) - self.assertEqual( - visualization_messages, [VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="plan")] + ], + messages, ) def test_filters_typical_conversation(self): - human_messages, visualization_messages = filter_visualization_conversation( + messages = filter_messages( [ HumanMessage(content="Question 1"), RouterMessage(content="trends"), @@ -58,15 +45,30 @@ def test_filters_typical_conversation(self): AssistantMessage(content="Summary 2"), ] ) - self.assertEqual(len(human_messages), 2) - self.assertEqual(len(visualization_messages), 2) - self.assertEqual( - human_messages, [LangchainHumanMessage(content="Question 1"), LangchainHumanMessage(content="Question 2")] - ) + self.assertEqual(len(messages), 6) self.assertEqual( - visualization_messages, + messages, [ + HumanMessage(content="Question 1"), VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 1"), + AssistantMessage(content="Summary 1"), + HumanMessage(content="Question 2"), VisualizationMessage(answer=AssistantTrendsQuery(series=[]), plan="Plan 2"), + AssistantMessage(content="Summary 2"), + ], + ) + + def test_joins_human_messages(self): + messages = filter_messages( + [ + HumanMessage(content="Question 1"), + HumanMessage(content="Question 2"), + ] + ) + self.assertEqual(len(messages), 1) + self.assertEqual( + messages, + [ + HumanMessage(content="Question 1\nQuestion 2"), ], ) diff --git a/ee/hogai/trends/nodes.py b/ee/hogai/trends/nodes.py index b6b33cf6d8354..e430b4036e043 100644 --- a/ee/hogai/trends/nodes.py +++ b/ee/hogai/trends/nodes.py @@ -6,12 +6,12 @@ from ee.hogai.taxonomy_agent.nodes import TaxonomyAgentPlannerNode, TaxonomyAgentPlannerToolsNode from ee.hogai.trends.prompts import REACT_SYSTEM_PROMPT, TRENDS_SYSTEM_PROMPT from ee.hogai.trends.toolkit import TRENDS_SCHEMA, TrendsTaxonomyAgentToolkit -from ee.hogai.utils import AssistantState +from ee.hogai.utils.types import AssistantState, PartialAssistantState from posthog.schema import AssistantTrendsQuery class TrendsPlannerNode(TaxonomyAgentPlannerNode): - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: toolkit = TrendsTaxonomyAgentToolkit(self._team) prompt = ChatPromptTemplate.from_messages( [ @@ -23,7 +23,7 @@ def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: class TrendsPlannerToolsNode(TaxonomyAgentPlannerToolsNode): - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: toolkit = TrendsTaxonomyAgentToolkit(self._team) return super()._run_with_toolkit(state, toolkit, config=config) @@ -36,7 +36,7 @@ class TrendsGeneratorNode(SchemaGeneratorNode[AssistantTrendsQuery]): OUTPUT_MODEL = TrendsSchemaGeneratorOutput OUTPUT_SCHEMA = TRENDS_SCHEMA - def run(self, state: AssistantState, config: RunnableConfig) -> AssistantState: + def run(self, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: prompt = ChatPromptTemplate.from_messages( [ ("system", TRENDS_SYSTEM_PROMPT), diff --git a/ee/hogai/trends/prompts.py b/ee/hogai/trends/prompts.py index 2ac9496480cdd..dcc1daeaa5a00 100644 --- a/ee/hogai/trends/prompts.py +++ b/ee/hogai/trends/prompts.py @@ -12,6 +12,8 @@ {{react_format}} +{{react_human_in_the_loop}} + Below you will find information on how to correctly discover the taxonomy of the user's data. diff --git a/ee/hogai/trends/test/test_nodes.py b/ee/hogai/trends/test/test_nodes.py index 44973b3195377..369ce8bc9b292 100644 --- a/ee/hogai/trends/test/test_nodes.py +++ b/ee/hogai/trends/test/test_nodes.py @@ -4,6 +4,7 @@ from langchain_core.runnables import RunnableLambda from ee.hogai.trends.nodes import TrendsGeneratorNode, TrendsSchemaGeneratorOutput +from ee.hogai.utils.types import AssistantState, PartialAssistantState from posthog.schema import ( AssistantTrendsQuery, HumanMessage, @@ -17,6 +18,7 @@ class TestTrendsGeneratorNode(ClickhouseTestMixin, APIBaseTest): maxDiff = None def setUp(self): + super().setUp() self.schema = AssistantTrendsQuery(series=[]) def test_node_runs(self): @@ -26,16 +28,16 @@ def test_node_runs(self): lambda _: TrendsSchemaGeneratorOutput(query=self.schema).model_dump() ) new_state = node.run( - { - "messages": [HumanMessage(content="Text")], - "plan": "Plan", - }, + AssistantState( + messages=[HumanMessage(content="Text")], + plan="Plan", + ), {}, ) self.assertEqual( new_state, - { - "messages": [VisualizationMessage(answer=self.schema, plan="Plan", done=True)], - "intermediate_steps": None, - }, + PartialAssistantState( + messages=[VisualizationMessage(answer=self.schema, plan="Plan", id=new_state.messages[0].id)], + intermediate_steps=None, + ), ) diff --git a/ee/hogai/trends/toolkit.py b/ee/hogai/trends/toolkit.py index d69830d2f2cd6..5fd7a35f0f18a 100644 --- a/ee/hogai/trends/toolkit.py +++ b/ee/hogai/trends/toolkit.py @@ -1,8 +1,6 @@ from ee.hogai.taxonomy_agent.toolkit import TaxonomyAgentToolkit, ToolkitTool -from ee.hogai.utils import dereference_schema -from posthog.schema import ( - AssistantTrendsQuery, -) +from ee.hogai.utils.helpers import dereference_schema +from posthog.schema import AssistantTrendsQuery class TrendsTaxonomyAgentToolkit(TaxonomyAgentToolkit): diff --git a/ee/hogai/utils.py b/ee/hogai/utils.py deleted file mode 100644 index 559a369df83c8..0000000000000 --- a/ee/hogai/utils.py +++ /dev/null @@ -1,117 +0,0 @@ -import operator -from abc import ABC, abstractmethod -from collections.abc import Sequence -from enum import StrEnum -from typing import Annotated, Optional, TypedDict, Union - -from jsonref import replace_refs -from langchain_core.agents import AgentAction -from langchain_core.messages import ( - HumanMessage as LangchainHumanMessage, - merge_message_runs, -) -from langchain_core.runnables import RunnableConfig -from langgraph.graph import END, START -from pydantic import BaseModel, Field - -from posthog.models.team.team import Team -from posthog.schema import ( - AssistantMessage, - FailureMessage, - HumanMessage, - ReasoningMessage, - RootAssistantMessage, - RouterMessage, - VisualizationMessage, -) - -AssistantMessageUnion = Union[ - AssistantMessage, HumanMessage, VisualizationMessage, FailureMessage, RouterMessage, ReasoningMessage -] - - -class Conversation(BaseModel): - messages: list[RootAssistantMessage] = Field(..., min_length=1, max_length=50) - session_id: str - - -class AssistantState(TypedDict, total=False): - messages: Annotated[Sequence[AssistantMessageUnion], operator.add] - intermediate_steps: Optional[list[tuple[AgentAction, Optional[str]]]] - plan: Optional[str] - - -class AssistantNodeName(StrEnum): - START = START - END = END - ROUTER = "router" - TRENDS_PLANNER = "trends_planner" - TRENDS_PLANNER_TOOLS = "trends_planner_tools" - TRENDS_GENERATOR = "trends_generator" - TRENDS_GENERATOR_TOOLS = "trends_generator_tools" - FUNNEL_PLANNER = "funnel_planner" - FUNNEL_PLANNER_TOOLS = "funnel_planner_tools" - FUNNEL_GENERATOR = "funnel_generator" - FUNNEL_GENERATOR_TOOLS = "funnel_generator_tools" - SUMMARIZER = "summarizer" - - -class AssistantNode(ABC): - _team: Team - - def __init__(self, team: Team): - self._team = team - - @abstractmethod - def run(cls, state: AssistantState, config: RunnableConfig) -> AssistantState: - raise NotImplementedError - - -def remove_line_breaks(line: str) -> str: - return line.replace("\n", " ") - - -def merge_human_messages(messages: list[LangchainHumanMessage]) -> list[LangchainHumanMessage]: - """ - Filters out duplicated human messages and merges them into one message. - """ - contents = set() - filtered_messages = [] - for message in messages: - if message.content in contents: - continue - contents.add(message.content) - filtered_messages.append(message) - return merge_message_runs(filtered_messages) - - -def filter_visualization_conversation( - messages: Sequence[AssistantMessageUnion], -) -> tuple[list[LangchainHumanMessage], list[VisualizationMessage]]: - """ - Splits, filters and merges the message history to be consumable by agents. Returns human and visualization messages. - """ - stack: list[LangchainHumanMessage] = [] - human_messages: list[LangchainHumanMessage] = [] - visualization_messages: list[VisualizationMessage] = [] - - for message in messages: - if isinstance(message, HumanMessage): - stack.append(LangchainHumanMessage(content=message.content)) - elif isinstance(message, VisualizationMessage) and message.answer: - if stack: - human_messages += merge_human_messages(stack) - stack = [] - visualization_messages.append(message) - - if stack: - human_messages += merge_human_messages(stack) - - return human_messages, visualization_messages - - -def dereference_schema(schema: dict) -> dict: - new_schema: dict = replace_refs(schema, proxies=False, lazy_load=False) - if "$defs" in new_schema: - new_schema.pop("$defs") - return new_schema diff --git a/ee/hogai/utils/__init__.py b/ee/hogai/utils/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/ee/hogai/utils/helpers.py b/ee/hogai/utils/helpers.py new file mode 100644 index 0000000000000..4fc8cf3b5d6a0 --- /dev/null +++ b/ee/hogai/utils/helpers.py @@ -0,0 +1,79 @@ +from collections.abc import Sequence +from typing import Optional, TypeVar, Union + +from jsonref import replace_refs +from langchain_core.messages import ( + HumanMessage as LangchainHumanMessage, + merge_message_runs, +) + +from posthog.schema import ( + AssistantMessage, + HumanMessage, + VisualizationMessage, +) + +from .types import AIMessageUnion, AssistantMessageUnion + + +def remove_line_breaks(line: str) -> str: + return line.replace("\n", " ") + + +def filter_messages( + messages: Sequence[AssistantMessageUnion], + entity_filter: Union[tuple[type[AIMessageUnion], ...], type[AIMessageUnion]] = ( + AssistantMessage, + VisualizationMessage, + ), +) -> list[AssistantMessageUnion]: + """ + Filters and merges the message history to be consumable by agents. Returns human and AI messages. + """ + stack: list[LangchainHumanMessage] = [] + filtered_messages: list[AssistantMessageUnion] = [] + + def _merge_stack(stack: list[LangchainHumanMessage]) -> list[HumanMessage]: + return [ + HumanMessage(content=langchain_message.content, id=langchain_message.id) + for langchain_message in merge_message_runs(stack) + ] + + for message in messages: + if isinstance(message, HumanMessage): + stack.append(LangchainHumanMessage(content=message.content, id=message.id)) + elif isinstance(message, entity_filter): + if stack: + filtered_messages += _merge_stack(stack) + stack = [] + filtered_messages.append(message) + + if stack: + filtered_messages += _merge_stack(stack) + + return filtered_messages + + +T = TypeVar("T", bound=AssistantMessageUnion) + + +def find_last_message_of_type(messages: Sequence[AssistantMessageUnion], message_type: type[T]) -> Optional[T]: + return next((msg for msg in reversed(messages) if isinstance(msg, message_type)), None) + + +def slice_messages_to_conversation_start( + messages: Sequence[AssistantMessageUnion], start_id: Optional[str] = None +) -> Sequence[AssistantMessageUnion]: + result = [] + for msg in messages: + result.append(msg) + if msg.id == start_id: + break + return result + + +def dereference_schema(schema: dict) -> dict: + new_schema: dict = replace_refs(schema, proxies=False, lazy_load=False) + if "$defs" in new_schema: + new_schema.pop("$defs") + return new_schema diff --git a/ee/hogai/utils/nodes.py b/ee/hogai/utils/nodes.py new file mode 100644 index 0000000000000..6a4358243b666 --- /dev/null +++ b/ee/hogai/utils/nodes.py @@ -0,0 +1,18 @@ +from abc import ABC, abstractmethod + +from langchain_core.runnables import RunnableConfig + +from posthog.models.team.team import Team + +from .types import AssistantState, PartialAssistantState + + +class AssistantNode(ABC): + _team: Team + + def __init__(self, team: Team): + self._team = team + + @abstractmethod + def run(cls, state: AssistantState, config: RunnableConfig) -> PartialAssistantState: + raise NotImplementedError diff --git a/ee/hogai/utils/state.py b/ee/hogai/utils/state.py new file mode 100644 index 0000000000000..3392f3362adb9 --- /dev/null +++ b/ee/hogai/utils/state.py @@ -0,0 +1,70 @@ +from typing import Any, Literal, TypedDict, TypeGuard, Union + +from langchain_core.messages import AIMessageChunk + +from ee.hogai.utils.types import AssistantNodeName, AssistantState, PartialAssistantState + +# A state update can have a partial state or a LangGraph's reserved dataclasses like Interrupt. +GraphValueUpdate = dict[AssistantNodeName, dict[Any, Any] | Any] + +GraphValueUpdateTuple = tuple[Literal["values"], GraphValueUpdate] + + +def is_value_update(update: list[Any]) -> TypeGuard[GraphValueUpdateTuple]: + """ + Transition between nodes. + + Returns: + PartialAssistantState, Interrupt, or other LangGraph reserved dataclasses. + """ + return len(update) == 2 and update[0] == "updates" + + +def validate_value_update(update: GraphValueUpdate) -> dict[AssistantNodeName, PartialAssistantState | Any]: + validated_update = {} + for node_name, value in update.items(): + if isinstance(value, dict): + validated_update[node_name] = PartialAssistantState.model_validate(value) + else: + validated_update[node_name] = value + return validated_update + + +class LangGraphState(TypedDict): + langgraph_node: AssistantNodeName + + +GraphMessageUpdateTuple = tuple[Literal["messages"], tuple[Union[AIMessageChunk, Any], LangGraphState]] + + +def is_message_update(update: list[Any]) -> TypeGuard[GraphMessageUpdateTuple]: + """ + Streaming of messages. + """ + return len(update) == 2 and update[0] == "messages" + + +GraphStateUpdateTuple = tuple[Literal["updates"], dict[Any, Any]] + + +def is_state_update(update: list[Any]) -> TypeGuard[GraphStateUpdateTuple]: + """ + Update of the state. Returns a full state. + """ + return len(update) == 2 and update[0] == "values" + + +def validate_state_update(state_update: dict[Any, Any]) -> AssistantState: + return AssistantState.model_validate(state_update) + + +GraphTaskStartedUpdateTuple = tuple[Literal["debug"], tuple[Union[AIMessageChunk, Any], LangGraphState]] + + +def is_task_started_update( + update: list[Any], +) -> TypeGuard[GraphTaskStartedUpdateTuple]: + """ + Streaming of messages. + """ + return len(update) == 2 and update[0] == "debug" and update[1]["type"] == "task" diff --git a/ee/hogai/utils/types.py b/ee/hogai/utils/types.py new file mode 100644 index 0000000000000..2df027b6f85af --- /dev/null +++ b/ee/hogai/utils/types.py @@ -0,0 +1,52 @@ +import operator +from collections.abc import Sequence +from enum import StrEnum +from typing import Annotated, Optional, Union + +from langchain_core.agents import AgentAction +from langgraph.graph import END, START +from pydantic import BaseModel, Field + +from posthog.schema import ( + AssistantMessage, + FailureMessage, + HumanMessage, + ReasoningMessage, + RouterMessage, + VisualizationMessage, +) + +AIMessageUnion = Union[AssistantMessage, VisualizationMessage, FailureMessage, RouterMessage, ReasoningMessage] +AssistantMessageUnion = Union[HumanMessage, AIMessageUnion] + + +class _SharedAssistantState(BaseModel): + intermediate_steps: Optional[list[tuple[AgentAction, Optional[str]]]] = Field(default=None) + start_id: Optional[str] = Field(default=None) + """ + The ID of the message from which the conversation started. + """ + plan: Optional[str] = Field(default=None) + + +class AssistantState(_SharedAssistantState): + messages: Annotated[Sequence[AssistantMessageUnion], operator.add] + + +class PartialAssistantState(_SharedAssistantState): + messages: Optional[Annotated[Sequence[AssistantMessageUnion], operator.add]] = Field(default=None) + + +class AssistantNodeName(StrEnum): + START = START + END = END + ROUTER = "router" + TRENDS_PLANNER = "trends_planner" + TRENDS_PLANNER_TOOLS = "trends_planner_tools" + TRENDS_GENERATOR = "trends_generator" + TRENDS_GENERATOR_TOOLS = "trends_generator_tools" + FUNNEL_PLANNER = "funnel_planner" + FUNNEL_PLANNER_TOOLS = "funnel_planner_tools" + FUNNEL_GENERATOR = "funnel_generator" + FUNNEL_GENERATOR_TOOLS = "funnel_generator_tools" + SUMMARIZER = "summarizer" diff --git a/ee/migrations/0018_conversation_conversationcheckpoint_and_more.py b/ee/migrations/0018_conversation_conversationcheckpoint_and_more.py new file mode 100644 index 0000000000000..ec48cc780ad57 --- /dev/null +++ b/ee/migrations/0018_conversation_conversationcheckpoint_and_more.py @@ -0,0 +1,147 @@ +# Generated by Django 4.2.15 on 2024-12-11 15:51 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import posthog.models.utils + + +class Migration(migrations.Migration): + dependencies = [ + ("posthog", "0528_project_field_in_taxonomy"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("ee", "0017_accesscontrol_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="Conversation", + fields=[ + ( + "id", + models.UUIDField( + default=posthog.models.utils.UUIDT, editable=False, primary_key=True, serialize=False + ), + ), + ("team", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="posthog.team")), + ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="ConversationCheckpoint", + fields=[ + ( + "id", + models.UUIDField( + default=posthog.models.utils.UUIDT, editable=False, primary_key=True, serialize=False + ), + ), + ( + "checkpoint_ns", + models.TextField( + default="", + help_text='Checkpoint namespace. Denotes the path to the subgraph node the checkpoint originates from, separated by `|` character, e.g. `"child|grandchild"`. Defaults to "" (root graph).', + ), + ), + ("checkpoint", models.JSONField(help_text="Serialized checkpoint data.", null=True)), + ("metadata", models.JSONField(help_text="Serialized checkpoint metadata.", null=True)), + ( + "parent_checkpoint", + models.ForeignKey( + help_text="Parent checkpoint ID.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="children", + to="ee.conversationcheckpoint", + ), + ), + ( + "thread", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, related_name="checkpoints", to="ee.conversation" + ), + ), + ], + ), + migrations.CreateModel( + name="ConversationCheckpointWrite", + fields=[ + ( + "id", + models.UUIDField( + default=posthog.models.utils.UUIDT, editable=False, primary_key=True, serialize=False + ), + ), + ("task_id", models.UUIDField(help_text="Identifier for the task creating the checkpoint write.")), + ( + "idx", + models.IntegerField( + help_text="Index of the checkpoint write. It is an integer value where negative numbers are reserved for special cases, such as node interruption." + ), + ), + ( + "channel", + models.TextField( + help_text="An arbitrary string defining the channel name. For example, it can be a node name or a reserved LangGraph's enum." + ), + ), + ("type", models.TextField(help_text="Type of the serialized blob. For example, `json`.", null=True)), + ("blob", models.BinaryField(null=True)), + ( + "checkpoint", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="writes", + to="ee.conversationcheckpoint", + ), + ), + ], + ), + migrations.CreateModel( + name="ConversationCheckpointBlob", + fields=[ + ( + "id", + models.UUIDField( + default=posthog.models.utils.UUIDT, editable=False, primary_key=True, serialize=False + ), + ), + ( + "channel", + models.TextField( + help_text="An arbitrary string defining the channel name. For example, it can be a node name or a reserved LangGraph's enum." + ), + ), + ("version", models.TextField(help_text="Monotonically increasing version of the channel.")), + ("type", models.TextField(help_text="Type of the serialized blob. For example, `json`.", null=True)), + ("blob", models.BinaryField(null=True)), + ( + "checkpoint", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="blobs", + to="ee.conversationcheckpoint", + ), + ), + ], + ), + migrations.AddConstraint( + model_name="conversationcheckpointwrite", + constraint=models.UniqueConstraint( + fields=("checkpoint_id", "task_id", "idx"), name="unique_checkpoint_write" + ), + ), + migrations.AddConstraint( + model_name="conversationcheckpointblob", + constraint=models.UniqueConstraint( + fields=("checkpoint_id", "channel", "version"), name="unique_checkpoint_blob" + ), + ), + migrations.AddConstraint( + model_name="conversationcheckpoint", + constraint=models.UniqueConstraint(fields=("id", "checkpoint_ns", "thread"), name="unique_checkpoint"), + ), + ] diff --git a/ee/migrations/max_migration.txt b/ee/migrations/max_migration.txt index 449d87290c304..fb889f1cc34cf 100644 --- a/ee/migrations/max_migration.txt +++ b/ee/migrations/max_migration.txt @@ -1 +1 @@ -0017_accesscontrol_and_more +0018_conversation_conversationcheckpoint_and_more diff --git a/ee/models/__init__.py b/ee/models/__init__.py index df7cfcba704e6..2067d11f7618f 100644 --- a/ee/models/__init__.py +++ b/ee/models/__init__.py @@ -1,3 +1,4 @@ +from .assistant import Conversation, ConversationCheckpoint, ConversationCheckpointBlob, ConversationCheckpointWrite from .dashboard_privilege import DashboardPrivilege from .event_definition import EnterpriseEventDefinition from .explicit_team_membership import ExplicitTeamMembership @@ -10,7 +11,11 @@ __all__ = [ "AccessControl", + "ConversationCheckpoint", + "ConversationCheckpointBlob", + "ConversationCheckpointWrite", "DashboardPrivilege", + "Conversation", "EnterpriseEventDefinition", "EnterprisePropertyDefinition", "ExplicitTeamMembership", diff --git a/ee/models/assistant.py b/ee/models/assistant.py new file mode 100644 index 0000000000000..390a7ab7a117f --- /dev/null +++ b/ee/models/assistant.py @@ -0,0 +1,83 @@ +from collections.abc import Iterable + +from django.db import models +from langgraph.checkpoint.serde.types import TASKS + +from posthog.models.team.team import Team +from posthog.models.user import User +from posthog.models.utils import UUIDModel + + +class Conversation(UUIDModel): + user = models.ForeignKey(User, on_delete=models.CASCADE) + team = models.ForeignKey(Team, on_delete=models.CASCADE) + + +class ConversationCheckpoint(UUIDModel): + thread = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name="checkpoints") + checkpoint_ns = models.TextField( + default="", + help_text='Checkpoint namespace. Denotes the path to the subgraph node the checkpoint originates from, separated by `|` character, e.g. `"child|grandchild"`. Defaults to "" (root graph).', + ) + parent_checkpoint = models.ForeignKey( + "self", null=True, on_delete=models.CASCADE, related_name="children", help_text="Parent checkpoint ID." + ) + checkpoint = models.JSONField(null=True, help_text="Serialized checkpoint data.") + metadata = models.JSONField(null=True, help_text="Serialized checkpoint metadata.") + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["id", "checkpoint_ns", "thread"], + name="unique_checkpoint", + ) + ] + + @property + def pending_sends(self) -> Iterable["ConversationCheckpointWrite"]: + if self.parent_checkpoint is None: + return [] + return self.parent_checkpoint.writes.filter(channel=TASKS).order_by("task_id", "idx") + + @property + def pending_writes(self) -> Iterable["ConversationCheckpointWrite"]: + return self.writes.order_by("idx", "task_id") + + +class ConversationCheckpointBlob(UUIDModel): + checkpoint = models.ForeignKey(ConversationCheckpoint, on_delete=models.CASCADE, related_name="blobs") + channel = models.TextField( + help_text="An arbitrary string defining the channel name. For example, it can be a node name or a reserved LangGraph's enum." + ) + version = models.TextField(help_text="Monotonically increasing version of the channel.") + type = models.TextField(null=True, help_text="Type of the serialized blob. For example, `json`.") + blob = models.BinaryField(null=True) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["checkpoint_id", "channel", "version"], + name="unique_checkpoint_blob", + ) + ] + + +class ConversationCheckpointWrite(UUIDModel): + checkpoint = models.ForeignKey(ConversationCheckpoint, on_delete=models.CASCADE, related_name="writes") + task_id = models.UUIDField(help_text="Identifier for the task creating the checkpoint write.") + idx = models.IntegerField( + help_text="Index of the checkpoint write. It is an integer value where negative numbers are reserved for special cases, such as node interruption." + ) + channel = models.TextField( + help_text="An arbitrary string defining the channel name. For example, it can be a node name or a reserved LangGraph's enum." + ) + type = models.TextField(null=True, help_text="Type of the serialized blob. For example, `json`.") + blob = models.BinaryField(null=True) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["checkpoint_id", "task_id", "idx"], + name="unique_checkpoint_write", + ) + ] diff --git a/ee/urls.py b/ee/urls.py index 7c722bc31852f..91b58e0fcb238 100644 --- a/ee/urls.py +++ b/ee/urls.py @@ -6,11 +6,11 @@ from django.urls.conf import path from ee.api import integration -from .api.rbac import organization_resource_access, role from .api import ( authentication, billing, + conversation, dashboard_collaborator, explicit_team_member, feature_flag_role_access, @@ -19,18 +19,20 @@ sentry_stats, subscription, ) +from .api.rbac import organization_resource_access, role from .session_recordings import session_recording_playlist def extend_api_router() -> None: from posthog.api import ( - router as root_router, - register_grandfathered_environment_nested_viewset, - projects_router, - organizations_router, - project_feature_flags_router, environment_dashboards_router, + environments_router, legacy_project_dashboards_router, + organizations_router, + project_feature_flags_router, + projects_router, + register_grandfathered_environment_nested_viewset, + router as root_router, ) root_router.register(r"billing", billing.BillingViewset, "billing") @@ -93,6 +95,10 @@ def extend_api_router() -> None: ["project_id"], ) + environments_router.register( + r"conversations", conversation.ConversationViewSet, "environment_conversations", ["team_id"] + ) + # The admin interface is disabled on self-hosted instances, as its misuse can be unsafe admin_urlpatterns = ( diff --git a/frontend/__snapshots__/scenes-app-max-ai--empty-thread-loading--dark.png b/frontend/__snapshots__/scenes-app-max-ai--empty-thread-loading--dark.png index ab8433877a9e564271919f01d63a2c4701a3050b..64cc334785edabb42271a6d71905ce311f7434d6 100644 GIT binary patch literal 11803 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 literal 12608 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 Nv^4b85o$JX{ufKTW~cxF diff --git a/frontend/__snapshots__/scenes-app-max-ai--empty-thread-loading--light.png b/frontend/__snapshots__/scenes-app-max-ai--empty-thread-loading--light.png index e5bac9eeee8f1b73f076dbeade800ed2544e328e..756eb74f6156b4941a8042cc825f61b30e1e4670 100644 GIT binary patch literal 11738 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 literal 12744 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 Ki3)L}!2brb$+}_y literal 16382 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

hu;%4q_E0(7BDRvdhdq*G5o zfDzcTh~GGIqlg?fVFS|FiTf`@fZMvw-yz&IeYTwb2##rnw@-0`sULi>3{6$DBx%3* zW_ppP^sVzSK6Ve-J@s$R9oxm>M5$7fE))EA;IuQaAb8OleDgC8tEoj$@H)#Bs z)VL=@26q&N%h11OV92)Yeax$gb)KLuKgAJKRaNhs|BDR)3Ip9_YMPoMJ9sU0kIN2L z%TUeZ$o&Kss}tHkpbMniRc@46evW^iYbvc&!H5Ft(b0M2>zCBn#vJ%E)EN~gk8;Xq z8J3S|(2h1w6pWWO@qrFytQj)+so~8N9)fljw!DD8)f0D}cz;iQFm@%Ec*ffj_N_C% zIuXqq-Y?*x1D~-B43;jMUPJ|SWP>5&ar5;dIqh%%!hZ3f=PiR;tuZp1%MFt+&Kf6P!E^v>qU3?#$uxJJf`8S-Zb zMN@KxbX=N5T1H*HXtKfR3ffabf||5aQfV22YD|_)5_OBz*v!m=%*;&A=TYV#J9{?Y zzM~joQdXQ~?LFMx66Npg5!y@?>Rjwi6uNKeoQaAY6atZwo4$1I-&aHmJ>3=bf8VS8 zP$~X>o4QJ5^6#5H&I7+If4??!{(rjgrYrJ&b1t~(V(kv1{Vx*r2sj*rQPhkr^uLrU z1U=S`-QU}b6`@AZMQiJ@x#A)(=0d$!?Oiu^f2r+rfx=3zhC2@~yQ>;vKX6&-eWwNQ z^}7*#AKA{%QCP82c&;o4{GBINNas@y&IVG#nXYa{PFD^Dp0aUjZ-3LmCT)g>dj}1U z->KA9-8LY8Gk-j`{126o?YEP4FIZZ*R_YE8K4 z4Rj%?^zR%ou71~@;;2^{D$mZ2%V!Uf_H0vg_{bbUJLMZp$TqVG{FqSw&7S+-=eaDnmLiB0Rq+na>x`pl z75*PzNbA#Hv&oMVW;7%IV}n$wJBqSlMSZ`DOD%GD6u;H7;myqi{xjMX2g07T`8-8( zthzq?m{v&(gzN^)6+Uxz3jHfq0?T~#cJ;^T^cgZ?v3yy|#U5BVS{^FoWAYCL>90;D z*rOwDpDK{t*z5MKTN!C-QXaaG9#Inz@Ysy;tX&mx-GEKG>$XKle~XT8?iIdxB)4u3 z-{DCSKLu`a3@RYc#@6hi3YB-_>c6f6;20W+1!9{PIg7J@Sr`Y%0`p;O&+DIW^La*oHMngBRvdj-< z0&ktb-F=(0~4uXWwLapT5JzD{wWK^2iykx>H`0f8ju@m4E7%GIk~U0r|j ziX1sts8?D7apc}m5K>b=($#%{IbQZm)zs8fT3Wh4Sxn;cY?nYN(42|;I?G?nr z!kV9-ho7@vyW#uC-ErJ+X(hyYd3o8`tX79}mzI`__#eRCp``4|-Us7bTU#ng!sE|# z(`yq8%v>wS2RfgPx(2Yv2(yI~l-f?$M$m}JN=w&owcv!2aYpIiU$7w88IKxl4p2@I zkoPsNaSS9_SX(o2AF>J<#d6j2e-wzq@AKD=Q%ZhMR&Bx-px$HQ=Dz&-R^sGvbGFLP znDxrVdetoIy$4E4d8mluL8EEa8{q_=P1E^fN0bxL!C&Br0*pb&CgtaVI-Tg^>gqX{pv4ys<)5Fas_UU5tVvSfhUEj*XSkG%7Yx-PIh>4DXnV`*&IqbWkJpw*o6 z^~tKHlhu6qy~nN{OV13OLZqt*1+(iZu^jg;G=m& zIIa0h zH>O3LSEJ+OCt&?7hqCaf@2cq+z(RT+{1I~bqg!m$fR0BcjY)aeK4mYMM$Vd03f9zI zY_#@iXVlTr5u;+P8p%HRgkMWb&&NJL{?+p_28S9hj;2eJ@by{hiec9&!o|j3oo%|T zn)OL|_imuc_Yy4ZbhF79EWBUrkedD}jN%mreo^8?jRpz*)A3R>SOwz>s~D+N3SlP; zbMs$=nTi?qX{LU{Ay=;vG5 z*EiP%``isxMjYRTiL|t-(Zp1`tWSW4D$uV`(9bk(@@Z>p`?0aWW-w?ql%)&BrF z+hMsZ6P(NKW{pm6^72Lzq>Zj&(O+C#n3 zzGebW@)85NYd~14FdHX3yY<=8_J|g^D4$GL$=}$_@*7o+waPC8WeSIsdJpPFq=e3b{@7~EpP#?pQwP}|imdfkl2ctavN~{_k z7l#kq-x`F+HL|v#Pa-3q{k*Ude15#$(x)5li-hr6u-SEsYA{orffKf?tdaSWLrTi;;1M^LaNlH1*>9}!#iux@%eM?)?X z%irX*J21LOG4YS-GAAW9^%?B@sMdjES%q;`9#e5y%awRU;=j;y3yuYBGVBI;jd#f_ z;Dk>0*Y-Uc)^T+sZZR{f#`9Z(4+w~yntD{HgO<9w;+t4(_9I;mU!BBrf0Z#)7vmbW zTKsH;0H zcD#dw61Oo{?eG(<-p+79{ndkAN4-mNpEI{Qm-WQaB`;2H?&7mEua(W+<$jU8;WnCi z)0_x}u!sm1MMYxNdtExoI!LmOJeO(H|!hrfH8wPj#NH3 zkdSBsFDq>I_&-Mcd}pzfyXma7919#ndBgMKbi+UOjG1^|OEEg&vDX!vf0z!J>uqj{ z)?^eYdES(GOF%?9&Qa^Ev|5rrxLcT>md9DI;*VM_H;;$Rl)^jC4`<;lF83uB@?ps{ z`+$q2Hng{1%P(%k#>4ZzI9`@GUVMMNyIl6i_*otJiHAulhrC$Pqg=+(o{Jyj1#A4@rat>YlqpkVehuQH;S&(c^Rz8hE zo^*xmYQvRJTpDQXd4}fSn_p@uT%H#h(sG|39ThhbNwu`Lc<%kyQ=X^4dj;J#d~j7^ zDFTUu;!DrYte=|-*M3f!yiFCpK2dR9#N`h}%cY1VJD4DH|6zRuoAq6DU1DZxofYF% z-Vp5UbaO+5UWa(QPn>dpTY|5gIasmqf2~-MrYS+wAJ@{^WCFS@Xn!8siy~sYK1Ilj~@TCHDfj^UO=M3-v3ke&oqAmVH4T=dKp|3nmbF zy{08SNg{ENKeUCB8*DD4;yr<+$fLmrJSaG599$RB8?Un$65IIN}_2y0Bs&&8)o zUyeupp=ilb0=O<8zZOgRp(74({FQ?h_&=Ba>IJP*O$uYl76bX3;m_agrPWzDIk&gAOiA7-ECGtH7AGZW77Ip#XcJya zcbsRulxJsGt;KdI5v%{U%)$cwRr0cX^3R_C0j8&FvQY+^QB-D?8C{X|ZhjsK4^K&T z_weYZ0Y0Sq<|7m&tcH*6mA?oOzEz|BGem%EWPg(l_?@4DYvT8fe$$|q z;}u(XMpqxf!_=t(xSF3sE9Cd2`rbO-kuwwF+YTB$1i#*Y$byaVAvTp;tXFFXZrqFZ zeKyg=w?xV0sswl8De#kEX0Di8*0$;1iq10d@RFzCu6cFUj|mft$1gD_?GtH}iYc)x zTMK#@4GtW_bd%26{_`DQug&MTa^6mrAS55YqW_L4RVT^*9ygkQgFQ}!0Ezje3B`4{ zt7n`n0wjlG{Z5yzVIbm3ASl~EAdv|RT)rFevq!YwA@L(Y3zFW@)9x%SkouxVqSZYp z#91aWg91j3@I(2=X>|5EOnROb%;US$94?RKkMfDq$tv5}#pKJ2xq(?;?l(8?SpDuG z{POa`ysXo7KKvVYVJtCfSXGs5`gWnranI{p0uIl_P=WgSF8%r3CA5i2Z$mW}25BJ- z@VEI!{|gzk(c+9hm*zuRT%51MnRZM5)E5{qIjfENzOM>GLkmq~Njf5xxtBde6^`zE zNf(sc*)Q)tTRNenRn@j%O$n=bC_ZHOLTI+=mTAV+ON1oxOE4|pbj~Jkugi@n89C^68#YU5Oxaa<`q9wOZ z?dG*8{_ZhD0_)&;W1UcXwvPaeM3N>IXsi@MNXCA>+kC%!?N&m$8HtQ=6*gml8p+Ba z+n$?BG99K5RYeYGqt|h$RO;uVw$iT1NX&Z#p<#v%zT8@|@~5svRqHTr3x?l*MVjN5 zHR%YyEFdPDKVjR=9Q~XuKJeYI*{6(8V+m!kh&Lm_4)j<5F4`V=oa^gxS#!~_&YZco zy}i4&<#-z-tE=ztQOqG*pG5=99;OGQ4j@PyidjB)SzO&@HB4_&F=Qinr@s*i7nlw8gzU%3kucw_mRKrtJmK(W)n_nCb0my=l zrJu*t(byH9R^;N;GACRK(YG?m9ojC#IFy zCWU$X&00WTfvAxL(ArPjf6DaSov|MgPVYs6WQ{rL~KR((?3h^YlPH z@!)n}p}knID|k>dQDNBZYxGgiDSxbp-}f?QaL{0%>hsc|?9VFoZ*~T{T;5v6q<^VJ zenxo9hDttXozFtZh6^hxfltX+Wgo-E*~P<5{a1RVBsUiAcs|OgmxrV|yW8Qr%(nMW z_tX`np!$Z7ewJ4)I^AJ6mXHWKexc71s|BWGRbf_9Ct<(u#(8*(qf{4%NmxDmj6GgL zibA6 zqvHOquDsT(*OSw)ad6jruRdv_zzrg0*A_jTdI3aBL~Kc=mof?u1+MSewj7NNHbU*m z1HUhYdK~o!-zs*ZOhXBzLZZxQ-(cyxXkHFS+Gdv((}>5M?smSITE9M=M6F9A6Ux9` zh7*z47|l;-yDgYqFEy#j6`N%ln#Mt<-_ty@Ate6n{6ch6U@(&o;lv(6v{cc~Jv5$LPAU{uo5fBC%ufEWU733>_Wu z`|EjgQZug6>ob=p9V1RCglPASr7>kN=}8!|$qKbkPkem|Wxl+P-dFsyys{!cbYRCA ztL2zjvw6E5?PT0Yh*Gv5}S%~t(X zhr3hOFMfJ3X}4J#i5?pp3xHZ*RaJH6vEg%n!xC0jR(#Pd$)&c9HZp6E)m(gC8ZnRZ zu7=qUHDfQAer(y%)|r=hj`74y1_p^#zTmR8)6=Z z7*q&9{rgvOZNq=|n2mDFOSmcaf5#3Aseix{pSGj8=b=I;O(OGBEg+2rO_}&*w&kj3 zqspyl$={z2*NeXEVEUWrIC|e|mYTtr`l4#nuZl%)Y8j*(e-HDqX8{qm50UNIr#|O} z-vjALw#9zlfBijhVi1f77`(Y*8hW)(mKqv^(X0=FapC5CA0IF5d0=C0z^PaI6f#1W zz__@${`fnJ#iPVhp*Z7>DSQj@4_?#W#iy!$dA6v!2DTo*IJTS0pShA=S2PQ*~ z*1J3<#pGmfC63Fm)Ykv~YNE8QR?S#XPY=);4cp(Y0}1APx?UA7`$XBGpzA2S2!v#Y?c3F2ICP{tNZcbX%Wy!uoMaWmV*G0 zJa&I6Pmbg}Idww_InvU3)&fVmztuc9;+D*3)jh;L`-hB_iAbHGt!RKyT49St_`%H^u1saA7%wt+s`lj8C(RGuB&3s_j51zb2A61 z`OuI)f9BBC&*-oC!?511Bq!&0Zgz61vR{9aNR??@D9|?LmP1VZ{QT_f09lM`YH9#b zzYYoc!1Gwj$48t#1Bf@U6M03&n*jwz4JX00J`}f20}PLIb8{^%Ev+g}07;*p>_23S zA!b(L;NVCUcFvoQ(l2xYm{ToOq00Ny&Bjy57cYb&p6HF3mWYDXP~32!Z);n$m+xg_ zG8ccx8XcEH5|fhO;ybXoK-amsxv8mr2PLBZ#j*;-J8&&P#15QP3|4yOMr!42!=gzG zk4HsDM#jZGt6Dre-ZiMOs(>J|G7p>(L|I8mFbN=|aj#tiVzPq2@(iwl(I9tCUjFr) zH;-x@GZiu!0=xjt1H0B2X9ojybS%}#y%jK3?_SomuAro($wKnUYeyd7`Sbr~j$^T$ z20lBT%!83FC>YnGqoap&+Njsb9G60e6=2)f#!AvVpE3k+7}dK0F9+`b zlb~HPd$^_b7k@O|R-5VC7&ARqonxREn3O)K{dJCMX=x*c2JNLbUS4NKhIK%{64TPE zVvd(Q3FiORBHvJ%Dm=lkEYdxJk%!mX)~n7I1F8swg_{9z*v;pfU+NcXrw`oKKZl9$ z9URP5JKSVEM`sQ-FB^9k{4ACq8x_Ut^jl4Cx;f^9;h$zM+eTo6CMv8^P*AYw*wRHn``ur?aP;8DYcy2a;h?5{_ zCy>xPT4Xp`>ue3&`C7Q8magtm8@VyHh>MbfXvSUOytKoR5g2kjt{8U+)!-}@uQOt{ zzy^UZvsiCrox`QQoPmEAzqM`W?F}(u^BxM;Q^o+GlnJCLf*i_g%bn%k-iRqRmI~RM zaT>&f1jmVO*DG=b=)Aq z8MmL1+Qu@xJBWVYH|J3{*7j$iJ#f9%YF1oDIBFN1TDjw-_JxT9iEqVqAJ9xABMH`? zHNJmKv*!8q+Z)P=$_7YK;50(ckze5@NBaTz1avHb7yw8t&Z-yEu=#xF%|CwmO8BJn zLUgm+^Wn3%t6chten|_-DmsvHr9z-yJ7q#Gp z%f`=Oi&YZ@I7LMt3Fm7+e=;HLwq*iCv&jw>b6AjucUsprvAN!|cCKXJ2FVw^FeH+d zcC#B9Mr*YZweNS6%qB4sgX!^%oHNhfN?eSc@Z7m`M_hbnXlOUVt})lqIah14yyVl< z8F|6G-Y4aE>fbY~0?>TGs=Z{ZRws~Kin2LbRXmXf%goEiSE!S$m@o@Lx=Un2-UM`_ zLDD9qw}yd$gkn*bo#-Srn$Q+gWw3;mXhSP}`NwhQU1E9Ed%bk<*UZ}@v-WkhiN1dx zY!a?qHIbY2`Tac=sW;2W$g5)eN5;k;z~R!GAw&3~ze1&$qH6ujR?)B8WR805=6n9o zBSC<0NuhV?P5RPOmEa$FK7G`81gbz_bTMGwP#9sreB&=bAr$C;pzf(c`C~blHlkwp zGc7uYp&{Bz zS_Awv1U4B2ItUP87Bs}f#8gyp>Wk6Q(14M59ptI}`t>X1@0$AON83M(>vy{R(Qw2; zMDRUdGmd$Np{%SN{^pGh$a8yP%@8ZHloMR$TLM875aTly>g3p>FWye@*nvv zqo$ee<{^F7pV07-kk6c)TwGi%EFA(5mN|d1_^e#5?{W}kQ%fHJZPIndeYvn|Ce3nU z2&faWbHKLmR0gWYK2+}P?w+5W72scj#0J3?RNuSUJ?f+p!clu$5|_tsD-t1|-^Tr! zVKnJ%nA;~$n=;vbZ+#-8>)M}%84{TRk8nTj{PUBw;?ZJbV|O_bRbDpuRy+L69 zWm}nSulL!PQKoBN-V4n&KQZdJj{M@7a6jCs-25YVDkhS(sYr~`uyyo`TXaOCx&3s& z;rz~x^2Yiej|feT;AeilP2%YxKJhzE{Ri^%+VN^<`Rja|CaN-;M6RY;@6$9iG(dD) zU0P!7%`0_WQjiL9l8{}Ke?9AaDaOVYnq`?ja5Q0Ete1V*)7Ph>pr8*WGk?a*D1(l{ z2jp`Y>$|lCU?Hm|_>Y(2$UqfIhWw)fVb={W(8@qO%gW9!)+_r3DY+|Pg-mW=D6Rlt zF-UC)D@zOF+p|yJ+Q%x_M}oPyHZAHh!rWLxqqqVKy&fkRB9s1yW+g;xLS+BlA?X+P z_4F*>wcVQgGHCx5B=?>KLDkyC$+mChq%<_%3twM@oT>P_{mEOggMQx&K3?9^tke9i zh+B9W^$tH}rKJ2TRx;uq6^zELK3YMEXt8&Ap~{{J)mJF72F^uezEWs z%hGBa9M82IqPp<~lj?%i-ls0^?#EwVVeIbg%=liOgM`UEJ!d=dwFsrI`k14eiJ4jP zRlw!;TVGtbhl*2Tk;qG+XJ%yd0Q}z( z1DbaAI+CX0=ti$xncG~tZKg?E5ogtzWgKfW_3YQtHFf$VeS^zD7b}`6R^f$W-@;eI zvS{scfo`5WFU5_9WDmH8Cw;kd^HFuHFOqZ*FeZ;@58g3*waqjQTy%0>5-O-*yGz_< z>vgd)u(9E2cVO2pi1e9Im6v~K6o1!Y0n$83fF|>GxMR)kP=z}+EFnV$R1RE0LFd5* zOinSG>QT9*XdHx8a2FRB0l|Iv@ByHZ&*k}{iv+>m`s5+x_-EkA0LMcf0e-FUWjNIx zE6^_Cd=3l@00O-(?p5t1pRoR1Q$}4$SvlrLZE1b_dXy0rD1wPXFEWnJ3#^6yx}7|Z zk)qT`cDLoWMM8(vUs8pCvk~BaRIR4p22O^Jt;?gO9qi?f*U{GF)}Z~Bp=>~jgJOo@ z3?VbpGLwt)Fo$HUsi}#7PLz{V*~(sL)wx7i=fp3a=g}0r>Q|zuI}|dpadGw82r$E@ z$GsBP_x>6}2=RAqvdT_V6GU1uWW5xK(IPA8=ck8uqeaso#zSF?)2#QQ9tYV!!?DGv z(a?lM61;RQw=sD7HAoL#Hm2N^nY(T>{Wi#_x~CW0QS_> z)RD08{PVHgO6E5=#Gu{+ocm$K>(_b^IiT94fc67yUq??*d7TF)BISyjzG3dvZA6DG zN!xYzIGx^)KEB0c`<{bL&TqRr+5Zd`@1oG?tC7g4OW$IUVYl2gNi)f58X*G0qHD(@ z{^HJ>KAI}`8Pje;@8yr9V};HPWOe3oYARvGwe=p?D>sM@PsS52f1)TJuubOs=Ie9lIOoLb7!8+99j_-6I*Y@(axRdu-37u;DoKyc>XeCxO^Ln9x8OvU(t(- zPPG-6Rpcz#t~zJdQmq&0M{{GgKn@PYaBTfa8y5S6gBlG+vjuv5lZ7va@ubx`xVaA@ zTeZfPn@+hl`Bh#Wsy8675O-eH)Ya96;aON%EGaTS;_j_s6S=RP(AKs87N;(_B4jp3 zJJ0gciNse~g3M~K zx}zvD(|FuOc%_$V-^!i$bT#rZ7QU0sJw-XQcy)^wOf%QHY)l!BOdVE+4D%&IsixDm zx0mM!l9GN+PB7Re<)L!@^{utQH7uU_E0|7PzK_1Ya&bs5L#EXC@Co&F$Qx|WAcz?o~r~*U#_}l z4;+aLTWYAB*xjuAb{(5LPt(=Mm0U)O-NG3&l_Vg<*FcU0GZnLt&cB1r!iYdj?1us) zdnkj9I`uPwk8i$wdF)I~yViY>x3E+1sh_W12&e1-^m)~fyo?MCrhC5!;CHpmjvbzY zjtIS{&z?b@nZgDOl}I@eLh^nKr3s>C_lz6k0E<%2S7Lunkl8V|amP(6n2cX}Xq87{ z{%yO6X@(ND`YZ^8tZy%Ha>xh09eP#_3MOY=`jAqZ?GmGA9C*3?!Df(Ym*93cN0 zf!WFe6$ZW;$4HEc}lHLIToW zvxKD4{SFN=VG1CUdAEOHJ(8(&I99}!bjm2L;MX?jM07hNSr959=4O~;lZm{ALH&mT zLz&NoY~jJYnDFed^oi!w7DsOun=PR_UD~*YPm&t8J`LM%TO#;xAc4XbHt(@aa8($x z#CpKF$$!(^e?!UJ76bI$hjbr8(VA>Q_D3jVKp{X*ytYBss5RxwUEXPpcssr<#|gKD zkhHm#_I$5ig(hsRNSV}~18tF-*<#|J5(k?z_Ry^_`vfA$N11d^wk8?$9nLWPm1}`h z_^bA|wK$kZ@a>AWJD5vhb|Y1CJ;rV}QfxFCp?IcRVK20@s_^!oC&Jf2u20G3u~px0 zEhg0eC;7rNBJs2myBK2Dr9+^RI*PpdfV~s}#g-|LbEQIzpAA%{ zw-xhY(Q>)+BfdZVTZxRc%qlO&FmKDZO0@jtx3$I_r zamaVxKHjD9UXJ9Pp#IDnnnpsxNuYkqUqz?Spl7EI3t8^6_=NZ48+4RbmW;25eUwJq zc9-Ml$f}WWogWwu?+8K(;?qd61Mh)Ks#J=GId=f{9+Efw={_i-2TIY4MPCcAOiwD5 zxDn=i9v=1z42OzlwXbBtqoeXhbzy62A@>0G#8Y0D7!H|vX%atN(AGQB)1->kSZsET zq%a4px+|8r|Z=8u>z z8Wg|D`MZ5j1uoo^2z5?Zo4&{DeGP&DH-x%||NCTNFFMe@Oed%r*WGTyViDKM~J&)&V z_L2K1P#Q@Q1Wh4Pf;qU*DnjJ*)8|rn3X7&(A{4_yP$?wQVH2Lx zVJ{?IM~4Q9R|7fPPne*H#g9p`aD)O+c8$}YHIxW#YVyjzEBBUJERvUb)=0s~h_f{F z?)G^xFJFO{si`a7dya@5p5#Vkum_c74Wgp6@9yb3jDCOnt4W)4a4QOI*ct1j}=OjE0nZwPSHw|@Bj1h>Z$kG$u7W^+Tc)WQC zrr3dL?z_LB{MI=EdO9@OVw#$Kf$?Jvt@Syt#j&2lLHOAqHyTp{8HIlgssNJ&PL>60uj^}o9=Dr-Zw7lGOv=9cBQU#UG!^6W+PkjpHumi7Mux9)^ zEj&{>SvLK69#V_Tb60SZezjjZXkdQ2va;yPLr_%<#H*`uJd|FRJdp#m2?Cxal|(2k z^ckbD@JUJgjmc88Fgi>zkG)9fF{(B=D#fvXONdI($M*s-9?0-NrY*iO3MZ+Mv_YNS z@$vBw89(G}ft2q-!VHj4fP#beA;c6X#P{lUNFj#!#Y~$0fZ+$MQT}{V+v5TxMaav! z+1bNFL*scK`xh4%uj`YAEe)wk-~n+f;Zwi93%C@nVGck_l2B*}Dh?I>B4B+e|9=3* z?G_flBkAui{QQ~p_2E(rG(13e${Yxh3YfV$Id0JM038+EP?b(31%S~F5FbF>k)p)2 zvol~Cy!Qs=MuLINF)Dp3@8fd;T3MFT$5K#gqN0%E1z<{7AfY4TiNQU2z?y+bcl;~C z?rwzQg9pfk$w9Uu9e&u`+dDdP3wjL0fGYG%)G-VGA}A||DtOQpB<$y!fy>QS`e<6G z4Ujf2ZWLg!xsE_8_T;h&61 z5B#~XAYeE1NL%~Y+FC+V5_BR2;*e*c9>F5QTZ#vm5|px__88EG@U2_GhCc%uNg)$j z9g2R1Lts}6grYYXs8zdGU{KHnXk=HeT)B&WWvUMBe@Zc9yB`EwRQe3yD}fccA!tjj zJVZ)LO5N_fKB1L9fUKdnErcTHxaer5PvQ+bjEx-KCr3vQ-g`Y*q6JA2hN=-kMM0sf zukQgS!bWVujAP9o-_g+ldNSmbKVh7JxFkIGSo(LSY+2_Zt8Ftq} zk%Qb>0-B2&y})4@4y^4%mP{ryaBPbHDm9his^1B;n#~zSy?_6tL>D*>OnMMWm)ylg zL_`Dx1c1SX)q_@x6;MWjh6RbEF#oBU+4^jgFK}tN56@4}VH-Re8Ua?0JX=^;h@ckI z2n(F7uy%z*#d;-K)O`oIc5dUwp51!cbl{F}1|aq7+6CZY=R|oXIiWkC7q%#G8qx^_ zIf$UljEq9YzW^K?du_{n68GN1;b<0vZnoxzTVi6<7W|DDCu@WElb3<-$f!vH!qy9V zR{&rGdshN19UO(|SRt6Oj5Se#l$pHGzM0zZg?jvER2PBlCDoefHVIsnGz zz?_b-rU2&)Q|7gux*OJ}p{Yp%aGsbL$u)*w7t|-7qvSLH?NkfiG&nF&>omRM)2B~W zRfmvk7ic9)_(GFVDH#qZMH&PFu+H+w?m#XeIc46D&<4W9NkC0)V-T;98MV=Q3-22j z(3ReIn3>y!$w#3V0Te{-)^Ffob26a`#Ac#=rSWvb6S@qRBG3>UWDpDN`#bvjKq3LX zC)ITi^q8l-z^ssvkZ>4Oa)3r&U40k07lEp$dIHe91A?8g&#~F#$Emw)7V*Hg&jHH? z%)WwxLIkY@5XJOzO)etFAJTHFtE-`xM}8C^q-|lS9M8$gfz~9g*k7BQo6E~puwEdJ z1Jw>-e4_CL*;^&%shzj9HQ#!4#z5cTM~&ACnW5p~ z%Brfqe@fbABd9+nCKimYfylYP_+E9QFyX@oK&I88ivlg-=I$<(o9TOb!LPhIRr9d6 zOkG1GDxEbY->@3=ZcxjE~P z8-<~h0DR}*;2`)W`S(onW{`IcBPY?;-i}WrthphHNU{A7bx@sDTBR0?w@27x0!RLQ&bfv1; z+OmJ+iw=3u(-K{(EQaLe>sbtl9Gbt;@7xCNT21PFYV{$s$NYtJ*xYw~I%edbi2nB2 zrmPID?T2SEP@nwQzEeazFC=A{QkIqtvnLtV)w%?zfl}(Tq*o*h=f#^5ylGo}Jm3Yq zp}>JHGpb5AG&B@|JE(%|y_lOf`ghIC^sk z1hV~76^?c?pOb&8IaOH4ML$qju0fqTO^Ew z<^E=@wGmB-hx~;mdhfEZWYEyjCmO60&KFYf=|ms~M!pM4pof*^f79G7nWU2a!>ZT( zMH2o?ZMGPy@T`d!O|I8Y9t2!@3Sx%jjfJFbRgzSSpryNf-E2$Oprty66~-6yV)*1q z!g+izQ>+$|)NARl2nz(|V@!TOT$a#Mp>1{#50BqY`a&pIAsG=iOE~ZQaQlAtkcXSc zS0MxUH1iXZ#dH_U$1$Rc*ve8&UN6~{^zY^(6M&WmL%q2*WO z7_N5s{p%EHUjN`;Hg1u|GRgi@exT4mL6(w)I`7~>oy|cER=l~P*G6E)X`&^~E3A!( zgv7%0JGo4nhWlp@cGouuTMXD5(LLe92bgAjG`D0zjEW&q5Tw`>?eC;UrZ`hHL60FB zgs#5&h97CYbI;Uo1u$6Ih1bttss3wv1gPryoKGkM_bC(Z-*jG{vyd6*K#n>*^!)Kj z-@UW`gv5;QdeCDrF)>au9737NI)?ExIYOzg5}ybiPT-~#b)wD0m6(X( z=%cE*j-;El9lQ0x!Ghg$gT{z)XcqA6k!JYq?4eG=5_+=hYTD)Vo22COI`UFAN9)us6>g@%A*j}=KU727<@=C znciH=u@MN5fuLWEtd9Nr`G2Y{ht%Nj&Hu+D^#6Y0k?)3k@b|pD=MRu~%FC!o7fG4; F{};8-(hdLs literal 16210 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 APXGV_ literal 15943 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 P2t-L~_KsuxwL_$JBxO9nhigcrbC56!s)0S|x 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 G_1^$FT0XA; literal 16015 zcmch8WmuG5_wNXbC=x1&k|Lne2uO#bpaTLD1JX)KceetfAfOtCcwGWB%zf{B?Y-7szt|t1%ReE$NOci`KoBFJK2|~?&cTm}2|_&h z|N8fkoAB+7ozfFYL{=-!JOXhOfqeW>#W`kq#K~O+HCVN7w_5N8$q-IH|LJ*n@B_TJ zc{ktQy&Eu@^)|0amE4*m+k)e%`U9ze0IrLiQgy#CvfeBsAPYB5HjzYntAsw|iF8~& ztnaz^uGxU_!qu;1yZP}BwoO89VovWw+G6b5!W-G^a3eu@%f>FP;(kCNipZXNpTYfb z=FQpbxbI~DpI+EHA%VM{B}1XuBY6C4C#?G^>Sk80y@?)1I1u=ByyX7cG7-wOFG6!O z;WGjs;Z5|*l!J?fB{{J z8fh4z=T#3k;Pg;6^Q*N~cxFDa`T*f2{5i}JKJ4@8h?bBnq3Aq3GM`{Nc36Igi12ev zcc*2cW+uf6$yvhZm);P-EXtz7LqjL|Ge!1A$PyHv%9z{i{i#Su7%v$i%_!<_bl03Q zLFY><(n<>YePVj`+`uQ9^fU29$@ujo&M!Kg2}b{3CB0zl+4JYdDq%0Y&g1c2lag}b zFTKysJ{hy4M6(e2=-QJwG8vA@d~uB+V@eDu12F+6>7S9Y!^80q?2BAXxO^}2E&B}f zT#gTGW3UEWUTn>ba%yh=bJk&<%bi+QmX@Jg%_77{|I8sDyMOH~9tfHjcjQ}I68{it zHhHYELh<59vPmr^b(D&MNah_{xe{Nhn)!qnKUZ0Q&4sV#DGj^C2T zy9Dp^iDT{#8!B3CTc6K14GVD8Jj=rU;NN#7Q`EybWy@Q1XIR5z@>v(t`Nk)u{(Xxx zKpRW9=@7wAuO1?FosgKDh!3vSUPMlxv$yw)vG7iut0ZAZhWaMbX-s+Z8fV&`H*<;X zi(_9xt(d=OV6K8{zOX1yPENA2vKB}X5fLRNCT5lIXzUWudu%DHsC54hqV^+GR8-`u zJjy$LD^*_HuaMGX8Y}MMDkCE!DOr>7(1+h*(Eh}*ga(t8ApTY)FzG=NjXfUYzw1w$ zfrF!E-u`5*mUJ-w#*G`2@9DjcI*js7WPe(XW%Vt(?XGASZgA~9P9N>Kxve)?U?LmA zenZT4%PhOxh(Fq@e7jfIbALv(seodD2|K!Sk}0FatogZ|L56_y8dDX7Rq?5;>{ZF7 z{@h20yo`*s20R1Hj!rUV`t`wdVy?dL-W}Y3Hm=~fGAv~Ox1Cbg-Dc}=2izieXj#{N zz0M`Vq-l5la>~OK&5Z0^@gG!%^bCVyM4H6iA9}v*k*w}djZ@q@;@;cd! z7bZ{Hhj<_EEJuI()Lv*cMk;aCUM?ykau}g$iIkVO9LSsb_2H_ltgMI0ryoCl^y#=Q zkGieO!EHKIWU-9fDrj^$W}Dw_!F}iW@t#szYNsz5#B3h!p{FG5trVsdI~setEPtP=zt27`qaC3>1Ztc&-n+;`a7PcDjpvor(b`xQ)`m*B%ZS>|^V=1|GhI94nKU}?I*`M=J z)1jW$$-u~{HBRghruSqv+UmP}EUWJi>_cC&&AGO|#cf;=`?AN9Lc~jqTB0--H}cRS z?xL5)-z}sS+&Ale7B4Qw!C|pJSr?GVu2a^p{ROS8r7x?BeO3EzeX3s9alnxANw8>Y zl6bU6TbG1x5iY*!31<)w^gqYNRTy_9Ha9m94{J#&c6N3)HZ{e&F0iC_6?+`+BqSt2 zHWUf03>B_!wn=dL!ZSZXb87SO@RT^MdhU)mkv@*=_=p7+LUS6Jb2%VVY(u+(Nx!Yl6w*&?I-Fyfc zqVAjVd!4v5{Br2i-tAJ5;Ls>AZvJ|2^nZzNT_B~?LbnN|=pZLP!KJ(G>>1nSef^=9 zY^oVWs}+X?#8d^=6E#$AQ$mU~i4Rv0C*{^RKmD_X{!Y}b^vq+o8mkbybLS2-bBamV z_xbtxnVA`Qqyboq**fJIBU)Nv`*t0sJ9mf}Ao6}>a#4INEG#Yu^Qm6P+kIkgJA@3Z zY;0zkeN8$Rg9SEL--+8SN6U)cKIrIfs->xBYM5{pwDx@cdY!>Hf>J$G)uIQIThL*l z`|aDeP@Zw~zCRu2z-c;G;ZZp1nl7K(CAk1AvA3Gd_p7MO1|$jzjUX=DE01QlDij!c z%iWxI7e~w8Do;-ivAr{fT=e_Sx7;uFD4Rq^Mivg+2D+hPO)L!+D!47rkh@tmsOh-O zY&3B}K83UMR$MXe-g}RS@cw;ERJ2SM%{f$P)%NF4yR=B5X%DNAkPEDnF|SjPp7l1# z_f)(=Y09aijzbnHDJeeZNp(u>(?YfniqK3*jl#^KJMr$eD^K>LVNK{nz~J^KVGRmI zx2(u_3qMDpLX%Bp3n)V*PWA-G-0fQK_btJw`*U6?h_3sF7?x;xHPe}h)Ufy!SbSs+#B&rYt`qm%utPBXHj)=ap}2{BdT8Ry1lnP^}02N#di;iMtUadOH}IVdIegu zElv!DS{^AajL`Gg?ol(!-=_&#CGF_wfHkytwAlt5ravh?9XYwmT%xek%F%M+Scw*A zto)q4FfR`eyu$7a1-8G$!3?$n`u&kS!zLZ)iF3Qbaa50^(M`v$@bdCd;jRvPw_H+E zQa(;Jtz6uBEg@CL<3DAgH&YWL3ilM?DO2rO|`?V zY0N<|cH-4Uk`+y6%av3PQj&{V*#BC;(o9fOPLB5T*mqaQbTxTiR(bD^d5xL#J3&_V zj)aAVRvc|K^`xt*sj6~nmlT+i8bFewbv*|%H4DEz@;|6hLy?MYwHMh;-G27fdMZeO z4)S=mIkIrf(=A@yW5GQ~VAOf4mQ-TNTJXDEwCbFsFBz%$-Z*?LKmL50~ql`X*$%!e*KUem%O$dIkbw} z^>UAvt-M4$4hmM?Q1_wBfVdp4P1Ih!Bd?Wb(13f+seE^LrxAxfWGFcWMM{ey>h&Xk z5|7Om0jJebF`LO>u4P6$m!lx2(&V*C5xP1^;cfNN(`Ed_c}Q_TwU!miP3#|8S8)-PmQz)RoXQf@|MMagxid3^$7W_`MDk? z+-E+Y8Gc7ghhI>w8a-b-)Z>w7Gu~On^a95IAn9ta@Kchs!cK#HLbK)NotHNsd;-J< z9eHpDIt-bi?L}<7L%ym?Sl>>$&;K9AI4>9av4dgUhUTuPS1YUJq3+B2MI)q8IQNtcfr^ZO&O6wcwDJpKoR zAqWYF@o@+ae!w9T7>D=2v-N-Uf~(hg#98704bLO0;tkYab6+PW@EM@tM25oc5n84& z&t7>NO|mipNlDS~{3D-zzX`x|^*#J`jJ$t~s)NF=DOX6S4>C1inqigxYQYpFAuD<1K_faSrnSh~!m2QM0epL!1OK#l+ z;~}&y#q`$^&$*s@zljPD507CH)ZSXGR^azMJU)4pBppn{KUvcM_qJnc%Z#n6s_K_; zA@Vv3DynPOuGy^kp{1qj1fGo}tdSpr0}5ZrEUs~JAP}#o=w2oWr*?hzTKLs*IDiRXDHTNV5W0W23F1DJAbxN$RaIGS$j_Y=?35F)mM`nv zk_~0L5X(-zB^)cC+SefOpJbA}zeu@4-9Oi8TI%orgy5XtUK~EIwiH#t8e;Ixc_Cw0 zG3L7Df4bP4H=mWEjDfGcojG>Y)0CNB>}g@TonF`#S(gtfiE4|#EY63plFUVhcAEP6 zzKbFzNUBrv5W+3<-`;NCXC~~0gO^8&PdCFunG6{6x&PLBjjidtvX{jbI#V91=IV#vsvmY-{+3U_Zx%+P zt$SFhKQj2FeBWyeUR7%mP`D+I>YS7=5?@b2kLf1TqH4BnX+V$Y&Dmrp;A=*6aUe0FgCCX8Ze z2({1qQ8xUI@EZnmi-`dlDj(rD`Nhzr4;jeHB=u)3*RNl|C&Y@nbSekkC#lI${X(WUkY@<* z$OGromD&ctz?A)tkkCG0q!oZa(1Am64p%XuWUfOkcWdu5_ljB3Loq^XY zSU*2M*)SPYkI&|L00Fxb-2OWT$;5BMC9xZdh@c}Et^I{+KT}$0tNGFhTinU0N+w&dw%HM>vlJDXP5^6GhezUTe$yqnowrh*3l-Td=8y5=ZP9WYJPK=sIUAMQLEKvg|70k z!rOI}x<{}OZY#tc?N*+Sh~3^_^*TL-E;oWpw*v0c-rnv{=lWFhWFbAfHD2O01+6n! zY&VCy-I&L2vFs-KCg3YzD1a!)ve;R#rw0g9ZrBuoVq7m+#m&&)b_R~^!92r2`BXdl zKL8rCi)X3mqdlNqwkliy{f?L#cnlml%vJxFS%Y370R0;m3>Z2*boA%X9dJ89jS*^?Qa52aoxw(HB!qekbufwf*I{;E>G}^3E z4@Vk~)(RflJDZ+Su-ec_-`Ofo4^MSmvt>uMZM&Q>;v*g$hk~AS5`1PYx+~R=zg%>= z6ki=J5YpA4r1~ah?b!fvU#g$~b+-Z&j6r`1WI zh|+0b_$s$LW#NXS`V~Mp-(qCcb(x`H_;tTe3-a$PGVFHoQ1xMjnzlm^IH75C`frHB?&^x1P|3Wxda`W}P5F^)%T!e_C%(n;f~r zLCG^76B7eHV`6;i;H0zSF+~^Il0QWlrryy+~A$9K2INWPhT1saEKL}OIPPU z!Rnz75OC1~R&)ZE!w~Kqg}4itn7Qp*$sm zQH>j{KFlp@bBI0tdea9>U%q_)e9yG|F2iF3gJk~O`%p)J2)7rK zERRp6*4E$NzITa>V|<2qJkTy6pH*RbTtBRLnf?X>L4|h#`Z66W?#SB86HtJi|88VE z6}y?_5LOFf(bkDsBl6Xw0|IPg~oq`F=hYKQz}o zytz5RED}q?aWBgWW)%_}5|jR(p6^eCuJ3Ztq!#a@qClDAmXJ98IbMRuFR#Z&{hY zE+(}&gH+OJ#m7 z^tH7O0zt31R_&nW@x!_D)>X&E*8^ znkt~#eF5S1tdy8ES25FjY3gWPp`ufCIPYHz+_e(dL)F+u!Mk_wa&oTViUKqo4F)oD zayYsg*v=BYx{H}%U0tkl*4As#A{-CfwaI4tX%Q zHDLKD0jmmwhEUj;p_A_I+9)b2nx3AX?@B%0j6W51-&+ghs`xu^?{@JS(QL;P`uou> zp!xtO{i4`5#jW0Lr8pHjsner5iFArR;1R8ci`ECtvOSh^>v16N`STq2{S6@6%~%q5 z9bvxwM6{p<=v;u&Mc(RV(jJ`vF6AkMoKs)+Z$1NnzDd zA@vHdTvB)+y^Xl9Ck{E9Ub$l5r0W{_@c@ms2-OPjIuR}~8#34r5obUWqMmwPN}#|f zqx@tkBcwp6|pdgU9@Wx6EJN20#VZ`ckK~m$fQIC2E2d2cQ|%R zcjXF?{_l@~FaY;NTb7tKeqpIV&&sYvK}#XA)&8hHgx(5PPPA1;+IHT%cki;)^Jgdg z>EmEG0$G*bW%_eN*d4PWg?RB110J!XwOUYWU?Diqr=X$KreD9dA_jjxfrM(D3#^#9$H5N_W+YO5&S>{Pc*WV|2znD^7IjZgPf&;fkiZ&SFx-bB z?6*H61=$Lk=Vg#>=_EYegX$-lb8zxxYM0nfzy2MCI8dYxLFON!J@F@@#Rx}@aNNE7 z7nXV?0Hv!6qCnw)0OfmYYYPH|lQeo4K*6}h=hmti1_fLFqmSY0V2AxxyDfztIQ$siYE)7W6C`7 zv;K~yMKU2nnN+^XjS?$%!Io z=2|sycTY_H7B1e37nJW3mD4AJLIF+W7BCK&a@3}tcBOtA&v^RlIV!|+U1t&~{Y=P- z%ZhSYe|NK;#KU54BTz@(HMhDNkU{NQrL&7K@t^%;a+(Bw9Y0K zQ_`g!n9^-dlD_HA`@;!9-7jK+;Wg+CV?i!&ir~O0e4vXQ(~<*a!pZmU0b{$LpVTm) z=h5epv5>#L9Z|c14O@pFzhhVpV$FlEhRVN}kVt)u^8MtSaW%lSy+K}zn1r7?hyaCR zM%oH4@oO5In=4n+keKp88;GMl;$Ta5*)Y)38V2xu`s=E~p2%IemzFV*n7&=}!y7%W zhHZrsO9v~pu}@~@sfP-cdDSBf?Ua#o^G|M}eqhqKlDp>nH=E|qpO%-rxG+#^g-*P? zzn==W2YWgva=47QT@9Hr;(^C2LcW_@t{&MZCUT|r9O?E1{^ueuxuO#Z8IXDMgbKA^ znU@)2F{~;ePY1O&Hi~;Ly--*GFhDaa1{^B_F;2-;+>f#!i4+F# zE;vX!lr+j?0v-`!56Fxed@(1EVL41JoHHwnhokL?u+*8iu*BQ#0!Y3TEqtWbT)&c&Lji{CCHuVE4~0eTIT0osH} z6zY+&fl}Y7m{Q69=q_6maIxR|sHyh65#x!_SQ|v%A;3p0oWH=mb_WPi-oF7|uNo#O zimU7VQv&e5u}We~WfTaJccP~#noQtVhYJiW4EKKi>AkQVmXxU1I$xpdQ*E4&3Ps>Q z$OKZp`sLQY1nLFu=dn83Awd=zs@Js41G?CP$u!?|cuHHbg|oF4H4=UKWSq)UWBD{451^fbnd^zj{Y1f)`i@N?i$O&bvp(`LIr3n_lbxCxua{fInlM3btiJ5 zNAa)Cau{9MCh)1gZo&-=EOLWSu0UWmXASIeQICJ~J-{*1EO$kF3QY1UlVCyJf=n5w zS2ZghSpjz&&LfwwF8p~jy@8yZhGr6eMW6}ke9rDYv9|uvVcgizvg33U(f)L1+9>*2 zwLvAq+x0Oi_qc+pSzRQr-@OjGAZd^?nmsHLcrjh`IRhDvrI`t9Dh39816+s%Pp~0) z{zERp(3Tua6N`>$Nf3Ks?28|zlXR^3qH7oU8-V50`By(e;Nk85BP78OI3)vtxOwxx zzw>|R1#6o>t*xAFY@0AWpax1kTLoWax8?ezfP~KB2Qt0MU!|5sHY(-l(_S2&Ro+{hX@A!yfKaN&wfgTvLqjt%mZs_zz{eqA)m!3_4h+U73k_U$E%@wp zyAaVwxfCw_0mY`YJ7;; zy{T}tFWHnOt=Ht^)_tZb4CZ8`DI3)Cr_aoriMYhUtbu7TxFQJ<$j#ovR4pO(f zgtehVp7mft3> zYmq6R8qG7qP_DDPk~cG_OoWRM+zpUdLNjh?U<3>EE=l)wHIzHdW3G znn1P%EveQIG!`-#BK@WQ==$LDfhGd!r1jv+V6U0*fvc+(Y_NU4JqPwf?*|~2@d*>@ z=sIuxRb+T9Y4`4Pk8)1i0 z=QV}e*(&rF--0VANP|uh6j-Ns&;MO8y;n4uUfss7;lNO~ifg$J@Z{+cbGxpIBVA^N zL*wYxKda%^83HP_U6e8Kqsjq^ zOt*V&F4>hjTGZCm^k(c^_vd`;=uo_Gxw+j(4OY#al>s*2qT`=7%R{NXnVbxd&9%%e z<+HdnN8LBSe!n2MsCD_DS-bLb{8mJ?9S`*Q{QTc7OZ|O)pT!Cr{{E=Gn!TscGlNg? z-AsU*9{e+1zgu{9T)@o}JV$SV=8R=yWp!K~wX35*yN%_&&W@YYrNv#5_yV`}Wf7an z$DwVc_b`fuD*<{^9v2lqq)~xH`yyO7p|@}wl(Rl9n|FeyGZP{;?>z(DUI#= zF8*_~bQK7ck@SlfFOIsKnLPA-2%lZZmHua_4DAo5Ix#5Oz&aj}K?$q`C5MI{39*

56nW& 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 diff --git a/frontend/__snapshots__/scenes-app-max-ai--thread-with-rate-limit--light.png b/frontend/__snapshots__/scenes-app-max-ai--thread-with-rate-limit--light.png index b785e83e2206f758009bc570d75c7d1c123811fd..1bcb94198a8b68a53acb5a67867c26b3e1b517bc 100644 GIT binary patch literal 13345 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 OWTl@y%9hf9_kRHb(C+~N literal 13106 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 P10O)F(1^XtV1DL5BECA` diff --git a/frontend/__snapshots__/scenes-app-max-ai--welcome-loading-suggestions--light.png b/frontend/__snapshots__/scenes-app-max-ai--welcome-loading-suggestions--light.png index cf026b65c4994f8b75992976ed9e32bebf326040..358c01f1ccdae5fadb838900d319a89a5036a57e 100644 GIT binary patch literal 20284 zcmc$`byQVf6fSzO00BWzI;6Y1rKP32q`SLR5Tq3j-Q6JFAR-{$-QC^&*3sX+h;R`Uhr_h)TBB)k`P^3_&Fl|TVclZ;Ex}_rrD`l zZEnfS5AcH50BuT2it9#xpk6xz)z^<--!Z0(ym|YPZTL;rcUk%B-rkLiPJaX0^29l1 zVcmg1RHBNC)cC92jg$Dk<|}AAd)4bF;3mMfXwvEI`t096r2f$({eQcguhAfn|L(pm z{RqMS_o2trCqMt~4br}W{5$BQE*u=wzYkBJBYcGWyPKx=7J~fm!>0c`rxe=UhaEn< zYNQckERIkyCBYr;fPasJ3xT9b6T_~&=~hn1+JS8dRZQ8?LUtyXB=KkJ;0hOE0>=$L z{d@0DOyoH#dI`8P>{ZTCrtr-2UIgM$G1X;}@}nf{H`30otEIvc83 z(PhQL{yw~rU1PlD{`xYUUajJUrFiz+8%_et#YPc|{-(SAY*+W%Exe+P*u+G1WLo}? zxjSNTw>FxUIn@~rCt%l(^&45xhP&{)?6T#Rf#z$gz3a4ck_$Aw6pZzbYYXo3J*)ZN z^p}q$jD>4VRkTa;x*1CeaHk%|XRzpXU1~}rYQ)iuG&=0eAdru8pJC=tYl3by<9Ej6 zZkN7_?oF*3T#%hmGmPiu`@E7zC27itCn0v0?aw5x=n#Sd|D-F@?B!aU#ur1wUjp1o zdrL%KGkZv}vki2U3-Wv7`}+~VAMGlHU~@F6TTW)rmzwN-R6;I2M?coO|7-T;cU+-1 z$Jw$FHF2@?XtE$h50YFnWh1?K=I~+YY-HIGF^i%9w{C* zsawI|lm9xz+XgBO*OTMp)MnrK>U0ZT;mS9M_}QK1rDUQ{s1B8B;3@0S0I_0f#HLxZXX_zH{r{dB`@;8{Co`mXMnKL?*p-ndOj;#5*<&r+=WQ( zW?N5hfB(_%K#a!=bkCP+x=%hlC$G4m^#u5}QuEzw`#6|rL1%Us*h5yA91qN@OF4__ut&-|{9-Z=| z4d+T#K?WO5hMGTKO_&;oknkOXd2=$;o4CwXQIit!#m)|Yi>CrrX)>gTEAZzd=KgIs zPc_4sdez+QwuTGW#KTvpvb&-B;Jjv(f0rJElDL3Y@wmL;9m(C#63+m58C-Po9y<0? zf3!$&TILL!p3vlOdXrG{7YHVy_>JFs5R$P4v+7F>Rz4}IRGqa6dlgvha1ik5sAvgN z(D*YuaFOa&2kZGX*OwP0pplUNU0Vp)k5E8lV9CCzsXg63Zir>TO*=X}muh~`L{t9d zs)@Qz%rH{~F&jEeiu088mtQcRx{hC7h-9y{po*gI_Q9I@qIr`_0ZYiUlyOWQT|WD` zv?1n>^H-V}CjIWWM-p7RXkrRKy}-CWp8s{iMOq-!5gj)r36AE!`EUd zD?S1THTFKX#a+iML#Nz9LmtUFpD&m_E(W+fGkm{Yq!P7^w53&ijbv$4Tdd`YTB~`u z->&G(&%Bk1=t~^buWqhtuxpE05ET!wti0a8p9PlWC;S`ZCa8|AB{i;;F6a`mw&bXn zsJOo?6`ezJS2=8ygqsE9=@A=S+hi#>w|hrz-)9IGUd}bK&d#0k6vt#LyAwvT{EkOg z)v&zxdT1}^b&1xd9#P7-%}b4;q;`+ybv-~hyij78n}db@L8T+crqhP~eh^OIMx-PA za3uB;c^^;Jk-{&F@QpCU!n@UfxpqSrmRpNHJv-W*=}cBkh*VOveK`<2!S8zzY8oRG z&ucjBiRW^5F@C%Ceddw(%^@EnD@6~Bp{Qy~?r$UUue!9Mp{H8k*nTZze$&z}zaBsx zz$5VC;XjZHOJZ`UeS^?lKRBlYi>dtRn5`fU*?pqx@&6VOaBVwYaxtVNG>D9NrnY{k zC?ziXguUckX7ljytyKOe$`l(9TUYvGMm!vX`;E?Z_|qdRruZUNTJr8ueTO|kDTME2 z8a<9ag!OBNjilbr4s$BDu5EV}5(JayBG$6>#G)}}Z!p2Y@%~0_yQkUyXhNAoF|X!$ zvWO(Mwrcrp%UU5YS*mcL188UdD-J|aP}E7Mz* z^$h}97%@JK>=X(daMr}3^ECRtk$C6y)i$W?I-*cs6j{#8PKIb|TK~0JM_l{tn~9j0 z9VN+%tqA1MOw7V}U%eKv7hVQs`aX;WaklVBs+f?wVWWdl-7J2BCHekHl)aMKg@V=82p0f-vxzZj2w3t zTHqOV?XE2`^YJL_NA88{f`Ar86>RRVR#;G2#TRn1?t**y2{Ee5dl)h29?V@Oss}93 zCS}V&N{aIN8=X(~{BBvL3$P7A=t3aQP3VJ@`-*_+mf*$FlCf-|s238hDH^Xj!G60} zxN572hg$??(kP)NE%$1|h*PAHF50({mF<&G-PiNY`NUj@nm>$XDl3`rfe9PRJUpij z8zDdD{a0}1+76x4D(G5+`PI@=>%_(#N8)2JPEiv&W)8kVB?+UDUMfa}@~UQEtJ^}~ zhrn3dZ)6FGhW<>#*oHDOIEMc3!_hP_Wxv}WwLJ9f5c;G~Q9li=2oa;Zhb1DLzY~{~ z?VlFyjAD`;EA~nr3ySVwJ#SmV{S%CgOG@4tYg~g&m|F@lN=#_S=k#!CBh|OLq#TM> z8Ome~XBNJ~hO4{CO`<_X{CWI`&p^7Gu2*`_t(4IR(W1b!#qeV1DrXBuHZYz_xW7Rl zm#M=og)@HO$t4^ zFzVWA!63I>Nz&CYDL1Op+}V~rqd+pw|vj+;)58@|F7P8w-e)t&KW!p-ZnJ)x!fb_Umw zxzzDuvFjcE2)e3O`RZ#m7`!WoN)id&(3^yn(d$5WJQ)jj6%Um{+pz$yq8U8oGHhkmqrRxfkh*01WOv1 zZn?>kQVtiL=U3_c2j&nK!*V(%3cI9OUEUvhZ%ypQEacMZr2h+V*#9Q>y;PscNsXRw z=E!!q$$C&E6x-cpBbbsXo-B12Z39 zl6S}@@d_IJI<5bi*H|Gz*yNlil=~WCiORq~&bR|R9cqVP%&B2afyBocjNWPQQT*mG z$>`<6mCb{Sp|hgQpR1m=+@4}ed4hwHPOV6?l#Palci>(;28w0ioc7Q-XmfNjQ->JPk=M8>8y&(D%sVNYquR}HFPDVyl*%tm zUk~7M9)5p{fx;6;*5!{b8Ht?%6ofR039m0eP+R zl7#W1`}LqsqA|Qv9bGVN2!q{&xgQ zwm2Cg8=g2F)5#iZzlPYF$ZUq|Ti6AWPGPlD4seL9}SEB;$qMHyYn30Jr9Wo6g{ zR^_9y=SixCLtCfM7T){m#kJO?wlelE;uVZ+RcRK><&GSV>g3|!zTC{2NTgQ{`?+(w zGVXm=)o%5#dU*DCg=?mZCVDjG$i%!b)gkt$YHFTHxx*7hlIOip`PM$Y#rwT=D*ih##HC`X52tl!^ z))Ji76~l9(xs3rQ2w6~4nQ?@qKVM@gNxkuAw;6*%^9{Jc<+xD)A`8-Tx)VNImiUPh z4NtLG;U;UW9pe7*FckM%NlIM&33GgMH{;&H4i5h2Nl$xJrtmMq6xJY1CaDau+>seN zUonsu9g-y8tg%nz+?Sh782NYkGfTPuI`V(7VS<~I2}S&E!Q^IEh3l-(@xNSEDrsu) z{li(kZJT=EW$nAaa4C`dXEuw0Pl}CJgz3eR6IiHxW9Dp_AzdF{(`OhJ6)4GzBM?J# zp_HL7Klma$8p<=T%|6CiW8)*>dGnt1P&FO_pPG@A21f)LmLJgn78>DHLOEnoUR}{I z4emP`Fkyq6>4$qU`uw)c(cLfL%L1$C>GocyrG@A(GT}{f!@X+Oy7bm&{HAjw((sMh z$G9t6%WlZ5r2dNGN|A_sx&Dn`#K@7>85^M6mE`i5{YRc=dwcU4#V7YO z8u*jzCO)=`UcLO&tQn3$-t{3nqBnU~pU#45Y$8t7FZcfY6^0Y8P~@6rQ+?xgoL(Rm zvFP5ianaGo`&OU2I`^8sh80C2xDWckYO#M=3cKKzL?$-6v_*^81NPnB1C$QIROD?p z@VXr1DRfly`9hBjBbL8`=n~ciiEOABUIt7_rlXMG%P) zEWJ8Sbp>8Fx^K)bOef_kk&m0^E^^n^MT5|XCu$<(7USsFV^ypL6DI^G)pB2+YNp#0 zn~5W&iE-q@MQH|{<5-g8+izkCwgaE4y$({+Fn36V3J8NWyPqYgx*^$$mqLe4_5?4$~~hb!kK{F|7AxUUAMEbObk`$OOir) zbQ7qcJuz=7MW)np<)aAGMUjU^{H%*Cp110_7#K_DX6C24-*dg~{$hWef8~avB4Y|;BM(nK5iwn69v{PRMut)O7!5h|_S2)XjbIvvXq8fR zHg^Z%q%M+c58UPJ8@$TTWEJB)X6A-oMSO%o!|O{9eqwy7dL}Q)!`{4?GPd*sX=H&B zWU<13*z^^VB;oLYU~Xk38q1GTqI9+uDelf8JKqCs!?UB^2S`xgO35!u~8VF`!rUFs8LWL($OVhTxo5j*=kpW#B`X z?C>G}8W&A)7`H zC7w4bx$VSD<_9A&85j#O8OQI4+{(OqpS0A81nbT)_A||}w}|FUT1t#DS-Nx_8e-(o zs=J5{D%*-yN9YPy%F3uKHF?JGZJBY$D`QLrhmoBk)uWKT{O_4Le8-26d78rib4c>b zHbmeX2%YwkWLqfL+0qBNql|9v-14Zp;@e+CBWuwFf2nswJH(f5z9mBS+waZamFFNT zC}2P0lc28B%uev=0jSp_N(E{ZqEZv+ z`;A|#-Tm=cx&d71OR2899%GT~r-%4zN~ZNRJv}B7XD$=09SGT~g(|sA)Y;u_-?+kU z1!&{YNoYg#U;YBrtsS3fnN;XY1$27Vtu~@?cC?!K8hW?WBMEj~loZ&)82krDl2mkb zDz;Y!CQ9|>EokDi4W@s-4PQ}o2nJc%^65N@6ez?o3~$sX#`VgidN$K zQqyWGNO6T*64tQ@8)=w?YVS-NLlu()I5o25tFvPgYqi$rnvJ&R;F{;5ib6j{@WD{0 zAN}JQic~F)jmOjqCwv-wnsPRuAF-_BS++Hv+VG^$ljs((d~x5YdgNQyXk(=OO?**I zvLL^J4S}yyp^!|=czrGxb>OI+j#j6(TUl2jrht}~9e<4~8mFSWC?d7Rs9@CNOL&6A z&afWc*~!tUh4b9q7BIL6$KOoNz{(yclk-dgny|a^biNybShPnWsw-<`OIljWq%Wbu zO8skKpq7Y8o0@5`&-As(!nber^_tnFYOlmxT_ZAR)l3HBUFE$%6>u?Ke_g7`XuD8H zPenCo#NzJhzJuaHuG}%I2b}x-CpHZjFxH>)UhEOPQ>pjUG_I~Smp>qSdPGEw5dxYX zW#MATUk7*2=I3>*yPSCT;^10IGus;Z_zEm7E4I~Je)!lt^Q)=hBo>V%lcFB?f`Rn^ zXwCN-??KS-@kdM9uwJOrEb>RMs)^b7GCf_-Q&CmPBvNofPFVjeL|I9#bZnarv%;Z> zOj5JBB&Vc#o*4kS(ddQ*-%}j zv|rjI$&{pU4E)ig@>D7r8V|nvdAjPE_?Kq0#>y}x1Af2f-ExOnV& zzDGQkG2gT>dvoyW38+lMEQMJ~5co1+R&n`-uMhrkl*iG3ft}eNe*n8-e8}*OP`1adA!Q2XbEz2cHCms8Ej&V4o7XXmS)Z399{u(5IFiC!<^}@ z*1JfjE5+|qRzo{y+h}V60nd!n%3(r7+vhiVBd*38m|{a5Vdd!*nSYrT@S!oy$%$k zd1yNPB`rIzQ?-q8HaFLB!RGGT6`8%=5cqe_DOaF#~VCP_9mwpDRZ@1N6R3iHPk{FA|r%A?;!T<(dZ*Acad7mDQQnQX1Yc0&-Jb`TDz(lc15;8KQ!89&oUP{WE%1UML z9*ZhMZU-6f_}df6rPH=~#Z-}%yj()okQs=|KqI=^tzc}v@+dBsr49=V+nZoicBc+r zK54nPHW4?eF+qc*nf|@=`c&sfL`{T8DKgwDYHGR7y?_4vK#8z6T-h2aO@)K}R4Ht^ z)|?u&sM7O284FL=LM4#9@3ZwO-ZctM$G`@73Rc5uavF9xIp6$Q+R042prquKbYJ{Q z>^#GvoWfx(buyw!7vtM>mzvcz@*x90jDYnBm9a%pLt~6gRzqWI;o&|)qox;!k4k{S zW%G2aBIR~%1n8b+vYE&S%6j{g%@tiKn8qeX_uKIs;t{w+1g|+AJ9ZgcF1v+3NJetl z%sL(LqlOZ&GJ^@)jSIS%yIJfAOC*zYup7-y^(hG#us+_o4eZkzt(m_&xGB;7GoLmd zNaQ2qUD!@nd~YEer7;zJbD&kSKhV>ob|EjBP_ou{Pg)?u04il9=q5{R0U->~1gZrOG7;z2EV#c1M%I$AfkDWpEA$@KA8*=;GIx z*UH$m2*eT*glZRsQ9kY|YCa4^?*x9B4G9~+{~*;Zxi+`dw(|JVqlDD+Aj*{KGVk1S z9cAUyDN{W%BCg9eR6Kq@K4@}h1c6@j?f30%+_P`HMZ4hDm*{)B78UWZ@7iL0LOI>! zIl!2D>3eo|*4@+F+Z(HUEpl?GrJ$;M{u@J{pyFy#?C*ASzNc4lop`bEP@3AJ7Xsg( z$ax`OdJ808;XlnyH?eeeO5TvE$iUKI4t7E{sxwHHU1z`^zW`F)S=B-}|fBt}b|=$?~p|k&0xZTrmQz zP%MTl!Mg_^z~A~mdG^fbcuS(MVs0DWiA8#^lt8Uc8D`)9N|=4+1$4%4X*^vHRBCR% zoLOoS1Ac)9hV{(|I_;H8@7M-}5Q$Y(#&YFT6-bEs1dnP8pZ`*+wAG+tW%_^ zl*TLgq#8#EM}MlPve5Vt+o#&&fig&S`>cN>008DB6=tM?gRT-}PjhX0htleRp@S;&uM$ zX|>Wbp)xZ1 zU4a(eL>#sX9OGVhYYC+-$`=bPmL^gBvPBwqVbj*modh*R0+EddD>a@Md&np#<|%G% z0kWMD$?pfeRySJ@IEr{3R(}>(m{Rjb;B#N!T_o=5Shnt+M2B?;caM&Cu$K*_w!BwV z%&=N$wi61}{^K4+DX+*Om%^7lnzpC!JX5b^$b^a-;s5(PgGz8gNy)z6!~F(`a*4{P z@`BcC9NHxUfm^&kumLA+7$Kmb?k1+;yw&n>=jwI)W4%lB?jwV7K7Z9nzVX_y&WabW z{r$5t0AlYhSL6jQc27`HP^32RTOXPP_1}tyd;y8H8WzjBjJu)Kd;_#I-SUU4kHc0{ zO?%_^mzJ1qX4TiS%O57I^!D!W@96_j%F4=I=c?4Fnw`&!TK(XuXTHQDMTgbZ^l>|$ zE8FOf=Z|7+lS0LjiAqw^D~pP(Q;j$Jb-DxOa&J!bbX8(<_h)JjPFmbc+?T`fIH>V( z!`6vxZ|C~@WVevL?hbsFCaUgj?!-MO68Jr17A=VxEkeLESo zuV!b5C8cWO7~N7z|5up0bzXIY9KUfneTv>7SQkPkCntFi8bzk6i#K~>8EPIX*d{); zUnXwoz4z4ceK-!OuxZL}H!Ajd8(Th_GHo9%jpMjgV&9z1$c0P0)WPxSzI9`D+3Nt`WJU79r*K_V(@2oi9jOUdLMn&E7ZF`M^y) zNMQ=VV$s$}F7?P$;KScyTo?pfKB6tJujkN>u{y4Ys@-1}luM1rvzm@=`JQ{2P+e5` zezqbc3(2?~*Ru7!YFDe?+0088+fz^(Sc~J)iMyZCUrR ziKJvyj*P5k-SGny4akK|A?+FX8R0$FBH5LiLj;Aa!9!LYI!zv93kxmnu3G;sk`H95yU)9SOlf7I|RI2$$u0RUk2Q$Nf=&jsGhgu7*}GWK%D??c$ZONPaDTqxywfZY?J*l9qP4U(c8N4^ zR`g@emJK(N^8D<~nwgn6Dfl z!@?1S(GKl;OKl5Ex%oFVO}c`{Qjy1c@7^J?eggms4p!{-<6;BJtEFQo#j`X_A<6vY ztG@HA({pQHXc)7hcwtMT&e>l50W_?(cCyNHV%GJc$*?o5Otam^>$CXXO&~R%x%q}w z>V(A&nxTq}Ol3mt?e)NTuU6BTB^q#lpMd5&JJaA&`rCKNA zD%QQ>Y+-hCLm7h6?a9@w4uLjWwUQ#e7NIFQsf4UCi)hNC^$jemdJf3s`4nHW8Z|-o z-C6c@^GfGSQrz6){pqTWLPj-L*_df<-sA-4*nQmC>7XdiuQbfeOCgMcD)&3PaG-MG zB!Z2T!Au}!%H_)jPM@l$js2Y|Wxfal9xg7lN*6@Xo|cEqrqk_me)4wm@PQ(q=Y?a} zxoWy9^@-1skRGnaXZkQ08&_P|j}CNin<6oyx=xp+0mxEaD;Ap=e_Jl0v+Z&)f7jr; zXU=!BCx|z{HT-2X+m)4trCIkRjAipQjB~zmb+%ivEdWI&)y466m74rL;`8U_Va*KM z@6CP2{8D)_-H1O_@yvF1*pi#%6j3r(UYCI z5!Rp2>G}#>r!nM;)lwx?NXltgX=_uWMJDE#)Z$b)Q$KR!0!<+7D$kbc7{Wo=vYLh)3X5 z>9_KArWzlwN_M{=E-zhw|4^7eD*Og4f z72M$~6tcHdgHF!G+0>FziCIZpTOLwswC(QwYN;!y1d4jwkv&603fpue*VoskzGs%? zhmJvoFgKqZA^w}^EO_nLpDBO@LJP~Ty1MtnA&fQGQ*e+p#J@g(E$EZPf~3G~*fFqd z5WbQlo1D>%jCw#sKrps$d3SwfT4&jVgcH`W(kTaxjeS@vEuEHrSXGkGn*oVNtJ+FM z4bg&xifXfubD?&gUR5zN(xE&(eaW`;F5CTs5ZZmsu@1XjUQr&O{jERGf&FQm0l&K2Se4P;M+CfI&D>pYc-!t{hA$1)cM}Q|H2wA7?<&-V-K19V1 zF%MPCyATNQ&2!|rR4FEHc)<`8@tN^XdLW_lGeQhuxn!xk1KxwW%dY9j?CP;_egZ`p zkDA%+b_GF8ArV^5dPDvWs*0u=f$!SiZjx3WpW0k zmekcv!3{GROsiEP@~t)MNgwWgmgt>_WP@zPlgz+Fah<*Ju)pWqv?e3>N-0PCKzelN z%+QKtql;rMRF;Gp<_-)h|J;E_YHQ}fLrEh$2<^C^_aKEQdV4$9kzLxlkie6tpeU)u za(YlMsZQusgy8*Tnvgw!w+}0QExad3AoyVx=Udkw11$bF5JSxV{Vdbk)DM7dI_2?Q z@`=zF*8}jLK+-JJ-~ltw6I-4#SfG+hCAg1BjD*?S7U2nbYJQnvEpX;ItMev6Y zzd*NVv}p#7^szwg?Wg^Ite+@woCuJ&Sg;v@ByVmG8G@^C4l*o|y!L%tktv(L^C0&!ygE4_mh3~74wx8Dlv z{lC&F`@c_b@ax*!AB8YBTQv*?v|a4aP23g_2;LlL$KDSLO_5pm8^oH6oP)w>G{9j!7C^x%Pj`$J-NwPbuGIL)KQ3jtpe*$?s zRN)H`c`K1$l-JwURW%nC>7bZ9=Asv#Hgd$bFsHFe;_U1kT9z&nc)GPR2bdY2aymUO zUxVvYbF6;79}@>a+8UfefYDW(ZU9g{cs}?&tr>W*?@?_M@*Uvs=}(mxbX!ad%0MB8u(B=-XybS%`3~Rx@=(E8-r9OEoS=arsYRb9o+Oh5i!Sb9 zXGh7W>UQD5XJbi%F!(D1rN7tNnuSni-OWCEctvb-bko*NU-awfyS5ufU9a2-c_QP7 zBg4)pRQ#f%ywjtzcsBcAo6MPG11!F+je9dLqGA%%l(x*-pl}&cExk`bJ6;Z91O?1; zNdqQ^@C!mdr_I5o?Sk!}KYzy8^kTD7mE28L(4RyI++H@{9O$&%H#|2Kd;9k1Db7g1 z3xeU*)%^TifTPdaV|CM@=)$2L!JG@X8;%3~7-(qvjDawVTn#jUVpeYqsp+KOv$W|v zcAdM$tL|{dd8enR=kD$GW@(Bef6i5Ne9iMm*lBH z(Ad2>@XaeIQq&B&mVa05qu`{$Jwprcxi{M-$QEvk0ofb^T9w@i!iyKGzVKfod&&X zvq2m_7#ZNp9#tC>`2zZ>--Fs+)0mLJ8I}})3eW6(J3?@sv`0-}(Wkbqc5Le#O9@#joE- zJL5i}x|t&D19U~NiKC58$}tN)eX*Wd-f_s_75;R^QbU-dZQ|XoV@m>;`LCLt@!Z_p z2B+G{z$VX&fkf9C#}*%#TmPd(&YD?GO)KPT8Hy3muE%XNJDHp6-rdtTzvAaFDl$OSG zPG~j!%<3#H#;{aeR1&O?Kau1iFuR6KHC|0$q_fs-mpj;eZ*tjRCBju}X$9P-5GvEp zKFOFV`kLO|kxtKF{ztiT+5G_i7mmrrG2LgPs#V1a@K8+m%vYMLnM{ClRN5By@xTPfWE!a)vVW$T1jQ#9%NhHv;uf82nRAY^ODg3llRG=BF zhn_WB4zXi$f6g<8zCmj%>-g-9I0W_?d|RT(ewlty1NU!Z=%Ko%Fd1C$)$#iv`k;%bA3Zw-bqwfQmU}kD?e7u z9gIa}TJ{T)2Cf?KkyEra*qy@#Qfw|7BdVU6|7 z5MOpvM_NIt?T_H)!=)pD5RtGL%+Bh&ZcyoT%c)HCk%^7=tsT9*yn9V5#52w9TT~0H z)ClNx)iQ@BIRdt~EjD_7+gD9j%fPd5dbL(-QUEkjo!Wkc145+T#9Rm>u}I*HdzU#^ zaNb3(e)};pN}{~HJoV<=Pnf1sy!Wd1#~k*vKAKi-*0sC|UXLfYCp`zNQP_t3G{c*4 zzZ-Ly_7}@GqbB#N#6~-YXj*ObKcq9`(k4ATd;F0Q9**g!tTav`1p;5Ta^V=w3}#_f z`)}y*P^LsOR|<>IZ7l$)m6eVW0_R_?9|@rikFY*JMhX>QiOwJ?q@uZD$L;(4^rZRP zK~`UXanjx}sY_j7KdpReYt6h`9f24Sx$IR{sov*bt}7BjGNX#UZpa;j(iapJ6%>}L zb(qsWdlAY#tGJ*%Q=W^ISwg zP1|-`_5F_Te^~T5z4zUUB|k9PWH|t8 zq~bS|ch-xGTJ2XezlA_#%;R0%|1nl}k>;0Ilv{}p&pv#6GkB%#tijDW@qyR%9#xjo zI|}j>1*UwmNy*4emKmBF7#Qg4Hn+6+KHM9CJkhNcS_W|3s1*w;(Muv6_P2{)A_T8` z&((o3F`AML7}r2{H@N9ZL~o#U5-`RqA4=Jsx{rx8u4`99!N(>I`rzZ!y4Dk$nL*Oj zxGP6*d1MDa8gJ;865#kzH+}{9Ckkx3Q6~2ONPcd-rFlM|qyG(pOT^Hpm zpnXRH_mS|4<43r(@42}fX|=T&l!}USN%-tML+A9Mu;%1EXkEJim^wTpq+(CJTC;e; z_n~dJuA&d~1f;fUmn{MBM*i8k!otG4QiYt7#QnIqMllc=lyxhDBxq%?u?dJu(Q!9;`=`jsabG^4)Gt{lM^Io@e zaY4d3!Do9GJ!V1Azz`lBoW$ySzDynxPQbhWhSJ;Xx)ui+BpND4V+{d^OEX#7u(`YQ z!p!y^jwP>8Tnv-z*`}q>2woKx8V_5jj-74{B)6=^;5-~`QAR(Q^e6e;&$?^G5WE^X z4kT1R4{4~mt8BWObvEU4Pvo)@q{nm)oF%B{IUuykkf_8r-QT3Zaaj+^u{LpKl;#LZ9OwqbG|=U zI%Y-*^bg;8Yd}ic-E-E{gUV1Y@p!KWuRleW6_>W0r9$9lH$)^bypl4zp@BF1^w*cu z>uV42Ga1=ypON*`Q>V(uwa_ghMe4IO6+zo*<)x;Y~yey3~_o54syohNUPqd;4aJ z?`54j_$Oz>W*w69zhiP=i4C!@Ssi@CoT)omSUtCi!nEatY}zwD!G)BB1O)*WGZ+EY z70CQ^r6XH}+V?*@P!e7CCt-kYdz5--yjZRN#reU^!{RYKS9k~xHmm;I-w|5X^G&A= z!hg>9cu!7FfDCCs$;fZN{G?dC!g6CD@P-Eh^KF5ssX){7RP^xhm~Ym<4>;hU8K{io za|iv+iJZ0ygpFmpSgs9~&Cn<;oge!5K#8NFr2$G+3`2t!j{UT@(JDk2?E}0hHu5v) z+$MZQsaSfgdVoj{Za3z94DM{2oX2DJN^E@OK4D?b)!EHWp8-Jy4h~LwUlSh>Z|8O? zI4i3QztejDR>WDwS^eP$xin@e03Nc#la|QU*Jnt{V5J100Gf8}4E4bGVwgPKk;Qsi zIecZ@wMXv^uv`AlIrX6cN-^wSKxa!rCtX3wWH0W9XjKR^RUA}SGo zq6-5Bg{EZbxTcIuZ+wM$2qwLgwkQUr92G6?&Fu+es@sPziHV6Tk>);DAoTpNlT|*1 zbD{6Z>Cj`JBfe|@Ki1B9^Yf%ziW*n9kQzM5DcApa>X0+N%ChC`^l+2^BqxC{e3-do zQp>i9-?V>s9rbwtUa<7obN+Xx3w~zY&%Gw#exe|N7P>*|ZmAye(66o}2Bk`>sxJGp z?vjzq%O4te86izC_ghOoIXwG_2Wz9)bPFx(?v|R7`1sLctU#U(w{JMxmDg<&8RA4Z z>!93AcKH$rA_|9gO7k-~LLQK0ka6I|>5KA?0;8Y*B0Lv05eBt%7BUB$r<7XxAv6OF6c-aLD@sT}th zc`zun!yyF&?d}nnlfq}nU(@|ral_`4Uw%x)d8GeQ&WKGH1p#4uUmG0zb^r|x&5BKT z54@Z01zM9+28f`pt^@o1u$w+bCL~NjL2;f-M*8nyvS8IB57<|7!xqk}w1n7jgF%Pb zWZ(nH$DL#OqWt0Xx7Cl7k`FvM zZPzIdT5XG4dm0YD4C8dbs2GxOH8)=4!Y_9ljyy?`~|fGxi^X&Xx*$FgJ%L!iTLOL;;Y31aoPtrsY>k#+NnBcs?ICq8>a*p0j{LFN!Ubg*XrZvPigQm5%CDk%*lagWT^K|YRy z)7O5=%FPwLm~{kMC_L)*>(~5XpPZarCmQrHLhOEzjGTa0gLjgWTl=Aqx3pkJX5Y%C z3iR1)TUc1Qn!+CC)<%2%+Id39)fyC+mq#me$ygA(Cjj<_3CFBeR#s|Mn1GVAt9*72 z5Uik!OH@LFQKQ^wrrP@JCpa4;FbOuVG&D3oT1pj2MvF{NF5U-9x7S|Pgs$7tmuOlw zRTUL>Zct8N%zGZ}l(yLT{FI#g2;Czj1&3}QK z@$uZ&(`@9!gnVv4njV9KwBBWJIw>hBFOSN&(nVbz;C+3p7&PETu0Y?Fla;l0hN`}Q zKUkvE0BWR_<>h)BOUNP}Ag(!|7~h_N`4d41iipswFzH)v58)0kEG@0kEJj2`1eH?= z7Nd>B=zNoBnD~le1b>G;edv1a!~G2+u}4!18jJh!Dr#Ve;3Wj|VjlK?0R%4JYGh~X zwfMxx#{(s)zqdCDsFQ%n6;m(@1rijUcJ1K&C(UA)&B1ga2`D^TWHs#%#pfjS{$bDtT~xBazrEU{Cz&DCNpo`v`YT!V`G`<_#Sk9h5y!h2HZxL_;p43~8xq!c`7CM7F4@V6R3BeF!W@Ic)OM62>(b3aW_|JIowN6{Zz=tG4vD1q- z;UJuEzzomH$E;PD4)n*fQVIy@PB~tmZUG^%Jp@ZSQdw0Mli=@$fpK!Rky;3M82p;n z!qQT$So1YL{_JgPJRr&d{eHP!SPHNYXxtti9<2fFk+H^ng=qx3J}N%fA?P<^N$hjn z=y&#dGjMWrbObt07^tWKbEX{&1tjI$fp4j)EgomvOKpKb#4Lubl^;kT<2tnz714#X zM?#^mocf^DN`SX{hBr7Rsi-! zE|(XS!b4`g{r&Uh#v#|3*=!69`-e+y-8@>~zkdf!JOD&7Gc$*ylKGR7kpYR}6(;5j zJdUChB*3hvnf~eOQpnI-(~E*z4EO?a(hYTHvNKO|y+rOvdq+o3PENp=FW`#4bM?-) zjYr~ui{PYMbOpWh(kvi;F#a*{IuE_KScwY3GJ1yHo*V3AQ%$kKwg zo);CuSsX)FPHJayuu*AihMgg^cm1uOu& z6k6(irbRb{W>A;kD|a)*qrsOMJDVZz{Lw0!Q;U#J5v9$J=YjtVTrT>BHM6~?^jP6-#ioCX@|>b+x&_T z;tP=oRm<)1LKPAmq+{*=^hU7dRxR{stgvN_0VRh{S664W1dd{#=;3k!dqiIFzB2^t z!A6h?VmGn3*9XG;&cp+j((TQ{Lxl@Bll?P&w-;DgEqCWLGkxm?2RGAI5C|z~BULTL z+Su4&)T=*7lJAFMqMvk=ut$P_(qVrDc8GiW7zSo4^noq|(Z@vo3EuR6h69?~M-3*} zyfGm?(+8iKwEjEx|8&scI0Gclq^WtfabaEC0_)LP`zy7=v8kz$mP?Pl#}eC~i3D+R zZtl(s4bRd(PqZ{65|^m<7}r?M5)O%H_ ztGkOc5yn9QCX|hhQYq>k*H~cQltege6Vnr)3Qgy#!jIp#>-Rl8?*{d%P-Vm?=of6O zs|q21e?qxYz8eGkg(ibMk$YwI`KK;*m!2fYJ(`aj>(qlx5)pjWrq}^VJ%IyHvZ2_S znJdt6Vt=TjRGE46{8LIwq&?QIOkZdnfl@GY2Q(q)>iR6DJtK@*VE*9eDfY)t5mMCN zEwtJGio&5w`|zEf5?5@;_Nr%D?qBV}Syz}=+cWN%u*_4rSBd@YF)4gA_%_fD|A?dp zjuMtkeKOp@G+WGUK9C0gu?B5r!?3X;MxK$#RAnG4R`I%dLg1vU5bI58G%EMS-TwQ& z7x=u7na~7g>opweZuqZ>cH}Qmx^urrR20y-QycsrZJT{mQg;}~FOj+?rCV0$RAQSo zvJf}($W&U`%d#mOt;8mVRKDOu(`8bm)XduE$u?a&B|T2lS&}jn=L;{{lC-c>%#oK- zX0W?74B?0&i2KnxJNskzulvVypYyzZKhN)+dwy7V<9p?OM^WZ#L@M;U_f)YMgy&7Y zNv`ysDlBs<_fkZB;qH+kFY_z@#ylV1%p(0^ONyy4c(p5yTqk@4*%xR~-&7{<7CFB4 zpk&s4Wf<{1mr#f(l z^*`569gX_FH0Ag?+~&KK?cyuRg2TGQW}o!ci_s+0C+hJ3*yy4h4Kg^5JvrgV&@ge0 zqbHVtF~t|3qjRc#C`FmhCyc2eGr{nn(38vWS=iOBi`Em;cjL{Y^#id2uY7u2ZR@k@db{z9**;@*=dgCxE1 zG<_=SzICeb60L5XUReXmnRW|wyQxg`xjYq{%X^+7hLHk`*OBXI+exI$}+ zKmRA91Zr3ZW;-4umg_DMX|g#@6EBA(Apd1|ekXJ7( z)6;7#>?Nh!6`%BKpU!g`qQKAdS(h+C zgUqhJ?(R537ZIu9nfju-N^i3RK4UI47)-8(wVtjOW`DvBWPZ} zD@n8`EiDAVE9VaYzlHB!U`kdnoqdZtDv2}Z3YNgel1Wzt^N31C+f=6^dW@a11+WD3 zQs+#ZIvj8Ye0{rL+qH^!GnFNfj-oN`%`B4 zH6xcUGu=ob%r3rs^!F=^#u&Z=l(ix+G5;348?pcfkrD9XAMAB)(fx;S(WHJQc*OiD zk3$F=_zOJGRr_N@2de1muzm7aG2?3Jugy_n)JH#{uw#n<5N5bpoAZnjjEh;C%M;By zB7z+eXoSvw!4o2kRrNrp^()f_=t;W_AZ$r!A!Q(PGE~3+r_IE+nOa=hsiL9NLVIC? zo$pTC=*|+v_?6V_b7x<@<6;T5EXd{9mS=d}qHjaUND7uyHv+A>0bz1JBqOi)>w?Ke zt;+Ld_3qt{Zu9h~W~Pl)@L+`k4{}{bK-!oizUpPyr+-^rxv7X!8|!2TQd^GiL09>p zNecW8?0}@)^oFKTnNXAD{K8hS&UTAuEca7f1^Z_iq2&1uA|*3(@=&XC*WeM+E`SO_ z%)1cUpYCGCc!xw1s02jeYkD#Q9pXcf6?8wMv4XB{d~ND&zCJ<%>&?xDcgi~Y?Y!8~ zAlwMFY%p=fn4-GL*AUwzN*(_Ii2rjUWLI`%c*+SRKz3??kV zVJ#Vd97Ovw9?o{|gd*4!A?j)NvE$6%#jIqBb`l%q=f|RfYDZu6**Oe$n~^H$X0(OG zn6w{4_B_upMeWM9->7d48r$vjnJT)Py^)mGH<3OYxdDo(FCH>cxvPH++y=d`+FoaiyAR9Uh0gDr`| z;;qJ$f|g*HaJh-S-h%xH!9P)FvdocEX(58MJ1Zg3w1r+82cHt|q(s{5^EX1|m*X@yI7d1<6Oq`NOjcXv0;0e>^! zTHnk+Gqb)~Yu?4;o?GvUz0cmyv-dtXKtWCd{W0NV2n2#I`A$>`0(k_Q52#TPz(X;A zPR!jidnE}GNI@U*It20pA}RVt#U){D+S%iozlm{q#;M3-R60uGcQ1}`bKW6i ziS-Af3tX?SyRNQ3@28(}U3G=QNWGrdypR@yd-3Ao!^VHD0h$*Or2FP)!3Pgs|9kNW73K4T`(}##|C3vy2qc29 zf7VK1=q~O-`KWeD+pZYeeESV_w5E1{Yu;pr?qHTS{7mTTx!PMD$#4|%V3yED%qL)| z4v6=IOOy#PgGaZC8(8TGPz5h$F|diCP_#5RzbzcoAQM4Jg`OxxfnNTbN}_m_krZ9# zh#@;cNCvO~3-x@WrBk%ObE&&SO6 zN|?|?2&9oQXmnKd#DNsd+xk&BPQ^D}6Q4l_Wr3h8}b!kUz zVrfbHiYZaZ=4V50L2?^E4PE$R7OkX7A)&bVX}a%gak-VqV0=tU=`;-}#_F@s0QB3X zPX#sJdsBWvJJq){AH0Rih^k;hxL~;XNO#f_mgo#`+4r#CX|36@Im#>A|H*e~F?+Ul*wKOFD9i!Y(gF8gn%fzn9`$a=8$|troV@S)(FngR zKGmhg$9mp_KTj(VsmH1>{F{=EZ&IrNb^jq@$nPBm@vozY)68lT;^ObpI?&!1U_&4& z!}rQ+N@MgV$Kv@nzLh?A%E8%QG4H*mWh_LW$iZ&dp|rmQ65Vz4=p^hw>Kg_MwJ$bx zau2;EDQ4ai2SKkQ7g3epaxfgms0gDry(wm7hM0c}hOO@fYo8 zmF{My^1WNo;H*hj1K6Pyz5xZ&LpJfJN&N=Ca_@+hlxHi~8`n3gzon83!X1y}F*4Q1 zID$i;ehen=d-a2HAv(D*C#Qhix8tAe5Wfk7DRIlL-!RhElv}O~1RyZ+` zyRK#;Tw4wFMNl)LLlhd({$dX!*^4j474i69-Zrfd1+sd}W8fumzRxg^SZf-tUdWZE ze)621x9x9|i1b15xV+#wemhzk?c*2>U~C3h?z>D#msu`*&#>E2qu*>o*6#Lh7~6m` zt;2pwe)XEOcQ>Ccu0hD{jYIyvReOZ?gklSF|*p^i;?`I+^dG`65{)@Jp&O1Jd$ zvD@>F5=JwM>tOjFqFK}8+NzpQi>DT8X)oC|J>|Wv!5Zpf+%Kg)^sKzJb|Ln1ClPO1 zoNk_$$2MDZ`+;EWSg9=P`H4yac0q+}in>4Sl;%6mYvr|ITy{d)7<~&l7tK@WV z6X-swI{xZE)w}xGUNDqqClGnzcVXsg$J$`NxN_ndGyXHl6D_c5e?$>y$#S{zF!SVg@#G!(~?~8DrnIvZg)ShDUVGJp~V3sWNkK zj@D`wvDWbpg3Onf<|Tbz&~A#HI_2?arZ>IHfE9*n&cQ+7f|Tr{e#kq>s)WK_QI z!t<7Sgq){~2Xz(ed8=;XqT{>2mWY%7J<-vzA|V%zDtZCzjW8eiouBxGloRM3Hpn=i!VEadf>-{OSwc(Hg~TAI`AjHJ`Wo)H)rE2nF# z%i-5}Nj;)~Dul){z9c_DO!xE;!9SiXl)JLO{+VEh_NVy=SW8|c3>EP33ChsT-_ONK zsGhIO$ItpfrZl=|Y4|)7Y{K0B8(C6JetqHB6kCS6wkgpjmAT8eUfl8gUI)+{(e?m> z^XG8KkYd9F2WR;})aNmV0_o8#O^PoA^~G<>HsS~w`?2dP6Pd0Wmn1cJ(H<&$1E;`? zB+v-D4OOCssKZA|Za#K5y}23Bga;SR|9s>7W0-LTLL-5M#xEGS415(rF*R^Q6!!xft`BFYuSM+Tbp;c&Uh40g zfQ|X|?H&A>qosP7pJT(m&fl9B<5d;>WAnZnQlJ}(ZoC!0XBe=$6s2Mq9&1*l`(rem zEd(8_8r7y4`O={Y+Y`eE_YnHAI%QC?N|}gGh~18tqs{39)5JyFsyo)Be3f2yw}{bP?SIkmNf|^Vjv$Djsw($YusiY?+$1XqBzC&o^!BDW zb@*n;ax*_zRe+#p!m7a#WoDpX$ddm@VSRbo@5=xf!uB`P58#2Oa=mo*ppdw$o}p7_ z{kV(+`Ao$tpy!>wAtv)EDnE0#OzwO{x(~J6db?T1N5Zsu?$AZ`cPO!R9 z{&h*4NgZK!J!avcIX$@Lp%^YMIvsOrA%N^+s3N3jEWv~ugnI~`-)|O|opMMUP|$H^ zYPho0ja4)*et~af2c9@sOB(+vs}?_88DWb*P2N8~I@@1b-hG-BVf1Sd1y>{lcV9|X zXB2M1@hC~zPBx$>i`ItJ3pvrf4)`kP1K^1EY zP3meWs=IB9R1jh=(SYLf*)rK*otYhf89X-$o$s@u9Rs z2G>RR8CB@wk>;B@o=%4P!`>T5jJXUchI>`H&)ArYT%atP3fgnaie`OR(faCp|+Xq+Z5Pl zIvTa5OfRaP1%8GzSDcK14!~QGI{bU|TFxO0DX{o)EXXf`(}aj(KyxT1F8+u{?4gp# z9byeZCBVfhT{qmhNeFi}Ko%_j0A^1i$(kw}PT7{#gRj{3njVcOcQKakeEqMnII60X z`J-2+N2uxTyXUqReH@D$suoFZ2ex%V^^?G}LFUvxli%sP)eocCMec~fqnW@kr-Ey{ zPRxd$w62yt2-PdW$UE2>gVm;7ecPm(_yQl_{Zrq2XS_tQaN11bOx}m_ zA|D-L#mTSR2z#;TeZ|xFn|qP%H+4>|Cf8QhGYzxj1%>XwG{U_>w)zl^B@*1$2DcbP z|3P##@gsHq!O=zS?5|bGYUIZP1k>C8eCv9L zppd8_oLYuTO8oRJK1w-xCmxagN-;usyHM=Wu#{s30Ojo^L^PqS^< z#m_3k0(K-&c&v`MW`PU#4aZXaW9CYASzKWLaGp8YOG0egz#gVRJvPeKp@ybMM^XVD z{*-j>6854C4QkiRYfsm%+@-ABT!pOEg+*=gP|i@aJ7T^*qySxAv~A=!G3bui|BeSY zl4?Y`CUFtUTBsK?V*91@JPI!Qi1h`y-*YNxIB>)=#xS(5i%YkM$QM^0of&IC_9RT? zmk80rO-I*{)VlQ)oA6VKS4rQw_8w<9_S-705kgUAWu-+K zq2mWm&@1=Td%Gqel`#JI@7{6s@qC_>w#PFHhHJZ6IPn~7>}w&6F|yEAH}^_As8Z4f zJ7LMVY58|xLRH3cX()U#yOg_d+E9uFzEx$zV(hHH1ltiNQ9nT$L8%p^Mv!>*>WNev z<{g#ce)L~QKj;Fm^)%TTrf&bXVz>SMjk3wp_(k)JkQ0;lz`*xQ;}FfVYZt5hcQK+U zb^2M8PSniof1}gKk}X^I6SRbtJ*2n7J{tQfblFO=ep!Y7sQ6v!@aIZQOucZAKkQzE z<=)&o{2%6W?;5m6``dM*G~C|MQCPMd^EBd0$^~!0mbn$fdvGuQCP;>STM##Ro3H4w zKOn^ET`NcNuGphaJzAaY&;pOjH%q5Bn1f_M=r1!fv-eIdUx9XgF#es8= zHV@{G0@}J>rgy%#C|#RA#^@*6TwKS-+iAB?==pn`SxEb{$4-Z5l~kBce&o2wI>q-j zun1yR^$DHW%iJP{$C6FK2Q+=FD`Up0(g|vF`!XLu&&a9Bck(4CPr5k6ysa&MCG{?= z5GEFGA4qT5_7b6%DN=n5(~h-dN~{@&dKs!xFdc!lwmHAN5{^kQmov<+w;yXKTHaCC zs$_*4A=YHrR8VNtYS?5*9i(IxpsgiiSb{ypDvn~YfyRlmvuNNN8~MwIv*38|KtK2& zlX>WN7vmyUQZaD&8PYkJ>ieU%xa+T%7vkG16!PF|xQ$-!m?0WDi%D(EMu z9kHXWJ)eV%fuSfjv+2M!@Nje7M|UwfZqfAz^(vZTC;_MkRKD7@c_~nFmYY*-78m-StLxGgYSg<7Wh}X*YaAVa3fSGF~m56z_S6!vrh!$H9&7~Jt$q@#oT5VzY^(a^z!l@%1e73nvQj&>-sHj zv5Lz3+Ooa1NlHC&R1Ef$Kv})k&f$x3*Ux06(k@BLSfiEbG0qqsDIO^zS?c^6ArZa* z1L%JU!!|!aVD|k++@Q_#Vju`JTv`7`aEsqrXYg{QDtZ`-W)f?R2E($dV;Ut+n+)!{ zrLM-;&X?VJ>t7mzS;V()BH}aWdAe*>o>4`JWk40A=q)zKNb6Z|%FlOpc6>2In`dpo zENJF1Xh5F=Qjo1Q=c>a#zC+om*ePlL{&u8GY?0!?S19DReAZc3 z1LhB<-lcbjI~B2S>jwX93rFf#3|({yRi4d+e#g$`((HQ8n~t%5#M^d{vLLYk`X+9d49{>LoVD+t{@Uc@FX|f@o}i;7Nf&^zPZ4bN zT(u*(_2ETj&01eV-?d?P>IN@GzdckbNG!+db#gbe&1+3jNlq=%AX>WHo;1Vq355zq z@bK;qinksGQJGa`ikDFC)ec&+1SydW&pO}M?U3%o+}XW>r~d=`Do5O5ub|e0hEt)x zMMlzQqXFy9NL7`UhPri8n52#PsnCQPO+iYA&#RRaZi^fm_0FUC%v=Hn!3yR@?;Qw} zlydl=1T3CKmH5z+7Z;=8k_Tfpp|sl@2esg1Q4L!D6ujDbPhp$L;{0b(Ua(-$bZ_f_ z2zxFoE^Z={%)9<$Gm?^4Yfp$WGLjv!^MTU-UdM<0#(1%HL*0Gf${P+xbTM4B@hh++ zLufmLv0_<_#OS3^+ie*s)WyGp2=mWPotj{!oZ&t3lB3S@(#I6eleK=%(;4wL?p$~2|Cn}b8xvk+-+P#LXDzlTS^ z;V*$~_D~m^F|qLq@5?H;`A@^3p4!?v*U)GqjrAtgUk8E3D$1&v^RyD#Juza&GhR30 zMN*ptymklMr&n8~LSz{EYGcF6AkduR{EsoS8>A1KT0RrY9{19nV+_U=DJAA04B<_z z&ntCU=@~zLif-%dY-MSAwIQ<~Cgd^3Oqo$%FSp+DTx_tnx2`hFbac!0l#!2F&j}JnKczQG}dPc0!w=3oXn4JME{I`I@&OOusRNf7*4jWqD+1ty(!rwSH z*4{omttHJoTGH|0r(4}jclR)FtQ?F);Dw8eOeL(wT~>EkojakET$io^=5%7>n6-SbJQ9TjHmXb~1WUtDvr~XHyJ)ZY+6R-=Z^S8fCso zRH~UcZmAR+Nhz5NhmUfx$Fyz)>Ki*1=38XtbH48}URX#=P?HSsD+G7{-{YpVnPbiB zfHsa(1S%*ZcUw>f z@s%mw`)o_8Kc%F4;j?%d<2v2Kr;65=mMTcE=>Pm4Vq-w@;q_FHGE#h&KTJzx&MJjM z;mbme;uH66kz29U*B1#5!G=p2O#B1JJ@qt^({F>5i#j51?XhM%k zuYL$V_6SmG^slGVb!GT=xF88gWT<}qc*X8o);CA3*4CdOK#rc? zf#C^EY;0Cjh1z1zbtPR5jo6E*R(&43rI-uWW7vaFZkpAdhDzl$nKC1$?9-7(kV?46 z$vB=xy>70hvee?jLQ|7SWON#B{aLr3iHXsvDbfRo+r3P!E+uL928pnCvm;=eo15@x z4GoRa`65S4eyfQPHh@+v;OctxM4I_0n;d%Ef6tp!o3k4(;LMSU!7L64h!E~OwG*EF zC7=AunvBQ8;Mvj5$cQl=1~GSrwU®EC0eRkLLLYPVuWE|#5x(|90e5XbAbMm;wt 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 K$D_`jc=>N0jHhD& diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 37d394a7fa483..f1497f937c334 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -845,9 +845,9 @@ class ApiRequest { return apiRequest } - // Chat - public chat(teamId?: TeamType['id']): ApiRequest { - return this.environmentsDetail(teamId).addPathComponent('query').addPathComponent('chat') + // Conversations + public conversations(teamId?: TeamType['id']): ApiRequest { + return this.environmentsDetail(teamId).addPathComponent('conversations') } // Notebooks @@ -2547,12 +2547,10 @@ const api = { }) }, - chatURL: (): string => { - return new ApiRequest().chat().assembleFullUrl() - }, - - async chat(data: any): Promise { - return await api.createResponse(this.chatURL(), data) + conversations: { + async create(data: { content: string; conversation?: string | null }): Promise { + return api.createResponse(new ApiRequest().conversations().assembleFullUrl(), data) + }, }, /** Fetch data from specified URL. The result already is JSON-parsed. */ diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index f05e54564ad49..b81e4669af38a 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -569,7 +569,7 @@ "type": "string" }, "AssistantEventType": { - "enum": ["status", "message"], + "enum": ["status", "message", "conversation"], "type": "string" }, "AssistantFunnelsBreakdownFilter": { @@ -1060,9 +1060,8 @@ "content": { "type": "string" }, - "done": { - "description": "We only need this \"done\" value to tell when the particular message is finished during its streaming. It won't be necessary when we optimize streaming to NOT send the entire message every time a character is added.", - "type": "boolean" + "id": { + "type": "string" }, "type": { "const": "ai", @@ -1437,6 +1436,15 @@ ], "type": "string" }, + "BaseAssistantMessage": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "type": "object" + }, "BaseMathType": { "enum": [ "total", @@ -6191,16 +6199,15 @@ "content": { "type": "string" }, - "done": { - "const": true, - "type": "boolean" + "id": { + "type": "string" }, "type": { "const": "ai/failure", "type": "string" } }, - "required": ["type", "done"], + "required": ["type"], "type": "object" }, "FeaturePropertyFilter": { @@ -7530,17 +7537,15 @@ "content": { "type": "string" }, - "done": { - "const": true, - "description": "Human messages are only appended when done.", - "type": "boolean" + "id": { + "type": "string" }, "type": { "const": "human", "type": "string" } }, - "required": ["type", "content", "done"], + "required": ["type", "content"], "type": "object" }, "InsightActorsQuery": { @@ -11128,9 +11133,8 @@ "content": { "type": "string" }, - "done": { - "const": true, - "type": "boolean" + "id": { + "type": "string" }, "substeps": { "items": { @@ -11143,7 +11147,7 @@ "type": "string" } }, - "required": ["type", "content", "done"], + "required": ["type", "content"], "type": "object" }, "RecordingOrder": { @@ -11590,17 +11594,15 @@ "content": { "type": "string" }, - "done": { - "const": true, - "description": "Router messages are not streamed, so they can only be done.", - "type": "boolean" + "id": { + "type": "string" }, "type": { "const": "ai/router", "type": "string" } }, - "required": ["type", "content", "done"], + "required": ["type", "content"], "type": "object" }, "SamplingRate": { @@ -12780,8 +12782,11 @@ } ] }, - "done": { - "type": "boolean" + "id": { + "type": "string" + }, + "initiator": { + "type": "string" }, "plan": { "type": "string" diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index 4ba0f07499016..7375910003a3f 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -2452,48 +2452,41 @@ export enum AssistantMessageType { Router = 'ai/router', } -export interface HumanMessage { +export interface BaseAssistantMessage { + id?: string +} + +export interface HumanMessage extends BaseAssistantMessage { type: AssistantMessageType.Human content: string - /** Human messages are only appended when done. */ - done: true } -export interface AssistantMessage { +export interface AssistantMessage extends BaseAssistantMessage { type: AssistantMessageType.Assistant content: string - /** - * We only need this "done" value to tell when the particular message is finished during its streaming. - * It won't be necessary when we optimize streaming to NOT send the entire message every time a character is added. - */ - done?: boolean } -export interface ReasoningMessage { +export interface ReasoningMessage extends BaseAssistantMessage { type: AssistantMessageType.Reasoning content: string substeps?: string[] - done: true } -export interface VisualizationMessage { +export interface VisualizationMessage extends BaseAssistantMessage { type: AssistantMessageType.Visualization plan?: string answer?: AssistantTrendsQuery | AssistantFunnelsQuery - done?: boolean + initiator?: string } -export interface FailureMessage { +export interface FailureMessage extends BaseAssistantMessage { type: AssistantMessageType.Failure content?: string - done: true } -export interface RouterMessage { +export interface RouterMessage extends BaseAssistantMessage { type: AssistantMessageType.Router content: string - /** Router messages are not streamed, so they can only be done. */ - done: true } export type RootAssistantMessage = @@ -2507,6 +2500,7 @@ export type RootAssistantMessage = export enum AssistantEventType { Status = 'status', Message = 'message', + Conversation = 'conversation', } export enum AssistantGenerationStatusType { diff --git a/frontend/src/scenes/max/Intro.tsx b/frontend/src/scenes/max/Intro.tsx index c43cd86b53d2a..97f4f9fbfdc56 100644 --- a/frontend/src/scenes/max/Intro.tsx +++ b/frontend/src/scenes/max/Intro.tsx @@ -3,6 +3,7 @@ import { LemonButton, Popover } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { HedgehogBuddy } from 'lib/components/HedgehogBuddy/HedgehogBuddy' import { hedgehogBuddyLogic } from 'lib/components/HedgehogBuddy/hedgehogBuddyLogic' +import { uuid } from 'lib/utils' import { useMemo, useState } from 'react' import { maxGlobalLogic } from './maxGlobalLogic' @@ -19,13 +20,13 @@ export function Intro(): JSX.Element { const { hedgehogConfig } = useValues(hedgehogBuddyLogic) const { acceptDataProcessing } = useActions(maxGlobalLogic) const { dataProcessingAccepted } = useValues(maxGlobalLogic) - const { sessionId } = useValues(maxLogic) + const { conversation } = useValues(maxLogic) const [hedgehogDirection, setHedgehogDirection] = useState<'left' | 'right'>('right') const headline = useMemo(() => { - return HEADLINES[parseInt(sessionId.split('-').at(-1) as string, 16) % HEADLINES.length] - }, []) + return HEADLINES[parseInt((conversation?.id || uuid()).split('-').at(-1) as string, 16) % HEADLINES.length] + }, [conversation?.id]) return ( <> diff --git a/frontend/src/scenes/max/Max.stories.tsx b/frontend/src/scenes/max/Max.stories.tsx index bec5a519de8e0..51dc03ab0cb5c 100644 --- a/frontend/src/scenes/max/Max.stories.tsx +++ b/frontend/src/scenes/max/Max.stories.tsx @@ -6,7 +6,13 @@ import { projectLogic } from 'scenes/projectLogic' import { mswDecorator, useStorybookMocks } from '~/mocks/browser' -import { chatResponseChunk, failureChunk, generationFailureChunk } from './__mocks__/chatResponse.mocks' +import { + chatResponseChunk, + CONVERSATION_ID, + failureChunk, + generationFailureChunk, + humanMessage, +} from './__mocks__/chatResponse.mocks' import { MaxInstance } from './Max' import { maxGlobalLogic } from './maxGlobalLogic' import { maxLogic } from './maxLogic' @@ -16,7 +22,7 @@ const meta: Meta = { decorators: [ mswDecorator({ post: { - '/api/environments/:team_id/query/chat/': (_, res, ctx) => res(ctx.text(chatResponseChunk)), + '/api/environments/:team_id/conversations/': (_, res, ctx) => res(ctx.text(chatResponseChunk)), }, }), ], @@ -28,10 +34,7 @@ const meta: Meta = { } export default meta -// The session ID is hard-coded here, as it's used for randomizing the welcome headline -const SESSION_ID = 'b1b4b3b4-1b3b-4b3b-1b3b4b3b4b3b' - -const Template = ({ sessionId: SESSION_ID }: { sessionId: string }): JSX.Element => { +const Template = ({ conversationId: CONVERSATION_ID }: { conversationId: string }): JSX.Element => { const { acceptDataProcessing } = useActions(maxGlobalLogic) useEffect(() => { @@ -40,7 +43,7 @@ const Template = ({ sessionId: SESSION_ID }: { sessionId: string }): JSX.Element return (

@@ -69,7 +72,7 @@ export const Welcome: StoryFn = () => { acceptDataProcessing(false) }, []) - return