Skip to content

Python API reference

Key classes and functions in researcher_profiles. The core install (pydantic + pyyaml) provides the schema models, ResearcherProfile, and the OpenAlex parser. Other surfaces attach or import only when their extra is present.

SurfaceImportExtra
Schema models, ResearcherProfile, OpenAlex parserresearcher_profilescore
.build_index(), .search(), ProfileIndex, ProfileRegistryresearcher_profiles.embeddings, researcher_profiles.registry[embeddings]
.ask(), .review()attached on import when available[llm]
.innovate(), .riff()attached on import when available[llm]
RemoteResearcherProfile, .from_api(), rank_against()researcher_profiles.client[client]
SQL tables, create_all, rows_for_profileresearcher_profiles.db[sql]

Optional capability methods are attached to ResearcherProfile as a side effect of importing their module; on a core-only install the imports are guarded and the methods are simply absent.

The [llm] persona methods (ask, review, innovate, riff) additionally require a persona-ready profile at call time, not just the extra: a full/deep profile with non-empty expertise and soul (has_persona is True). Called on any other profile they raise PersonaUnavailableError.


researcher_profiles.ResearcherProfile — a profile loaded from a directory.

ResearcherProfile.from_files(path, *, eager=False, validate=False)
  • path — the profile directory.
  • eager — if True, read and validate every file immediately (surfaces ProfileLoadError up front). Default False loads files lazily on first access.
  • validate — if True, run the quality audit and raise ProfileValidationError on errors. Requires the external build package.
ResearcherProfile.from_api(url, *, slug=None, token=None, timeout=60.0, client=None)

Construct a RemoteResearcherProfile from a server URL. Requires the [client] extra. url may be a server root (with slug=...) or a slug-qualified /api/v1/profiles/<slug> URL. token falls back to RESEARCHER_PROFILES_TOKEN.

ResearcherProfile.list_remote(url, *, token=None, timeout=30.0, client=None)

Return a list of profile-summary dicts from a server.

All properties are lazily loaded and cached on first access.

PropertyTypeSource
slugstrDirectory name.
metadataProfileMetadataprofile.yaml.
configProfileConfig | Noneconfig.json.
levelstrDepth tier: one of lite, full, deep. Reads metadata.level, then config.level, defaulting to "full" when absent. A deep profile also carries supplied non-paper sources (grants/CV/web). See profile depth levels.
has_personaboolTrue only for a full/deep profile with non-empty expertise and soul; lite profiles are never persona-ready. Gates the persona methods.
name, orcid, affiliation, field, summarydelegate to metadata
expertisestrpersonality/expertise.md body (empty if absent).
soulstrpersonality/SOUL.md body (empty if absent).
paperslist[PaperRecord]sources/papers.yaml.
grantslist[GrantRecord]sources/grants.yaml (deep profiles); [] when the file is absent.
citationsAny | Nonesources/citations.json (parsed JSON).
summariesMapping[str, str]paper_id -> summary markdown, lazy.
pathPathResolved directory path.
MethodReturnsNotes
validate()AuditReportRuns quality contracts; requires the build package.
to_dict(*, include_summaries=False)dictJSON-ready. Includes summary_ids unless include_summaries=True. The level surfaces inside the nested metadata and config dicts (it is not a top-level key).
to_agent_seed()dictMinimal identity + expertise/soul bodies + paper count. Includes a top-level level.
save_papers()NoneWrite papers back to sources/papers.yaml.
set_paper_contaminated(paper_id, flag)boolToggle a paper’s contaminated flag; True if found.

Methods attached by the [embeddings] extra:

MethodReturns
build_index(*, force=False, backend=None)IndexReport
search(query, k=5, filter=None)list[SearchHit]
search_similar(source_type, source_id, chunk_index=0, k=5)list[SearchHit]

Methods attached by the [llm] extra: ask, review, innovate, riff — see How to use the persona methods.

  • ProfileError — base exception.
  • ProfileLoadError(path, message, original=None) — a file is malformed or unreadable. Carries .path and .original.
  • ProfileValidationError(report) — raised by from_files(validate=True) on a non-ok audit. Carries .report.
  • PersonaUnavailableError(slug) — subclass of ProfileError, raised by ask, review, innovate, and riff when has_persona is False (e.g. a lite profile, or one missing expertise/soul). Raised before any LLM call; carries .slug. Over HTTP the persona endpoints surface this as a 409.

researcher_profiles.ProfileRegistry — a collection of profiles rooted at a directory, with cross-profile ranking. Requires the [embeddings] extra (it needs numpy); it is None on a core-only install.

ProfileRegistry.from_directory(root, *, skip_unindexed=False)

Loads every immediate subdirectory containing a profile.yaml. Malformed profiles are skipped with a warning. With skip_unindexed=True, only profiles that have a built meta/embeddings.sqlite are included.

