Menganalisis log percakapan
Dokumen ini menunjukkan cara membuat kueri, mengevaluasi, dan memecahkan masalah log percakapan analisis percakapan di BigQuery menggunakan resep GoogleSQL, notebook Colab interaktif, dan SDK Python BigQuery Agent Analytics. Untuk mengetahui informasi selengkapnya tentang kolom tabel tujuan dan struktur JSON, lihat Skema log percakapan.
Menggunakan notebook Colab dan Python SDK
Anda dapat menjalankan resep SQL dalam dokumen ini bersama dengan
bigquery-agent-analytics
Python SDK (v0.5.4 atau yang lebih baru) menggunakan Customer Analytics, FinOps & Evaluation
Starter Notebook (buka di
Colaboratory
| lihat di
GitHub).
Untuk menggunakan Python SDK dengan tabel log percakapan, buat tampilan kompatibilitas v_bqca_sdk_events terlebih dahulu. Pada baris AGENT_RESPONSE,
analisis percakapan menyimpan objek JSON di content.response, sedangkan evaluator
LLMAsJudge SDK mengharapkan JSON_VALUE(content, '$.response') menampilkan
string skalar. Tampilan ini menetapkan $.text_summary dan $.response ke
jawaban teks biasa sambil mempertahankan objek asli di $.raw_response.
Dalam cuplikan SQL dan Python berikut, ganti placeholder ini:
PROJECT_ID: ID project yang berisi agen data dan set data log Anda.LOG_DATASET: ID set data BigQuery yang menyimpan tabel log percakapan mentah Anda.LOG_TABLE: ID tabel log percakapan, sepertiagent_events.ANALYTICS_DATASET: ID set data BigQuery di lokasi yang sama tempat Anda membuat tampilan analisis dan fungsi tabel.LOCATION: lokasi BigQuery dari set data analisis, sepertiUS.
Membuat tampilan dan fungsi tabel di ANALYTICS_DATASET
memerlukan akses tulis di ANALYTICS_DATASET, seperti
peran BigQuery Data Editor
(roles/bigquery.dataEditor), dan peran BigQuery Data
Viewer
(roles/bigquery.dataViewer) di LOG_DATASET.
Analis yang hanya mengkueri tampilan memerlukan peran BigQuery Data Viewer di ANALYTICS_DATASET dan peran BigQuery Job User (roles/bigquery.jobUser) di project tempat mereka menjalankan kueri.
Kueri berikut membuat tampilan v_bqca_sdk_events, yang digunakan pada langkah berikutnya untuk memvalidasi skema tabel dan cakupan peristiwa Anda:
CREATE OR REPLACE VIEW `PROJECT_ID.ANALYTICS_DATASET.v_bqca_sdk_events` AS WITH allowlisted AS ( SELECT *, IF( event_type = 'AGENT_RESPONSE' AND content IS NOT NULL, COALESCE( NULLIF(JSON_VALUE(content, '$.text_summary'), ''), NULLIF(ARRAY_TO_STRING(ARRAY( SELECT JSON_VALUE(part, '$.markdown') FROM UNNEST(JSON_QUERY_ARRAY(content, '$.response.parts')) AS part WITH OFFSET AS part_offset WHERE JSON_VALUE(part, '$.markdown') IS NOT NULL ORDER BY part_offset ), '\n\n'), ''), NULLIF(JSON_VALUE(content, '$.response.clarifying_question'), ''), JSON_VALUE(content, '$.response') ), NULL ) AS answer_text FROM `PROJECT_ID.LOG_DATASET.LOG_TABLE` WHERE event_type IN ( 'INVOCATION_STARTING', 'USER_MESSAGE_RECEIVED', 'AGENT_RESPONSE', 'INVOCATION_COMPLETED', 'LLM_RESPONSE', 'EMBEDDING_SUGGESTION', 'INVOCATION_ERROR', 'AGENT_ERROR', 'LLM_ERROR' ) ) SELECT * EXCEPT(answer_text) REPLACE ( CASE WHEN event_type = 'AGENT_RESPONSE' AND content IS NOT NULL THEN JSON_SET( content, '$.raw_response', JSON_QUERY(content, '$.response'), '$.text_summary', answer_text, '$.response', answer_text ) ELSE content END AS content, JSON_SET( COALESCE(attributes, JSON '{}'), '$.root_agent_name', JSON_VALUE(attributes, '$.adk.app_name') ) AS attributes ) FROM allowlisted;
Setelah membuat tampilan kompatibilitas, lakukan inisialisasi SDK Client. Anda dapat
menjalankan client.doctor() secara opsional untuk memvalidasi skema tabel dan cakupan
peristiwa:
from datetime import datetime, timedelta, timezone from bigquery_agent_analytics import Client, TraceFilter client = Client( project_id="PROJECT_ID", dataset_id="ANALYTICS_DATASET", table_id="v_bqca_sdk_events", location="LOCATION", ) # Optionally run diagnostic checks over recent partitions: report = client.doctor( filters=TraceFilter(start_time=datetime.now(timezone.utc) - timedelta(days=7)) ) print(report)
Fungsi client.doctor() menampilkan kamus yang memvalidasi skema tabel dan membandingkan cakupan peristiwa dengan jenis peristiwa Agent Development Kit (ADK) standar. Karena tampilan kompatibilitas v_bqca_sdk_events hanya mengekspos
jenis peristiwa analisis percakapan yang diizinkan, peringatan tentang peristiwa
AGENT_COMPLETED, AGENT_STARTING, LLM_REQUEST, TOOL_COMPLETED, atau
TOOL_STARTING yang tidak ada adalah hal yang wajar. Peringatan error atau kolom yang tidak ada
berarti parameter Client tidak cocok dengan tempat tampilan dibuat
atau Anda tidak memiliki akses baca ke set data, sedangkan hasil event_coverage
kosong berarti tampilan tidak memiliki percakapan dari 7 hari terakhir.
Untuk mengetahui informasi selengkapnya tentang cara menggunakan SDK untuk mengevaluasi kualitas agen, merekonstruksi trace, dan memantau drift, lihat Menggunakan SDK BigQuery Agent Analytics dan notebook Colab interaktif yang ditautkan di bagian ini.
Resep SQL
Dalam kueri GoogleSQL berikut, gunakan placeholder
PROJECT_ID, LOG_DATASET,
LOG_TABLE, dan
ANALYTICS_DATASET yang sama seperti yang dijelaskan dalam Menggunakan
notebook Colab dan Python SDK.
Mulailah dengan Membuat tampilan peristiwa lintas agen
(v_ca_events) dan Membuat fungsi tabel giliran (ca_turns). Sebagian besar kueri berikut
menggunakan fungsi tabel ca_turns atau tampilan v_ca_events; kueri
ringkasan error AI.GENERATE dan kueri penghapusan duplikat membaca tabel log
secara langsung.
Membuat tampilan peristiwa lintas agen (v_ca_events)
Tampilan ini menampilkan satu baris per peristiwa, dengan ID agen, ID percakapan, pertanyaan,
teks jawaban, insight, token, dan latensi sebagai kolom biasa. Filter pada
timestamp diteruskan ke tabel log, sehingga pruning partisi tetap berfungsi. Tampilan
tidak menghapus peristiwa duplikat; percobaan ulang penulisan jarang terjadi, tetapi jika Anda ingin
memeriksa atau memfilter peristiwa duplikat berdasarkan event_id, lihat Menghapus baris
duplikat:
CREATE OR REPLACE VIEW `PROJECT_ID.ANALYTICS_DATASET.v_ca_events` AS SELECT timestamp, DATE(timestamp) AS event_date, event_id, event_type, status, error_message, user_id, COALESCE(NULLIF(user_id, ''), JSON_VALUE(attributes, '$.session_metadata.user_id')) AS internal_user_key, JSON_VALUE(attributes, '$.session_metadata.state."data-agent-id"') AS data_agent_id, JSON_VALUE(attributes, '$.adk.app_name') AS data_agent_resource_name, JSON_VALUE(attributes, '$.session_metadata.state."conversation-id"') AS conversation_id, session_id AS turn_id, invocation_id, IF(event_type = 'USER_MESSAGE_RECEIVED', JSON_VALUE(content, '$.text_summary'), NULL) AS user_question, IF(event_type = 'AGENT_RESPONSE', COALESCE( NULLIF(JSON_VALUE(content, '$.text_summary'), ''), NULLIF(ARRAY_TO_STRING(ARRAY( SELECT JSON_VALUE(p, '$.markdown') FROM UNNEST(JSON_QUERY_ARRAY(content, '$.response.parts')) AS p WITH OFFSET AS o WHERE JSON_VALUE(p, '$.markdown') IS NOT NULL ORDER BY o), '\n\n'), ''), NULLIF(JSON_VALUE(content, '$.response.clarifying_question'), ''), JSON_VALUE(content, '$.response')), NULL) AS agent_response, IF(event_type = 'AGENT_RESPONSE', NULLIF(ARRAY_TO_STRING(ARRAY( SELECT JSON_VALUE(p, '$.insight') FROM UNNEST(JSON_QUERY_ARRAY(content, '$.response.parts')) AS p WITH OFFSET AS o WHERE JSON_VALUE(p, '$.insight') IS NOT NULL ORDER BY o), '\n'), ''), NULL) AS agent_insights, IF(event_type = 'AGENT_RESPONSE', NULLIF(ARRAY_TO_STRING(ARRAY( SELECT JSON_VALUE(p, '$.assumption') FROM UNNEST(JSON_QUERY_ARRAY(content, '$.response.parts')) AS p WITH OFFSET AS o WHERE JSON_VALUE(p, '$.assumption') IS NOT NULL ORDER BY o), '\n'), ''), NULL) AS agent_assumptions, IF(event_type = 'AGENT_RESPONSE', NULLIF(ARRAY_TO_STRING(ARRAY( SELECT JSON_VALUE(q) FROM UNNEST(JSON_QUERY_ARRAY(content, '$.response.followup_questions')) AS q WITH OFFSET AS o WHERE JSON_VALUE(q) IS NOT NULL ORDER BY o), '\n'), ''), NULL) AS followup_questions, IF(event_type = 'AGENT_RESPONSE', NULLIF(ARRAY_TO_STRING(ARRAY( SELECT artifact_name FROM UNNEST(JSON_QUERY_ARRAY(content, '$.response.parts')) AS p WITH OFFSET AS o, UNNEST([JSON_VALUE(p, '$.data_result_artifact_name'), JSON_VALUE(p, '$.chart_artifact_name')]) AS artifact_name WITH OFFSET AS a_o WHERE artifact_name IS NOT NULL ORDER BY o, a_o), ', '), ''), NULL) AS result_artifact_names, IF(event_type = 'LLM_RESPONSE', JSON_VALUE(attributes, '$.model_version'), NULL) AS model_version, IF(event_type = 'LLM_RESPONSE', SAFE_CAST(JSON_VALUE(content, '$.usage.prompt') AS INT64), NULL) AS prompt_tokens, IF(event_type = 'LLM_RESPONSE', SAFE_CAST(JSON_VALUE(content, '$.usage.completion') AS INT64), NULL) AS completion_tokens, IF(event_type = 'LLM_RESPONSE', SAFE_CAST(JSON_VALUE(content, '$.usage.total') AS INT64), NULL) AS total_tokens, IF(event_type = 'LLM_RESPONSE', SAFE_CAST(JSON_VALUE(attributes, '$.usage_metadata.thoughts_token_count') AS INT64), NULL) AS thoughts_tokens, IF(event_type = 'INVOCATION_COMPLETED', SAFE_CAST(JSON_VALUE(latency_ms, '$.total_ms') AS INT64), NULL) AS turn_latency_ms, is_truncated, trace_id, span_id, parent_span_id FROM `PROJECT_ID.LOG_DATASET.LOG_TABLE`;
Buat fungsi tabel giliran (ca_turns): satu baris per pertanyaan dan jawaban
Fungsi tabel ini mengambil periode waktu dan menampilkan satu baris per giliran. Karena menerapkan filter waktu ke tabel log, Anda hanya membayar untuk hari yang Anda minta:
CREATE OR REPLACE TABLE FUNCTION `PROJECT_ID.ANALYTICS_DATASET.ca_turns`(start_ts TIMESTAMP, end_ts TIMESTAMP) AS SELECT turn_id, MIN(timestamp) AS turn_start, MAX(timestamp) AS turn_end, DATE(MIN(timestamp)) AS turn_date, NULLIF(MAX(user_id), '') AS user_id, MAX(internal_user_key) AS internal_user_key, MAX(data_agent_id) AS data_agent_id, MAX(conversation_id) AS conversation_id, ARRAY_AGG(user_question IGNORE NULLS ORDER BY timestamp, event_id LIMIT 1)[SAFE_OFFSET(0)] AS user_question, ARRAY_AGG(agent_response IGNORE NULLS ORDER BY timestamp DESC, event_id DESC LIMIT 1)[SAFE_OFFSET(0)] AS agent_response, ARRAY_AGG(agent_insights IGNORE NULLS ORDER BY timestamp DESC, event_id DESC LIMIT 1)[SAFE_OFFSET(0)] AS agent_insights, ARRAY_AGG(agent_assumptions IGNORE NULLS ORDER BY timestamp DESC, event_id DESC LIMIT 1)[SAFE_OFFSET(0)] AS agent_assumptions, ARRAY_AGG(followup_questions IGNORE NULLS ORDER BY timestamp DESC, event_id DESC LIMIT 1)[SAFE_OFFSET(0)] AS followup_questions, ARRAY_AGG(result_artifact_names IGNORE NULLS ORDER BY timestamp DESC, event_id DESC LIMIT 1)[SAFE_OFFSET(0)] AS result_artifact_names, MAX(turn_latency_ms) AS turn_latency_ms, SUM(total_tokens) AS total_tokens, COUNTIF(status = 'ERROR') AS error_events, LOGICAL_OR(IFNULL(is_truncated, FALSE)) AS any_truncated FROM `PROJECT_ID.ANALYTICS_DATASET.v_ca_events` WHERE timestamp >= start_ts AND timestamp < end_ts AND turn_id IS NOT NULL GROUP BY turn_id;
Setiap pertanyaan dan jawaban untuk satu pengguna (30 hari terakhir)
Tetapkan target_user ke alamat email pengguna, atau ke kunci internal untuk
penelepon tanpa alamat email:
DECLARE target_user STRING DEFAULT 'analyst@example.com'; SELECT turn_start, data_agent_id, conversation_id, user_question, agent_response, agent_insights FROM `PROJECT_ID.ANALYTICS_DATASET.ca_turns`( TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY), CURRENT_TIMESTAMP()) WHERE user_id = target_user OR internal_user_key = target_user ORDER BY turn_start;
Contoh pertanyaan dan jawaban di semua agen untuk ditinjau
Kueri ini menampilkan sekitar 10% giliran dari 24 jam terakhir. Ubah 0.10
untuk menetapkan rasio ulasan:
SELECT data_agent_id, turn_start, user_question, agent_response, agent_assumptions FROM `PROJECT_ID.ANALYTICS_DATASET.ca_turns`( TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY), CURRENT_TIMESTAMP()) WHERE user_question IS NOT NULL AND RAND() < 0.10 ORDER BY data_agent_id, turn_start;
Adopsi berdasarkan hari
Kolom internal_user_key akan kembali ke
attributes.session_metadata.user_id jika user_id adalah NULL (misalnya, untuk
beberapa identitas gabungan), sehingga jumlah pengguna harian tetap konsisten per orang:
SELECT turn_date, COUNT(DISTINCT internal_user_key) AS active_users, COUNT(DISTINCT data_agent_id) AS active_agents, COUNT(DISTINCT conversation_id) AS conversations, COUNT(*) AS questions FROM `PROJECT_ID.ANALYTICS_DATASET.ca_turns`( TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY), CURRENT_TIMESTAMP()) GROUP BY turn_date ORDER BY turn_date DESC;
Adopsi oleh pengguna: agen, percakapan, dan pertanyaan per orang
Untuk menghitung agen, percakapan, dan pertanyaan yang berbeda per pengguna selama 30 hari terakhir, jalankan kueri berikut:
SELECT t.internal_user_key, COUNT(DISTINCT t.data_agent_id) AS agents_used, COUNT(DISTINCT t.conversation_id) AS conversations, COUNT(*) AS questions, MIN(t.turn_start) AS first_seen, MAX(t.turn_start) AS last_seen FROM `PROJECT_ID.ANALYTICS_DATASET.ca_turns`( TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY), CURRENT_TIMESTAMP()) AS t GROUP BY internal_user_key ORDER BY questions DESC;
Adopsi dan kondisi menurut agen
Untuk membandingkan pengguna, percakapan, volume pertanyaan, latensi rata-rata, tingkat error, dan total token di semua agen data dalam project Anda, jalankan kueri berikut:
SELECT data_agent_id, COUNT(DISTINCT internal_user_key) AS users, COUNT(DISTINCT conversation_id) AS conversations, COUNT(*) AS questions, ROUND(AVG(turn_latency_ms)) AS avg_turn_latency_ms, ROUND(100 * SAFE_DIVIDE(COUNTIF(error_events > 0), COUNT(*)), 2) AS pct_turns_with_errors, SUM(total_tokens) AS total_tokens FROM `PROJECT_ID.ANALYTICS_DATASET.ca_turns`( TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY), CURRENT_TIMESTAMP()) GROUP BY data_agent_id ORDER BY questions DESC;
Ringkasan percakapan
Untuk menggabungkan percakapan multi-turn selama 7 hari terakhir, jalankan kueri berikut:
SELECT conversation_id, MAX(data_agent_id) AS data_agent_id, MAX(internal_user_key) AS internal_user_key, COUNT(*) AS turns, MIN(turn_start) AS started_at, TIMESTAMP_DIFF(MAX(turn_end), MIN(turn_start), SECOND) AS duration_seconds, ARRAY_AGG(user_question IGNORE NULLS ORDER BY turn_start LIMIT 1)[SAFE_OFFSET(0)] AS first_question FROM `PROJECT_ID.ANALYTICS_DATASET.ca_turns`( TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY), CURRENT_TIMESTAMP()) WHERE conversation_id IS NOT NULL GROUP BY conversation_id ORDER BY started_at DESC LIMIT 100;
Penggunaan token menurut model dan menurut agen (7 hari terakhir)
Untuk mengelompokkan jumlah panggilan model dan penggunaan token (termasuk token penalaran) berdasarkan tanggal, data_agent_id, dan model_version selama 7 hari terakhir, buat kueri v_ca_events untuk baris LLM_RESPONSE:
SELECT event_date, data_agent_id, model_version, COUNT(*) AS model_calls, SUM(prompt_tokens) AS prompt_tokens, SUM(completion_tokens) AS completion_tokens, SUM(thoughts_tokens) AS thoughts_tokens, -- thinking tokens, already included in total_tokens SUM(total_tokens) AS total_tokens FROM `PROJECT_ID.ANALYTICS_DATASET.v_ca_events` WHERE event_type = 'LLM_RESPONSE' AND timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) GROUP BY event_date, data_agent_id, model_version ORDER BY event_date DESC, total_tokens DESC;
Error menurut agen, dengan ringkasan akar masalah yang didukung AI
Untuk memeriksa jumlah error menurut agen dan hari selama 7 hari terakhir, jalankan kueri berikut:
SELECT event_date, data_agent_id, COUNT(*) AS error_events, COUNT(DISTINCT turn_id) AS turns_with_errors, ANY_VALUE(error_message) AS sample_error FROM `PROJECT_ID.ANALYTICS_DATASET.v_ca_events` WHERE status = 'ERROR' AND timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) GROUP BY event_date, data_agent_id ORDER BY event_date DESC, error_events DESC;
Untuk meringkas akar masalah error terbaru, gunakan fungsi
AI.GENERATE.
CTE membatasi baris sebelum model dipanggil, sehingga biaya tetap dapat diprediksi:
WITH recent_errors AS ( SELECT timestamp, JSON_VALUE(attributes, '$.session_metadata.state."data-agent-id"') AS data_agent_id, session_id AS turn_id, error_message FROM `PROJECT_ID.LOG_DATASET.LOG_TABLE` WHERE status = 'ERROR' AND timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY) ORDER BY timestamp DESC LIMIT 10 ) SELECT timestamp, data_agent_id, turn_id, error_message, AI.GENERATE( CONCAT( 'Analyze this BigQuery Conversational Analytics error log and summarize the likely root cause in two sentences. Error: ', IFNULL(error_message, '(none)')) ).result AS ai_root_cause_summary FROM recent_errors;
Hapus baris duplikat
Langkah ini opsional. Penulisan yang dicoba ulang terkadang dapat menghasilkan peristiwa yang sama dua kali. Kueri berikut menunjukkan cara memilih baris unik per event_id
selama 24 jam terakhir:
SELECT * EXCEPT (row_num) FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY event_id ORDER BY timestamp) AS row_num FROM `PROJECT_ID.LOG_DATASET.LOG_TABLE` WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY) ) WHERE row_num = 1 OR event_id IS NULL;
Memecahkan masalah logging percakapan
Jika tabel log percakapan Anda tidak ada, kosong, atau tidak memiliki giliran dari pengguna tertentu, atau jika Anda mengalami error validasi tujuan, lihat Memecahkan masalah logging percakapan.