ovos-ddg-plugin

September 1, 2026 · View on GitHub

PyPI License Python

DuckDuckGo Instant Answers plugin for OpenVoiceOS.

Provides factual Q&A, structured infobox lookups, and image URLs via the DDG Instant Answers API. No API key required.

It implements the opm.agents.retrieval and opm.agents.toolbox entry points. Use it as a standalone Python library or as an OVOS agent plugin.


Installation

pip install ovos-ddg-plugin

Legacy users: the old ovos-ddg-solver-plugin package remains on PyPI. New code should import from ovos_ddg_plugin.


Standalone usage

Text answers

from ovos_ddg_plugin import DuckDuckGoRetrievalEngine

ddg = DuckDuckGoRetrievalEngine()

# Returns up to k (sentence, score) tuples, scored 0.9 → 0.8 → 0.7 …
results = ddg.query("who is Isaac Newton", lang="en-us", k=3)
for sentence, score in results:
    print(f"[{score:.1f}] {sentence}")
# [0.9] Sir Isaac Newton was an English polymath active as a mathematician …
# [0.8] He was a key figure in the Scientific Revolution …
# [0.7] His book Philosophiæ Naturalis Principia Mathematica …

# Infobox field queries return a single result at score 0.9
results = ddg.query("when was Stephen Hawking born", lang="en-us")
print(results)
# [('January 8, 1942', 0.9)]

Infobox

infobox, related = ddg.get_infobox("Stephen Hawking", lang="en-us")
from pprint import pprint
pprint(infobox)
# {'born': 'January 8, 1942',
#  'died': 'March 14, 2018',
#  'known_for': 'Hawking radiation, A Brief History of Time …',
#  'alma_mater': 'University of Oxford; University of Cambridge',
#  ...}
print(related[:3])
# ['A Brief History of Time', 'Cambridge University', 'General relativity']

Image URL

url = ddg.get_image("Eiffel Tower", lang="en-us")
print(url)
# https://duckduckgo.com/i/...jpg  (or None if DDG has no image)

Agent tools

DuckDuckGoToolbox exposes three tools for OVOS agent pipelines:

ToolDescription
search_duckduckgoBest text answer (infobox field or abstract sentence)
duckduckgo_infoboxFull structured infobox + related topics for an entity
duckduckgo_imageImage URL for a topic, or null
from ovos_ddg_plugin import DuckDuckGoToolbox, SearchDuckDuckGoArgs, DDGInfoboxArgs, DDGImageArgs

tb = DuckDuckGoToolbox()

answer = tb.search_ddg(SearchDuckDuckGoArgs(query="Marie Curie birthdate", lang="en-us"))
print(answer.result)
# November 7, 1867

box = tb.ddg_infobox(DDGInfoboxArgs(query="Marie Curie", lang="en-us"))
print(box.infobox.get("born"))
# November 7, 1867
print(box.related_topics[:2])
# ['Radioactivity', 'Nobel Prize in Physics']

img = tb.ddg_image(DDGImageArgs(query="Marie Curie", lang="en-us"))
print(img.url)
# https://duckduckgo.com/i/...jpg

Infobox intent matching

When query() receives a natural-language question, it runs it through a bank of Padacioso intent files to check whether the question targets a specific infobox field. If a match is found the engine fetches the infobox for the extracted entity and returns the exact field value at score 0.9, bypassing the abstract entirely.

How it works

  1. Intent detection_match_infobox_intents() calls Padacioso's calc_intents() (plural). It returns all candidate matches sorted by confidence, each as (intent_name, keyword, entity_type).

  2. Entity type — the slot name used in the intent file becomes the entity_type ("person", "movie", "place", "animal", etc.). This gives the keyword context for downstream use.

  3. Infobox-based disambiguation — when two intents tie (for example length and running_time both match "how long is Amazon"), query() walks the candidate list. It fetches the DDG infobox for the extracted keyword and returns the first candidate whose field is non-empty in the infobox. The infobox result decides the winning entity type, so query() does not need to know it ahead of time.

  4. Field aliases — DDG does not always name its keys the same as the intent. FIELD_ALIASES maps intent names to the list of DDG keys to try in order (for example alma_mater["education"], resting_place["resting_place", "burial", "burial_place"]).

  5. Fallback — if no candidate's field is present in the infobox, query() falls through to the full-text abstract.

Supported intents

79 intents across 10 locales. Entity-typed slots ({person}, {movie}, {place}, …) are used instead of a generic {keyword} wherever the context constrains the entity type.

Person / biography