OperationResult
len(reg)Number of profiles.
iter(reg)Iterate profiles (sorted by slug).
reg[key]Look up by slug, or by ORCID. Raises KeyError if absent.
key in regMembership by slug or ORCID.
reg.slugslist[str] of slugs.
reg.profileslist[ResearcherProfile].
MethodReturnsNotes
rank_against(text, k=5, *, prefilter=10, normalize=True, require_topics=None, diversify=True, lambda_=0.5, topk_chunks=5)list[Match]Centroid prefilter + chunk re-rank + optional MMR.
diversify(matches, k=5, lambda_=0.5)list[Match]MMR diversification of ranked matches.
topics_index()dict[str, list[ResearcherProfile]]Topic label -> profiles.
cluster(k=None)dict[str, list[ResearcherProfile]]KMeans over centroids (needs scikit-learn).
coverage_report()list[dict]Per-profile index/topic/calibration status.
rebuild_all_indexes(force=False)dictRebuild every profile’s index.
centroidsnp.ndarrayCached, normalized per-profile centroid matrix.
invalidate_caches()NoneDrop cached centroids and topics.

researcher_profiles.RemoteResearcherProfile (via ResearcherProfile.from_api) — a subclass whose data comes from a remote API server. It overrides the file loaders and capability methods to call HTTP endpoints, so it behaves like a local profile: same properties, same method signatures. Requires the [client] extra.

  • Supports the context-manager protocol (with ... as p:) to close its HTTP client.
  • refresh() drops cached resources so the next access refetches.
  • build_index and search_similar raise NotImplementedError (indexing lives on the server); validate() raises NotImplementedError (no local directory).
from researcher_profiles.client import rank_against
rank_against(base_url, query, *, k=5, token=None, prefilter=10,
require_topics=None, diversify=True, lambda_=0.5,
topk_chunks=5, normalize=True, include_chunks=False,
timeout=60.0, client=None)

POSTs to /api/v1/match and returns the parsed matches list — dicts shaped like MatchResult ({slug, name, orcid, score, evidence}). Registry ranking is not profile-scoped, so this is a function, not a method.


researcher_profiles.openalex — pure record parsing, no HTTP. Available in core.

FunctionReturnsNotes
parse_work(raw)PaperRecord | NoneConvert one raw OpenAlex work dict to a PaperRecord.
decode_abstract_inverted_index(idx)strReconstruct an abstract from OpenAlex’s inverted-index format. Returns "" on empty or malformed input.
to_work_dict(raw)dictLite shape with a coauthors key.
to_normalized_dict(raw)dictLite shape with an authors key.
from researcher_profiles.openalex import parse_work, decode_abstract_inverted_index
record = parse_work(raw_work_dict) # PaperRecord
abstract = decode_abstract_inverted_index(raw_work_dict["abstract_inverted_index"])

researcher_profiles.embeddings — per-profile vector store. Requires the [embeddings] extra.

  • ProfileIndex(profile_dir, *, profile_config=None) — the sqlite-vec store at <profile>/meta/embeddings.sqlite.
    • build_index(*, force=False, backend=None) -> IndexReport
    • search(query, k=5, filter=None) -> list[SearchHit]
    • search_similar(source_type, source_id, chunk_index=0, k=5) -> list[SearchHit]
  • build_index(profile, *, force=False, backend=None) — module-level helper accepting a ResearcherProfile or a path.
  • SearchHit — dataclass: text, source_type, source_id, chunk_index, section, score, meta.
  • IndexReport — dataclass: added, updated, skipped, removed, backend_name, duration_s.
  • Exceptions: MissingEmbeddingBackendError, IndexBackendMismatchError, IndexNotBuiltError.

researcher_profiles.responses — return types for capability methods.

DataclassUsed byKey fields
AskResponseasktext, citations, model, usage, refused, refusal_reason, grounded
ReviewResponsereviewsame shape as AskResponse
Ideainnovatehypothesis, approach, rationale, related_works; .resolve_citations(profile)
Riffriffangle, text, related_work
CitationRefevidence refspaper_id, relevance, span
Citationcitefull citation record; .to_bibtex(), .to_csl()
Matchrank_againstprofile, score, evidence, .explain()
MatchEvidenceMatch.evidencetop_chunks, top_papers, overlapping_topics, centroid_score
Topictopicslabel, weight, paper_ids, chunk_ids
Scopescopelanding-page metadata

GenerativeParseError is raised by innovate / riff when the model fails to return valid JSON after one retry.

PersonaUnavailableError (in researcher_profiles.responses, a ProfileError subclass) is raised by all four persona methods when the profile has no synthesized persona; see ResearcherProfile exceptions.


Requires the [sql] extra. See How to load profiles into SQL.

Table class__tablename__Key columns
ProfileRowprofilesslug (PK), name, level (indexed, default "full"), orcid, affiliation, field, summary, JSON subfields/interests/artifacts/anchor
PaperRowpapersid (PK), profile_slug (FK), paper_id, title, year, journal, doi, author_position, is_corresponding, identity_verified, status
ExpertiseTopicRowexpertise_topicsid (PK), profile_slug (FK), topic
FunctionReturnsNotes
create_all(engine)NoneCreate all tables if absent.
rows_for_profile(slug, meta, papers)listProfile row + paper rows + expertise-topic rows.
ProfileRow.from_metadata(slug, meta)ProfileRow
PaperRow.from_paper(slug, paper)PaperRow