feat(mcp): the bundle's map, and a working method that reads it first
C5. `bundlemap.build_map` lists a bundle in its own words: one line per source document -- its name, then the titles of its concepts in document order -- and documents whose names differ only in their numbers (a changelog per release, a note per week) as ONE line: the name with every number as `#`, the count, the first and last by natural order, and the titles across the series that are words. `SERIES_MIN` = 5, at most `TITLES_PER_LINE` = 24 titles a line, the lines capped at `MAP_MAX_BYTES` = 48 000 together with `lines_truncated` counting the rest. Derived on every call, never stored. The card (`okf card`, `okf_describe`) carries it as `map` and no longer carries `source_files`: that list named every document a second time with no series collapsed, a quarter of the reply on a large bundle, for names the map already carries. Chose removal over keeping both because the describe reply has to fit a client's tool-reply limit and the map says more. The working method now reads: take the map first (`okf card`, or `okf_describe`), write two to four sub-questions in its words, and send them in ONE call (`--question` repeated, or `okf_ask` `questions`). Changed in the skill template, the generated `skills/okf-consume`, and the server instructions (held under the 2 KB a client keeps). The regeneration recipe for `skills/okf-consume` gains `--for-bundle`: since v1.1 the generator writes the generic skill by default, so the recipe as published produced the other file. A test holds a four-sub-question `okf_ask` over concepts far over the passage size under 50 000 bytes of reply text (25 000 tokens at a pessimistic two bytes a token). The real-collection measurements are kept in local state. Suite on a clean tree after `git add`: 2429 passed, 2 skipped, 4 xfailed. ruff, ruff format, mypy --strict clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f7cd84c5e6
commit
da6faf8776
9 changed files with 450 additions and 64 deletions
|
|
@ -84,22 +84,21 @@ CLIENT_TRUNCATION_BYTES = 2048
|
|||
#: under the cap by a test, with a control so the assertion is a measurement.
|
||||
SERVER_INSTRUCTIONS = (
|
||||
"Bundles are read-only and no call here runs a model.\n\n"
|
||||
"HOW TO USE THIS SERVER. Read the bundle's map first with `okf_describe`, "
|
||||
"then put the question into the bundle's own words -- its documents may be "
|
||||
"HOW TO USE THIS SERVER. Read the bundle's `map` first with `okf_describe`: "
|
||||
"one line per document with its section titles -- the bundle's own words. "
|
||||
"Then write two to four sub-questions in THOSE words (its documents may be "
|
||||
"written in another language than the question, and the ranking matches "
|
||||
"words. Split a broad question into two to four sub-questions and call "
|
||||
"`okf_ask` once per sub-question. After each call read BOTH what came back "
|
||||
"and what lay just outside the cut: `withheld.nearest` names the "
|
||||
"best-ranked concepts that missed, with their titles. If one of them is "
|
||||
"what you wanted, that is a fact about the WORDS, not a closed door -- ask "
|
||||
"again with that concept's own words, or fetch it by name with "
|
||||
"`okf_fetch`. Several calls are normal and expected; there is no limit and "
|
||||
"no penalty. When `coverage.weak` is true, rephrase in the bundle's words, "
|
||||
"and if it stays weak say the bundle does not cover the question. Then "
|
||||
"write ONE answer, ordered by sub-question, in the "
|
||||
"questioner's language and in ordinary prose, citing the document and the "
|
||||
"section (and the bundle, when you read more than one). Say plainly what "
|
||||
"the bundles do not cover.\n\n"
|
||||
"words) and send them in ONE call: `okf_ask` with `questions`. Each excerpt "
|
||||
"names the sub-questions it answered. Read BOTH what came back and what lay "
|
||||
"just outside the cut: `withheld.nearest` names the best-ranked concepts "
|
||||
"that missed, with their titles. If one of them is what you wanted, that is "
|
||||
"a fact about the WORDS, not a closed door -- ask again with that concept's "
|
||||
"own words, or fetch it by name with `okf_fetch`. Asking again is normal and "
|
||||
"expected. When `coverage.weak` is true, rephrase in the bundle's words, and "
|
||||
"if it stays weak say the bundle does not cover the question. Then write ONE "
|
||||
"answer, ordered by sub-question, in the questioner's language and in "
|
||||
"ordinary prose, citing the document and the section (and the bundle, when "
|
||||
"you read more than one). Say plainly what the bundles do not cover.\n\n"
|
||||
"Every excerpt carries the bundle id and concept id a claim must be "
|
||||
"attributed to; the payload states what it withheld and why."
|
||||
)
|
||||
|
|
@ -310,21 +309,11 @@ def card(bundle_root: Path, *, profile: BundleProfile, concept_sample: int = 50)
|
|||
card would also be one more artefact that can be stale, which is the defect
|
||||
it was meant to remove.
|
||||
"""
|
||||
from . import bundlemap
|
||||
from . import skill as okf_skill
|
||||
|
||||
bundle_id = okf_consume.root_bundle_id_of(bundle_root, profile=profile)
|
||||
concepts = okf_consume.link_parents(
|
||||
[
|
||||
okf_consume.read_concept(
|
||||
okf_consume.read_path_in_bundle(
|
||||
bundle_root, f"{concept_id}{profile.paths.concept_suffix}"
|
||||
),
|
||||
bundle_root=bundle_root,
|
||||
root_bundle_id=bundle_id,
|
||||
)
|
||||
for concept_id in okf_consume.enumerate_concepts(bundle_root, profile=profile)
|
||||
]
|
||||
)
|
||||
concepts = bundlemap.read_concepts(bundle_root, profile=profile)
|
||||
counts = okf_skill.field_counts(concepts)
|
||||
return {
|
||||
"bundle_id": bundle_id,
|
||||
|
|
@ -334,15 +323,17 @@ def card(bundle_root: Path, *, profile: BundleProfile, concept_sample: int = 50)
|
|||
"concept_count": len(concepts),
|
||||
"concepts": [concept.concept_id for concept in concepts[:concept_sample]],
|
||||
"concepts_truncated": len(concepts) > concept_sample,
|
||||
"source_files": sorted(
|
||||
{concept.source_file for concept in concepts if concept.source_file}
|
||||
),
|
||||
"conditional_fields": {
|
||||
field: counts.get(field, 0) for field in okf_skill.CONDITIONAL_FIELDS
|
||||
},
|
||||
"whole_bundle_bytes": okf_skill.whole_bundle_cost(concepts),
|
||||
"budget_unit": okf_consume.BUDGET_UNIT,
|
||||
"default_limit": okf_consume.DEFAULT_LIMIT,
|
||||
# v1.1 C5: the bundle's own words, to write sub-questions in. It
|
||||
# replaces the flat `source_files` list, which named every document a
|
||||
# second time with no series collapsed -- a quarter of the reply on a
|
||||
# large bundle, for names the map already carries.
|
||||
"map": bundlemap.build_map(concepts),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -379,7 +370,7 @@ def tools(surface: Surface) -> tuple[Tool, ...]:
|
|||
Tool(
|
||||
"okf_list",
|
||||
"Every OKF bundle this server can currently reach, with its content "
|
||||
"identity and concept count. Re-read from disk on every call, so a "
|
||||
"identity and concept count; `okf_describe` gives each one's map. Re-read from disk on every call, so a "
|
||||
"bundle added, removed or rebuilt since the last call is reflected "
|
||||
"without restarting anything. Exists because a client that cannot "
|
||||
"discover bundles must be told their names out of band, which is the "
|
||||
|
|
@ -391,9 +382,10 @@ def tools(surface: Surface) -> tuple[Tool, ...]:
|
|||
Tool(
|
||||
"okf_describe",
|
||||
"What one bundle is: its id, its content identity, how many concepts "
|
||||
"it holds, which source documents it was built from, and which "
|
||||
"conditionally-written fields are present on how many concepts. "
|
||||
"Read it BEFORE asking, so the question can be put into the "
|
||||
"it holds, which conditionally-written fields are present on how "
|
||||
"many concepts, and its `map` -- one line per source document with "
|
||||
"its section titles, a series of like-named documents as one line. "
|
||||
"Read it BEFORE asking, so the sub-questions can be put into the "
|
||||
"bundle's own words. On a multi-bundle server, omitting `bundle_id` "
|
||||
"describes every served bundle, as `okf_ask` does. "
|
||||
"Exists because an answer must be attributable -- a claim from a "
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue