Redacting Japanese PII at Production Scale: Securing Long-Context VoC Data for AI Innovation

Redacting Japanese PII at Production Scale: Securing Long-Context VoC Data for AI Innovation

An inside look at Money Forward's engineering journey to secure long-context Japanese VoC data for AI innovation using an LLM-based PII redaction pipeline.
Redacting Japanese PII at Production Scale: Securing Long-Context VoC Data for AI Innovation

Hi, this is Emrys 👋

I'm an AI Engineer on the Money Forward Vietnam (MFV) AI Data team, currently working on the VoC (Voice of Customer) project in PMxAI Lab at Money Forward.

This work is being shaped by a cross-functional core team: me (Emrys), Quinn, Tony Hoang, Remi, Morio, and Elly. Together, we work across AI engineering, product thinking, data governance, and operational design.

We believe that every sufficiently messy problem hides a surprisingly elegant structure. Whether it's customer feedback, knowledge scattered across products and tools, or LLMs that don't quite behave the way we'd like, the fun part is uncovering that structure.

On the VoC project, that means building a trusted Voice of Customer intelligence pipeline — one that safely turns customer feedback from large volumes of support tickets, sales conversations, and other unstructured sources into actionable insights for the people building Money Forward products.

In the long term, we want VoC to become Money Forward's single source of truth for understanding customer needs, revenue risks, competitive gaps, and product opportunities. The hard question underneath it all: how can Money Forward make production-like customer feedback useful for AI and analytics without compromising security or trust?

This blog series is where we share that journey. Some posts will dive into engineering challenges, some into product ideas, and some into the small design decisions that don't usually make it into architecture diagrams. We'll start with one of the less glamorous, but essential topics: Redacting Japanese PII at Production Scale: Securing Long-Context VoC Data for AI Innovation


🧭 The Statement

As engineers, we all understand the hesitation around touching production. Breaking production can lead to financial loss, loss of trust, and a painful recovery effort. That's why Money Forward keeps production data inside a dedicated, isolated environment.

Unfortunately, cruel life happens.

Isolation protects the data, but it also makes the data harder to learn from. AI applications, analytics, and product improvements all rely on production data, yet that same data is often the most restricted. The challenge is no longer "how do we use production data?" but "how do we use it safely?"

💡 Personally Identifiable Information (PII) redaction is one pragmatic answer.

When data is neatly organized in tables, life is simple. Each column represents a single attribute, sometimes even standardized, making sensitive fields easy to identify, remove, hash, or encrypt before sharing the rest. In some cases, a simple regex would do the work.

But cruel life happens, again!

Real customer inquiries rarely arrive in perfect tables. They arrive as support tickets, sales call transcripts, chat logs, emails, and long conversations. Somewhere inside those thousands of words may be customer names, email addresses, phone numbers, usernames, passwords shared during debugging, or other sensitive information nobody intended to expose.

Suddenly, PII is no longer a column in a database. It's hidden somewhere inside tens of thousands of tokens of free-form text. And that's where our story begins.


🧩 The Definition

Before diving into the implementation, we first had to answer a deceptively simple question:

What exactly should we redact?

Not all entities carry the same level of sensitivity. A customer name is fundamentally different from a password, and treating them the same would either remove useful context or expose information that should never leave production.

So we classified entities into three sensitivity levels using two criteria: how risky the information would be if exposed, and how useful it remains for downstream product analysis.
 

LevelEntity NameDescriptionExamples
Level 1COMPANYCompany or organization names.Example Co., Sample Client Ltd., 架空株式会社
Level 1DEPARTMENTDepartment or team names.サンプル経理部, テストCSチーム, Example Operations Team
Level 1COMPETITORCompetitor product or company names.DemoProduct, SampleSuite, 架空会計クラウド
Level 1BUDGETBudget amounts from the client side.月額10万円の検討予算, annual demo budget, sample renewal budget
Level 2PERSON_NAMEReal person names, including honorifics and variants.山田太郎, 田中花子様, Sample Taro, Example Hanako, サンプル リコ
Level 2EMAILEmail addresses, excluding MF staff names.sample.user@example.test, demo.customer@example.invalid
Level 2PHONEPhone numbers, including signatures and Japanese formats.03-1234-56XX, 050-2345-67XX, 080-3456-78XX, 090の4567の89XX
Level 2ADDRESSDetailed physical addresses, postal codes, buildings, and rooms.〒123-4567 東京都サンプル区架空町12-34-56 サンプルビル8F, 大阪府テスト市例町23-45-67, 福岡県デモ市仮町34-56-78
Level 3IDAccount, ticket, company, channel, UUID, or similar IDs.ID-1234, ACCOUNT-87654321, ACC-20991231-4242, #TICKET-246810, CEXAMPLE01, p-example-13579, 123e4567-e89b-12d3-a456-426614174000
Level 3SSNSocial security numbers, including Japan My Number.123-45-67XX, 1234-5678-90XX, sample government identifier
Level 3CREDIT_CARD_NUMBERCredit card numbers.4111-1111-1111-1111, 5555-5555-5555-4444
Level 3CREDIT_CARD_PROVIDERActual card brand or provider names.Example Card, Sample Pay Card, Demo Credit, 架空カード
Level 3IP_ADDRESSIPv4 or IPv6 addresses.192.0.2.10, 198.51.100.25, 203.0.113.42, 2001:db8::1
Level 3URLPrivate, internal, tracking, or user-specific URLs.https://example.test/unsubscribe?token=sample-token-000&email=demo.customer@example.invalid&customer_id=CUST-0000
Level 3PINPIN numbers.0000, 9999, 1234 as a dummy PIN
Level 3PASSWORDActual password or credential values.DummyPassw0rd!, ExampleTemp@Pass123!, sample temporary password

🟢 Level 1: Business Context These entities provide useful business context while posing relatively low privacy risk. Company names, departments, competitors, and budget references fall into this category. They are useful for product analysis and can generally be retained within our internal analytics workflows.

 

🟡 Level 2: Personal Information Data that can identify a person, such as names, emails, phone numbers, addresses, etc. These require protection, but authorized users may still need access, so we support selective unmasking.

🔴 Level 3: Confidential Credentials This category includes passwords, PINs, credit card numbers, government identifiers, IP addresses, and other credentials. Unlike Level 2 entities, these provide almost no value for downstream AI analysis while carrying the highest security risk. If one of these leaks, all hell breaks loose at Money Forward, and quite frankly, nobody needs to see a customer's password in the first place. So the decision was simple: always mask them.


 

🛠️ The Solution

In case you're wondering whether we jumped directly to the solution after defining the entities: no, we didn't. But to save some time, I'll leave the comparisons and results for the later sections.

🔎 Tuning the detector

When people hear LLM-based PII detection, they often imagine the hardest part is prompt engineering. That wasn't our experience.

From an implementation perspective, the detector itself is almost boring. Given a transcript, we ask an LLM to identify sensitive entities and return structured output. The implementation fits comfortably on a single screen. The difficult part starts after the first successful inference.

How do we know the detector is actually good enough?

Unlike traditional software, there isn't a unit test that guarantees an LLM will never miss a password hidden inside a 30,000-token transcript. Every prompt change improves some cases while potentially hurting others. Without a disciplined evaluation process, "it feels better" quickly becomes the only metric, and that's not a place we wanted to be.

So instead of treating prompt engineering as trial and error, we built a feedback loop around it.

Figure 1: The tuning loop that kept prompt changes tied to benchmark results, instead of vibes.

Figure 1: The tuning loop that kept prompt changes tied to benchmark results, instead of vibes.

The tuning process came down to four ideas.

  1. A model is only as good as its benchmark

    Before tuning anything, we needed an evaluation dataset we could trust. We used an LLM to draft labels, then had human reviewers verify every entity. Each disagreement helped tighten the ground truth.

  2. Change one thing, measure everything

    We isolated variables like prompt design, chunking strategy, output structure, and speaker-label handling. Every change ran against the same benchmark, so regressions showed up immediately.

  3. Every improvement has to earn its place

    Higher overall accuracy did not always mean a safer detector. A version that caught more passwords might miss more phone numbers, so each iteration had to prove it was better than the previous checkpoint.

  4. Ship the strongest system, not the strongest model

    No single model won everywhere. Different passes were stronger for different entity types, so we combined the best ones into a union pipeline. That gave us better recall than relying on one detector.

🏭 The Pipeline in Production

⚙️ From Experiment to Production

One thing I learned from this project: the best experiment is not always the best production system.

During experimentation, our objective was simple: maximize recall. The strongest setup was a two-pass pipeline. The first pass acted as a general detector across all three sensitivity levels. The second pass revisited the transcript with a recall-focused model, looking for Level 2 and Level 3 entities the first pass was likely to miss. We then merged and deduplicated both outputs.

Figure 2: The experimental two-pass detector optimized for recall before production constraints entered the room.

Figure 2: The experimental two-pass detector optimized for recall before production constraints entered the room.

On the benchmark, it worked. The second pass consistently recovered sensitive entities that would otherwise slip through.

But experiments do not pay cloud bills. Production does.

In production, we could only run Claude Sonnet 4.6 on Databricks. A second LLM pass would nearly double inference cost, increase latency, complicate deployment, and require an additional recall model that the environment could not support.

