我在「醫」世界用 Python 召喚虛擬病人這件事

邵軒磊 Hsuanlei Shao

Claudia Ng
If you've shipped an AI product to real users, your conversation logs are a rich source of product signal. User prompts reveal intent, but they can also contain sensitive content like family stories and personal details.
This talk shows how to learn from those logs responsibly using a Python topic-modeling pipeline (BERTopic, pandas, Plotly) I built for CantoAI (www.canto-ai.com), my Cantonese conversation tutor for heritage speakers (400+ users, 1,200+ conversations, 6,000+ turns). I cover handling multilingual data, using min_cluster_size as a privacy threshold, scrubbing PII with regex at storage time, and pandas queries that only return aggregates.
I will be explicit about operational boundaries, including what appears in public visualizations versus offline analysis. Attendees leave with code patterns and a checklist for their own AI products.
Basic Python and pandas are sufficient; no Cantonese or BERTopic background required.
If you ship an AI product to real users, conversation logs are your best product signal, but they can contain family stories, health details, and other sensitive content. The naive approach is to read a sample of conversations for user insights, but that does not scale and creates real privacy risk.
This talk walks through the Python topic-modeling pipeline I built for CantoAI, an AI Cantonese conversation partner. The dataset is 400+ users, 1,200+ conversations, and 6,000+ logged turns. CantoAI is the case study, but this pipeline can be applied to any AI product with sensitive user inputs: customer support agents, copilots, and more.
With this pipeline, I do not qualitatively review individual user turns to label topics. Instead, this is done algorithmically: clustering runs on embeddings computed from user text in an offline script.
Outputs use aggregates and keyword-derived labels, not verbatim quotes to keep anonymity. I generate a public treemap that shows category names, topic labels, counts, and percentages. These topic labels come from BERTopic keywords.
I'll cover the full pipeline:
min_cluster_size=10 set deliberately as a privacy threshold, so a topic only forms if at least 10 user turns cluster together in the embedding space. Smaller or sparse groups are labeled noise and excluded from visualizations.topic_summary = (
df.groupby(["topic", "scenario"], dropna=False)
.agg(
turns=("query", "size"),
users=("user_id", "nunique"),
)
.reset_index()
)
This pattern is the discipline I use throughout the analysis: every chart and table starts from groupby + agg, never from printing or plotting raw query rows.
Attendees will leave with the code patterns and a concrete checklist they can apply to their own AI products immediately.
Related reading: my Substack series introducing this topic (part 1, part 2) on the original 2,800-conversation analysis; Anthropic's Clio paper as related work at million-conversation scale.

Claudia Ng is a machine learning engineer with six years of experience building scalable machine learning systems at Silicon Valley tech startups. She writes about applied AI engineering in her newsletter called the AI Weekender (aiweekender.substack.com), and is the creator of CantoAI, a Cantonese conversation tutor. She is a polyglot who speaks nine languages and holds a Master's in Public Policy from Harvard University.