Skip to content

Commit

Permalink
Merge branch 'master' into async-except-on-cache-miss-suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
Twixes authored Oct 25, 2024
2 parents a4c79dd + db1e4ad commit 244cfef
Show file tree
Hide file tree
Showing 62 changed files with 1,868 additions and 415 deletions.
3 changes: 3 additions & 0 deletions ee/hogai/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ def stream(self, conversation: Conversation) -> Generator[str, None, None]:

chunks = AIMessageChunk(content="")

# Send a chunk to establish the connection avoiding the worker's timeout.
yield ""

for update in generator:
if is_value_update(update):
_, state_update = update
Expand Down
6 changes: 5 additions & 1 deletion ee/hogai/trends/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,11 @@ def _events_prompt(self) -> str:
if not isinstance(response, CachedTeamTaxonomyQueryResponse):
raise ValueError("Failed to generate events prompt.")

events = [item.event for item in response.results]
events: list[str] = []
for item in response.results:
if len(response.results) > 25 and item.count <= 3:
continue
events.append(item.event)

# default for null in the
tags: list[str] = ["all events"]
Expand Down
24 changes: 20 additions & 4 deletions ee/hogai/trends/test/test_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

from ee.hogai.trends.nodes import CreateTrendsPlanNode, GenerateTrendsNode
from posthog.schema import AssistantMessage, ExperimentalAITrendsQuery, HumanMessage, VisualizationMessage
from posthog.test.base import (
APIBaseTest,
ClickhouseTestMixin,
)
from posthog.test.base import APIBaseTest, ClickhouseTestMixin, _create_event, _create_person


@override_settings(IN_UNIT_TESTING=True)
class TestPlanAgentNode(ClickhouseTestMixin, APIBaseTest):
def setUp(self):
super().setUp()
self.schema = ExperimentalAITrendsQuery(series=[])

def test_agent_reconstructs_conversation(self):
Expand Down Expand Up @@ -70,6 +68,24 @@ def test_agent_reconstructs_conversation_and_omits_unknown_messages(self):
self.assertIn("Text", history[0].content)
self.assertNotIn("{{question}}", history[0].content)

def test_agent_filters_out_low_count_events(self):
_create_person(distinct_ids=["test"], team=self.team)
for i in range(26):
_create_event(event=f"event{i}", distinct_id="test", team=self.team)
_create_event(event="distinctevent", distinct_id="test", team=self.team)
node = CreateTrendsPlanNode(self.team)
self.assertEqual(
node._events_prompt,
"<list of available events for filtering>\nall events\ndistinctevent\n</list of available events for filtering>",
)

def test_agent_preserves_low_count_events_for_smaller_teams(self):
_create_person(distinct_ids=["test"], team=self.team)
_create_event(event="distinctevent", distinct_id="test", team=self.team)
node = CreateTrendsPlanNode(self.team)
self.assertIn("distinctevent", node._events_prompt)
self.assertIn("all events", node._events_prompt)


@override_settings(IN_UNIT_TESTING=True)
class TestGenerateTrendsNode(ClickhouseTestMixin, APIBaseTest):
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions frontend/src/layout/navigation-3000/navigationLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IconHome,
IconLive,
IconLogomark,
IconMegaphone,
IconNotebook,
IconPeople,
IconPieChart,
Expand Down Expand Up @@ -526,6 +527,15 @@ export const navigation3000Logic = kea<navigation3000LogicType>([
to: urls.pipeline(),
}
: null,
featureFlags[FEATURE_FLAGS.MESSAGING] && hasOnboardedAnyProduct
? {
identifier: Scene.MessagingBroadcasts,
label: 'Messaging',
icon: <IconMegaphone />,
to: urls.messagingBroadcasts(),
tag: 'alpha' as const,
}
: null,
].filter(isNotNil),
]
},
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/layout/navigation/EnvironmentSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ function determineProjectSwitchUrl(pathname: string, newTeamId: number): string
// and after switching is on a different page than before.
let route = removeProjectIdIfPresent(pathname)
route = removeFlagIdIfPresent(route)

// List of routes that should redirect to project home
// instead of keeping the current path.
const redirectToHomeRoutes = ['/products', '/onboarding']

const shouldRedirectToHome = redirectToHomeRoutes.some((redirectRoute) => route.includes(redirectRoute))

if (shouldRedirectToHome) {
return urls.project(newTeamId) // Go to project home
}