So the question changed:

❌ which architecture gives the highest recall?
✅ which architecture gives the best trade-off between recall, latency, cost, and operational simplicity?

That led to a simpler answer. We did not ship the two-pass pipeline. Instead, we folded what it taught us back into production: remove Level 1 from PII extraction, keep the detector focused on entities that actually require masking, and preserve business context for downstream analysis.

Then, the experiment did not become production. It became the teacher for production.

Once the production shape was clearer, the next question was not just what the model found, but what the system should do with it.

🎭 Detection is Only Half the Story

Finding a suspicious span of text is not the same as deciding what to do with it.

After the detector returns an entity, the system still has to answer a few less model-shaped, more policy-shaped questions:

  • Is this actually PII, or just useful business context?

  • Is this a Money Forward employee name, or a customer name?

  • Should this value be masked, retained, or skipped?

  • If it appears ten times in the transcript, should every occurrence receive the same label?

Those decisions should not live inside the detector. They belong to the masking pipeline.

Figure 3: The production masking pipeline turns uncertain detections into consistent masking behavior.

Figure 3: The production masking pipeline turns uncertain detections into consistent masking behavior.

This separation became one of the more important architectural decisions in the project.

🕵️ The detector's job is to be suspicious: find candidate sensitive entities with high recall. 
The masking pipeline's job is to be deliberate: apply business rules, access policy, and consistency guarantees before anything is exposed downstream.

That distinction sounds small, but it made the system much easier to evolve. We can improve the detector without rewriting masking rules. We can change masking policy without reprompting the model. And when something goes wrong, we can ask a cleaner question:

Did the model miss the entity, or did the policy layer handle it incorrectly?

In practice, the masking stage loads detections, removes duplicates, identifies Money Forward staff, groups equivalent entities, assigns stable labels, and generates the final masked transcript.

The important part is that the final transcript is not just "LLM output with brackets around it." It is the result of a second layer that turns uncertain detections into consistent, auditable masking behavior.


📏 The Metrics

To better tune and record final results, we needed a way to compare detector versions.

The usual metrics still mattered: precision, recall, and F1. But for PII redaction, the most important lesson was simple:

⚖️ Not all mistakes are equally bad.
False positives remove context. False negatives leak data.

So recall mattered more than precision, especially for Level 2 and Level 3 entities. But precision still mattered. If the model masks too aggressively, the downstream AI receives a transcript that is technically safe but no longer useful.

For evaluation, a detection counted as correct only when it matched the ground-truth entity at the right sensitivity level.

 GT: PII at the correct levelGT: Not PII
Model: DetectedTrue Positive (TP) — the model found the entity and assigned the correct sensitivity levelFalse Positive (FP) — the model flagged something that should not be treated as PII
Model: Not detectedFalse Negative (FN) — the model missed real PII, or classified it at a lower level than the labelTrue Negative (TN) — neither the model nor the label treats it as PII

The "classified too low" part matters, too. If the label says PASSWORD is Level 3, but the model detects it as Level 2, we still treat that as a risky miss. Worse, if a Level 2 or Level 3 entity is classified as Level 1, it may not be masked at all. For evaluation, we mark these cases as false negatives for the correct sensitivity level.

We tracked the standard metrics:

MetricMeaningFormula
PrecisionOf detected entities, how many were correct?$P = TP / (TP + FP)$
RecallOf real PII entities, how many were detected?$R = TP / (TP + FN)$
F1Balance between precision and recall.$F1 = \frac{2 \times P \times R}{P + R}$

 

Then we added sensitivity-weighted metrics, because:

🔐 Missing a company name and missing a password should not carry the same penalty.

MetricFormula
Weighted Precision$WP = 0.10 \times P_{\text{lv1}} + 0.40 \times P_{\text{lv2}} + 0.50 \times P_{\text{lv3}}$
Weighted Recall$WR = 0.10 \times R_{\text{lv1}} + 0.40 \times R_{\text{lv2}} + 0.50 \times R_{\text{lv3}}$
Weighted F1$WF1 = \frac{2 \times WP \times WR}{WP + WR}$

 

After the metrics were in place, the question became simpler: did the detector catch the risky things often enough to be useful?


📊 The Results

After several tuning rounds, the LLM-based detector reached the best overall weighted recall: 0.99 (with human validation). More importantly, it kept misses for Level 2 and Level 3 entities low, avoided obvious business-context false positives, and handled the messy Japanese formats that rule-based systems tend to miss.

Figure 4: Final benchmark results after tuning and human validation.

Figure 4: Final benchmark results after tuning and human validation.

The strongest signal was recall. For PII redaction, we would rather review a few extra detections than silently miss a password, phone number, or private URL. The final version still produced some false positives, but most of them were acceptable because they removed low-value text rather than exposing sensitive data.

