Skip to content

Port 'LLM example retrieval' from machine.py - #642

Closed
claude[bot] wants to merge 1 commit into
masterfrom
claude/issue-641-20261112-1529
Closed

claude[bot] wants to merge 1 commit into
masterfrom
claude/issue-641-20261112-1529

Conversation

@claude

@claude claude Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Quick summary

Callers can now pick few-shot translation examples with a TF-IDF or a coverage retriever, ported from machine.py. Both are new types in SIL.Machine.Translation.Llm, with no new package dependency. Nothing existing is touched.

Where to look

  • TfidfIndex reimplements scikit-learn's TfidfVectorizer defaults (raw counts, smoothed idf, L2 norm) -- pinned by the ranking tests in TfidfExampleRetrieverTests.
  • CoverageExampleRetriever ports the anchor and greedy selection, with char_wb 2-5 grams -- pinned by CoverageExampleRetrieverTests, including the duplicate and exclude-index cases.

Deliberately not included

  • pyproject.toml, uv.lock and the llm extra: Python packaging only.
  • __init__.py and the sklearn availability check: no C# counterpart.

Validation

  • ./local_check.sh --agent-strict -- local_check: PASSED

Issue / porting context

Ports sillsdev/machine.py#412.


Decisions, and why
  • scikit-learn and numpy have no equivalent in this library, so TF-IDF is a small internal sparse implementation instead of a new dependency.
  • Subclasses call Fit() at the end of their own constructor. Python calls it from the base constructor, but in C# derived fields are not yet initialized at that point.
  • Ties are broken by corpus order. numpy's argsort and argpartition leave tie order unspecified, so results can differ from Python only among equal scores.
  • Example is a class with value equality, because the library does not use C# 9 records at netstandard2.0.
  • Python counts code points and C# counts UTF-16 units, so char n-grams of astral characters can differ.

Closes #641


This change is Reviewable

Add TF-IDF and coverage example retrievers under Translation.Llm.

Co-authored-by: Eli C. Lowry <83078660+Enkidu93@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@claude claude Bot mentioned this pull request Nov 12, 2026
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.33333% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.47%. Comparing base (0cbf85f) to head (0f60c7b).

Files with missing lines Patch % Lines
...achine/Translation/Llm/CoverageExampleRetriever.cs 96.15% 4 Missing and 2 partials ⚠️
...rc/SIL.Machine/Translation/Llm/ExampleRetriever.cs 70.58% 2 Missing and 3 partials ⚠️
src/SIL.Machine/Translation/Llm/Example.cs 75.00% 2 Missing and 2 partials ⚠️
...IL.Machine/Translation/Llm/TopKExampleRetriever.cs 89.47% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #642      +/-   ##
==========================================
+ Coverage   75.33%   75.47%   +0.14%     
==========================================
  Files         454      461       +7     
  Lines       38509    38809     +300     
  Branches     5335     5389      +54     
==========================================
+ Hits        29010    29293     +283     
- Misses       8364     8373       +9     
- Partials     1135     1143       +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Enkidu93 Enkidu93 closed this Nov 12, 2026
// Strip all punctuation-only tokens.
return _tokenizer
.Tokenize(text.ToLowerInvariant())
.Where(token => token.Any(char.IsLetterOrDigit))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: F1. Supplementary-plane scripts lose all their tokens. char.IsLetterOrDigit tests one UTF-16 unit, and a surrogate is never a letter, so an Adlam (U+1E900) corpus indexes nothing: TfidfIndex.Create returns null and Rank returns the first k examples for any query. machine.py ranks them, because str.isalnum tests code points. Use char.IsLetterOrDigit(token, i) over code points. Unverified: reasoned from the .NET contract, not run.

protected IReadOnlyList<Example> ExampleList => _examples;

/// <summary>
/// Builds the retrieval index. Each most-derived constructor must call this once its own state is set up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: F2. A subclass of the public, unsealed TfidfExampleRetriever or CoverageExampleRetriever cannot follow this contract. Their constructors already call Fit(), so an overriding Fit runs before the subclass constructor has assigned its fields, and calling Fit() again as documented builds the index twice. Seal the two concrete retrievers, or fit lazily on the first Rank. Sealing later would break callers. Unverified: follows from C# constructor order, not run.

if (k <= 0)
return Array.Empty<Example>();
if (excludeIndex.HasValue)
scores[excludeIndex.Value] = double.NegativeInfinity;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: F3. This writes into the array that ScoreExamples returned. A subclass that caches its scores then ranks the excluded example last on every later query. machine.py copies first (scores = scores.copy() in top_k_example_retriever.py). The built-in retrievers return a fresh array, so only subclasses are affected.

@claude

claude Bot commented Nov 12, 2026

Copy link
Copy Markdown
Contributor Author

Verdict: approve with fixes. This is a faithful port of sillsdev/machine.py#412, and the TF-IDF and coverage logic match the Python apart from the points below.

Most important: F1, src/SIL.Machine/Translation/Llm/TfidfExampleRetriever.cs:36. TF-IDF retrieval silently stops ranking for supplementary-plane scripts such as Adlam, because the punctuation filter tests UTF-16 units instead of code points.

Counts: 0 Critical, 2 Important (F1, F2), 1 Low (F3).

Ran: gh pr checks 642. Build on ubuntu-22.04 and windows-latest pass; Create NuGet package and claude-review are pending; Comment hygiene passes but is advisory. I compared the port with gh pr diff 412 -R sillsdev/machine.py.

Not verified: F1 and F2 are not reproduced. The sandbox refused a scratch build, so both rest on the .NET char and constructor-order contracts. The char_wb n-gram difference for astral characters is disclosed in the PR body and is not raised.

Public API: new public types in SIL.Machine.Translation.Llm (Example, ExampleRetriever, TopKExampleRetriever, TfidfExampleRetriever, CoverageExampleRetriever, ExampleRetrieverFactory, ExampleRetrievalMethod). No existing API, target framework, or package dependency changed. Parity with machine.py: the new contract, with the divergences F1 and F3.

Upstream in sillsdev/machine.py, not this PR: coverage_example_retriever.py returns [] when no source has content, while top_k_example_retriever.py returns the first k examples, so switching ExampleRetrievalMethod changes whether a prompt gets any examples.

Findings: F1 new, unverified; F2 new, unverified; F3 new.

Reviewed at 0f60c7b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Port 'LLM example retrieval'

2 participants