This is an automated email from the ASF dual-hosted git repository. FreeOnePlus pushed a commit to branch agent/refine-domain-routing in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git
commit 99ac2936dff7ccbd621a7eb05ddda2a9d8b7060c Author: FreeOnePlus <[email protected]> AuthorDate: Sat Aug 1 15:49:16 2026 +0800 fix: constrain speculative domain discovery --- CHANGELOG.md | 4 +++ docs/integrations/hosts.md | 19 ++++++++++++ docs/integrations/hosts.zh-CN.md | 14 +++++++++ doris_mcp_server/tools/domain_catalog.py | 51 +++++++++++++++++++------------ doris_mcp_server/tools/domain_manifest.py | 6 ++-- test/tools/test_domain_dispatcher.py | 2 ++ test/tools/test_domain_manifest.py | 15 +++++++++ 7 files changed, 89 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70200b7..5bcfb51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,10 @@ under **Unreleased** until a new version is selected and published. ### Fixed +- Added explicit, mutually exclusive top-level domain boundaries and a + single-domain discovery rule, preventing Hosts from speculatively expanding + unrelated manifests for ambiguous operational questions. Unqualified cluster + history requests now preserve every usable recorded series. - Declared the exact `storage`, `query_volume`, and `user_activity` selectors for resource-growth analysis, including their recorded evidence sources, so Hosts reject ambiguous values before dispatching a Doris query. diff --git a/docs/integrations/hosts.md b/docs/integrations/hosts.md index d535745..dacd715 100644 --- a/docs/integrations/hosts.md +++ b/docs/integrations/hosts.md @@ -60,6 +60,25 @@ The second-level selection can be model-driven because the full authorized child list and exact schemas are now present. The Server does not use probabilistic intent routing to guess a child. +### Single-domain-first discipline + +A hierarchical Host should select one best-matching domain before it opens a +manifest. It must not speculatively discover several domains merely to make an +answer look more complete. This prevents repeated manifest payloads from being +carried through every model step. + +- An unqualified current or historical cluster-operation question starts with + `doris_cluster` only. +- Catalog, Query, Pipeline, and Governance are added only when the user + explicitly asks for object metadata, SQL/query history, ingestion, or + audit/lineage evidence. +- For an unqualified cluster-history request, call + `analyze_resource_growth` without `resource`; the Server then preserves every + currently usable recorded series and reports unavailable series separately. + +This is a deterministic Host orchestration rule, not probabilistic Server-side +routing. A later user turn can immediately select a different registered domain. + ## Fast intent switching Example conversation: diff --git a/docs/integrations/hosts.zh-CN.md b/docs/integrations/hosts.zh-CN.md index bfecd7a..2ec7c77 100644 --- a/docs/integrations/hosts.zh-CN.md +++ b/docs/integrations/hosts.zh-CN.md @@ -52,6 +52,20 @@ Server 不会根据对话意图修改 Host 已注册的一级工具列表。8 二级选择可以由模型完成,因为完整授权 Child 与精确 Schema 已经进入上下文。 Server 不使用概率 Intent Routing 去猜 Child。 +### 单领域优先纪律 + +Hierarchical Host 应先选择唯一最匹配的领域,再展开 Manifest。不能为了让答案看起来 +更完整而推测性地发现多个领域,否则每轮模型调用都会重复携带多份 Manifest。 + +- 未限定范围的当前或历史集群运行问题,只从 `doris_cluster` 开始; +- 只有用户明确要求对象元数据、SQL/查询历史、摄入任务或审计/血缘证据时,才增加 + Catalog、Query、Pipeline 或 Governance; +- 对未限定具体资源的集群历史问题,调用 `analyze_resource_growth` 时省略 + `resource`,由 Server 保留当前所有可用记录序列,并分别报告不可用序列。 + +这是确定性的 Host 编排规则,不是概率 Server-side Routing。用户下一轮改变问题后, +仍可立即选择另一个已注册领域。 + ## 快速意图切换 示例对话: diff --git a/doris_mcp_server/tools/domain_catalog.py b/doris_mcp_server/tools/domain_catalog.py index a515aaf..d15a490 100644 --- a/doris_mcp_server/tools/domain_catalog.py +++ b/doris_mcp_server/tools/domain_catalog.py @@ -1029,8 +1029,9 @@ DOMAIN_DEFINITIONS = ( _domain( "doris_catalog", "Doris Catalog", - "Explore Doris catalogs, databases, tables, schemas, comments, indexes, " - "key models, storage metadata, and object sizes.", + "Object metadata only: catalogs, databases, tables, schemas, comments, " + "indexes, key models, storage metadata, and sizes. Excludes SQL results, " + "cluster operations, pipelines, governance, search, and semantics.", ( _child( "doris_catalog", @@ -1125,9 +1126,9 @@ DOMAIN_DEFINITIONS = ( _domain( "doris_query", "Doris Query", - "Execute read-only Doris SQL, inspect plans and profiles, review slow " - "queries, and use advanced ADBC only when the end user explicitly " - "requests ADBC or Arrow Flight SQL.", + "Read-only SQL only: execution, plans, profiles, slow-query history, and " + "diagnosis. ADBC requires an explicit end-user request. Excludes general " + "cluster operations, pipelines, object discovery, and governance.", ( _child( "doris_query", @@ -1365,8 +1366,10 @@ DOMAIN_DEFINITIONS = ( _domain( "doris_cluster", "Doris Cluster", - "Inspect Doris health, nodes, tasks, monitoring, memory, cache, " - "compaction, workload, compute groups, and runtime capabilities.", + "Default and sufficient for unqualified current or historical Doris " + "cluster-operation questions: health, nodes, tasks, metrics, memory, " + "cache, compaction, workloads, compute groups, resource growth, and " + "capabilities. Open other domains only when explicitly requested.", ( _child( "doris_cluster", @@ -1506,7 +1509,9 @@ DOMAIN_DEFINITIONS = ( "analyze_resource_growth", "Analyze resource growth", "Analyze recorded resource-growth evidence without inventing " - "missing history.", + "missing history. For an unqualified cluster-history request, " + "omit resource so every currently usable recorded series is " + "attempted and partial evidence is preserved.", _input_schema( { "resource": _string( @@ -1550,8 +1555,10 @@ DOMAIN_DEFINITIONS = ( _domain( "doris_pipeline", "Doris Pipeline", - "Inspect ingestion, load health, materialized views, data freshness, " - "and upstream or downstream dependencies.", + "Data-pipeline operations only: ingestion and load health, " + "materialized-view refresh, data freshness, and dependencies. Excludes " + "general cluster health or history, arbitrary SQL, catalog discovery, " + "audit, and lineage.", ( _child( "doris_pipeline", @@ -1676,8 +1683,9 @@ DOMAIN_DEFINITIONS = ( _domain( "doris_search", "Doris Search", - "Search Doris data with text, vector, or hybrid retrieval and inspect " - "or diagnose search analyzers and indexes.", + "Search only: text, vector, or hybrid retrieval plus analyzer and index " + "diagnosis. Excludes catalog discovery, arbitrary SQL, cluster " + "operations, pipelines, governance, and semantic metrics.", ( _child( "doris_search", @@ -1751,8 +1759,10 @@ DOMAIN_DEFINITIONS = ( _domain( "doris_governance", "Doris Governance", - "Analyze columns and storage, inspect lineage and access evidence, " - "read audit metadata, and review UDF or authentication mappings.", + "Data governance only: column or table analysis, lineage, audit and " + "access evidence, UDF metadata, and authentication mappings. Excludes " + "general cluster operation or history, SQL performance, pipelines, and " + "catalog discovery.", ( _child( "doris_governance", @@ -1904,8 +1914,10 @@ DOMAIN_DEFINITIONS = ( _domain( "doris_lakehouse", "Doris Lakehouse", - "Inspect external catalogs, lakehouse tables, snapshots, partitions, " - "pushdown behavior, and Variant semi-structured columns.", + "External lakehouse objects only: catalogs, tables, snapshots, " + "partitions, pushdown, and Variant columns. Excludes internal catalog " + "discovery, arbitrary SQL, cluster operations, pipelines, governance, " + "and semantics.", ( _child( "doris_lakehouse", @@ -1973,9 +1985,10 @@ DOMAIN_DEFINITIONS = ( _domain( "doris_semantic", "Doris Semantic", - "Discover and use validated Ossie or MetricFlow models bound to Doris. " - "MetricFlow compiles SQL only; MCP executes it through the guarded " - "Doris Query runtime.", + "Semantic models only: an exact authorized Ossie or MetricFlow model is " + "required. MetricFlow compiles SQL; MCP executes it through guarded Doris " + "Query. Never infer a model. Excludes ordinary schema, SQL, and cluster " + "requests.", ( _child( "doris_semantic", diff --git a/doris_mcp_server/tools/domain_manifest.py b/doris_mcp_server/tools/domain_manifest.py index f4cca7a..2de94ed 100644 --- a/doris_mcp_server/tools/domain_manifest.py +++ b/doris_mcp_server/tools/domain_manifest.py @@ -56,9 +56,9 @@ MAX_TOP_LEVEL_TOOL_LIST_BYTES = 24 * 1024 MANIFEST_FORMAT_VERSION = "1" DOMAIN_DISCOVERY_DESCRIPTION_SUFFIX = ( - " Call with an empty object to discover the exact child tools and schemas " - "available in this domain. Then call this same tool with the returned " - "child_tool name and arguments." + " Choose one matching domain; do not speculatively open others. Call with " + "an empty object to discover its exact child tools and schemas, then call " + "it again with the returned child_tool and arguments." ) DOMAIN_TOOL_INPUT_SCHEMA: dict[str, Any] = { diff --git a/test/tools/test_domain_dispatcher.py b/test/tools/test_domain_dispatcher.py index 11ea194..3cbedef 100644 --- a/test/tools/test_domain_dispatcher.py +++ b/test/tools/test_domain_dispatcher.py @@ -164,6 +164,8 @@ def test_resource_growth_declares_exact_resource_selectors() -> None: "query_volume", "user_activity", ) + assert "unqualified cluster-history request" in child.canonical_description + assert "omit resource" in child.canonical_description @pytest.mark.asyncio diff --git a/test/tools/test_domain_manifest.py b/test/tools/test_domain_manifest.py index 8b2cd0a..ef9e25d 100644 --- a/test/tools/test_domain_manifest.py +++ b/test/tools/test_domain_manifest.py @@ -149,6 +149,7 @@ def test_top_level_catalog_is_exactly_eight_stable_read_only_domains() -> None: for tool in tools: assert tool.description is not None assert tool.description.endswith(DOMAIN_DISCOVERY_DESCRIPTION_SUFFIX) + assert "do not speculatively open others" in tool.description assert tool.annotations is not None assert tool.annotations.read_only_hint is True assert tool.annotations.destructive_hint is False @@ -157,6 +158,20 @@ def test_top_level_catalog_is_exactly_eight_stable_read_only_domains() -> None: for child_name in EXPECTED_DOMAIN_CHILDREN[tool.name]: assert child_name not in tool.description + descriptions = {tool.name: tool.description for tool in tools} + assert "Default and sufficient for unqualified" in descriptions[ + "doris_cluster" + ] + assert "current or historical Doris cluster-operation" in descriptions[ + "doris_cluster" + ] + assert "Excludes general cluster operation or history" in descriptions[ + "doris_governance" + ] + assert "Excludes general cluster health or history" in descriptions[ + "doris_pipeline" + ] + def test_manager_mixin_lazily_builds_discovery_service() -> None: manager = DomainManifestManagerMixin() --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
