diff --git a/packages/claude/dist/packages.json b/packages/claude/dist/packages.json index 73249ad..f8a2113 100644 --- a/packages/claude/dist/packages.json +++ b/packages/claude/dist/packages.json @@ -3,12 +3,12 @@ { "archive": "zpwiki-rag.zip", "name": "rag", - "sha256": "3eb3a59827b3eda400c7a5cd5dc3909831d5343903b159a71d843fb712970b52" + "sha256": "c710593a12c030c716ad4ba620e13182c35a86ded339e6180c235941398e2911" }, { "archive": "zpwiki-graphrag.zip", "name": "graphrag", - "sha256": "99cf35d9050b5942b1d33744c49ade436e8c4b811d536257a2d0b090700dd3e0" + "sha256": "160f58d665aa5b156589731427eeb4113a4eb0020291d338a2ec35a0c81b7696" } ], "schema_version": 1 diff --git a/packages/claude/dist/zpwiki-graphrag.zip b/packages/claude/dist/zpwiki-graphrag.zip index 165b64f..16846de 100644 Binary files a/packages/claude/dist/zpwiki-graphrag.zip and b/packages/claude/dist/zpwiki-graphrag.zip differ diff --git a/packages/claude/dist/zpwiki-rag.zip b/packages/claude/dist/zpwiki-rag.zip index cd84b26..af87f36 100644 Binary files a/packages/claude/dist/zpwiki-rag.zip and b/packages/claude/dist/zpwiki-rag.zip differ diff --git a/packages/claude/graphrag/SKILL.md b/packages/claude/graphrag/SKILL.md index 64f2756..fc7fb8c 100644 --- a/packages/claude/graphrag/SKILL.md +++ b/packages/claude/graphrag/SKILL.md @@ -39,7 +39,13 @@ that subject is unambiguous in the immediately preceding exchange. Preserve the and every remaining condition. Otherwise ask for the name. Document authorship does not establish supervision. -1. With a ZP Wiki MCP connector, call `zpwiki_graphrag` with the complete question. +1. With a ZP Wiki MCP connector, load its current tool definition before calling it. + When tools are deferred, use the host's tool search (for example `tool_search`) to find + `zpwiki_graphrag`. Use the exact loaded identifier and schema; never guess a tool name or arguments. + Pass the complete question as `query`, for example `{"query":"the complete question"}`. + The optional `limit` is an integer. Do not send an argument named `question`. + If the host reports that the tool has not been loaded, discover it first and retry the same + question with the loaded schema. This is a tool-discovery error, not evidence of a DNS failure. 2. Otherwise, with securely configured backend access in a Python execution environment, run `python scripts/query.py "the complete question"` relative to this directory. Keep every condition in the same query (maximum 500 characters): names, work type, diff --git a/packages/claude/mcp_server.py b/packages/claude/mcp_server.py index 05ae32f..740d2a5 100644 --- a/packages/claude/mcp_server.py +++ b/packages/claude/mcp_server.py @@ -20,6 +20,10 @@ from starlette.responses import JSONResponse # Host-model rules belong to MCP initialization and Skills, not tool-result data. QUESTION_POLICY = ( + 'Load each tool definition using the host tool search when required before calling it. ' + 'Use the exact loaded tool identifier and schema. For zpwiki_rag and zpwiki_graphrag, ' + 'the question belongs in the query argument, never an argument named question. ' + 'A tool-not-loaded or invalid-argument error is a host invocation error, not a DNS failure. ' 'For a follow-up omitting a person or supervisor, inherit the subject only from an unambiguous ' 'immediately preceding exchange and reconstruct one full question with the new year and all constraints. ' 'Otherwise ask for the name. Never infer supervision from document authorship. ' @@ -75,6 +79,7 @@ def tool_result(result): async def zpwiki_rag(query: str, limit: int = 5) -> CallToolResult: """Verified ZP Wiki text answer. Keep the full question, names, work type and year. + Pass the full question in query, not question. Optional limit is an integer. Present the accepted answer with its citations; add no guesses or history commentary. """ return tool_result(answer_for_presentation(await asyncio.to_thread(query_backend, query, 'rag', limit=limit))) @@ -83,6 +88,7 @@ async def zpwiki_rag(query: str, limit: int = 5) -> CallToolResult: async def zpwiki_graphrag(query: str, limit: int = 5) -> CallToolResult: """Verified graph answer: relationships, related topics, progress, dated outcomes, years and counts in ZP Wiki. + Pass the full question in query, not question. Optional limit is an integer. Present the accepted answer with its citations; no_answer means no supporting evidence. """ return tool_result(answer_for_presentation(await asyncio.to_thread(query_backend, query, 'graphrag', limit=limit))) diff --git a/packages/claude/rag/SKILL.md b/packages/claude/rag/SKILL.md index 1d05426..83b38b6 100644 --- a/packages/claude/rag/SKILL.md +++ b/packages/claude/rag/SKILL.md @@ -37,7 +37,13 @@ is unclear, ask for the name; never send a subjectless search or infer supervisi For multiple explicit commands in one message, handle each question separately in order; keep all conditions belonging to each question together. -1. If a ZP Wiki MCP connector is available, call `zpwiki_rag` with the complete question. +1. If a ZP Wiki MCP connector is available, load its current tool definition before calling it. + When tools are deferred, use the host's tool search (for example `tool_search`) to find + `zpwiki_rag`. Use the exact loaded identifier and schema; never guess a tool name or arguments. + Pass the complete question as `query`, for example `{"query":"the complete question"}`. + The optional `limit` is an integer. Do not send an argument named `question`. + If the host reports that the tool has not been loaded, discover it first and retry the same + question with the loaded schema. This is a tool-discovery error, not evidence of a DNS failure. 2. Otherwise, in a Python execution environment with securely configured backend access, run `python scripts/query.py "the complete question"` relative to this skill's directory. The question limit is 500 characters. Preserve names, work type, year and all constraints. diff --git a/test/test_academic.py b/test/test_academic.py index c8aa22d..c01b49a 100644 --- a/test/test_academic.py +++ b/test/test_academic.py @@ -164,3 +164,11 @@ def test_academic_intents_use_the_shared_backend(question,mode): def test_completed_subtasks_are_not_routed_to_degree_completion(): assert academic_intent('Aké experimenty dokončil Ján Holp?') is None assert academic_intent('Ktoré úlohy dokončil Ján Holp?') is None + + +def test_colloquial_thesis_name_keeps_the_work_type_instead_of_listing_all_works(engine): + data=index(('Ján Holp','# Diplomová práca 2021\nVedúci: Daniel Hládek\n' + '# Bakalárska práca 2019\nVedúci: Ján Staš\n')) + r=answer_academic('Kto vedie diplomovku Jána Holpa?',data,engine=engine) + assert r['status']=='ok' and 'Vedúci práce: Daniel Hládek' in r['answer'] + assert 'Bakalárska práca' not in r['answer'] and 'Ján Staš' not in r['answer'] diff --git a/test/test_claude_packages.py b/test/test_claude_packages.py index 620b810..66f0dc3 100644 --- a/test/test_claude_packages.py +++ b/test/test_claude_packages.py @@ -336,6 +336,12 @@ def test_mcp_stdio_initialization_discovery_call_and_rejection(backend): assert ANSWER_PRESENTATION_POLICY in initialized.instructions listed = await session.list_tools() assert {row.name for row in listed.tools} == {'zpwiki_rag', 'zpwiki_graphrag', 'zpwiki_health'} + for tool in listed.tools: + if tool.name in {'zpwiki_rag','zpwiki_graphrag'}: + assert tool.inputSchema['required']==['query'] + assert 'question' not in tool.inputSchema['properties'] + wrong=await session.call_tool(tool.name,{'question':'Práca Evy Novej?'}) + assert wrong.isError for engine in ['rag', 'graphrag']: result = await session.call_tool('zpwiki_' + engine, {'query': 'Práca Evy Novej?'}) assert not result.isError diff --git a/tools/claude/build_claude_packages.py b/tools/claude/build_claude_packages.py index fd675f4..0bc79a2 100644 --- a/tools/claude/build_claude_packages.py +++ b/tools/claude/build_claude_packages.py @@ -11,7 +11,7 @@ import zipfile ROOT = Path(__file__).resolve().parents[2] BASE = ROOT / 'packages/claude' ENGINES = ('rag', 'graphrag') -PACKAGE_VERSION = '1.8.0' +PACKAGE_VERSION = '1.8.1' def json_bytes(value): diff --git a/tools/knowledge/academic.py b/tools/knowledge/academic.py index a2e5217..e39bec4 100644 --- a/tools/knowledge/academic.py +++ b/tools/knowledge/academic.py @@ -144,11 +144,22 @@ def answer_academic(question, index, *, engine, limit=5, units=None, mode=None): status='ambiguous',reason='ambiguous_advisor_filter') from tools.answer_evaluation.rag_evidence import requested_types types=requested_types(resolved,[m['name'] for m in students]) + # Ordinary wording of the same work-type constraint must not silently + # broaden "diplomovku" to every bachelor thesis and project on a profile. + informal={kind for pattern,kind in [(r'\bdiplomovk\w*\b','diploma_thesis'), + (r'\bbakalark\w*\b','bachelor_thesis')] + if re.search(pattern,text)} + if len(informal)>1 or informal and types and not set(types.values())<=informal: + return respond('Upresni, či sa pýtaš na bakalársku alebo diplomovú prácu.', + status='ambiguous',reason='ambiguous_work_type') + if informal: + types={m['name']:next(iter(informal)) for m in students} global_types=set(types.values()) if not students: from tools.answer_evaluation.rag_evidence import work_type kind=work_type(resolved) if kind:global_types={kind} + elif informal:global_types=informal # Completion-year filters apply to the EVENT, not the work heading. from tools.knowledge.query import _period try:period=_period(resolved)