return urls.project(newTeamId, route)
}

Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ export const FEATURE_FLAGS = {
LEGACY_ACTION_WEBHOOKS: 'legacy-action-webhooks', // owner: @mariusandra #team-cdp
SESSION_REPLAY_URL_TRIGGER: 'session-replay-url-trigger', // owner: @richard-better #team-replay
REPLAY_TEMPLATES: 'replay-templates', // owner: @raquelmsmith #team-replay
MESSAGING: 'messaging', // owner @mariusandra #team-cdp
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down
47 changes: 17 additions & 30 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -10322,6 +10322,21 @@
"required": ["k", "t"],
"type": "object"
},
"RecordingOrder": {
"enum": [
"duration",
"recording_duration",
"inactive_seconds",
"active_seconds",
"start_time",
"console_error_count",
"click_count",
"keypress_count",
"mouse_activity_count",
"activity_score"
],
"type": "string"
},
"RecordingPropertyFilter": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -10411,35 +10426,7 @@
"$ref": "#/definitions/FilterLogicalOperator"
},
"order": {
"anyOf": [
{
"$ref": "#/definitions/DurationType"
},
{
"const": "start_time",
"type": "string"
},
{
"const": "console_error_count",
"type": "string"
},
{
"const": "click_count",
"type": "string"
},
{
"const": "keypress_count",
"type": "string"
},
{
"const": "mouse_activity_count",
"type": "string"
},
{
"const": "activity_score",
"type": "string"
}
]
"$ref": "#/definitions/RecordingOrder"
},
"person_uuid": {
"type": "string"
Expand All @@ -10463,7 +10450,7 @@
"type": "object"
}
},
"required": ["kind", "order"],
"required": ["kind"],
"type": "object"
},
"RecordingsQueryResponse": {
Expand Down
22 changes: 13 additions & 9 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
ChartDisplayCategory,
ChartDisplayType,
CountPerActorMathType,
DurationType,
EventPropertyFilter,
EventType,
FeaturePropertyFilter,
Expand Down Expand Up @@ -313,6 +312,18 @@ export interface RecordingsQueryResponse {
has_next: boolean
}

export type RecordingOrder =
| 'duration'
| 'recording_duration'
| 'inactive_seconds'
| 'active_seconds'
| 'start_time'
| 'console_error_count'
| 'click_count'
| 'keypress_count'
| 'mouse_activity_count'
| 'activity_score'

export interface RecordingsQuery extends DataNode<RecordingsQueryResponse> {
kind: NodeKind.RecordingsQuery
date_from?: string | null
Expand All @@ -326,14 +337,7 @@ export interface RecordingsQuery extends DataNode<RecordingsQueryResponse> {
operand?: FilterLogicalOperator
session_ids?: string[]
person_uuid?: string
order:
| DurationType
| 'start_time'
| 'console_error_count'
| 'click_count'
| 'keypress_count'
| 'mouse_activity_count'
| 'activity_score'
order?: RecordingOrder
limit?: integer
offset?: integer
user_modified_filters?: Record<string, any>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/scenes/appScenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,6 @@ export const appScenes: Record<Scene, () => any> = {
[Scene.Heatmaps]: () => import('./heatmaps/HeatmapsScene'),
[Scene.SessionAttributionExplorer]: () =>
import('scenes/web-analytics/SessionAttributionExplorer/SessionAttributionExplorerScene'),
[Scene.MessagingProviders]: () => import('./messaging/Providers'),
[Scene.MessagingBroadcasts]: () => import('./messaging/Broadcasts'),
}
4 changes: 2 additions & 2 deletions frontend/src/scenes/max/Max.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect } from 'react'

import { mswDecorator, useStorybookMocks } from '~/mocks/browser'

import chatResponse from './__mocks__/chatResponse.json'
import { chatResponseChunk } from './__mocks__/chatResponse.mocks'
import { MaxInstance } from './Max'
import { maxLogic } from './maxLogic'

Expand All @@ -13,7 +13,7 @@ const meta: Meta = {
decorators: [
mswDecorator({
post: {
'/api/environments/:team_id/query/chat/': chatResponse,
'/api/environments/:team_id/query/chat/': (_, res, ctx) => res(ctx.text(chatResponseChunk)),
},
}),
],
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/scenes/max/__mocks__/chatResponse.mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import chatResponse from './chatResponse.json'

export const chatResponseChunk = `data: ${JSON.stringify(chatResponse)}\n\n`
77 changes: 26 additions & 51 deletions frontend/src/scenes/max/maxLogic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { shuffle } from 'd3'
import { createParser } from 'eventsource-parser'
import { actions, kea, key, listeners, path, props, reducers, selectors } from 'kea'
import { loaders } from 'kea-loaders'
import api from 'lib/api'
Expand Down Expand Up @@ -121,20 +122,22 @@ export const maxLogic = kea<maxLogicType>([
messages: values.thread.map(({ status, ...message }) => message),
})
const reader = response.body?.getReader()

if (!reader) {
return
}

const decoder = new TextDecoder()

if (reader) {
let firstChunk = true
let firstChunk = true

while (true) {
const { done, value } = await reader.read()
if (done) {
actions.setMessageStatus(newIndex, 'completed')
break
}
const parser = createParser({
onEvent: (event) => {
const parsedResponse = parseResponse(event.data)

const text = decoder.decode(value)
const parsedResponse = parseResponse(text)
if (!parsedResponse) {
return
}

if (firstChunk) {
firstChunk = false
Expand All @@ -148,6 +151,17 @@ export const maxLogic = kea<maxLogicType>([
status: 'loading',
})
}
},
})

while (true) {
const { done, value } = await reader.read()

parser.feed(decoder.decode(value))

if (done) {
actions.setMessageStatus(newIndex, 'completed')
break
}
}
} catch {
Expand All @@ -166,50 +180,11 @@ export const maxLogic = kea<maxLogicType>([
* Parses the generation result from the API. Some generation chunks might be sent in batches.
* @param response
*/
function parseResponse(response: string, recursive = true): RootAssistantMessage | null {
function parseResponse(response: string): RootAssistantMessage | null | undefined {
try {
const parsed = JSON.parse(response)
return parsed as RootAssistantMessage
return parsed as RootAssistantMessage | null | undefined
} catch {
if (!recursive) {
return null
}

const results: [number, number][] = []
let pair: [number, number] = [0, 0]
let seq = 0

for (let i = 0; i < response.length; i++) {
const char = response[i]

if (char === '{') {
if (seq === 0) {
pair[0] = i
}

seq += 1
}

if (char === '}') {
seq -= 1
if (seq === 0) {
pair[1] = i
}
}

if (seq === 0) {
results.push(pair)
pair = [0, 0]
}
}

const lastPair = results.pop()

if (lastPair) {
const [left, right] = lastPair
return parseResponse(response.slice(left, right + 1), false)
}

return null
}
}
Loading

0 comments on commit 244cfef

Please sign in to comment.