Figure 5: Additional result breakdown showing by level and type score

Figure 5: Additional result breakdown showing by level and type score

 

Figure 6: Entity-level behavior matters

Figure 6: Entity-level behavior matters

The charts show how well we detect PII, but not why one approach succeeds where another fails — which is what the next section unpacks.

🧪 Comparisons of different approaches

Before moving to the LLM-based detector, we also tested a more traditional approach: regex rules plus Japanese NER (Named Entity Recognition) using Presidio. It was fast and explainable, but the failure modes were exactly the kind of messy Japanese input we expected to see in real customer conversations.

CaseRegex + Presidio behaviorWhy it failsLLM behaviorTakeaway
Year-like number: 2024年から始まった新しいプラン⚠️ Detects 2024 as IDThe ID regex treats any 4+ digit sequence as an identifier, even when it is clearly a year.✅ Ignores itRegex sees digits. The LLM reads sentence role.
Money Forward product names: 経費精算と給与システムを導入⚠️ Tags product names as COMPANYspaCy maps some product-like terms to ORG, without knowing they are internal Money Forward products.✅ Ignores themDomain context matters. Product names are not customer companies.
Generic card brand mention: JCBやVisaなど主要クレジットカード会社⚠️ Detects JCB and Visa as sensitive valuesThe regex fires on brand names anywhere, even when the text is only a generic business statement.✅ Ignores themKeyword presence is not the same as PII.
Full postal address: 〒108-0023 東京都港区芝浦3-1-21 ... 21F⚠️ Extracts only 東京都港区spaCy catches the geographic entity, but misses postal code, street number, building, and floor.✅ Extracts the full addressFor masking, partial address detection is not enough.
Full-width IP address: 192.168.1.100❌ Misses itThe regex expects half-width periods (.), not full-width Japanese periods ().✅ Detects the IP addressByte-level patterns break on ordinary Japanese typing variants.
Phone number with : 090の1234の5678❌ Misses itThe phone regex allows hyphens and spaces, but not the Japanese connector .✅ Detects the phone numberSpoken Japanese formatting does not look like a standard regex format.
Password with : 仮パスワードはTempPass2025!です❌ Misses itThe password rule expects separators like : or =, but Japanese often uses the topic marker .✅ Detects the passwordThe dangerous part is not the separator character. It is the disclosure pattern.

The point is not that regex is useless. Regex is excellent when the surface form is stable: emails, standard phone numbers, UUIDs, and URLs. But customer text is not stable. It contains Japanese grammar, full-width characters, product names, shorthand, dictated phone numbers, and partial addresses.

That is where the LLM helped: not by being magical, but by using context when the surface pattern was ambiguous or slightly broken.

That still does not mean a third-party LLM is the final destination. It just gave us a strong place to start.


🔮 The Future

A third-party LLM is probably not the final answer to this problem.

It gives us a strong baseline today: high recall, reasonable precision, and enough flexibility to handle messy Japanese text. More importantly, it gives us a way to create human-validated training data from production-like examples.

The next step is a smaller, self-hosted PII detection model tuned for Money Forward's domain, policies, and Japanese customer conversations.


🧵 Final words

This project started with a very practical question: can we use production-like customer feedback safely enough to learn from it?

The answer was not a single model, a clever prompt, or a perfect regex. It was a pipeline: define what risk means, measure the detector honestly, separate detection from masking, and keep improving the boring parts until the system becomes trustworthy. 

PII redaction is not the most glamorous AI problem. Nobody puts "masked a password correctly" on an architecture diagram and gets applause. But it is one of those problems that decides whether the more exciting systems can exist at all.

If we want VoC data to become useful for product decisions, we first need a safe path for that data to move. This work is one small piece of that path.

As it turned out, cruel life had once again refused to fit neatly into a database schema. Fortunately, neither did our solution.

Thank you for reading!


📚 References

[1] Microsoft. (n.d.). Presidio [Computer software]. GitHub. https://github.com/microsoft/presidio

[2] Microsoft. (n.d.). What is personally identifiable information (PII) detection in Azure AI Language? Microsoft Learn. https://learn.microsoft.com/en-us/azure/ai-services/language-service/personally-identifiable-information/overview

[3] Google. (n.d.). Classification: Accuracy, precision, and recall. Machine Learning Crash Course. Google Developers. https://developers.google.com/machine-learning/crash-course/classification/accuracy-precision-recall


 

More like this

Creating PDFs using React JS
Oct 07, 2025

Creating PDFs using React JS

Một Team Ăn Ý Là Team Một Màu?
Nov 30, 2022

Một Team Ăn Ý Là Team Một Màu?