<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrea Volpini</title>
    <description>The latest articles on DEV Community by Andrea Volpini (@cyberandy).</description>
    <link>https://dev.to/cyberandy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2192151%2F0cb0dce1-6e7f-4631-ad9d-0d285f362893.png</url>
      <title>DEV Community: Andrea Volpini</title>
      <link>https://dev.to/cyberandy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cyberandy"/>
    <language>en</language>
    <item>
      <title>Which Apple? Giving an agent grounded identities in five minutes</title>
      <dc:creator>Andrea Volpini</dc:creator>
      <pubDate>Sun, 25 Oct 2026 20:05:04 +0000</pubDate>
      <link>https://dev.to/cyberandy/which-apple-giving-an-agent-grounded-identities-in-five-minutes-2gpb</link>
      <guid>https://dev.to/cyberandy/which-apple-giving-an-agent-grounded-identities-in-five-minutes-2gpb</guid>
      <description>&lt;p&gt;A jaguar swam through the Amazon rainforest. In one of our demos, a&lt;br&gt;
search-based agent files that story under automotive news — because the&lt;br&gt;
first hit for "jaguar" is Jaguar Cars. Nothing crashes. The wrong fact&lt;br&gt;
just moves into the graph, the markup and the agent's memory, and every&lt;br&gt;
system downstream repeats it.&lt;/p&gt;

&lt;p&gt;I'm Andrea, co-founder of WordLift. We have spent years linking text to&lt;br&gt;
knowledge graphs, and that one decision — &lt;em&gt;which thing is this?&lt;/em&gt; — is&lt;br&gt;
where it goes wrong. So we made it an API call. It's called resolve(),&lt;br&gt;
and this is the five-minute version of using it.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/7frMh_aMfrw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — try it with no key.&lt;/strong&gt; There is an open demo endpoint, 600&lt;br&gt;
characters, 10 calls a minute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://resolve.wordlift.io/demo/resolve &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"text": "Apple opened a new store in Rome.", "language": "en"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get Apple → Q312 (Apple Inc.) and Rome → Q220, each with a score.&lt;br&gt;
Now try an invented company name. You should get &lt;code&gt;unresolved&lt;/code&gt;, with a&lt;br&gt;
reason, instead of a plausible-looking link. That refusal is the part I&lt;br&gt;
care about most: a resolver that cannot say "I don't know" will&lt;br&gt;
eventually hand your agent a confident wrong answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — a free key.&lt;/strong&gt; Request one at wordlift.io/resolve/#access:&lt;br&gt;
name, email, use case, it arrives by email. 1,000 requests a month; past&lt;br&gt;
that you get a 429, nothing is charged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — the open pipeline.&lt;/strong&gt; The pipeline and clients are Apache-2.0&lt;br&gt;
(github.com/wordlift/content-analysis). The hosted decision engine is&lt;br&gt;
ours and stays closed — that's the business model, stated plainly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"resolve-pipeline @ git+https://github.com/wordlift/content-analysis.git"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;WL_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
python &lt;span class="nt"&gt;-m&lt;/span&gt; resolve_pipeline &lt;span class="s2"&gt;"Apple opened a new store in Rome."&lt;/span&gt; &lt;span class="nt"&gt;--language&lt;/span&gt; en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apple    resolved    Q312    Apple Inc.    score 0.99
Rome     resolved    Q220    Rome          score 0.98
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4 — resolve against your own world.&lt;/strong&gt; Wikidata is the default,&lt;br&gt;
not the limit. Send your own vocabulary (up to 5,000 entities) and every&lt;br&gt;
answer is one of your ids, or &lt;code&gt;unresolved&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;resolve_pipeline&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WordLiftResolver&lt;/span&gt;

&lt;span class="n"&gt;resolver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WordLiftResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://acme.example/kg/acme&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acme&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aliases&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACME Corp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;types&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Organization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A name that matches none of yours returns &lt;code&gt;no_candidates&lt;/code&gt;. A match whose&lt;br&gt;
context does not fit returns &lt;code&gt;low_relevance&lt;/code&gt;. Same gate as Wikidata mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — handle the "no" in your agent loop.&lt;/strong&gt; Treat &lt;code&gt;unresolved&lt;/code&gt; as a&lt;br&gt;
signal: ask a clarifying question, skip the action, flag it for review.&lt;br&gt;
Never replace it with a guess, and never treat a transport error as an&lt;br&gt;
unresolved — they are different problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The numbers, including where we lose.&lt;/strong&gt; We ran resolve() on VoxEL, a&lt;br&gt;
public news benchmark (ISWC 2018; 1,005 mentions, five languages) that&lt;br&gt;
nobody tuned on. With the mentions given, F1 is 91.8 and it makes 37&lt;br&gt;
wrong links; a first-hit Wikidata lookup makes 238. End to end, where&lt;br&gt;
each system finds its own mentions, Google Cloud NL is ahead, 89.9 to&lt;br&gt;
our 87.0 — it detects more mentions than we do, mostly short second&lt;br&gt;
references. On our own development sets the first-hit lookup got 61 of&lt;br&gt;
351 names wrong; those sets are ours, so read them as favourable to us.&lt;br&gt;
The harness and the raw results are in the repo (&lt;code&gt;benchmarks/2026-10-19&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;tools/compare.py&lt;/code&gt;) if you want to rerun any of it. Full write-up:&lt;br&gt;
wordlift.io/blog/en/entity-linking-apis-compared/&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it breaks.&lt;/strong&gt; People named by surname alone (the "Jordan scored&lt;br&gt;
45 points" problem — pass the type and it gets Michael Jordan, don't and&lt;br&gt;
it may not). Japanese and Chinese trail Google. A stricter NIL decision&lt;br&gt;
layer exists but has not passed its own published gates in any language,&lt;br&gt;
so it is not in production and I am not going to pretend otherwise.&lt;/p&gt;

&lt;p&gt;There is an experimental MCP adapter in the repo (&lt;code&gt;clients/mcp&lt;/code&gt;) that&lt;br&gt;
exposes &lt;code&gt;resolve_text&lt;/code&gt; as a tool, and an llms.txt at&lt;br&gt;
resolve.wordlift.io/llms.txt written for agents.&lt;/p&gt;

&lt;p&gt;If you try it, the useful feedback is not "nice" — it is the sentence&lt;br&gt;
where it picks the wrong entity, or refuses one it should have taken.&lt;br&gt;
Those cases are what improve a resolution engine.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>nlp</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
