Skills that create report apps
Turn “make me a report” into a workspace app through conversation, without building a UI by hand.
Source docs/en/site/skill-report-apps.md
Doc version: 1.0 Date: 2026-07-19 Status: Implemented (backend/internal/appfromchat) Related: [docs/core-mechanisms/工作区应用.md](/docs/mech-workspace-apps) §7.2 / §9, [help/product-features/skill-content-rules.md](/docs/help-skill-content-rules)
1. The motivating case
Conversation example (debug-mirror):
| Round | User | Result |
|---|---|---|
| 1 | /日隆部门职级统计图 部门职级统计图 | Success: skill produced an HTML report |
| 2 | “Please create an app… so tapping the app produces this same report” | Failed at the time: whole-app LLM context deadline exceeded |
User goal: one tap in the app = the same report the skill produced in conversation (same template, same grouping, same numbers), with the stats logic baked into the app code so it no longer depends on the conversation model improvising.
2. What you must keep separate
2.1 Where does the stats logic live?
Take “Rilong department–rank chart” as an example:
| Where | What it contains | Can it compute? |
|---|---|---|
| Skill pack | Markdown grouping rules, HTML template, placeholder list | No (no build_report / run_script) |
| Conversation | skill_read → two query.run calls → the model groups and fills the table on the spot → file_write | Yes (logic lives in the conversation LLM) |
Observed tool chain: skill_read → skill_script_read → data_source_invoke×2 → file_write, and no run_script.
So:
- The skill supplies definitions + a delivery template (the authoritative spec);
- Executable statistics happen in the model step inside the conversation;
- “Turn it into an app” must turn that same conversation-time computation into Python inside the app pack.
2.2 Product principles (decided)
- Skill + conversation = app. The main path is the LLM compiling conversation stats into app code — not a one-size-fits-all report engine in the platform.
- All calculation lives in the generated app pack. The platform only orchestrates creation and the data-fetch bridge; it does not re-run business stats at runtime.
- Future skills, reports, and fill rules each live in their own app pack. There is no shared “universal backend calculator”.
- If the skill already ships
scripts/compute scripts (includingbuild_report), copy them as-is at create time — do not regenerate.
3. Why it failed early (for contrast)
3.1 Create failed
- Round 2 “please create an app” often did not type / to pick the skill → no reference skill pack → HTML report fast path skipped.
- Fell through to whole-app
GenerateAppScaffold(long handbook) → Minimaxi timed out.
3.2 Even a successful create could drift
The platform generic assemble_html_report.py would:
- Use a different rank-bucket scheme;
- Rewrite the HTML skeleton (keep CSS only);
- Usually run only one query.
That does not match the skill’s “one template + conversation grouping + two queries”.
4. Target architecture
Conversation report (skill spec + LLM stats on the spot)
│
▼ User says “create an app” (no / needed; can inherit this conversation’s skill_read)
│
├─ Skill already has build_report* ──► copy into the app pack
│
└─ Skill has no compute script ──► LLM (long timeout)
Input: skill body + HTML template + this conversation’s report summary
Output: app scripts/build_report.py
+ handlers (fetch data → call in-app script → save_upload)
│
▼
One-tap generate on the app desktop (runs only app-pack logic; no conversation-time improvisation)
5. Implementation notes
5.1 Inherit the reference skill from the conversation
- File:
session_skill_inherit.go - When the current message has no
skill_ids, parseskill_idfrom recent assistanttool_traceofskill_read/skill_script_read. - Fixes: “the report looks great → please create an app” with no / going down the wrong path.
5.2 Conversation summary (so the LLM can match conversation stats)
- File:
session_report_hint.go - Extract the latest successful report: query_id list, assistant conclusion summary, corresponding user request.
- Tell the model clearly: stats were done in conversation; the skill may have no executable script.
5.3 LLM-generated in-app compute script (main path)
- File:
skill_report_build_llm.go - Entry:
CompileAppBuildReportViaLLM - Require a complete Python file that includes
def build_report(app_root, template_rel, period, emp_rows, dict_rows=None); - Keep the skill HTML skeleton; update the data region only; no matplotlib / CDN / PNG;
- Write to the app
scripts/build_report.pyafteragentscript.ScanScriptContentsecurity scan. - Timeout: when creating a report app, use
PlatformAppImproveLLMTimeout(about 10–15 minutes) so we do not hit the short whole-app timeout again.
5.4 If the skill already has a script, copy it
- File:
app_local_build_report.go→FindSkillReportComputeScript - Prefer
def build_report; otherwise a non-matplotlib script whose filename contains fill / build / assemble / report.
5.5 Fallback
- If the LLM fails and the skill body’s grouping table can be parsed:
GenerateAppLocalBuildReportPy(deterministic script with rules inlined). - If that also fails: tell the user to type
/for the original skill or ask the app-dev assistant for a whole-pack alignment, not a vague “simplify the request”.
5.6 What handlers do
- File:
skill_faithful_report.go→skillFaithfulHandlersPy - Only:
query_run(emp + dict) → load in-appBUILD_SCRIPT→build_report→save_upload+ history table. - Do not aggregate business stats in the platform backend.
5.7 Orchestration entry
execute.go:ResolveReferenceSkillIDs→TryScaffoldHTMLReportFromSkills(with conversation ID + long-timeout LLM).html_report_scaffold.go: faithful path first; otherwise the old generic assemble (not recommended).
6. Key files
| Path | Role |
|---|---|
backend/internal/appfromchat/session_skill_inherit.go | Inherit skill_id from the conversation |
backend/internal/appfromchat/session_report_hint.go | Summary of the conversation report |
backend/internal/appfromchat/skill_report_build_llm.go | LLM generates build_report.py |
backend/internal/appfromchat/skill_faithful_report.go | Assemble the faithful report-app draft |
backend/internal/appfromchat/app_local_build_report.go | Discover skill scripts / deterministic fallback |
backend/internal/appfromchat/skill_report_rules.go | Parse grouping tables from the body (fallback / check) |
backend/internal/appfromchat/html_report_scaffold.go | HTML report scaffold fork |
backend/internal/appfromchat/execute.go | Overall orchestration for creating an app from chat |
backend/internal/appfromchat/persist.go | Persist to disk; never overwrite in-app compute scripts with platform constants |
7. Recommended flow (user side)
- In a work-agent conversation, type
/to pick a report skill and produce a good report first. - In the same conversation, send: “Please create an app so tapping the app produces this same report” (you may type
/to pick the skill again). - Open the app desktop → one-tap generate → preview the HTML and check numbers against the conversation result.
- If the skill should stand on its own later: copy the stable
build_report.pyback into the skillscripts/so later app creates can sync it directly.
8. How to write the skill (less drift)
See [skill-content-rules.md](/docs/help-skill-content-rules) “fetch data + fixed HTML report”:
- Spell out the grouping table, placeholders, and the single template path in the body;
- Prefer shipping
build_report(...)in the skill (no charts); - If stats still happen only in conversation: creating the app depends on LLM compile — the clearer the grouping table, the better the generated code.
9. Acceptance
- [ ] “Create an app” with no
/inherits the report skill just used in this conversation, and does not die on a whole-app timeout. - [ ] The app pack contains a compute script (copied from the skill or LLM-generated
scripts/build_report.py). - [ ] One-tap generate: two queries fetch data + in-app script fills the template; numbers match the successful conversation report (or differences are explainable).
- [ ] Editing one app’s script does not affect other report apps; there is no coupling to a platform “universal report service”.
10. Explicitly out of this round
- Invoking an app directly from conversation (mechanism doc P2).
- Stretching whole-app
GenerateAppScaffoldtimeout as the main report fix (treats the symptom). - Hard-coding one business grouping scheme into resident Go backend logic.
11. One-line reminder
The skill gives the spec and template; the conversation does the stats. When creating the app, the LLM compiles that conversation stats into in-app Python; at runtime only the app pack runs.