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.
Capability by extra
Section titled “Capability by extra”| Surface | Import | Extra |
|---|---|---|
Schema models, ResearcherProfile, OpenAlex parser | researcher_profiles | core |
.build_index(), .search(), ProfileIndex, ProfileRegistry | researcher_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_profile | researcher_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.
ResearcherProfile
Section titled “ResearcherProfile”researcher_profiles.ResearcherProfile — a profile loaded from a directory.
Construction
Section titled “Construction”ResearcherProfile.from_files(path, *, eager=False, validate=False)path— the profile directory.eager— ifTrue, read and validate every file immediately (surfacesProfileLoadErrorup front). DefaultFalseloads files lazily on first access.validate— ifTrue, run the quality audit and raiseProfileValidationErroron 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.
Properties
Section titled “Properties”All properties are lazily loaded and cached on first access.
| Property | Type | Source |
|---|---|---|
slug | str | Directory name. |
metadata | ProfileMetadata | profile.yaml. |
config | ProfileConfig | None | config.json. |
level | str | Depth 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_persona | bool | True 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, summary | delegate to metadata | |
expertise | str | personality/expertise.md body (empty if absent). |
soul | str | personality/SOUL.md body (empty if absent). |
papers | list[PaperRecord] | sources/papers.yaml. |
grants | list[GrantRecord] | sources/grants.yaml (deep profiles); [] when the file is absent. |
citations | Any | None | sources/citations.json (parsed JSON). |
summaries | Mapping[str, str] | paper_id -> summary markdown, lazy. |
path | Path | Resolved directory path. |
Methods
Section titled “Methods”| Method | Returns | Notes |
|---|---|---|
validate() | AuditReport | Runs quality contracts; requires the build package. |
to_dict(*, include_summaries=False) | dict | JSON-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() | dict | Minimal identity + expertise/soul bodies + paper count. Includes a top-level level. |
save_papers() | None | Write papers back to sources/papers.yaml. |
set_paper_contaminated(paper_id, flag) | bool | Toggle a paper’s contaminated flag; True if found. |
Methods attached by the [embeddings] extra:
| Method | Returns |
|---|---|
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.
Exceptions
Section titled “Exceptions”ProfileError— base exception.ProfileLoadError(path, message, original=None)— a file is malformed or unreadable. Carries.pathand.original.ProfileValidationError(report)— raised byfrom_files(validate=True)on a non-ok audit. Carries.report.PersonaUnavailableError(slug)— subclass ofProfileError, raised byask,review,innovate, andriffwhenhas_personaisFalse(e.g. aliteprofile, or one missingexpertise/soul). Raised before any LLM call; carries.slug. Over HTTP the persona endpoints surface this as a409.
ProfileRegistry
Section titled “ProfileRegistry”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.
Construction
Section titled “Construction”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.
Lookup
Section titled “Lookup”| Operation | Result |
|---|---|
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 reg | Membership by slug or ORCID. |
reg.slugs | list[str] of slugs. |
reg.profiles | list[ResearcherProfile]. |
Ranking and analysis
Section titled “Ranking and analysis”| Method | Returns | Notes |
|---|---|---|
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) | dict | Rebuild every profile’s index. |
centroids | np.ndarray | Cached, normalized per-profile centroid matrix. |
invalidate_caches() | None | Drop cached centroids and topics. |
RemoteResearcherProfile
Section titled “RemoteResearcherProfile”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_indexandsearch_similarraiseNotImplementedError(indexing lives on the server);validate()raisesNotImplementedError(no local directory).
rank_against (module-level)
Section titled “rank_against (module-level)”from researcher_profiles.client import rank_againstrank_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.
OpenAlex parser
Section titled “OpenAlex parser”researcher_profiles.openalex — pure record parsing, no HTTP. Available in core.
| Function | Returns | Notes |
|---|---|---|
parse_work(raw) | PaperRecord | None | Convert one raw OpenAlex work dict to a PaperRecord. |
decode_abstract_inverted_index(idx) | str | Reconstruct an abstract from OpenAlex’s inverted-index format. Returns "" on empty or malformed input. |
to_work_dict(raw) | dict | Lite shape with a coauthors key. |
to_normalized_dict(raw) | dict | Lite shape with an authors key. |
from researcher_profiles.openalex import parse_work, decode_abstract_inverted_index
record = parse_work(raw_work_dict) # PaperRecordabstract = decode_abstract_inverted_index(raw_work_dict["abstract_inverted_index"])Embeddings store
Section titled “Embeddings store”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)->IndexReportsearch(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 aResearcherProfileor 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.
Response dataclasses
Section titled “Response dataclasses”researcher_profiles.responses — return types for capability methods.
| Dataclass | Used by | Key fields |
|---|---|---|
AskResponse | ask | text, citations, model, usage, refused, refusal_reason, grounded |
ReviewResponse | review | same shape as AskResponse |
Idea | innovate | hypothesis, approach, rationale, related_works; .resolve_citations(profile) |
Riff | riff | angle, text, related_work |
CitationRef | evidence refs | paper_id, relevance, span |
Citation | cite | full citation record; .to_bibtex(), .to_csl() |
Match | rank_against | profile, score, evidence, .explain() |
MatchEvidence | Match.evidence | top_chunks, top_papers, overlapping_topics, centroid_score |
Topic | topics | label, weight, paper_ids, chunk_ids |
Scope | scope | landing-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.
SQL layer (researcher_profiles.db)
Section titled “SQL layer (researcher_profiles.db)”Requires the [sql] extra. See How to load profiles into SQL.
| Table class | __tablename__ | Key columns |
|---|---|---|
ProfileRow | profiles | slug (PK), name, level (indexed, default "full"), orcid, affiliation, field, summary, JSON subfields/interests/artifacts/anchor |
PaperRow | papers | id (PK), profile_slug (FK), paper_id, title, year, journal, doi, author_position, is_corresponding, identity_verified, status |
ExpertiseTopicRow | expertise_topics | id (PK), profile_slug (FK), topic |
| Function | Returns | Notes |
|---|---|---|
create_all(engine) | None | Create all tables if absent. |
rows_for_profile(slug, meta, papers) | list | Profile row + paper rows + expertise-topic rows. |
ProfileRow.from_metadata(slug, meta) | ProfileRow | |
PaperRow.from_paper(slug, paper) | PaperRow |