IntentSample pattern
bornwhen was {person} born
diedwhen did {person} die
agehow old is {person}
age_at_deathhow old was {person} when they died
known_forwhat is {person} known for
father / mother / childrenwho is {person} father
spouse / partnerwho is {person} married to
nicknamewhat is {person} nickname
citizenshipwhat is {person} nationality
net_worthwhat is {person} net worth
heighthow tall is {person}
resting_placewhere is {person} buried
notable_workwhat is {person} most famous work
notable_awardswhat awards has {person} won

Person / career & academic

IntentSample pattern
occupationwhat does {person} do
fieldswhat field does {person} work in
institutionswhat institution does {person} work for
alma_materwhere did {person} go to university
educationwhere did {person} study
thesis{person} doctoral thesis
doctoral_studentswho were {person} doctoral students
movementwhat movement is {person} associated with
religionwhat religion is {person}
official_website{person} official website
years_activewhen was {person} active
political_partywhat party does {person} belong to
predecessor / successorwho came before / after {person}
coached_bywho coaches {person}
sportwhat sport does {person} play

Film / media

IntentSample pattern
directorwho directed {movie}
starringwho stars in {movie}
releasedwhen was {movie} released
running_timehow long is {movie}
budgetwhat was the budget of {movie}
box_officehow much did {movie} earn
produced_bywho produced {movie}
written_bywho wrote the screenplay for {movie}
composerswho composed the music for {movie}
original_titlewhat is the original title of {movie}
genrewhat genre is {movie}
languagewhat language is {book} in

Book / literature

IntentSample pattern
authorwho wrote {book}
publisherwho published {book}

Music

IntentSample pattern
labelwhat record label is {artist} on
memberswho are the members of {group}

Game / software

IntentSample pattern
developerwho developed {software}
platformswhat platforms is {software} on
licensewhat license is {software} under

Organisation / company

IntentSample pattern
foundedwhen was {organization} founded
founderswho founded {organization}
industrywhat industry is {company} in
ceowho is the CEO of {company}
ownerwho owns {keyword}
revenuewhat is {company} revenue
number_of_employeeshow many employees does {company} have
headquarterswhere is {company} headquartered
head_coachwho is the head coach of {team}
stadiumwhat stadium does {team} play in
championshipshow many championships has {team} won

Geography / place

IntentSample pattern
capitalwhat is the capital of {place}
countrywhat country is {place} in
locationwhere is {place} located
areawhat is the area of {place}
populationhow many people live in {place}
elevationhow high is {mountain}
lengthhow long is {place}

Buildings / structures

IntentSample pattern
openedwhen was {place} opened
designed_bywho designed {place}

Biology / nature

IntentSample pattern
scientific_namewhat is the scientific name of {animal}
habitatwhere does {animal} live
dietwhat does {animal} eat
conservation_statusis {animal} endangered

Generic

IntentSample pattern
other_nameswhat are {keyword} other names
inventorwho invented {keyword}

Supported locales

Intent files ship for 10 locales, each with 6–12 sample patterns per intent:

LocaleLanguage
en-USEnglish
ca-ESCatalan
da-DKDanish
de-DEGerman
es-ESSpanish
eu-ESBasque
fr-FRFrench
gl-ESGalician
it-ITItalian
pt-PTPortuguese

Matching is case-insensitive and possessive 's is stripped before matching so "Darwin's father" and "Darwin father" both work.

Adding or extending intent files

Intent files live in ovos_ddg_plugin/locale/<locale>/. Each file is named <intent>.intent and contains one Padacioso pattern per line:

who directed {movie}
{movie} director
who made {movie}

Slot names constrain the entity type and are used as the entity_type value returned by _match_infobox_intents(). Use specific types ({person}, {movie}, {place}, {animal}, {company}, {software}, {book}, {artist}, {group}, {organization}, {mountain}, {team}) instead of the generic {keyword} wherever the context restricts what the entity can be. Use {keyword} only for truly open-ended slots (e.g. inventor, owner, other_names).

If the DDG infobox key differs from the intent name, add an entry to FIELD_ALIASES in __init__.py:

FIELD_ALIASES["my_intent"] = ["ddg_key_1", "ddg_key_2"]

Supported locales (DDG API)

The engine maps any BCP-47 tag to the closest DDG locale code with langcodes. It formats infobox date fields (born, died) in the requested language using ovos-date-parser.


Configuration

All keys are optional and read from the OVOS plugin config block for ovos-ddg-plugin:

KeyDefaultDescription
keyword_extractor"ovos-rake-keyword-extractor"OPM keyword extractor plugin used when a direct query returns no result


License

Apache 2.0. See LICENSE.