diff --git a/frontend/__snapshots__/components-activitylog--insight-activity--dark.png b/frontend/__snapshots__/components-activitylog--insight-activity--dark.png index 9fa6256d0c217..b4d37292ee9a8 100644 Binary files a/frontend/__snapshots__/components-activitylog--insight-activity--dark.png and b/frontend/__snapshots__/components-activitylog--insight-activity--dark.png differ diff --git a/frontend/__snapshots__/components-activitylog--insight-activity--light.png b/frontend/__snapshots__/components-activitylog--insight-activity--light.png index bc40839af4c33..12512e9a25861 100644 Binary files a/frontend/__snapshots__/components-activitylog--insight-activity--light.png and b/frontend/__snapshots__/components-activitylog--insight-activity--light.png differ diff --git a/frontend/__snapshots__/components-property-key-info--property-key-info--dark.png b/frontend/__snapshots__/components-property-key-info--property-key-info--dark.png index 0f7a15872752d..c7ceacfd4fd49 100644 Binary files a/frontend/__snapshots__/components-property-key-info--property-key-info--dark.png and b/frontend/__snapshots__/components-property-key-info--property-key-info--dark.png differ diff --git a/frontend/__snapshots__/components-property-key-info--property-key-info--light.png b/frontend/__snapshots__/components-property-key-info--property-key-info--light.png index b9cd979649fc2..649dba618ee27 100644 Binary files a/frontend/__snapshots__/components-property-key-info--property-key-info--light.png and b/frontend/__snapshots__/components-property-key-info--property-key-info--light.png differ diff --git a/frontend/src/lib/components/PropertyKeyInfo.tsx b/frontend/src/lib/components/PropertyKeyInfo.tsx index d7efdeb051c65..e2e006915252e 100644 --- a/frontend/src/lib/components/PropertyKeyInfo.tsx +++ b/frontend/src/lib/components/PropertyKeyInfo.tsx @@ -38,11 +38,8 @@ export const PropertyKeyInfo = React.forwardRef(empty string) : valueDisplayText - const recognizedSource: 'posthog' | 'langfuse' | null = coreDefinition - ? 'posthog' - : value.startsWith('langfuse ') - ? 'langfuse' - : null + const recognizedSource: 'posthog' | 'langfuse' | null = + coreDefinition || value.startsWith('$') ? 'posthog' : value.startsWith('langfuse ') ? 'langfuse' : null const innerContent = (
- {!!coreDefinition && } + {!!coreDefinition && ( + + )} {coreDefinition.label}
{coreDefinition.description || coreDefinition.examples ? ( diff --git a/frontend/src/lib/taxonomy.tsx b/frontend/src/lib/taxonomy.tsx index 5ee7691128d42..f339332a36456 100644 --- a/frontend/src/lib/taxonomy.tsx +++ b/frontend/src/lib/taxonomy.tsx @@ -177,7 +177,12 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { }, $ai_generation: { label: 'AI Generation', - description: 'A call to a generative AI model, e.g. an LLM', + description: 'A call to a generative AI model (LLM)', + }, + $ai_trace: { + label: 'AI Trace', + description: + 'A generative AI trace. Usually a trace tracks a single user interaction and contains one or more AI generation calls', }, $ai_metric: { label: 'AI Metric', @@ -1378,9 +1383,9 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { description: 'The number of tokens in the input prmopt that was sent to the LLM API', examples: [23], }, - $ai_output: { + $ai_output_choices: { label: 'AI Output (LLM)', - description: 'The output JSON that was received from the LLM API', + description: 'The output message choices JSON that was received from the LLM API', examples: [ '{"choices": [{"text": "Quantum computing is a type of computing that harnesses the power of quantum mechanics to perform operations on data."}]}', ], @@ -1420,6 +1425,29 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { description: 'The parameters used to configure the model in the LLM API, in JSON', examples: ['{"temperature": 0.5, "max_tokens": 50}'], }, + $ai_stream: { + label: 'AI Stream (LLM)', + description: 'Whether the response from the LLM API was streamed', + examples: ['true', 'false'], + }, + $ai_temperature: { + label: 'AI Temperature (LLM)', + description: 'The temperature parameter used in the request to the LLM API', + examples: [0.7, 1.0], + }, + $ai_input_state: { + label: 'AI Input State (LLM)', + description: 'Input state of the LLM agent', + }, + $ai_output_state: { + label: 'AI Output State (LLM)', + description: 'Output state of the LLM agent', + }, + $ai_trace_name: { + label: 'AI Trace Name (LLM)', + description: 'The name given to this trace of LLM API calls', + examples: ['LangGraph'], + }, $ai_provider: { label: 'AI Provider (LLM)', description: 'The provider of the AI model used to generate the output from the LLM API', diff --git a/frontend/src/queries/nodes/DataVisualization/Components/Charts/LineGraph.tsx b/frontend/src/queries/nodes/DataVisualization/Components/Charts/LineGraph.tsx index 9ae2642378e40..77f54c5628b3a 100644 --- a/frontend/src/queries/nodes/DataVisualization/Components/Charts/LineGraph.tsx +++ b/frontend/src/queries/nodes/DataVisualization/Components/Charts/LineGraph.tsx @@ -188,10 +188,28 @@ export const LineGraph = (): JSX.Element => { }, scaleID: hasLeftYAxis ? 'yLeft' : 'yRight', value: cur.value, + enter: (ctx) => { + if (ctx.chart.options.plugins?.annotation?.annotations) { + const annotations = ctx.chart.options.plugins.annotation.annotations as Record + if (annotations[`line${curIndex}`]) { + annotations[`line${curIndex}`].label.content = `${cur.label}: ${cur.value}` + ctx.chart.update() + } + } + }, + leave: (ctx) => { + if (ctx.chart.options.plugins?.annotation?.annotations) { + const annotations = ctx.chart.options.plugins.annotation.annotations as Record + if (annotations[`line${curIndex}`]) { + annotations[`line${curIndex}`].label.content = cur.label + ctx.chart.update() + } + } + }, } acc.annotations[`line${curIndex}`] = { - type: 'line', + type: 'line' as const, ...line, } diff --git a/frontend/src/queries/nodes/DataVisualization/Components/Variables/variablesLogic.ts b/frontend/src/queries/nodes/DataVisualization/Components/Variables/variablesLogic.ts index d66b5e87c32e1..052537c6f82d6 100644 --- a/frontend/src/queries/nodes/DataVisualization/Components/Variables/variablesLogic.ts +++ b/frontend/src/queries/nodes/DataVisualization/Components/Variables/variablesLogic.ts @@ -60,6 +60,7 @@ export const variablesLogic = kea([ }), setEditorQuery: (query: string) => ({ query }), updateSourceQuery: true, + resetVariables: true, })), propsChanged(({ props, actions }, oldProps) => { if (oldProps.queryInput !== props.queryInput) { @@ -103,6 +104,9 @@ export const variablesLogic = kea([ return stateCopy }, + resetVariables: () => { + return [] + }, }, ], editorQuery: [ @@ -191,6 +195,11 @@ export const variablesLogic = kea([ editorQuery: (query: string) => { const queryVariableMatches = getVariablesFromQuery(query) + if (!queryVariableMatches.length) { + actions.resetVariables() + return + } + queryVariableMatches?.forEach((match) => { if (match === null) { return @@ -212,10 +221,16 @@ export const variablesLogic = kea([ return } + const queryVariableMatches = getVariablesFromQuery(query.source.query) + const variables = Object.values(query.source.variables ?? {}) if (variables.length) { - actions.addVariables(variables) + variables.forEach((variable) => { + if (queryVariableMatches.includes(variable.code_name)) { + actions.addVariable(variable) + } + }) } }, })), diff --git a/frontend/src/queries/nodes/DataVisualization/dataVisualizationLogic.ts b/frontend/src/queries/nodes/DataVisualization/dataVisualizationLogic.ts index e2b0f57d11623..304367fd01e69 100644 --- a/frontend/src/queries/nodes/DataVisualization/dataVisualizationLogic.ts +++ b/frontend/src/queries/nodes/DataVisualization/dataVisualizationLogic.ts @@ -780,7 +780,10 @@ export const dataVisualizationLogic = kea([ dataVisualizationProps: [() => [(_, props) => props], (props): DataVisualizationLogicProps => props], isTableVisualization: [ (state) => [state.visualizationType], - (visualizationType): boolean => visualizationType === ChartDisplayType.ActionsTable, + (visualizationType): boolean => + // BoldNumber relies on yAxis formatting so it's considered a table visualization + visualizationType === ChartDisplayType.ActionsTable || + visualizationType === ChartDisplayType.BoldNumber, ], showTableSettings: [ (state) => [state.visualizationType], diff --git a/frontend/src/queries/nodes/HogQLX/render.tsx b/frontend/src/queries/nodes/HogQLX/render.tsx index 7b9ab93a1a53a..684f80026e4b8 100644 --- a/frontend/src/queries/nodes/HogQLX/render.tsx +++ b/frontend/src/queries/nodes/HogQLX/render.tsx @@ -59,7 +59,7 @@ export function renderHogQLX(value: any): JSX.Element { const { href, children, source, target } = rest return ( - + {children ?? source ? renderHogQLX(children ?? source) : href} diff --git a/frontend/src/scenes/data-warehouse/settings/source/Schemas.tsx b/frontend/src/scenes/data-warehouse/settings/source/Schemas.tsx index ed17057809093..5419cbd174259 100644 --- a/frontend/src/scenes/data-warehouse/settings/source/Schemas.tsx +++ b/frontend/src/scenes/data-warehouse/settings/source/Schemas.tsx @@ -75,6 +75,7 @@ export const SchemaTable = ({ schemas, isLoading }: SchemaTableProps): JSX.Eleme return ( updateSchema({ ...schema, sync_frequency: value as DataWarehouseSyncInterval }) diff --git a/package.json b/package.json index e5b88cabe27d3..40d5611260245 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "pmtiles": "^2.11.0", "postcss": "^8.4.31", "postcss-preset-env": "^9.3.0", - "posthog-js": "1.210.0", + "posthog-js": "1.210.2", "posthog-js-lite": "3.0.0", "prettier": "^2.8.8", "prop-types": "^15.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e50baef4e1081..68b074f5dff66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -312,8 +312,8 @@ dependencies: specifier: ^9.3.0 version: 9.3.0(postcss@8.4.31) posthog-js: - specifier: 1.210.0 - version: 1.210.0 + specifier: 1.210.2 + version: 1.210.2 posthog-js-lite: specifier: 3.0.0 version: 3.0.0 @@ -18184,8 +18184,8 @@ packages: resolution: {integrity: sha512-dyajjnfzZD1tht4N7p7iwf7nBnR1MjVaVu+MKr+7gBgA39bn28wizCIJZztZPtHy4PY0YwtSGgwfBCuG/hnHgA==} dev: false - /posthog-js@1.210.0: - resolution: {integrity: sha512-T7KWBKNxGW0j57FADXmLRjtrmSOFhTJ0joF/VfVTrVLVtu3yyj4MpCRytifQFdD5F8fLHgmexwKSIetTv09eoA==} + /posthog-js@1.210.2: + resolution: {integrity: sha512-rIbn/h9ur7uA0PS4dClOr9w6txLfHS94yh9yafA5VM2eXToM951XtMYtIQ6bi6wFzpvpFvTQFeYLQ/9/xZ59AQ==} dependencies: core-js: 3.40.0 fflate: 0.4.8 diff --git a/posthog/hogql/database/database.py b/posthog/hogql/database/database.py index 7c746ecf2e73a..c014099691818 100644 --- a/posthog/hogql/database/database.py +++ b/posthog/hogql/database/database.py @@ -586,7 +586,7 @@ def serialize_database( warehouse_schemas = ( list( ExternalDataSchema.objects.exclude(deleted=True) - .filter(table_id__in=[table.id for table in warehouse_tables]) + .filter(team_id=context.team_id, table_id__in=[table.id for table in warehouse_tables]) .all() ) if len(warehouse_tables) > 0 diff --git a/posthog/temporal/data_imports/external_data_job.py b/posthog/temporal/data_imports/external_data_job.py index 711d9d621d42d..dcfdd0e7427ae 100644 --- a/posthog/temporal/data_imports/external_data_job.py +++ b/posthog/temporal/data_imports/external_data_job.py @@ -287,6 +287,7 @@ async def run(self, inputs: ExternalDataWorkflowInputs): run_id=job_id, schema_id=inputs.external_data_schema_id, source_id=inputs.external_data_source_id, + reset_pipeline=inputs.reset_pipeline, ) timeout_params = ( diff --git a/posthog/temporal/data_imports/workflow_activities/import_data_sync.py b/posthog/temporal/data_imports/workflow_activities/import_data_sync.py index c4d509a72b4dc..0d566087520a5 100644 --- a/posthog/temporal/data_imports/workflow_activities/import_data_sync.py +++ b/posthog/temporal/data_imports/workflow_activities/import_data_sync.py @@ -2,7 +2,7 @@ import uuid from datetime import datetime from dateutil import parser -from typing import Any +from typing import Any, Optional from django.conf import settings from django.db import close_old_connections @@ -35,6 +35,7 @@ class ImportDataActivityInputs: schema_id: uuid.UUID source_id: uuid.UUID run_id: str + reset_pipeline: Optional[bool] = None def process_incremental_last_value(value: Any | None, field_type: IncrementalFieldType | None) -> Any | None: @@ -92,7 +93,14 @@ def import_data_activity_sync(inputs: ImportDataActivityInputs): schema: ExternalDataSchema | None = model.schema assert schema is not None - reset_pipeline = schema.sync_type_config.get("reset_pipeline", False) is True + + if inputs.reset_pipeline is not None: + reset_pipeline = inputs.reset_pipeline + else: + reset_pipeline = schema.sync_type_config.get("reset_pipeline", False) is True + + logger.debug(f"schema.sync_type_config = {schema.sync_type_config}") + logger.debug(f"reset_pipeline = {reset_pipeline}") schema = ( ExternalDataSchema.objects.prefetch_related("source") @@ -101,24 +109,26 @@ def import_data_activity_sync(inputs: ImportDataActivityInputs): ) endpoints = [schema.name] + processed_incremental_last_value = None - if settings.TEMPORAL_TASK_QUEUE == DATA_WAREHOUSE_TASK_QUEUE_V2: - # Get the V2 last value, if it's not set yet (e.g. the first run), then fallback to the V1 value - processed_incremental_last_value = process_incremental_last_value( - schema.sync_type_config.get("incremental_field_last_value_v2"), - schema.sync_type_config.get("incremental_field_type"), - ) + if reset_pipeline is not True: + if settings.TEMPORAL_TASK_QUEUE == DATA_WAREHOUSE_TASK_QUEUE_V2: + # Get the V2 last value, if it's not set yet (e.g. the first run), then fallback to the V1 value + processed_incremental_last_value = process_incremental_last_value( + schema.sync_type_config.get("incremental_field_last_value_v2"), + schema.sync_type_config.get("incremental_field_type"), + ) - if processed_incremental_last_value is None: + if processed_incremental_last_value is None: + processed_incremental_last_value = process_incremental_last_value( + schema.sync_type_config.get("incremental_field_last_value"), + schema.sync_type_config.get("incremental_field_type"), + ) + else: processed_incremental_last_value = process_incremental_last_value( schema.sync_type_config.get("incremental_field_last_value"), schema.sync_type_config.get("incremental_field_type"), ) - else: - processed_incremental_last_value = process_incremental_last_value( - schema.sync_type_config.get("incremental_field_last_value"), - schema.sync_type_config.get("incremental_field_type"), - ) if schema.is_incremental: logger.debug(f"Incremental last value being used is: {processed_incremental_last_value}") diff --git a/posthog/temporal/utils.py b/posthog/temporal/utils.py index a1c22b0e0827f..23d8e406ea8c0 100644 --- a/posthog/temporal/utils.py +++ b/posthog/temporal/utils.py @@ -1,4 +1,5 @@ import dataclasses +from typing import Optional import uuid @@ -9,3 +10,4 @@ class ExternalDataWorkflowInputs: external_data_source_id: uuid.UUID external_data_schema_id: uuid.UUID | None = None billable: bool = True + reset_pipeline: Optional[bool] = None diff --git a/products/llm_observability/frontend/ConversationDisplay/ConversationMessagesDisplay.tsx b/products/llm_observability/frontend/ConversationDisplay/ConversationMessagesDisplay.tsx index c9093181da741..98e966049c7b6 100644 --- a/products/llm_observability/frontend/ConversationDisplay/ConversationMessagesDisplay.tsx +++ b/products/llm_observability/frontend/ConversationDisplay/ConversationMessagesDisplay.tsx @@ -67,11 +67,11 @@ export function LLMMessageDisplay({ message, isOutput }: { message: CompatMessag 'rounded border text-default', isOutput ? 'bg-[var(--bg-fill-success-tertiary)]' - : role === 'system' - ? 'bg-[var(--bg-fill-tertiary)]' : role === 'user' - ? 'bg-[var(--bg-fill-primary)]' - : 'bg-[var(--bg-fill-info-tertiary)]' + ? 'bg-[var(--bg-fill-tertiary)]' + : role === 'assistant' + ? 'bg-[var(--bg-fill-info-tertiary)]' + : null // e.g. system )} >
diff --git a/products/llm_observability/frontend/LLMObservabilityTracesScene.tsx b/products/llm_observability/frontend/LLMObservabilityTracesScene.tsx index 76f672b71a426..29cbcc764993c 100644 --- a/products/llm_observability/frontend/LLMObservabilityTracesScene.tsx +++ b/products/llm_observability/frontend/LLMObservabilityTracesScene.tsx @@ -62,12 +62,14 @@ export function LLMObservabilityTraces(): JSX.Element { const IDColumn: QueryContextColumnComponent = ({ record }) => { const row = record as LLMTrace return ( - - {row.id} - + + + {row.id.slice(0, 4)}...{row.id.slice(-4)} + + ) } diff --git a/products/llm_observability/frontend/llmObservabilityLogic.tsx b/products/llm_observability/frontend/llmObservabilityLogic.tsx index 5f338322e2f58..e001c698469fe 100644 --- a/products/llm_observability/frontend/llmObservabilityLogic.tsx +++ b/products/llm_observability/frontend/llmObservabilityLogic.tsx @@ -13,6 +13,7 @@ import { ChartDisplayType, EventDefinitionType, HogQLMathType, + PropertyFilterType, PropertyMathType, } from '~/types' @@ -130,7 +131,10 @@ export const llmObservabilityLogic = kea([ }, ], dateRange: { date_from: dateFilter.dateFrom, date_to: dateFilter.dateTo }, - properties: propertyFilters, + properties: propertyFilters.concat({ + type: PropertyFilterType.HogQL, + key: 'distinct_id != properties.$ai_trace_id', + }), filterTestAccounts: shouldFilterTestAccounts, }, }, @@ -183,7 +187,10 @@ export const llmObservabilityLogic = kea([ decimalPlaces: 2, }, dateRange: { date_from: dateFilter.dateFrom, date_to: dateFilter.dateTo }, - properties: propertyFilters, + properties: propertyFilters.concat({ + type: PropertyFilterType.HogQL, + key: 'distinct_id != properties.$ai_trace_id', + }), filterTestAccounts: shouldFilterTestAccounts, }, }, @@ -249,8 +256,7 @@ export const llmObservabilityLogic = kea([ }, trendsFilter: { aggregationAxisPostfix: ' s', - decimalPlaces: 3, - yAxisScaleType: 'log10', + decimalPlaces: 2, }, dateRange: { date_from: dateFilter.dateFrom, date_to: dateFilter.dateTo }, properties: propertyFilters, @@ -328,14 +334,17 @@ export const llmObservabilityLogic = kea([ kind: NodeKind.EventsQuery, select: [ '*', + ` + {f'{left(toString(uuid), 4)}...{right(toString(uuid), 4)}'} + -- ID`, + ` + {f'{left(properties.$ai_trace_id, 4)}...{right(properties.$ai_trace_id, 4)}'} + -- Trace ID`, 'person', - // The f-string wrapping below seems pointless, but it actually disables special rendering - // of the property keys, which would otherwise show property names overly verbose here - "f'{properties.$ai_trace_id}' -- Trace ID", "f'{properties.$ai_model}' -- Model", - "f'${round(toFloat(properties.$ai_total_cost_usd), 6)}' -- Total cost", + "f'{round(properties.$ai_latency, 2)} s' -- Latency", "f'{properties.$ai_input_tokens} → {properties.$ai_output_tokens} (∑ {properties.$ai_input_tokens + properties.$ai_output_tokens})' -- Token usage", - "f'{properties.$ai_latency} s' -- Latency", + "f'${round(toFloat(properties.$ai_total_cost_usd), 6)}' -- Total cost", 'timestamp', ], orderBy: ['timestamp DESC'], @@ -358,6 +367,7 @@ export const llmObservabilityLogic = kea([ TaxonomicFilterGroupType.HogQLExpression, ], showExport: true, + showActions: false, }), ], }), diff --git a/products/llm_observability/frontend/llmObservabilityTraceLogic.ts b/products/llm_observability/frontend/llmObservabilityTraceLogic.ts index 075a302a73728..0a8f449f9fa98 100644 --- a/products/llm_observability/frontend/llmObservabilityTraceLogic.ts +++ b/products/llm_observability/frontend/llmObservabilityTraceLogic.ts @@ -101,6 +101,11 @@ export const llmObservabilityTraceLogic = kea([ (s) => [s.traceId], (traceId): Breadcrumb[] => { return [ + { + key: 'LLMObservability', + name: 'LLM observability', + path: urls.llmObservability('dashboard'), + }, { key: 'LLMObservability', name: 'Traces',