@shulex/cxclawctl 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -22
- package/dist/cli.js +2202 -127
- package/dist/cli.js.map +1 -1
- package/package.json +19 -6
- package/capabilities.lock.json +0 -898
- package/requirements-cli.txt +0 -2
- package/skills/__lib__/__init__.py +0 -0
- package/skills/__lib__/skill-runtime/bootstrap.py +0 -134
- package/skills/__lib__/solvea-api/__init__.py +0 -0
- package/skills/__lib__/solvea-api/client.py +0 -1944
- package/skills/__lib__/solvea-api/dashboard.py +0 -100
- package/skills/__lib__/solvea-api/solvea_api.py +0 -1341
- package/skills/agent-analytics/SKILL.md +0 -74
- package/skills/agent-analytics/references/chat-report-fields.md +0 -36
- package/skills/agent-analytics/references/report-fields.md +0 -130
- package/skills/agent-analytics/references/transcript.md +0 -110
- package/skills/agent-builder/SKILL.md +0 -42
- package/skills/agent-builder/interfaces/fallback-chatflow.md +0 -47
- package/skills/agent-builder/references/chatflow/graph-spec.md +0 -259
- package/skills/agent-builder/references/chatflow/node-selection.md +0 -89
- package/skills/agent-builder/references/chatflow/nodes/agent.md +0 -350
- package/skills/agent-builder/references/chatflow/nodes/business-track.md +0 -43
- package/skills/agent-builder/references/chatflow/nodes/code.md +0 -157
- package/skills/agent-builder/references/chatflow/nodes/collect-order.md +0 -47
- package/skills/agent-builder/references/chatflow/nodes/finish.md +0 -34
- package/skills/agent-builder/references/chatflow/nodes/human-review.md +0 -60
- package/skills/agent-builder/references/chatflow/nodes/if-else.md +0 -100
- package/skills/agent-builder/references/chatflow/nodes/intent.md +0 -79
- package/skills/agent-builder/references/chatflow/nodes/knowledge-reply.md +0 -43
- package/skills/agent-builder/references/chatflow/nodes/llm.md +0 -117
- package/skills/agent-builder/references/chatflow/nodes/note.md +0 -38
- package/skills/agent-builder/references/chatflow/nodes/question.md +0 -97
- package/skills/agent-builder/references/chatflow/nodes/rag-reply.md +0 -68
- package/skills/agent-builder/references/chatflow/nodes/reply.md +0 -97
- package/skills/agent-builder/references/chatflow/nodes/start.md +0 -35
- package/skills/agent-builder/references/chatflow/nodes/sub-graph.md +0 -48
- package/skills/agent-builder/references/chatflow/nodes/tool.md +0 -138
- package/skills/agent-builder/references/chatflow/nodes/transfer-to-human.md +0 -63
- package/skills/agent-builder/references/chatflow/nodes/variable-assignment.md +0 -52
- package/skills/agent-builder/references/chatflow/nodes/variable-extract.md +0 -50
- package/skills/agent-builder/references/chatflow/nodes/variable-process.md +0 -70
- package/skills/agent-builder/references/chatflow/overview.md +0 -53
- package/skills/agent-builder/references/chatflow/visual-translation.md +0 -64
- package/skills/agent-builder/references/chatflow/workflow.md +0 -134
- package/skills/agent-builder/references/multi-agent/create-update.md +0 -185
- package/skills/agent-builder/references/multi-agent/graph-spec.md +0 -246
- package/skills/agent-builder/references/multi-agent/graph-structure.md +0 -12
- package/skills/agent-builder/references/multi-agent/migrate-to-chatflow.md +0 -13
- package/skills/agent-builder/references/multi-agent/migration/large.md +0 -554
- package/skills/agent-builder/references/multi-agent/migration/principles.md +0 -446
- package/skills/agent-builder/references/multi-agent/migration/small.md +0 -225
- package/skills/agent-builder/references/multi-agent/orchestration.md +0 -11
- package/skills/agent-builder/references/multi-agent/overview.md +0 -48
- package/skills/agent-builder/references/multi-agent/update.md +0 -128
- package/skills/agent-builder/references/multi-agent/workflow.md +0 -87
- package/skills/agent-builder/references/single-agent/overview.md +0 -48
- package/skills/agent-builder/references/single-agent/prompt.md +0 -16
- package/skills/agent-builder/references/single-agent/tools.md +0 -36
- package/skills/agent-builder/references/single-agent/voice.md +0 -46
- package/skills/agent-builder/references/tools/http-tool.md +0 -315
- package/skills/agent-builder/references/tools/mock-tool.md +0 -279
- package/skills/agent-builder/references/tools/overview.md +0 -46
- package/skills/agent-builder/references/tools/tool-config.md +0 -43
- package/skills/agent-builder/references/tools/x-token.md +0 -34
- package/skills/agent-builder/scripts/chatflow/chatflow_builder.py +0 -3458
- package/skills/agent-builder/scripts/chatflow/validate_graph.py +0 -1697
- package/skills/agent-builder/scripts/copy_marketplace_agent.py +0 -61
- package/skills/agent-builder/scripts/multi-agent/legacy_graph_analyzer.py +0 -5974
- package/skills/agent-builder/scripts/multi-agent/replay_compare.py +0 -679
- package/skills/agent-builder/scripts/patch_agent_tools.py +0 -135
- package/skills/agent-builder/scripts/tools/validate_tool_config.py +0 -384
- package/skills/agent-tester/SKILL.md +0 -35
- package/skills/agent-tester/batch-test/overview.md +0 -86
- package/skills/agent-tester/scripts/batch_test_sheet.py +0 -213
- package/skills/agent-tester/single-test/badcase-retest.md +0 -85
- package/skills/agent-tester/single-test/direct-test.md +0 -24
- package/skills/agent-tester/single-test/overview.md +0 -142
- package/skills/delivery/SKILL.md +0 -112
- package/skills/delivery/interfaces/classify.md +0 -38
- package/skills/delivery/interfaces/create-agents.md +0 -89
- package/skills/delivery/interfaces/delivery-test.md +0 -67
- package/skills/delivery/references/flow.md +0 -40
- package/skills/delivery/references/validation.md +0 -15
- package/skills/delivery/scripts/batch_test.py +0 -704
- package/skills/delivery/scripts/classify.py +0 -22
- package/skills/delivery/scripts/create_agents.py +0 -695
- package/skills/delivery/scripts/gen_delivery_report.py +0 -335
- package/skills/delivery/scripts/gen_excel.py +0 -234
- package/skills/delivery/scripts/gen_report.py +0 -208
- package/skills/delivery/scripts/plan.py +0 -163
- package/skills/delivery/scripts/render.py +0 -713
- package/skills/delivery/scripts/render_summary.py +0 -38
- package/skills/delivery/scripts/sample_history_cases.py +0 -109
- package/skills/kb-builder/SKILL.md +0 -80
- package/skills/kb-builder/interfaces/qa-upload.md +0 -55
- package/skills/kb-builder/interfaces/web-kb.md +0 -84
- package/skills/kb-builder/references/directory.md +0 -373
- package/skills/kb-builder/references/export.md +0 -282
- package/skills/kb-builder/references/isolation-keywords.md +0 -113
- package/skills/kb-builder/references/knowledge.md +0 -153
- package/skills/kb-builder/references/product.md +0 -52
- package/skills/kb-builder/references/qa-from-help-center.md +0 -58
- package/skills/kb-builder/references/qa-from-pdf.md +0 -94
- package/skills/kb-builder/references/qa-from-tickets.md +0 -168
- package/skills/kb-builder/references/qa-from-web.md +0 -98
- package/skills/kb-builder/references/qa-quality.md +0 -73
- package/skills/kb-builder/references/reply-template.md +0 -237
- package/skills/kb-builder/references/tag-isolation-migration.md +0 -61
- package/skills/kb-builder/references/tags.md +0 -45
- package/skills/kb-builder/references/upload.md +0 -130
- package/skills/kb-builder/scripts/batch_extract_ticket_qa.py +0 -333
- package/skills/kb-builder/scripts/build_directory_plan.py +0 -144
- package/skills/kb-builder/scripts/crawl_site.py +0 -433
- package/skills/kb-builder/scripts/create_dirs.py +0 -146
- package/skills/kb-builder/scripts/export_knowledge.py +0 -454
- package/skills/kb-builder/scripts/extract_ticket_qa.py +0 -441
- package/skills/kb-builder/scripts/extract_web_qa.py +0 -118
- package/skills/kb-builder/scripts/import_qa.py +0 -219
- package/skills/kb-builder/scripts/import_source_documents.py +0 -143
- package/skills/kb-builder/scripts/import_templates.py +0 -409
- package/skills/poc/SKILL.md +0 -125
- package/skills/poc/assets/default_manual.css +0 -279
- package/skills/poc/assets/notice_escalate.png +0 -0
- package/skills/poc/assets/notice_escalate2.png +0 -0
- package/skills/poc/assets/notice_new_chat.png +0 -0
- package/skills/poc/assets/poc_test_template.html +0 -645
- package/skills/poc/assets/poc_test_template.md +0 -104
- package/skills/poc/assets/step3_preview.png +0 -0
- package/skills/poc/assets/step4_order_note.png +0 -0
- package/skills/poc/interfaces/classify.md +0 -38
- package/skills/poc/interfaces/create-agents.md +0 -108
- package/skills/poc/interfaces/poc-test.md +0 -94
- package/skills/poc/references/flow.md +0 -47
- package/skills/poc/references/kb-directory.md +0 -22
- package/skills/poc/references/validation.md +0 -17
- package/skills/poc/scripts/classify.py +0 -22
- package/skills/poc/scripts/create_agents.py +0 -818
- package/skills/poc/scripts/gen_excel.py +0 -234
- package/skills/poc/scripts/gen_poc_manual.py +0 -249
- package/skills/poc/scripts/gen_report.py +0 -208
- package/skills/poc/scripts/inspect_chatflow.py +0 -209
- package/skills/poc/scripts/plan.py +0 -178
- package/skills/poc/scripts/poc_test.py +0 -495
- package/skills/poc/scripts/render.py +0 -713
- package/skills/poc/scripts/render_summary.py +0 -41
- package/skills/runtime-inspector/SKILL.md +0 -51
- package/skills/runtime-inspector/references/frontend-rendering.md +0 -126
- package/skills/runtime-inspector/references/model.md +0 -59
- package/skills/runtime-inspector/references/sandbox.md +0 -131
- package/skills/runtime-inspector/scripts/model_inspect.py +0 -68
- package/skills/runtime-inspector/scripts/prepare.py +0 -98
- package/skills/runtime-inspector/scripts/process.py +0 -84
- package/skills/runtime-inspector/scripts/read_state.py +0 -32
- package/skills/runtime-inspector/scripts/sandbox_inspect.py +0 -463
- package/skills/runtime-inspector/scripts/test_claude_proxy.py +0 -49
- package/skills/runtime-inspector/scripts/test_openai_proxy.py +0 -41
- package/skills/runtime-inspector/scripts/verify_persistence.py +0 -101
- package/skills/runtime-inspector/scripts/write_test.py +0 -41
- package/skills/setup/SKILL.md +0 -17
- package/skills/shulex-support/SKILL.md +0 -39
- package/skills/shulex-support/references/badcase-analysis/overview.md +0 -34
- package/skills/shulex-support/references/badcase-analysis/text/conversation-record.md +0 -105
- package/skills/shulex-support/references/badcase-analysis/text/core-flow.md +0 -89
- package/skills/shulex-support/references/badcase-analysis/text/log-query-rules.md +0 -36
- package/skills/shulex-support/references/badcase-analysis/text/m1-entry.md +0 -53
- package/skills/shulex-support/references/badcase-analysis/text/m10-chatflow-service.md +0 -259
- package/skills/shulex-support/references/badcase-analysis/text/m11-algorithm-service.md +0 -148
- package/skills/shulex-support/references/badcase-analysis/text/m12-quality-inspection.md +0 -57
- package/skills/shulex-support/references/badcase-analysis/text/m2-intent.md +0 -120
- package/skills/shulex-support/references/badcase-analysis/text/m3-strategy.md +0 -126
- package/skills/shulex-support/references/badcase-analysis/text/m4-aifield.md +0 -121
- package/skills/shulex-support/references/badcase-analysis/text/m5-agent.md +0 -208
- package/skills/shulex-support/references/badcase-analysis/text/m6-tool.md +0 -115
- package/skills/shulex-support/references/badcase-analysis/text/m7-reply.md +0 -72
- package/skills/shulex-support/references/badcase-analysis/text/m8-error.md +0 -92
- package/skills/shulex-support/references/badcase-analysis/text/m9-knowledge-search.md +0 -147
- package/skills/shulex-support/references/badcase-analysis/text/sls-query.md +0 -283
- package/skills/shulex-support/references/badcase-analysis/voice/core-flow.md +0 -77
- package/skills/shulex-support/references/badcase-analysis/voice/dependencies.md +0 -19
- package/skills/shulex-support/references/badcase-analysis/voice/platform-api.md +0 -23
- package/skills/shulex-support/references/badcase-analysis/voice/sls-playbook.md +0 -31
- package/skills/shulex-support/references/product-docs.md +0 -70
- package/skills/shulex-support/references/tickets.md +0 -184
- package/skills/shulex-support/scripts/support.py +0 -427
- package/skills/solvea-desk/SKILL.md +0 -39
- package/skills/solvea-desk/references/report/fields.md +0 -82
- package/skills/solvea-desk/references/report/report-design.md +0 -200
- package/skills/solvea-desk/references/report/report-template.html +0 -44
- package/skills/solvea-desk/references/report/report.css +0 -517
- package/skills/solvea-desk/references/report/workflow.md +0 -464
- package/skills/solvea-desk/references/tickets/query.md +0 -103
- package/skills/solvea-desk/references/tickets/tags.md +0 -83
- package/skills/solvea-desk/scripts/report/fetch_sources.py +0 -580
- package/skills/solvea-desk/scripts/report/render_report.py +0 -763
- package/skills/solvea-desk/scripts/tickets/desk_api.py +0 -73
- package/skills/solvea-desk/scripts/tickets/fetch_tickets.py +0 -403
- package/skills/solvea-desk/scripts/tickets/ticket_tags.py +0 -305
- package/skills/solvea-kit/SKILL.md +0 -84
- package/skills/solvea-kit/references/agent.md +0 -145
- package/skills/solvea-kit/references/chat.md +0 -244
- package/skills/solvea-kit/references/intent.md +0 -69
- package/skills/solvea-kit/references/plugin.md +0 -53
- package/skills/solvea-kit/references/quality-inspection.md +0 -75
- package/skills/solvea-kit/references/strategy.md +0 -62
- package/skills/solvea-kit/references/tagging.md +0 -231
- package/skills/solvea-kit/references/variables.md +0 -53
- package/skills/solvea-kit/scripts/generate_label_import_file.py +0 -151
- package/skills/solvea-kit/scripts/intent_classify.py +0 -843
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Import qa_plan into Solvea knowledge base as QA entries.
|
|
4
|
-
|
|
5
|
-
Two input modes:
|
|
6
|
-
|
|
7
|
-
1. --qa-file <path> already-merged qa_plan_<ts>.json
|
|
8
|
-
2. --from-dir <dir> merge all *.json files in <dir> (each is a QA list).
|
|
9
|
-
Useful when the agent wrote multiple small batches into
|
|
10
|
-
/workspace/tmp/kb-builder/qa_parts/ to avoid the LLM max_tokens
|
|
11
|
-
truncation that hits any single huge tool_call. The
|
|
12
|
-
merged plan is written to OUTPUTS_DIR/qa_plan_<ts>.json
|
|
13
|
-
for traceability before upload.
|
|
14
|
-
|
|
15
|
-
QA item format (each list entry):
|
|
16
|
-
[
|
|
17
|
-
{
|
|
18
|
-
"dir_path": ["Product Introduction", "Smart Plugs", "Kasa EP25"],
|
|
19
|
-
"question": "Kasa EP25 Smart Plug — What is the Kasa EP25 and what does it do?",
|
|
20
|
-
"answer": "The Kasa EP25 is ...\n\n"
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
Usage:
|
|
25
|
-
python3 import_qa.py --qa-file /workspace/outputs/kb-builder/qa_plan_<ts>.json
|
|
26
|
-
python3 import_qa.py --qa-file /workspace/outputs/kb-builder/qa_plan_<ts>.json --dry-run
|
|
27
|
-
python3 import_qa.py --from-dir /workspace/tmp/kb-builder/qa_parts/
|
|
28
|
-
python3 import_qa.py --from-dir /workspace/tmp/kb-builder/qa_parts/ --dry-run
|
|
29
|
-
|
|
30
|
-
SolveaClient() picks token / project_id / env from environment variables
|
|
31
|
-
(SOLVEA_TOKEN / SOLVEA_PROD_TOKEN / SOLVEA_PROJECT_ID / SOLVEA_ENV).
|
|
32
|
-
"""
|
|
33
|
-
from __future__ import annotations
|
|
34
|
-
import argparse, json, os, sys
|
|
35
|
-
sys.path.insert(0, os.path.join(os.environ.get("SOLVEA_LIB_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "__lib__"))), "skill-runtime"))
|
|
36
|
-
from bootstrap import *
|
|
37
|
-
from pathlib import Path
|
|
38
|
-
|
|
39
|
-
def validate_qa_item(item: object, index: int, max_depth: int = 3) -> tuple[list[str], str, str]:
|
|
40
|
-
"""Return normalized QA fields or raise a clear schema error."""
|
|
41
|
-
if not isinstance(item, dict):
|
|
42
|
-
raise ValueError(f"QA item #{index} must be an object, got {type(item).__name__}")
|
|
43
|
-
|
|
44
|
-
dir_path = item.get("dir_path")
|
|
45
|
-
question = item.get("question")
|
|
46
|
-
answer = item.get("answer")
|
|
47
|
-
|
|
48
|
-
if not isinstance(dir_path, list) or not dir_path:
|
|
49
|
-
raise ValueError(
|
|
50
|
-
f"QA item #{index} dir_path must be a non-empty list of folder names, "
|
|
51
|
-
f'e.g. ["Product Introduction", "Speakers", "B0117-03018-BK"]; '
|
|
52
|
-
f"got {dir_path!r}"
|
|
53
|
-
)
|
|
54
|
-
if not all(isinstance(name, str) and name.strip() for name in dir_path):
|
|
55
|
-
raise ValueError(f"QA item #{index} dir_path entries must be non-empty strings: {dir_path!r}")
|
|
56
|
-
if len(dir_path) > max_depth:
|
|
57
|
-
raise ValueError(f"QA item #{index} dir_path has {len(dir_path)} levels; maximum is {max_depth}")
|
|
58
|
-
if any("/" in name or "\\" in name for name in dir_path):
|
|
59
|
-
raise ValueError(
|
|
60
|
-
f"QA item #{index} dir_path entries must be folder names, not slash paths: {dir_path!r}"
|
|
61
|
-
)
|
|
62
|
-
if not isinstance(question, str) or not question.strip():
|
|
63
|
-
raise ValueError(f"QA item #{index} question must be a non-empty string")
|
|
64
|
-
if not isinstance(answer, str) or not answer.strip():
|
|
65
|
-
raise ValueError(f"QA item #{index} answer must be a non-empty markdown string")
|
|
66
|
-
|
|
67
|
-
return [name.strip() for name in dir_path], question.strip(), answer.strip()
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def validate_plan(plan: object, max_depth: int = 3) -> list[tuple[list[str], str, str]]:
|
|
71
|
-
if not isinstance(plan, list):
|
|
72
|
-
raise ValueError(f"QA plan must be a list, got {type(plan).__name__}")
|
|
73
|
-
return [validate_qa_item(item, i + 1, max_depth=max_depth) for i, item in enumerate(plan)]
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def flatten_folders(nodes: list, result: list | None = None) -> list:
|
|
77
|
-
"""Recursively flatten a nested folder tree into a flat list."""
|
|
78
|
-
if result is None:
|
|
79
|
-
result = []
|
|
80
|
-
for node in nodes:
|
|
81
|
-
result.append(node)
|
|
82
|
-
children = node.get("children", [])
|
|
83
|
-
if children:
|
|
84
|
-
flatten_folders(children, result)
|
|
85
|
-
return result
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def get_or_create_dir(client: SolveaClient, path: list[str], dir_cache: dict) -> str | int:
|
|
89
|
-
"""Walk dir_path, creating missing folders. Returns final directory ID."""
|
|
90
|
-
parent_id = 0
|
|
91
|
-
current_path: list[str] = []
|
|
92
|
-
for name in path:
|
|
93
|
-
current_path.append(name)
|
|
94
|
-
key = "/".join(current_path)
|
|
95
|
-
if key in dir_cache:
|
|
96
|
-
parent_id = dir_cache[key]
|
|
97
|
-
continue
|
|
98
|
-
# Search in existing folders (API returns {"list": [nested tree]})
|
|
99
|
-
folders = client.list_directories()
|
|
100
|
-
if isinstance(folders, list):
|
|
101
|
-
all_folders = flatten_folders(folders)
|
|
102
|
-
else:
|
|
103
|
-
raw = folders.get("list") or folders.get("records") or folders.get("data") or []
|
|
104
|
-
all_folders = flatten_folders(raw)
|
|
105
|
-
match = next(
|
|
106
|
-
(f for f in all_folders
|
|
107
|
-
if f.get("name") == name and (f.get("parentId") or 0) == parent_id),
|
|
108
|
-
None,
|
|
109
|
-
)
|
|
110
|
-
if match:
|
|
111
|
-
fid = match["id"]
|
|
112
|
-
else:
|
|
113
|
-
result = client.create_folder(name, parent_id=parent_id)
|
|
114
|
-
fid = result.get("id") if isinstance(result, dict) else None
|
|
115
|
-
if not fid:
|
|
116
|
-
print(f" [warn] Could not create folder '{name}' under parentId={parent_id}: {result}", file=sys.stderr)
|
|
117
|
-
fid = parent_id # fallback: use parent
|
|
118
|
-
dir_cache[key] = fid
|
|
119
|
-
parent_id = fid
|
|
120
|
-
return parent_id
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def merge_dir(src_dir: Path) -> tuple[list, Path]:
|
|
124
|
-
"""Merge every *.json in src_dir (each must be a QA list) into one list,
|
|
125
|
-
then write the result to OUTPUTS_DIR/qa_plan_{ts}.json for traceability.
|
|
126
|
-
|
|
127
|
-
Files are merged in filename-sorted order so the result is deterministic.
|
|
128
|
-
Files whose top-level JSON isn't a list are skipped with a warning.
|
|
129
|
-
"""
|
|
130
|
-
if not src_dir.exists() or not src_dir.is_dir():
|
|
131
|
-
raise FileNotFoundError(f"--from-dir not a directory: {src_dir}")
|
|
132
|
-
|
|
133
|
-
merged: list = []
|
|
134
|
-
parts = sorted(src_dir.glob("*.json"))
|
|
135
|
-
if not parts:
|
|
136
|
-
raise FileNotFoundError(f"No *.json found under {src_dir}")
|
|
137
|
-
|
|
138
|
-
for p in parts:
|
|
139
|
-
try:
|
|
140
|
-
data = json.loads(p.read_text(encoding="utf-8"))
|
|
141
|
-
except Exception as e:
|
|
142
|
-
print(f" [warn] skip {p.name}: {e}", file=sys.stderr)
|
|
143
|
-
continue
|
|
144
|
-
if not isinstance(data, list):
|
|
145
|
-
print(f" [warn] skip {p.name}: top-level is not a list", file=sys.stderr)
|
|
146
|
-
continue
|
|
147
|
-
print(f" [merge] {p.name}: +{len(data)} QA")
|
|
148
|
-
merged.extend(data)
|
|
149
|
-
|
|
150
|
-
OUTPUTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
151
|
-
out = OUTPUTS_DIR / f"qa_plan_{timestamp()}.json"
|
|
152
|
-
out.write_text(json.dumps(merged, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
153
|
-
print(f"[merge] {len(merged)} QA total -> {out}")
|
|
154
|
-
return merged, out
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def run(qa_file: str | None, from_dir: str | None, dry_run: bool, max_depth: int = 3) -> None:
|
|
158
|
-
if from_dir:
|
|
159
|
-
plan, qa_file_path = merge_dir(Path(from_dir))
|
|
160
|
-
else:
|
|
161
|
-
qa_file_path = Path(qa_file)
|
|
162
|
-
plan = json.loads(qa_file_path.read_text(encoding="utf-8"))
|
|
163
|
-
|
|
164
|
-
normalized_plan = validate_plan(plan, max_depth=max_depth)
|
|
165
|
-
|
|
166
|
-
print(f"[import] {len(normalized_plan)} QA entries to import (dry_run={dry_run}) source={qa_file_path}")
|
|
167
|
-
|
|
168
|
-
dir_cache: dict[str, str | int] = {}
|
|
169
|
-
ok = 0
|
|
170
|
-
fail = 0
|
|
171
|
-
client = None
|
|
172
|
-
if not dry_run:
|
|
173
|
-
from client import SolveaClient
|
|
174
|
-
client = SolveaClient()
|
|
175
|
-
|
|
176
|
-
for i, (dir_path, question, answer) in enumerate(normalized_plan):
|
|
177
|
-
if dry_run:
|
|
178
|
-
print(f" [{i+1}] DRY dir={dir_path}")
|
|
179
|
-
print(f" Q: {question[:80]}")
|
|
180
|
-
print(f" A: {answer[:80]}...")
|
|
181
|
-
continue
|
|
182
|
-
|
|
183
|
-
try:
|
|
184
|
-
assert client is not None
|
|
185
|
-
dir_id = get_or_create_dir(client, dir_path, dir_cache) if dir_path else ""
|
|
186
|
-
client.upload_qa([{
|
|
187
|
-
"title": question,
|
|
188
|
-
"content": answer,
|
|
189
|
-
"directoryId": dir_id,
|
|
190
|
-
}])
|
|
191
|
-
ok += 1
|
|
192
|
-
print(f" [{i+1}/{len(normalized_plan)}] OK {'/'.join(dir_path)} | {question[:60]}")
|
|
193
|
-
except Exception as e:
|
|
194
|
-
fail += 1
|
|
195
|
-
print(f" [{i+1}/{len(normalized_plan)}] FAIL {question[:60]}: {e}")
|
|
196
|
-
|
|
197
|
-
if not dry_run:
|
|
198
|
-
print(f"\n[import] Done: {ok} OK, {fail} failed")
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def main():
|
|
202
|
-
ap = argparse.ArgumentParser()
|
|
203
|
-
src = ap.add_mutually_exclusive_group(required=True)
|
|
204
|
-
src.add_argument("--qa-file", dest="qa_file",
|
|
205
|
-
help="Path to a pre-merged qa_plan_<ts>.json")
|
|
206
|
-
src.add_argument("--from-dir", dest="from_dir",
|
|
207
|
-
help="Merge all *.json under this directory (each = QA list); "
|
|
208
|
-
"merged result is written to OUTPUTS_DIR/qa_plan_<ts>.json")
|
|
209
|
-
ap.add_argument("--dry-run", action="store_true", dest="dry_run")
|
|
210
|
-
ap.add_argument("--max-depth", type=int, default=3,
|
|
211
|
-
help="Maximum QA directory depth (default: 3; override only when explicitly requested)")
|
|
212
|
-
args = ap.parse_args()
|
|
213
|
-
if args.max_depth < 1:
|
|
214
|
-
ap.error("--max-depth must be positive")
|
|
215
|
-
run(args.qa_file, args.from_dir, args.dry_run, args.max_depth)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if __name__ == "__main__":
|
|
219
|
-
main()
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Import URL/Word/PDF sources as DOCUMENT knowledge.
|
|
3
|
-
|
|
4
|
-
Input is a JSON list of records:
|
|
5
|
-
{"source": "https://...", "dir_path": ["一级", "二级", "三级"]}
|
|
6
|
-
{"source": "/mounted/chat-files/manual.docx", "dir_path": ["..."]}
|
|
7
|
-
|
|
8
|
-
The default is a dry-run. URL duplicates in the input are reported as
|
|
9
|
-
``skipped_duplicate``; the API response is retained because the backend may
|
|
10
|
-
also deduplicate URLs already present in the knowledge base.
|
|
11
|
-
"""
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import argparse
|
|
15
|
-
import json
|
|
16
|
-
import os
|
|
17
|
-
import sys
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
sys.path.insert(0, os.path.join(os.environ.get("SOLVEA_LIB_DIR", os.path.abspath(
|
|
21
|
-
os.path.join(os.path.dirname(__file__), "..", "..", "__lib__"))), "skill-runtime"))
|
|
22
|
-
from bootstrap import * # noqa: F403
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def validate_record(item, index, max_depth=3):
|
|
26
|
-
if not isinstance(item, dict) or not isinstance(item.get("source"), str) or not item["source"].strip():
|
|
27
|
-
raise ValueError(f"source document #{index} needs a non-empty source")
|
|
28
|
-
path = item.get("dir_path", [])
|
|
29
|
-
if not isinstance(path, list) or any(not isinstance(x, str) or not x.strip() for x in path):
|
|
30
|
-
raise ValueError(f"source document #{index} dir_path must be a list of non-empty names")
|
|
31
|
-
if len(path) > max_depth:
|
|
32
|
-
raise ValueError(f"source document #{index} dir_path has {len(path)} levels; maximum is {max_depth}")
|
|
33
|
-
if any("/" in x or "\\" in x for x in path):
|
|
34
|
-
raise ValueError(f"source document #{index} dir_path entries cannot contain slashes")
|
|
35
|
-
source = item["source"].strip()
|
|
36
|
-
is_url = source.lower().startswith(("http://", "https://"))
|
|
37
|
-
if not is_url and not Path(source).is_file():
|
|
38
|
-
raise ValueError(f"source document #{index} file not found: {source}")
|
|
39
|
-
if not is_url and Path(source).suffix.lower() not in {".doc", ".docx", ".pdf"}:
|
|
40
|
-
raise ValueError(f"source document #{index} supports .doc/.docx/.pdf, got {Path(source).suffix}")
|
|
41
|
-
return {"source": source, "dir_path": [x.strip() for x in path], "is_url": is_url}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def validate_plan(data, max_depth=3):
|
|
45
|
-
if not isinstance(data, list):
|
|
46
|
-
raise ValueError("source document plan must be a JSON list")
|
|
47
|
-
normalized = [validate_record(item, i + 1, max_depth) for i, item in enumerate(data)]
|
|
48
|
-
seen = set()
|
|
49
|
-
for item in normalized:
|
|
50
|
-
if item["is_url"]:
|
|
51
|
-
item["status"] = "skipped_duplicate" if item["source"] in seen else "pending"
|
|
52
|
-
seen.add(item["source"])
|
|
53
|
-
else:
|
|
54
|
-
item["status"] = "pending"
|
|
55
|
-
return normalized
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _flatten(nodes, out=None):
|
|
59
|
-
out = [] if out is None else out
|
|
60
|
-
for node in nodes:
|
|
61
|
-
out.append(node)
|
|
62
|
-
_flatten(node.get("children", []), out)
|
|
63
|
-
return out
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def resolve_dir(client, path, cache):
|
|
67
|
-
parent = 0
|
|
68
|
-
prefix = []
|
|
69
|
-
for name in path:
|
|
70
|
-
prefix.append(name)
|
|
71
|
-
key = "/".join(prefix)
|
|
72
|
-
if key in cache:
|
|
73
|
-
parent = cache[key]
|
|
74
|
-
continue
|
|
75
|
-
raw = client.list_directories()
|
|
76
|
-
nodes = raw if isinstance(raw, list) else raw.get("list", raw.get("records", raw.get("data", [])))
|
|
77
|
-
match = next((x for x in _flatten(nodes) if x.get("name") == name and (x.get("parentId") or 0) == parent), None)
|
|
78
|
-
if match:
|
|
79
|
-
parent = match["id"]
|
|
80
|
-
else:
|
|
81
|
-
result = client.create_folder(name, parent_id=parent)
|
|
82
|
-
parent = result.get("id") if isinstance(result, dict) else None
|
|
83
|
-
if not parent:
|
|
84
|
-
raise RuntimeError(f"could not create directory {key!r}: {result}")
|
|
85
|
-
cache[key] = parent
|
|
86
|
-
return parent
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def run(plan_file, dry_run=True, max_depth=3):
|
|
90
|
-
data = json.loads(Path(plan_file).read_text(encoding="utf-8"))
|
|
91
|
-
plan = validate_plan(data, max_depth)
|
|
92
|
-
print(f"[source-documents] {len(plan)} records (dry_run={dry_run})")
|
|
93
|
-
for item in plan:
|
|
94
|
-
print(f" [{item['status']}] {item['source']} -> {'/'.join(item['dir_path']) or '(root)'}")
|
|
95
|
-
if dry_run:
|
|
96
|
-
return plan
|
|
97
|
-
|
|
98
|
-
from client import SolveaClient
|
|
99
|
-
client = SolveaClient()
|
|
100
|
-
cache = {}
|
|
101
|
-
results = []
|
|
102
|
-
for item in plan:
|
|
103
|
-
if item["status"] == "skipped_duplicate":
|
|
104
|
-
results.append({**item, "result": None})
|
|
105
|
-
continue
|
|
106
|
-
try:
|
|
107
|
-
directory_id = resolve_dir(client, item["dir_path"], cache)
|
|
108
|
-
if item["is_url"]:
|
|
109
|
-
documents = [{"filePath": item["source"], "docType": "TC", "fileType": "website"}]
|
|
110
|
-
response = client.create_documents(documents, directory_id=directory_id, task_type="WEB")
|
|
111
|
-
else:
|
|
112
|
-
key = client.upload_temp_file(item["source"], for_knowledge=True)
|
|
113
|
-
# The file-document endpoint derives the loader from the uploaded
|
|
114
|
-
# object key; unlike WEBSITE, it does not require fileType.
|
|
115
|
-
documents = [{"filePath": key, "docType": "TC"}]
|
|
116
|
-
response = client.create_documents(documents, directory_id=directory_id, task_type="FILE")
|
|
117
|
-
if not response:
|
|
118
|
-
raise RuntimeError("document API returned no created or refreshed record")
|
|
119
|
-
results.append({**item, "status": "submitted", "directory_id": directory_id, "result": response})
|
|
120
|
-
print(f" [submitted] {item['source']}")
|
|
121
|
-
except Exception as exc:
|
|
122
|
-
results.append({**item, "status": "failed", "error": str(exc)})
|
|
123
|
-
print(f" [failed] {item['source']}: {exc}", file=sys.stderr)
|
|
124
|
-
out = OUTPUTS_DIR / f"source_document_import_{timestamp()}.json"
|
|
125
|
-
out.parent.mkdir(parents=True, exist_ok=True)
|
|
126
|
-
out.write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
127
|
-
print(f"[source-documents] result={out}")
|
|
128
|
-
return results
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def main():
|
|
132
|
-
ap = argparse.ArgumentParser()
|
|
133
|
-
ap.add_argument("--plan-file", required=True)
|
|
134
|
-
ap.add_argument("--execute", action="store_true", help="Submit documents after explicit user confirmation")
|
|
135
|
-
ap.add_argument("--max-depth", type=int, default=3)
|
|
136
|
-
args = ap.parse_args()
|
|
137
|
-
if args.max_depth < 1:
|
|
138
|
-
ap.error("--max-depth must be positive")
|
|
139
|
-
run(args.plan_file, dry_run=not args.execute, max_depth=args.max_depth)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if __name__ == "__main__":
|
|
143
|
-
main()
|