Files
mims-harvard__tooluniverse/docs/guide/cache_system.rst
Shanghua Gao 08246bcd4d docs: expand redirect map (15 more paper-cited legacy paths) (#211)
* docs: expand redirect map with 15 additional legacy paths

Adds redirects for paths uncovered in a second-pass audit against
the live published site. Each destination verified to return HTTP 200
on https://zitniklab.hms.harvard.edu/ToolUniverse/.

New /tutorials/ → /guide/, /tools/, /expand_tooluniverse/ redirects:
- make_your_data_searchable, make_your_data_agent_searchable,
  build_search_and_share_datastores → guide/make_your_data_agent_searchable
- skills → guide/skills_showcase
- tool_finder → guide/finding_tools
- overview → guide/index
- remote_tools → tools/remote_tools
- mcp_integration → expand_tooluniverse/remote_tools/mcp_integration

Top-level legacy pages (404 at root, now redirected):
- getting_started → guide/python_guide
- deployment, contributing, changelog → about/<page>
- faq → help/faq

Local build confirms all 22 redirect stubs generate and that every
destination file exists in the build output.

* docs: fix broken sphinx-tabs, dead toctrees, orphan pages, missing image

Build now produces 0 warnings/errors of these structural categories
(down from ~280 such issues):

- Re-enable sphinx_tabs.tabs extension (3.5.0 supports Sphinx 9.x);
  fixes 7 broken "Unknown directive type tabs" errors that made
  help/faq.html and help/troubleshooting.html render incomplete.
- Drop 25+ dead toctree entries in api/modules.rst and
  api/tooluniverse.rst that referenced per-module pages never
  generated by sphinx-apidoc; modules.rst now points at the existing
  comprehensive tooluniverse autodoc page.
- Wire 16 orphan pages into the master toctree so they're reachable
  from the navigation, including tooluniverse_case_study,
  visualization_tutorial, expert_feedback, literature_search_web_ui,
  euhealth, logging, openrouter, streaming, vllm, wechat_community,
  simbad_tools, the guide/index landing page, and the full
  expand_tooluniverse sub-tree.
- Remove three stale ":doc:" links to old/{quickstart,installation,
  getting_started} from sitemap.rst (those directories are excluded
  from the build) and mark sitemap.rst as :orphan: since it's a
  parallel nav surface by design.
- Mark MCP_TASKS_GUIDE.md as orphan and exclude the internal
  DOCUMENTATION_STRUCTURE.md meta-doc from the build.
- Add the missing tools/remote/ui.jpg referenced by the remote
  expert_feedback page (previously: broken image).

* docs: eliminate all 49 structural Sphinx ERRORs (broken tables, directives, headings)

Round-3 build cleanup. Builds now finish with 0 structural ERRORs of any
category (previous: 49 ERRORs spread across 20 files). Total
warnings/errors dropped from 280 → 114; the remaining 114 are all in
Python source-file docstrings (out of scope for a docs PR).

Categories fixed:

**Malformed tables** (8 files)

  Replace ASCII grid tables with mis-aligned pipes and inline-markdown
  pipe tables (which RST mis-parses as substitution references) with
  ``list-table`` directives that render correctly across all themes:

  - guide/literature_search_tools_tutorial.rst (two tables)
  - guide/cache_system.rst (env-var table whose first column overflowed)
  - guide/clinical_guidelines_tools.rst
  - guide/make_your_data_agent_searchable.rst
  - expand_tooluniverse/contributing/index.rst
  - expand_tooluniverse/contributing/remote_tools.rst
  - expand_tooluniverse/reference/index.rst

**list-table indentation** (3 files)

  Option lines and list items were indented with 1 space (only valid for
  3-space) so Sphinx silently dropped them and reported "exactly one
  bullet list expected":

  - guide/finding_tools.rst
  - guide/http_api.rst
  - guide/tools.rst

**Code-block separators** (5 files, ~30 directives)

  Add the required blank line between an introductory paragraph and a
  following ``.. code-block::``. Without it, Sphinx treated the directive
  as a continuation of the paragraph and emitted "Unexpected indentation"
  for every line of the code:

  - guide/literature_search_tools_tutorial.rst
  - expand_tooluniverse/quick_start.rst
  - expand_tooluniverse/contributing/local_tools.rst
  - expand_tooluniverse/contributing/remote_tools.rst
  - guide/make_your_data_agent_searchable.rst

**Heading-style + indentation** (3 files)

  - guide/building_ai_scientists/mcp_name_shortening.rst — strip stray
    leading spaces from two section titles + downgrade unknown
    ``.. critical::`` to ``.. important::``
  - about/deployment.rst — remove rogue ``=========`` underline below
    numbered-list items that mis-led the parser into skipping heading
    levels
  - guide/make_your_data_agent_searchable.rst — extend four "title
    underline too short" underlines + convert four markdown ``` fences
    to RST literal blocks

**Misc directive / target fixes**

  - guide/python_guide.rst — replace nonexistent ``.. success::`` with
    a tip-styled ``.. admonition::``
  - guide/euhealth_tools_tutorial.rst — indent ``.. note::`` body so it
    is no longer an empty admonition
  - help/troubleshooting.rst — same fix + remove rogue ``=========``
    line that was being read as a section overline
  - tools/cellosaurus_tools.rst — wrap ``CVCL_`` in literal backticks
    so the trailing underscore stops triggering missing-target lookups
  - expand_tooluniverse/index.rst — promote two leading-space bullet
    lists to standalone lists so the indentation is correct
  - expand_tooluniverse/reference/architecture.rst — switch ``.. graphviz::``
    (extension not installed) to a plain ``.. code-block:: text``;
    the embedded content was Mermaid pseudo-code anyway
  - guide/tools.rst — strip leading space on a section title

* docs: clear all remaining content warnings (lists, headings, refs, grids)

Round-4 build cleanup. Builds now finish with 0 structural ERRORs and 0
content WARNINGs; the only remaining ~11 warnings are pre-existing autodoc
infrastructure noise (duplicate object index entries from autosummary, and
the ghost_tool / medrxiv_tool modules that genuinely fail to import) — none
are in hand-written documentation.

Fixes in this commit:

**sphinx-design grids** (python_guide.rst)

  Re-indent two ``.. grid::`` blocks whose first card + options used 1-space
  indentation (Sphinx silently dropped them → "parent of grid-item should be
  grid-row"). Also fix a ``.. button-ref::`` whose content was indented 1
  space, producing a broken ``:any:`` cross-reference, and point it at the
  absolute ``/api/modules``.

**Numbered/bulleted sub-lists** (tool_composition, literature_search ×2,
architecture, literature_search_web_ui, make_your_data, agentic_tools,
finding_tools, euhealth)

  Insert the required blank line before nested lists and re-indent 1-space
  sub-bullets to align under their parent list marker. Clears ~80
  "list ends without a blank line; unexpected unindent" warnings.

**Title underlines** (logging, tool_caller, loading_tools, tool_composition,
euhealth, make_your_data, contributing/local_tools, reference/index,
remote_tools/tutorial, troubleshooting + bulk pass)

  Extend underlines shorter than their title text; strip stray leading spaces
  from section titles that Sphinx read as block quotes.

**Stray markdown in RST** (make_your_data, local_tools)

  Convert leftover ``###`` headings and ``` ``` fences to proper RST
  directives; remove a rogue ``------`` separator that was being parsed as a
  section underline.

**Duplicate autosectionlabel** (make_your_data)

  Rename the second "How it works" heading to "How sharing works".

**uniprot_tools** (JSON + generated RST)

  Rephrase the ``min_length`` / ``max_length`` descriptions so the open-ended
  range syntax no longer contains a bare ``*`` that RST read as an unterminated
  emphasis marker. Fixed in the JSON source so it survives doc regeneration.
2026-05-30 22:34:05 -07:00

225 lines
7.6 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Result Caching
==============
ToolUniverse ships with a two-tier result cache (in-memory + SQLite) so
expensive tool calls can be reused across runs without any extra setup. This
page explains what is cached, how to configure the cache, and how to inspect or
clear cached entries.
Why Use Caching?
----------------
**The Problem**: Scientific API calls can be slow and rate-limited:
- PubMed searches: 0.3-2 seconds per query
- UniProt lookups: 0.5-1 second per protein
- ChEMBL queries: 1-3 seconds per compound
- Rate limits: 3-10 requests/second (even with API keys)
**The Solution**: Cache results to avoid redundant API calls.
**Benefits**:
- **Speed**: Cached queries return in <1ms (vs 0.3-3 seconds)
- **Rate limit protection**: Avoid hitting API limits in batch workflows
- **Reproducibility**: Same query always returns same result (until source data changes)
- **Cost savings**: Fewer API calls = lower costs for paid services
- **Offline work**: Previously cached data available without internet
**Example Impact**: A 1000-protein batch analysis:
- Without cache: 1000 API calls × 0.5s = **8.3 minutes**
- With cache (90% hit rate): 100 API calls × 0.5s + 900 cached × 0.001s = **51 seconds**
- **10x faster** with caching!
Overview
--------
* **In-memory LRU** – Fastest path for repeated calls during a single session.
* **SQLite persistence** – Stores results in ``~/.tooluniverse/cache.sqlite`` by
default so results survive restarts (path configurable via environment
variables).
* **Per-tool fingerprints** – Each tool automatically fingerprints its source
code and parameter schema. When either changes, the cache key changes and old
entries are ignored.
Quick Start
-----------
Caching is enabled by default. You only need to pass ``use_cache=True`` when
calling a tool to reuse results:
.. code-block:: python
from tooluniverse import ToolUniverse
tu = ToolUniverse()
result = tu.run({
"name": "UniProt_get_entry_by_accession",
"arguments": {"accession": "P05067"},
}, use_cache=True)
# Second call reuses the cached payload
cached = tu.run({
"name": "UniProt_get_entry_by_accession",
"arguments": {"accession": "P05067"},
}, use_cache=True)
For model-training style workloads you can pass a list of calls to ``tu.run`` and
enable parallel workers. Each call still consults the cache before hitting the
network:
.. code-block:: python
import json
batch = [
{"name": "UniProt_get_entry_by_accession", "arguments": {"accession": acc}}
for acc in accession_list
]
messages = tu.run(batch, use_cache=True, max_workers=16)
# Extract tool payloads (assistant metadata is at messages[0])
payloads = [json.loads(msg["content"])["content"] for msg in messages[1:]]
Batch Execution Tips
--------------------
The batch runner performs several optimisations automatically:
* **Call deduplication** – identical ``name``/``arguments`` pairs are executed
once and fanned out to every requester. With ``use_cache=True`` the result is
also stored so later batches return instantly.
* **Per-tool concurrency** – each tool can advertise a
``batch_max_concurrency`` limit in its JSON/definition. During a batch run the
scheduler enforces that limit so that a slow or rate-limited API does not
seize all workers.
* **Configurable capacity** – increase
``TOOLUNIVERSE_CACHE_MEMORY_SIZE`` if you expect millions of cached entries.
For example, setting it to ``5000000`` keeps roughly five million results in
RAM (watch RSS usage and adjust according to payload size).
Configuration
-------------
Use environment variables to tune cache behavior before creating a
``ToolUniverse`` instance:
.. list-table::
:header-rows: 1
:widths: 40 60
* - Variable
- Description
* - ``TOOLUNIVERSE_CACHE_ENABLED``
- Turn caching on/off (``true`` by default)
* - ``TOOLUNIVERSE_CACHE_PERSIST``
- Enable SQLite persistence (``true`` by default)
* - ``TOOLUNIVERSE_CACHE_PATH``
- Full path to the SQLite file
* - ``TOOLUNIVERSE_CACHE_DIR``
- Directory for the SQLite file (default: ``~/.tooluniverse``) if ``CACHE_PATH`` unset
* - ``TOOLUNIVERSE_CACHE_MEMORY_SIZE``
- Max entries in the in-memory LRU (default 256)
* - ``TOOLUNIVERSE_CACHE_DEFAULT_TTL``
- Expiration in seconds (None disables TTL)
* - ``TOOLUNIVERSE_CACHE_SINGLEFLIGHT``
- Deduplicate concurrent misses (``true``)
* - ``TOOLUNIVERSE_CACHE_ASYNC_PERSIST``
- Write cache entries to SQLite on a background thread (``true``)
.. seealso::
:doc:`../reference/environment_variables` for complete environment variable reference including logging, LLM, and other configuration options.
Example configuration:
.. code-block:: python
import os
from tooluniverse import ToolUniverse
os.environ["TOOLUNIVERSE_CACHE_PATH"] = "/tmp/tooluniverse/cache.sqlite"
os.environ["TOOLUNIVERSE_CACHE_MEMORY_SIZE"] = "1024"
os.environ["TOOLUNIVERSE_CACHE_DEFAULT_TTL"] = "3600" # expire after 1 hour
tu = ToolUniverse()
Inspecting & Managing Cache
---------------------------
``ToolUniverse`` exposes helpers to understand and control the cache:
.. code-block:: python
stats = tu.get_cache_stats()
print(stats)
# Export persisted entries (returns an iterator of dicts)
entries = list(tu.dump_cache())
# Clear all cached data (both layers)
tu.clear_cache()
Versioning & TTL
----------------
Every tool inherits a default fingerprint (`get_cache_version`) that combines
its source code and parameter schema. You can override the hook in a custom tool
if you want finer control (for example, adding a manual ``STATIC_CACHE_VERSION``
counter). Tools can also override ``get_cache_ttl`` to specify per-result
expiration.
Asynchronous Persistence
------------------------
SQLite persistence is still enabled by default, but writes now happen on a
background thread so tool calls are not blocked on disk I/O. You can tune this
behaviour in two ways:
* Set ``TOOLUNIVERSE_CACHE_ASYNC_PERSIST=false`` before constructing
``ToolUniverse`` to fall back to fully synchronous writes (useful when you
need the result on disk immediately).
* Create a custom ``ResultCacheManager`` with a larger queue if you expect an
extremely write-heavy workload:
.. code-block:: python
from tooluniverse import ToolUniverse
from tooluniverse.cache.result_cache_manager import ResultCacheManager
tu = ToolUniverse()
tu.cache_manager.close() # replace the default manager
tu.cache_manager = ResultCacheManager(
memory_size=1_000_000,
async_queue_size=50_000,
)
Use ``tu.cache_manager.flush()`` if you need to wait for pending writes (for
example, before shutting down a worker). ``tu.get_cache_stats()`` now reports
``pending_writes`` so you can monitor the queue depth during batch jobs.
Best Practices
--------------
* Use caching for deterministic or idempotent operations (read-only API calls,
expensive computations, etc.).
* Set an explicit TTL when results are time-sensitive.
* Call ``tu.clear_cache()`` in long-running services if you need a fresh start.
* For hands-on demos, run ``examples/cache_usage_example.py`` (basic walkthrough)
or ``examples/cache_stress_test.py`` (randomized load test with summary stats).
Per-tool Concurrency
--------------------
Set ``batch_max_concurrency`` in a tool config to cap how many concurrent
executions of that tool run during batch jobs:
.. code-block:: json
{
"name": "SlowTool",
"type": "SlowTool",
"batch_max_concurrency": 2,
"parameter": {"type": "object", "properties": {}}
}