技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
query【数据与研究】Use when the user wants to query or analyze data through the Honeydew semantic layer — including natural language analysis questions, deep multi-step investigations, and structured queries. For model/field discovery use the model-exploration skill.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Queries run against the workspace and branch set for the current session. Use get_session_workspace_and_branch to check the current context. If no workspace/branch is set, use list_workspaces, list_workspace_branches, and set_session_workspace_and_branch to select one. See the workspace-branch skill for the full workspace/branch tool reference.
Honeydew gives you two ways to answer a question about the data, plus tools for reviewing analyses and queries that already ran. Choose between the two by asking whether the specification — which fields, which filters, which population — is given to you or chosen by you.
| Method | Tool | Best For |
|---|---|---|
| Deep analysis | initiate_analysis + monitor_analysis | Default for any question about the data. The only path that applies the context layer — your organization's instructions, knowledge, and memory about which fields and definitions are correct for a given question. |
| Structured query | get_data_from_fields / get_sql_from_fields | Executing a specification you already trust. Deterministic execution — and no check that the specification is right. |
| Explain a prior step | get_analysis_step_details | User asks how a specific step in a prior analysis was calculated. |
| Browse past analyses | list_analysis_chats | User wants to see past conversations or find a prior analysis. |
| Read a past conversation | get_stored_conversation | User wants to read or review the full content of a specific past conversation. |
| Review query history | list_query_history | User wants to inspect past query executions — what ran, from which client, the query behind a run, or to debug a failure. |
initiate_analysis + monitor_analysis)Use when:
conversation_id)get_data_from_fields / get_sql_from_fields)Use when:
detailed_listings.price by detailed_listings.room_type" — so the specification is theirsDo NOT use when:
How it works:
get_data_from_fields — executes the query and returns data rowsget_sql_from_fields — returns the generated SQL without executing. Use it when the SQL itself is what's wanted: handing it to the warehouse, dbt, or another tool, or bisecting a failing query to watch a fragment appear and disappear (see the query-debugging skill). The output is long machine-generated SQL — to understand what a field computes, read the model with get_field / get_entity insteadBoth take the same field parameters.
User asks a data question
│
├─► User asks to explain / drill into a step from a prior analysis?
│ └─► get_analysis_step_details (step_id from monitor_analysis)
│ Returns semantic query, data results, and SQL for that step
│
├─► User wants to browse past conversations / find a prior analysis?
│ └─► list_analysis_chats (paginated list, newest first)
│
├─► User wants to read the content of a specific past conversation?
│ └─► get_stored_conversation (conversation_id from list_analysis_chats)
│
├─► User wants to inspect past query executions (what ran / from which client / the query behind a run / a failure)?
│ └─► list_query_history (filter by status/client/domain/user/time; most recent first)
│
├─► Investigation / "why" / "how" / trends / root cause / anything that
│ takes more than one query to answer?
│ └─► initiate_analysis + monitor_analysis
│ Check this BEFORE the field-names question below — a question
│ whose fields you happen to know is still an investigation
│
└─► Retrieving a specific figure or row set?
│
├─► The specification is given, not chosen by you — the user named
│ the fields, a prior analysis reported them, or the specification
│ is itself what you are testing → get_data_from_fields
│ (or get_sql_from_fields to preview SQL without executing)
│
└─► You would be choosing it → initiate_analysis + monitor_analysis
A structured query uses flat field parameters to define what data to retrieve:
attributes — dimensions to group by (columns in the output), e.g. ["entity.attribute_name"]metrics — aggregated measures (SUM, COUNT, AVG, etc.), e.g. ["entity.metric_name"]filters — row-level filters applied before aggregation, e.g. ["entity.field = 'value'"]. A filter written over an aggregate instead applies after aggregation — see the filtering skillorder_by — sort order for results, e.g. ["entity.field DESC"]. When a field is aliased, sort on the alias.domain — optional domain name for query contextlimit — max rows to return (default: 100)offset — rows to skip (for pagination)All fields use entity.field_name syntax. Cross-entity fields are supported when relations exist.
Before building a query, discover the available fields:
list_entities — see all entitiesget_entity with entity name — see its attributes, metrics, and relationsget_field with entity and field name — get detailed info about a specific fieldlist_domains — see all available domains (useful before passing domain parameter)search_model with a keyword and search_mode (OR for broad discovery, EXACT for known names) — find fields across the modelFiltered breakdown — entire homes by neighbourhood:
Call get_data_from_fields with:
attributes: ["detailed_listings.neighbourhood_cleansed"]metrics: ["detailed_listings.count"]filters: ["detailed_listings.room_type = 'Entire home/apt'"]order_by: ["\"detailed_listings.count\" DESC"]Attributes may span entities wherever a relation exists — adding dim_host.host_is_superhost to the list above joins host detail into the same breakdown.
Using aliases — rename fields or ad-hoc expressions:
You can alias any field or ad-hoc expression using AS "alias_name". This controls the column name in the output.
Call get_data_from_fields with:
attributes: ["detailed_listings.room_type"]metrics: ["detailed_listings.count AS \"total_listings\"", "AVG(detailed_listings.price) AS \"avg_price\""]order_by: ["\"total_listings\" DESC"]Once aliased, use the alias (not the original expression) in order_by.
Filters use standard comparison expressions: =, >, <, IN (...), ILIKE, SEARCH(...), IS NULL, booleans, date ranges, and AND/OR combinations.
For the complete filter expression reference — including SEARCH, date handling, and type casting — see the filtering skill.
Deep analysis is a stateful, resumable analyst sub-agent: you delegate a goal to it and monitor it, rather than driving its steps. It plans its own investigation, keeps its own memory (the conversation_id is its scratchpad), can pause to ask a clarifying question, and can be aborted and resumed without losing what it has computed. You reach it through a polling loop rather than a single call, so a figure whose definition is not in question — one the user specified, one a prior analysis established, one you are spot-checking — is not worth delegating. Knowing the field names is not that test. The rules below follow from that — hand it the goal rather than a plan, correct it by interrupting and resuming rather than starting over, and open a fresh conversation for an unrelated task rather than putting it in this one's context.
State the goal, not the plan. The analyst has context you do not: instructions, knowledge, and conventions held in the semantic layer about which fields, filters, and definitions are correct for a given question. An over-constrained question suppresses that context twice over — the steps you prescribe may be wrong because you are missing it, and a question that already contains a full plan gives the analyst little reason to load it.
Add precision through follow-ups instead, once the interpretation and plan come back — by then the analyst has its context loaded and a specific redirection lands on top of it rather than in place of it. Precision about what you want to know, though, not about how to compute it: a follow-up is as much a goal as the first question, so ask whether the pattern holds rather than naming the breakdown you expect.
Deep analysis is a two-step async process:
Step 1 — start the analysis:
Call initiate_analysis with:
question (required): the analysis questionagent (required for new conversations): agent name — use list_agents to discover available agents and their associated domains. Workspace and branch must be set before calling list_agents (agents are workspace-scoped)conversation_id (optional): ID from a previous call, for follow-up questionsReturns a conversation_id immediately. New conversations also return a ui_url — always display this URL to the user so they can follow the analysis in the Honeydew application.
Step 2 — poll until done:
Call monitor_analysis repeatedly with the conversation_id. Each call returns only new messages since the last call. status is "STILL_RUNNING" while the analysis works and "DONE" once it has stopped; "NO_CHAT_CURRENTLY_RUNNING" means it had already finished before you polled — read that one with get_stored_conversation rather than polling on.
status and stop_reason are separate fields and DONE belongs to both. status says whether the run has stopped; once it has, stop_reason says what kind of ending it was. NO_CHAT_CURRENTLY_RUNNING is never a stop_reason.
stop_reason | Meaning | What to do |
|---|---|---|
"DONE" | Finished normally | Read responses array for the final report |
"ASK" | Analysis paused to ask a clarifying question | Read responses for the question, answer with a new initiate_analysis on the same conversation_id |
"FAIL" | The run ended without producing an answer | There is nothing in responses to report. Do not present partial progress as a result — say the analysis did not complete. A follow-up on the same conversation_id starts fresh rather than resuming |
"ABORTED" | This execution run was stopped | Not terminal. Conversation state is preserved — continue with a new initiate_analysis on the same conversation_id |
Report progress when it's meaningful to the user — not every poll, but not silently either. Use judgement:
interpretation / plan: tell the user what the analysis intends to dostep_insight with a substantive finding: post a one-liner describing what was found — e.g. "58 menu items found, top is The King Combo at $431M"When status is "DONE", the final user-facing report is in the responses array.
# Example
initiate_analysis(question="What's driving revenue across cuisine types?", agent="listings-analyst")
→ { conversation_id: "abc123", ui_url: "https://..." }
# Poll loop — report to user after each call that has new content
monitor_analysis(conversation_id="abc123")
→ interpretation + plan received → tell user what the analysis will do
monitor_analysis(conversation_id="abc123")
→ step insight received → report: "Top item is Indian at $1B"
monitor_analysis(conversation_id="abc123")
→ step insight received → report: "% contribution: Indian leads at 34%, followed by Italian at 22%"
...
monitor_analysis(conversation_id="abc123")
→ { status: "DONE", responses: [{ text: "..." }] }
provide_analysis_feedback attaches feedback to a conversation_id:
"Good"<Reason>: <details> where Reason is one of Chart Issue, Data Issue, Wrong Judgement, or Other — e.g. "Data Issue: revenue figures don't match our BI tool"nullThere is one entry per conversation, covering the conversation as a whole, and a second call overwrites the first — so the user's judgement always wins. Never overwrite feedback the user gave with your own.
From the user — trigger when the user says things like: "that was correct", "that's wrong", "the data looks off", "great analysis", "this is incorrect because...". Record what they said.
On your own initiative — only from evidence, never from satisfaction. You asked the question, so "it answered what I needed" or "the numbers look plausible" is self-assessment: biased in your own favour, and it dilutes the feedback curators review. What qualifies is a check you can name:
Tag it [agent] and name the evidence, so a review pass can separate agent-sourced feedback from the user's. Keep <Reason>: leading on negative feedback so the reason stays machine-readable:
"Data Issue: [agent] reported total revenue 4.2M, but get_data_from_fields on order_detail.revenue with the same date filter returns 3.8M""[agent] verified against an independent get_data_from_fields call on order_detail.revenue by menu.item_category for 2021 — figures match"With no check to name, leave no feedback. That is a correct outcome, not a skipped step.
Use conversation_id from the previous analysis to ask follow-up questions that build on prior state. Start a new conversation (omit conversation_id) when the topic changes completely — reusing a conversation for an unrelated question carries stale context into the new analysis and can skew results.
initiate_analysis(
question="Does that pattern hold across room types, or is one of them driving it?",
conversation_id="abc123"
)
Once a conversation exists, each next question is a choice between a follow-up and dropping out to a structured query.
Send a follow-up when:
Drop out to get_data_from_fields when:
Only work done inside the conversation shows up at the ui_url you gave the user. Numbers you produce with a structured query afterwards won't be there, so report them yourself rather than leaving a gap between what the app shows and what you say.
A finished analysis is also the best source of field names for structured queries: it reports the exact entity.field references and filters it used, and get_analysis_step_details gives the semantic query behind any step. Feed those attributes, metrics, and filters into get_data_from_fields instead of rediscovering them from list_entities. The compiled SQL a step also returns is not an input to get_data_from_fields — it is for reading or handing to the warehouse.
abort_analysis with the conversation_id stops the current execution run. It is an interrupt, not a cancel: all conversation state survives, including groups already computed. Resume with a new initiate_analysis on the same conversation_id and the analysis continues from the aborted point, reusing prior results rather than restarting.
The question you send on resume is the correction. Interrupt-and-resume is how you steer an analysis mid-flight, rather than waiting for a wrong direction to run to completion and correcting afterwards.
Always abort when the user asks. On your own initiative, the bar is a contradiction with what the user asked for — not a difference from how you would have done it:
interpretation restates the question as something the user did not askstep_insight shows it working from a premise the user's request rules out — a filter that excludes the population in question, a date range other than the one asked aboutA plan that differs from the one you had in mind is not a misread. You handed over the goal because the analyst has context you lack, so an approach you would not have chosen is the expected case rather than a fault. Neither is a step whose purpose isn't obvious yet — the plan may need it later. When the analysis is merely slower or less direct than you would have been, let it finish.
When the user asks to explain, review, or drill into a specific step (e.g., "how was that calculated?", "explain the cuisine breakdown", "what happened in step 5"):
step_id from monitor_analysis progress messages (e.g. "STEP/5")get_analysis_step_details with conversation_id and step_idReturns:
References in the response can be expanded for deeper inspection:
entity.field_name) → get_field — shows the full definition, useful if the user wants to understand how a metric or attribute is calculatedget_context_item — shows the instruction or knowledge applied during that stepSimple natural language:
Complex analysis:
Step explanation (use get_analysis_step_details, not a new question):
A subagent does not inherit the skills you have loaded, and your brief outranks any skill it does load. Put these in it:
honeydew-ai:query skill before any Honeydew work — you do not inherit mine. Add honeydew-ai:model-exploration if you will be discovering entities or fields."initiate_analysis with agent <name> and hand it the goal, not a plan. For the lookups, the exact fields are given below."<conversation_id> rather than starting a parallel investigation."Name the agent yourself in point 2, looked up with list_agents — initiate_analysis needs either agent or conversation_id.
Scope tool guidance per question rather than per subagent: a brief covering both a count and an investigation gets governed by whichever instruction it states. Never make the analysis path conditional on the subagent judging the task multi-step.
Returns a paginated list of past analysis conversations for the current workspace, sorted newest first.
conversation_id, title, domain, agent, creation time, user feedback, and the display name of the user who created it.limit and offset for pagination.list_analysis_chats(limit=20, offset=0)
Use this when the user asks to:
Returns all messages from a past analysis conversation. Use this to read the full content of a specific conversation identified via list_analysis_chats.
is_complete in the response indicates whether the conversation reached a terminal state.with_step_ids: false (default) — returns only final responses and plan/interpretation messages. Use this for a readable summary.with_step_ids: true — also includes step_start and step_insight progress entries. Use this when you need to identify specific steps to pass to get_analysis_step_details.get_stored_conversation(conversation_id="abc123")
get_stored_conversation(conversation_id="abc123", with_step_ids=true)
To review or inspect queries that already ran — what ran, from which client (BI tools, SQL interface, MCP, deep analysis), the semantic definition or compiled SQL behind a run, who ran it and when, and debugging failures — use list_query_history (from the honeydew MCP server). It lists queries previously executed through the semantic layer, most recent first, with each query's status, error message, owner, client, domain, timestamps, warehouse query ID, and a link to open it in the app.
This is a distinct workflow from querying data — see the dedicated query-debugging skill.
Methods chain in both directions.
Discover → query → investigate — explore the model, spot-check a field with a structured query, then hand the actual question to deep analysis.
Investigate → query — let deep analysis answer the question, then use the field names and filters it reports to run fast deterministic cuts of its result. See Continuing Inside an Analysis for which direction a given follow-up belongs in.
User: "Help me understand pricing patterns for Airbnb listings."
list_entities / get_entity → find detailed_listings and what it holdslist_agents → the agent whose domain covers listingsinitiate_analysis → "What factors most influence listing price?" — show the user the ui_url it returnsmonitor_analysis and report progress as steps produce findingsget_data_from_fields with the fields the analysis reported → the exact numbers behind the driver it identifiedUse the search_docs and query_docs_filesystem tools from the honeydew MCP server to search the Honeydew documentation when:
Search for topics like: "queries", "perspectives", "dynamic datasets", "parameters", "query API".
To retrieve the distinct (unique) values of a field, include it in attributes and add a count metric in metrics.
Use the entity's built-in count metric (e.g., entity.count) if available, or an ad-hoc count metric using COUNT(entity.field)
on the field whose distinct values you want — never use COUNT(*).
The metric forces aggregation, which groups by the attribute and returns one row per distinct value.
Example — distinct room types:
Call get_data_from_fields with:
attributes: ["detailed_listings.room_type"]metrics: ["COUNT(detailed_listings.room_type)"]order_by: ["\"COUNT(detailed_listings.room_type)\" DESC"]This returns each unique room_type along with its count, ordered by frequency. The count is a useful bonus — it tells you how common each value is — but the key point is that the query returns one row per distinct value.
The count can also be filtered on. A breakdown by detailed_listings.host_name with filters: ["COUNT(detailed_listings.host_name) > 1"] returns only the values occurring more than once — which is how you surface duplicates.
This pattern is useful for:
Present analysis results clearly — format tables, highlight key numbers, and surface what's notable, unexpected, or actionable.
If your environment has visualization tools, render visualizations when they would reveal patterns faster than text — e.g. trends, distributions, or ranked lists of 5+ items. Skip it for 2–3 numbers or a single yes/no conclusion. When it might help but isn't a clear win, offer rather than render.
initiate_analysis: "why", "how", trends, drivers, or any question needing more than one query. Let the analysis engine plan the investigation instead of chaining structured queries by handget_data_from_fields executes them exactly and reproduciblylist_entities / get_entity so you reference real fields; a field list in hand is not a reason to answer the question yourselfget_data_from_fields when you just need deterministic numbersget_analysis_step_details with the step_id; the response includes the semantic query, data results, and SQL for that steplimit and offset in get_data_from_fields to avoid overwhelming outputget_field / get_entity; the compiled SQL is long machine output and is rarely the shortest route to the answerentity.field_name syntax in field parameters