@tencent-rtc/trtc-agent-skills 0.1.3 → 0.1.4

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.
Files changed (360) hide show
  1. package/.cursor/rules/main.mdc +12 -0
  2. package/AGENTS.md +14 -104
  3. package/CLAUDE.md +15 -127
  4. package/CODEBUDDY.md +32 -118
  5. package/README.md +7 -5
  6. package/README.zh.md +7 -5
  7. package/bin/cli.js +133 -41
  8. package/hooks/__pycache__/cursor-adapter.cpython-313.pyc +0 -0
  9. package/hooks/cursor-adapter.py +45 -18
  10. package/hooks/hooks-cursor.json +5 -18
  11. package/hooks/hooks.json +6 -31
  12. package/knowledge-base/conference/web/index.yaml +143 -0
  13. package/knowledge-base/platform-slice-template.md +1041 -133
  14. package/knowledge-base/products.yaml +76 -0
  15. package/knowledge-base/scenario-spec.md +1316 -115
  16. package/knowledge-base/scenarios/conference/base/general-conference.md +22 -2
  17. package/knowledge-base/scenarios/conference/base/webinar-conference.md +14 -0
  18. package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +19 -0
  19. package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +13 -0
  20. package/knowledge-base/scenarios/live/entertainment-live-room.md +12 -0
  21. package/knowledge-base/slice-spec.md +2377 -299
  22. package/knowledge-base/slices/conference/web/integration-audit.md +1 -1
  23. package/knowledge-base/slices/conference/web/login-auth.md +14 -1
  24. package/knowledge-base/tooling/aliases.yaml +92 -0
  25. package/knowledge-base/tooling/intent-signals.yaml +181 -0
  26. package/knowledge-base/tooling/symptom-keywords.yaml +21 -0
  27. package/package.json +1 -1
  28. package/skills/trtc/SKILL.md +202 -245
  29. package/skills/trtc/hooks/__pycache__/report_prompt.cpython-313.pyc +0 -0
  30. package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_read.py +12 -8
  31. package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_write.py +16 -12
  32. package/skills/{trtc-topic/guardrails → trtc/hooks}/stop_require_apply_evidence.py +22 -14
  33. package/skills/trtc/hooks/topic_phase_gate.py +161 -0
  34. package/skills/{trtc-topic → trtc}/runtime/README.md +2 -2
  35. package/skills/{trtc-onboarding/reference/reporting-protocol.md → trtc/runtime/REPORTING.md} +5 -5
  36. package/skills/{trtc-topic → trtc}/runtime/RUNTIME.md +5 -5
  37. package/skills/trtc/runtime/lib/__init__.py +0 -0
  38. package/skills/{trtc-topic → trtc}/runtime/package-lock.json +2 -2
  39. package/skills/{trtc-topic → trtc}/runtime/package.json +2 -2
  40. package/skills/{trtc-topic → trtc}/runtime/telemetry-bridge.mjs +1 -1
  41. package/skills/{trtc-topic/scripts → trtc/tools}/STATE-MACHINE-GUIDE.md +23 -23
  42. package/skills/trtc/tools/__init__.py +2 -0
  43. package/skills/trtc/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  44. package/skills/trtc/tools/__pycache__/query_classifier.cpython-313.pyc +0 -0
  45. package/skills/trtc/tools/__pycache__/reporting.cpython-313.pyc +0 -0
  46. package/skills/trtc/tools/__pycache__/search.cpython-313.pyc +0 -0
  47. package/skills/trtc/tools/__pycache__/session.cpython-313.pyc +0 -0
  48. package/skills/trtc/tools/apply.py +540 -0
  49. package/skills/trtc/tools/docs.py +712 -0
  50. package/skills/trtc/tools/docsbot.py +182 -0
  51. package/skills/trtc/tools/entry/render_ai_instructions.py +92 -0
  52. package/skills/trtc/tools/flow.py +1089 -0
  53. package/skills/{trtc-topic/scripts → trtc/tools}/init_slice_queue.py +5 -4
  54. package/skills/{trtc-topic/scripts → trtc/tools}/next_slice.py +5 -4
  55. package/skills/trtc/tools/query_classifier.py +301 -0
  56. package/skills/trtc/tools/reporting.py +447 -0
  57. package/skills/trtc/tools/search.py +817 -0
  58. package/skills/trtc/tools/session.py +1261 -0
  59. package/skills/trtc/tools/state_machine.py +690 -0
  60. package/skills/trtc-ai-service/README.md +195 -0
  61. package/skills/trtc-ai-service/README.zh-CN.md +193 -0
  62. package/skills/trtc-ai-service/SKILL.md +945 -0
  63. package/skills/trtc-ai-service/auto_adapters/README.md +40 -0
  64. package/skills/trtc-ai-service/auto_adapters/frontend-spa/README.md +27 -0
  65. package/skills/trtc-ai-service/auto_adapters/frontend-spa/angular/voice-agent.component.ts.tpl +131 -0
  66. package/skills/trtc-ai-service/auto_adapters/frontend-spa/manifest.yaml +57 -0
  67. package/skills/trtc-ai-service/auto_adapters/frontend-spa/react/VoiceAgent.tsx.tpl +142 -0
  68. package/skills/trtc-ai-service/auto_adapters/frontend-spa/vue/VoiceAgent.vue.tpl +121 -0
  69. package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-backend.md +45 -0
  70. package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-frontend.md +51 -0
  71. package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-rest-api.md +93 -0
  72. package/skills/trtc-ai-service/auto_adapters/java-backend/README.md +25 -0
  73. package/skills/trtc-ai-service/auto_adapters/java-backend/manifest.yaml +30 -0
  74. package/skills/trtc-ai-service/auto_adapters/java-backend/quarkus/VoiceAgentFilter.java.tpl +64 -0
  75. package/skills/trtc-ai-service/auto_adapters/java-backend/springboot/VoiceAgentFilter.java.tpl +91 -0
  76. package/skills/trtc-ai-service/auto_adapters/manifest.yaml +43 -0
  77. package/skills/trtc-ai-service/auto_adapters/node-backend/README.md +25 -0
  78. package/skills/trtc-ai-service/auto_adapters/node-backend/express.js.tpl +40 -0
  79. package/skills/trtc-ai-service/auto_adapters/node-backend/fastify.js.tpl +27 -0
  80. package/skills/trtc-ai-service/auto_adapters/node-backend/koa.js.tpl +31 -0
  81. package/skills/trtc-ai-service/auto_adapters/node-backend/manifest.yaml +47 -0
  82. package/skills/trtc-ai-service/auto_adapters/python-backend/README.md +22 -0
  83. package/skills/trtc-ai-service/auto_adapters/python-backend/django.py.tpl +32 -0
  84. package/skills/trtc-ai-service/auto_adapters/python-backend/fastapi.py.tpl +35 -0
  85. package/skills/trtc-ai-service/auto_adapters/python-backend/flask.py.tpl +31 -0
  86. package/skills/trtc-ai-service/auto_adapters/python-backend/manifest.yaml +45 -0
  87. package/skills/trtc-ai-service/capabilities/__init__.py +43 -0
  88. package/skills/trtc-ai-service/capabilities/conversation-core/.env.example +29 -0
  89. package/skills/trtc-ai-service/capabilities/conversation-core/INTEGRATION.md +134 -0
  90. package/skills/trtc-ai-service/capabilities/conversation-core/INTERFACE_ADAPT.md +111 -0
  91. package/skills/trtc-ai-service/capabilities/conversation-core/QUICK_START.md +62 -0
  92. package/skills/trtc-ai-service/capabilities/conversation-core/manifest.yaml +250 -0
  93. package/skills/trtc-ai-service/capabilities/conversation-core/requirements.txt +6 -0
  94. package/skills/trtc-ai-service/capabilities/conversation-core/src/__init__.py +10 -0
  95. package/skills/trtc-ai-service/capabilities/conversation-core/src/_capability_loader.py +218 -0
  96. package/skills/trtc-ai-service/capabilities/conversation-core/src/agent.py +231 -0
  97. package/skills/trtc-ai-service/capabilities/conversation-core/src/credentials.py +132 -0
  98. package/skills/trtc-ai-service/capabilities/conversation-core/src/health.py +355 -0
  99. package/skills/trtc-ai-service/capabilities/conversation-core/src/log_filter.py +76 -0
  100. package/skills/trtc-ai-service/capabilities/conversation-core/src/modality.py +109 -0
  101. package/skills/trtc-ai-service/capabilities/conversation-core/src/server.py +312 -0
  102. package/skills/trtc-ai-service/capabilities/conversation-core/src/trtc_client.py +315 -0
  103. package/skills/trtc-ai-service/capabilities/conversation-core/src/usersig.py +90 -0
  104. package/skills/trtc-ai-service/capabilities/conversation-core/tests/test_skeleton.py +216 -0
  105. package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/README.md +48 -0
  106. package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/app.js +415 -0
  107. package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/index.html +68 -0
  108. package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/styles.css +136 -0
  109. package/skills/trtc-ai-service/capabilities/digital-human/README.md +31 -0
  110. package/skills/trtc-ai-service/capabilities/digital-human/manifest.yaml +64 -0
  111. package/skills/trtc-ai-service/capabilities/digital-human/src/__init__.py +2 -0
  112. package/skills/trtc-ai-service/capabilities/digital-human/src/router.py +43 -0
  113. package/skills/trtc-ai-service/capabilities/human-handoff/INTERFACE_ADAPT.md +353 -0
  114. package/skills/trtc-ai-service/capabilities/human-handoff/README.md +44 -0
  115. package/skills/trtc-ai-service/capabilities/human-handoff/manifest.yaml +227 -0
  116. package/skills/trtc-ai-service/capabilities/human-handoff/src/__init__.py +2 -0
  117. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/__init__.py +9 -0
  118. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/default_rest.py +242 -0
  119. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/factory.py +89 -0
  120. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/local_queue.py +258 -0
  121. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/mock.py +132 -0
  122. package/skills/trtc-ai-service/capabilities/human-handoff/src/core/__init__.py +25 -0
  123. package/skills/trtc-ai-service/capabilities/human-handoff/src/core/intent_detector.py +75 -0
  124. package/skills/trtc-ai-service/capabilities/human-handoff/src/core/models.py +163 -0
  125. package/skills/trtc-ai-service/capabilities/human-handoff/src/core/service.py +192 -0
  126. package/skills/trtc-ai-service/capabilities/human-handoff/src/feedback_store.py +54 -0
  127. package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/__init__.py +4 -0
  128. package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/handoff_client.py +86 -0
  129. package/skills/trtc-ai-service/capabilities/human-handoff/src/queue.py +62 -0
  130. package/skills/trtc-ai-service/capabilities/human-handoff/src/router.py +201 -0
  131. package/skills/trtc-ai-service/capabilities/human-handoff/src/summary_link.py +77 -0
  132. package/skills/trtc-ai-service/capabilities/human-handoff/src/trigger.py +25 -0
  133. package/skills/trtc-ai-service/capabilities/knowledge-base/INTERFACE_ADAPT.md +297 -0
  134. package/skills/trtc-ai-service/capabilities/knowledge-base/README.md +51 -0
  135. package/skills/trtc-ai-service/capabilities/knowledge-base/data/faq.json +20 -0
  136. package/skills/trtc-ai-service/capabilities/knowledge-base/manifest.yaml +211 -0
  137. package/skills/trtc-ai-service/capabilities/knowledge-base/src/__init__.py +8 -0
  138. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/__init__.py +9 -0
  139. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/default_rest.py +209 -0
  140. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/factory.py +86 -0
  141. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/local_json.py +172 -0
  142. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/mock.py +91 -0
  143. package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/__init__.py +12 -0
  144. package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/models.py +77 -0
  145. package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/scoring.py +73 -0
  146. package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/service.py +78 -0
  147. package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/__init__.py +4 -0
  148. package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/kb_client.py +61 -0
  149. package/skills/trtc-ai-service/capabilities/knowledge-base/src/retriever.py +56 -0
  150. package/skills/trtc-ai-service/capabilities/knowledge-base/src/router.py +85 -0
  151. package/skills/trtc-ai-service/capabilities/session-summary/INTERFACE_ADAPT.md +99 -0
  152. package/skills/trtc-ai-service/capabilities/session-summary/README.md +47 -0
  153. package/skills/trtc-ai-service/capabilities/session-summary/data/test_session.json +18 -0
  154. package/skills/trtc-ai-service/capabilities/session-summary/manifest.yaml +165 -0
  155. package/skills/trtc-ai-service/capabilities/session-summary/src/__init__.py +2 -0
  156. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/__init__.py +5 -0
  157. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/base.py +31 -0
  158. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/default_rest.py +67 -0
  159. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/factory.py +51 -0
  160. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/local_json.py +42 -0
  161. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/mock.py +22 -0
  162. package/skills/trtc-ai-service/capabilities/session-summary/src/recorder.py +210 -0
  163. package/skills/trtc-ai-service/capabilities/session-summary/src/router.py +93 -0
  164. package/skills/trtc-ai-service/capabilities/session-summary/src/summarizer.py +163 -0
  165. package/skills/trtc-ai-service/capabilities/tool-calling/INTERFACE_ADAPT.md +158 -0
  166. package/skills/trtc-ai-service/capabilities/tool-calling/README.md +50 -0
  167. package/skills/trtc-ai-service/capabilities/tool-calling/data/tools.yaml +58 -0
  168. package/skills/trtc-ai-service/capabilities/tool-calling/examples/__init__.py +1 -0
  169. package/skills/trtc-ai-service/capabilities/tool-calling/examples/local_tools.py +101 -0
  170. package/skills/trtc-ai-service/capabilities/tool-calling/manifest.yaml +146 -0
  171. package/skills/trtc-ai-service/capabilities/tool-calling/src/__init__.py +8 -0
  172. package/skills/trtc-ai-service/capabilities/tool-calling/src/dispatcher.py +54 -0
  173. package/skills/trtc-ai-service/capabilities/tool-calling/src/registry.py +219 -0
  174. package/skills/trtc-ai-service/capabilities/tool-calling/src/router.py +50 -0
  175. package/skills/trtc-ai-service/references/business-contract-spec.md +263 -0
  176. package/skills/trtc-ai-service/scenarios/custom-builder/README.md +86 -0
  177. package/skills/trtc-ai-service/scenarios/custom-builder/output-templates/recipe.yaml.j2 +194 -0
  178. package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q1-business-scenario.md +43 -0
  179. package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q2-io-modality.md +57 -0
  180. package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q3-ui-form.md +55 -0
  181. package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q4-capabilities.md +78 -0
  182. package/skills/trtc-ai-service/scenarios/customer-service/README.md +114 -0
  183. package/skills/trtc-ai-service/scenarios/customer-service/recipe.yaml +154 -0
  184. package/skills/trtc-ai-service/scenarios/customer-service/sample-data/README.md +32 -0
  185. package/skills/trtc-ai-service/scenarios/customer-service/sample-data/faq-sample.json +37 -0
  186. package/skills/trtc-ai-service/scenarios/customer-service/system-prompt.template.md +94 -0
  187. package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/app.js +347 -0
  188. package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/index.html +125 -0
  189. package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/styles.css +487 -0
  190. package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/tokens.css +71 -0
  191. package/skills/trtc-ai-service/scenarios/customer-service/ui/design-system/DESIGN_GUIDELINES.md +370 -0
  192. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/README.md +68 -0
  193. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/app.js +1307 -0
  194. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/data.js +40 -0
  195. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/index.html +233 -0
  196. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/mock-shop.json +21 -0
  197. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/styles.css +603 -0
  198. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/tokens.css +71 -0
  199. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/agent-link.js +323 -0
  200. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/app.js +458 -0
  201. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/index.html +109 -0
  202. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/styles.css +489 -0
  203. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/tokens.css +59 -0
  204. package/skills/trtc-ai-service/scripts/add-capability.py +364 -0
  205. package/skills/trtc-ai-service/scripts/contract-adapt.py +334 -0
  206. package/skills/trtc-ai-service/scripts/detect-stack.py +40 -0
  207. package/skills/trtc-ai-service/scripts/lib/__init__.py +20 -0
  208. package/skills/trtc-ai-service/scripts/lib/adapter_codegen.py +509 -0
  209. package/skills/trtc-ai-service/scripts/lib/arbitrator.py +152 -0
  210. package/skills/trtc-ai-service/scripts/lib/contract_resolver.py +519 -0
  211. package/skills/trtc-ai-service/scripts/lib/credential_validators.py +253 -0
  212. package/skills/trtc-ai-service/scripts/lib/curl_parser.py +303 -0
  213. package/skills/trtc-ai-service/scripts/lib/degrader.py +140 -0
  214. package/skills/trtc-ai-service/scripts/lib/injector.py +347 -0
  215. package/skills/trtc-ai-service/scripts/lib/manifest_resolver.py +288 -0
  216. package/skills/trtc-ai-service/scripts/lib/openapi_parser.py +289 -0
  217. package/skills/trtc-ai-service/scripts/lib/stack_detector.py +159 -0
  218. package/skills/trtc-ai-service/scripts/lib/tokens_compile.py +204 -0
  219. package/skills/trtc-ai-service/scripts/post-install-patch.py +225 -0
  220. package/skills/trtc-ai-service/scripts/setup-credentials.py +393 -0
  221. package/skills/trtc-ai-service/scripts/verify-credentials.py +108 -0
  222. package/skills/trtc-ai-service/start.sh +111 -0
  223. package/skills/trtc-ai-service/tests/__init__.py +1 -0
  224. package/skills/trtc-ai-service/tests/test_arbitrator.py +64 -0
  225. package/skills/trtc-ai-service/tests/test_capability_overlay.py +85 -0
  226. package/skills/trtc-ai-service/tests/test_contract_resolver.py +190 -0
  227. package/skills/trtc-ai-service/tests/test_handoff_ports.py +195 -0
  228. package/skills/trtc-ai-service/tests/test_kb_ports.py +195 -0
  229. package/skills/trtc-ai-service/tests/test_manifest_resolver.py +95 -0
  230. package/skills/trtc-ai-service/tests/test_recipe_assembly.py +175 -0
  231. package/skills/trtc-ai-service/tests/test_stack_and_degrader.py +101 -0
  232. package/skills/trtc-ai-service/tests/test_verify_credentials.py +285 -0
  233. package/skills/trtc-ai-service/triggers.yaml +29 -0
  234. package/skills/trtc-conference/SKILL.md +324 -0
  235. package/skills/trtc-conference/flows/onboarding.md +205 -0
  236. package/skills/trtc-conference/flows/topic.md +474 -0
  237. package/skills/trtc-conference/flows/troubleshoot.md +85 -0
  238. package/skills/trtc-conference/hooks/pretooluse_require_business_decisions.py +213 -0
  239. package/skills/trtc-conference/playbooks/medical-quickstart.md +84 -0
  240. package/skills/trtc-conference/playbooks/official-roomkit.md +97 -0
  241. package/skills/trtc-conference/references/local-usersig/basic-info-config.ts +39 -0
  242. package/skills/trtc-conference/references/usersig-handling.md +134 -0
  243. package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
  244. package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
  245. package/skills/trtc-conference/tests/__pycache__/test_conference_onboarding_contract.cpython-313-pytest-9.0.2.pyc +0 -0
  246. package/skills/trtc-conference/tests/__pycache__/test_conference_topic_flow_contract.cpython-313-pytest-9.0.2.pyc +0 -0
  247. package/skills/trtc-conference/tests/test_conference_index_contract.py +43 -0
  248. package/skills/trtc-conference/tests/test_conference_onboarding_contract.py +103 -0
  249. package/skills/trtc-conference/tests/test_conference_template_contract.py +25 -0
  250. package/skills/trtc-conference/tests/test_conference_topic_flow_contract.py +132 -0
  251. package/skills/trtc-conference/tools/apply_checks.py +328 -0
  252. package/skills/trtc-conference/verify_lib/__init__.py +0 -0
  253. package/skills/{trtc-apply/guardrails/apply_lib → trtc-conference/verify_lib}/rule_parser.py +1 -1
  254. package/skills/trtc-docs/SKILL.md +91 -119
  255. package/.cursor/rules/ui-mode.mdc +0 -99
  256. package/ai-instructions/base.md +0 -13
  257. package/ai-instructions/ui-mode.md +0 -93
  258. package/knowledge-base/index.yaml +0 -462
  259. package/skills/trtc/room-builder/SKILL.md +0 -133
  260. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +0 -108
  261. package/skills/trtc/room-builder/tools/render_ai_instructions.py +0 -226
  262. package/skills/trtc-apply/SKILL.md +0 -97
  263. package/skills/trtc-onboarding/SKILL.md +0 -841
  264. package/skills/trtc-onboarding/reference/path-a1-demo.md +0 -103
  265. package/skills/trtc-onboarding/reference/path-a2-integrate.md +0 -737
  266. package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +0 -186
  267. package/skills/trtc-onboarding/reference/path-c-expand.md +0 -43
  268. package/skills/trtc-onboarding/reference/supported-matrix.md +0 -100
  269. package/skills/trtc-search/SKILL.md +0 -228
  270. package/skills/trtc-topic/SKILL.md +0 -622
  271. package/skills/trtc-topic/scripts/apply.py +0 -581
  272. package/skills/trtc-topic/scripts/lib/state_machine.py +0 -328
  273. package/skills/trtc-topic/tests/README.md +0 -70
  274. package/skills/trtc-topic/tests/conftest.py +0 -72
  275. package/skills/trtc-topic/tests/test_apply_cli.py +0 -480
  276. package/skills/trtc-topic/tests/test_end_to_end.py +0 -305
  277. package/skills/trtc-topic/tests/test_finalize_session.py +0 -51
  278. package/skills/trtc-topic/tests/test_gates.py +0 -316
  279. package/skills/trtc-topic/tests/test_session_resolver.py +0 -260
  280. package/skills/trtc-topic/tests/test_state_machine.py +0 -414
  281. package/skills/trtc-topic/tests/test_stop_require_apply.py +0 -99
  282. package/skills/trtc-topic/tests/test_topic_skill_invariants.py +0 -130
  283. /package/skills/{trtc-topic → trtc}/runtime/lib/platforms.py +0 -0
  284. /package/skills/{trtc-topic → trtc}/runtime/telemetry_collector.py +0 -0
  285. /package/skills/{trtc-onboarding/reference → trtc/runtime}/usersig-handling.md +0 -0
  286. /package/skills/{trtc-topic/scripts → trtc/tools}/finalize_session.py +0 -0
  287. /package/skills/{trtc-apply/guardrails/apply_lib → trtc-ai-service/capabilities/conversation-core/tests}/__init__.py +0 -0
  288. /package/skills/{trtc-topic → trtc-conference}/references/execution-units.yaml +0 -0
  289. /package/skills/{trtc/room-builder/templates/scenarios/medical-consultation/src/config → trtc-conference/references/local-usersig}/lib-generate-test-usersig-es.min.d.ts +0 -0
  290. /package/skills/{trtc/room-builder/templates/scenarios/medical-consultation/src/config → trtc-conference/references/local-usersig}/lib-generate-test-usersig-es.min.js +0 -0
  291. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/backend-contract.zh-CN.md +0 -0
  292. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/integration.zh-CN.md +0 -0
  293. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/theme.zh-CN.md +0 -0
  294. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/index.html +0 -0
  295. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/package.json +0 -0
  296. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/postcss.config.js +0 -0
  297. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/App.vue +0 -0
  298. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/ConsultationManagePanel.vue +0 -0
  299. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LanguageSwitch.vue +0 -0
  300. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LoadingSpinner.vue +0 -0
  301. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalAlert.vue +0 -0
  302. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalBusinessPanel.vue +0 -0
  303. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalButton.vue +0 -0
  304. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalConfirmDialog.vue +0 -0
  305. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalDataPanel.vue +0 -0
  306. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalRecordPanel.vue +0 -0
  307. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/PrescriptionPanel.vue +0 -0
  308. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/basic-info-config.ts +0 -0
  309. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/runtime-config.ts +0 -0
  310. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/env.d.ts +0 -0
  311. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +0 -0
  312. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +0 -0
  313. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +0 -0
  314. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +0 -0
  315. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +0 -0
  316. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +0 -0
  317. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/types.ts +0 -0
  318. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationChat.ts +0 -0
  319. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationDevices.ts +0 -0
  320. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationParticipants.ts +0 -0
  321. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationPermissions.ts +0 -0
  322. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/utils.ts +0 -0
  323. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/en-US/index.ts +0 -0
  324. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/index.ts +0 -0
  325. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/medicalTranslate.ts +0 -0
  326. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/state.ts +0 -0
  327. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/zh-CN/index.ts +0 -0
  328. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/main.ts +0 -0
  329. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/appointments.ts +0 -0
  330. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/users.ts +0 -0
  331. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/router/index.ts +0 -0
  332. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/index.ts +0 -0
  333. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/appointmentService.ts +0 -0
  334. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/authService.ts +0 -0
  335. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/launchContext.ts +0 -0
  336. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/userService.ts +0 -0
  337. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/appointmentService.ts +0 -0
  338. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/authService.ts +0 -0
  339. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/userService.ts +0 -0
  340. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/types.ts +0 -0
  341. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/shared/icons.ts +0 -0
  342. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/index.css +0 -0
  343. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/tailwind.css +0 -0
  344. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/theme.css +0 -0
  345. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/auth.ts +0 -0
  346. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/format.ts +0 -0
  347. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/navigation.ts +0 -0
  348. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/session.ts +0 -0
  349. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorConsultationView.vue +0 -0
  350. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorDashboardView.vue +0 -0
  351. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/LoginView.vue +0 -0
  352. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationFinishedView.vue +0 -0
  353. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationView.vue +0 -0
  354. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientSelectDoctorView.vue +0 -0
  355. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientWaitingView.vue +0 -0
  356. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.json +0 -0
  357. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.node.json +0 -0
  358. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/vite.config.ts +0 -0
  359. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation//346/216/245/345/205/245/350/257/264/346/230/216.md" +0 -0
  360. /package/skills/{trtc-topic/runtime/lib → trtc-conference/tools}/__init__.py +0 -0
@@ -0,0 +1,712 @@
1
+ """
2
+ tools/docs.py
3
+ ==============
4
+
5
+ TRTC AI Integration —— 官方文档检索编排工具。
6
+
7
+ 职责边界:
8
+ - 输入已确定的 product / platform / query / intent
9
+ - 先做 slice-first gate(仅 slice-lookup)
10
+ - 读取 trtc.io llms index,做 heading scoring
11
+ - 抓取目标 .md 文档并提取高相关 snippet
12
+ - 返回结构化 source bundle
13
+
14
+ 不负责:
15
+ - 意图推断
16
+ - session / interruption 状态写入
17
+ - 最终面向用户的自然语言回答
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import os
24
+ import re
25
+ import sys
26
+ import unicodedata
27
+ from dataclasses import dataclass
28
+ from pathlib import Path
29
+ from typing import Any, Optional
30
+ from urllib.error import HTTPError, URLError
31
+ from urllib.request import urlopen
32
+
33
+ import yaml
34
+
35
+ try:
36
+ from tools.search import InvalidInputError as SearchInvalidInputError
37
+ from tools.search import Search
38
+ from tools.search import SearchError
39
+ except ImportError:
40
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
41
+ from tools.search import InvalidInputError as SearchInvalidInputError # type: ignore
42
+ from tools.search import Search # type: ignore
43
+ from tools.search import SearchError # type: ignore
44
+
45
+
46
+ THIS_FILE = Path(__file__).resolve()
47
+ DEFAULT_REPO_ROOT = THIS_FILE.parent.parent.parent.parent
48
+ ENV_REPO_ROOT = "TRTC_REPO_ROOT"
49
+ BASE_TRTC_URL = "https://trtc.io"
50
+ KNOWN_PRODUCTS = {"conference", "chat", "call", "live", "rtc-engine"}
51
+ KNOWN_PLATFORMS = {"web", "android", "ios", "flutter", "electron", "unity"}
52
+ KNOWN_INTENTS = {"fact-lookup", "decision-lookup", "path-lookup", "slice-lookup"}
53
+ # 第一版把 heading 进入 resolved/ambiguous 路径的门槛压在 5 分:
54
+ # - 单个明确 heading 命中(title_hit=5)即可继续
55
+ # - 仅有很弱的 CJK 重叠/噪声匹配则仍留在 low_score_candidates
56
+ HEADING_RESOLVE_MIN_SCORE = 5
57
+ HIGH_VALUE_QUERY_KEYWORDS = {
58
+ "pricing", "bill", "quota", "usersig", "secretkey", "migration", "upgrade", "compatibility",
59
+ "计费", "配额", "鉴权", "密钥", "迁移", "升级", "兼容",
60
+ }
61
+ PLATFORM_SENSITIVE_KEYWORDS = {
62
+ "api", "参数", "callback", "sdk", "migration", "升级", "ios", "android", "web",
63
+ }
64
+ WORD_RE = re.compile(r"[A-Za-z0-9_]+|[一-鿿]+")
65
+ ERROR_CODE_RE = re.compile(r"(?<![A-Za-z0-9])(?:ERR[_-]?)?-?(\d{4,6})(?![A-Za-z0-9])", re.IGNORECASE)
66
+ LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
67
+ MD_HEADING_RE = re.compile(r"^(#{1,6})\s+(.+)$", re.MULTILINE)
68
+
69
+
70
+ class DocsError(Exception):
71
+ """docs.py 异常基类。"""
72
+
73
+
74
+ class InvalidInputError(DocsError):
75
+ """CLI 或 API 输入不合法。"""
76
+
77
+
78
+ class DependencyError(DocsError):
79
+ """本地依赖或解析依赖异常。"""
80
+
81
+
82
+ @dataclass(frozen=True)
83
+ class HeadingDoc:
84
+ title: str
85
+ url: str
86
+ description: str
87
+
88
+
89
+ @dataclass(frozen=True)
90
+ class HeadingCandidate:
91
+ heading: str
92
+ description: str
93
+ docs: tuple[HeadingDoc, ...]
94
+
95
+
96
+ @dataclass(frozen=True)
97
+ class ScoredHeading:
98
+ candidate: HeadingCandidate
99
+ score: int
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class DocChunk:
104
+ title: str
105
+ body: str
106
+ score: int
107
+
108
+
109
+ def _repo_root() -> Path:
110
+ env = os.environ.get(ENV_REPO_ROOT)
111
+ return Path(env).resolve() if env else DEFAULT_REPO_ROOT
112
+
113
+
114
+ def _kb_root() -> Path:
115
+ return _repo_root() / "knowledge-base"
116
+
117
+
118
+ def _products_yaml() -> Path:
119
+ return _kb_root() / "products.yaml"
120
+
121
+
122
+ def _load_yaml(path: Path) -> dict[str, Any]:
123
+ if not path.exists():
124
+ return {}
125
+ return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
126
+
127
+
128
+ def _normalize_basic(text: str) -> str:
129
+ text = unicodedata.normalize("NFKC", text or "").lower()
130
+ parts = WORD_RE.findall(text)
131
+ return " ".join(part.strip() for part in parts if part.strip())
132
+
133
+
134
+ def _tokenize(text: str) -> set[str]:
135
+ tokens: set[str] = set()
136
+ for part in WORD_RE.findall(text or ""):
137
+ if re.fullmatch(r"[A-Za-z0-9_]+", part):
138
+ tokens.add(part.lower())
139
+ continue
140
+ if len(part) == 1:
141
+ tokens.add(part)
142
+ continue
143
+ for i in range(len(part) - 1):
144
+ tokens.add(part[i : i + 2])
145
+ return tokens
146
+
147
+
148
+ def _iter_cjk_chars(text: str) -> set[str]:
149
+ return {ch for ch in text if "一" <= ch <= "鿿"}
150
+
151
+
152
+ def _cjk_overlap(a: str, b: str) -> int:
153
+ return len(_iter_cjk_chars(a) & _iter_cjk_chars(b))
154
+
155
+
156
+ def _extract_error_code(query: str) -> Optional[int]:
157
+ match = ERROR_CODE_RE.search(query or "")
158
+ if not match:
159
+ return None
160
+ return int(match.group(1))
161
+
162
+
163
+ def _product_llms_url(product: str) -> str:
164
+ index = _load_yaml(_products_yaml())
165
+ for raw in index.get("products") or []:
166
+ if str(raw.get("id") or "") == product:
167
+ llms_file = str(raw.get("llms_file") or "").strip()
168
+ if not llms_file:
169
+ break
170
+ if llms_file.startswith(("http://", "https://")):
171
+ return llms_file
172
+ return f"{BASE_TRTC_URL}/{llms_file.lstrip('/')}"
173
+ raise DependencyError(f"knowledge-base/products.yaml 缺少 product={product} 的 llms_file")
174
+
175
+
176
+ def _platform_llms_url(product: str, platform: str) -> str:
177
+ return f"{BASE_TRTC_URL}/llms/{product}/{platform}.txt"
178
+
179
+
180
+ def _conference_llms_url() -> str:
181
+ return f"{BASE_TRTC_URL}/llms/conference.txt"
182
+
183
+
184
+ def _fetch_text(url: str, timeout: float = 10.0) -> str:
185
+ with urlopen(url, timeout=timeout) as resp:
186
+ content_type = str(resp.headers.get("Content-Type") or "")
187
+ charset_match = re.search(r"charset=([\w-]+)", content_type)
188
+ encoding = charset_match.group(1) if charset_match else "utf-8"
189
+ return resp.read().decode(encoding, errors="replace")
190
+
191
+
192
+ def _looks_like_missing_platform_index(text: str) -> bool:
193
+ normalized = _normalize_basic(text)
194
+ return "404" in normalized or "not found" in normalized or "html" in normalized[:80]
195
+
196
+
197
+ def _needs_platform_index(query: str, platform: Optional[str], intent: str) -> bool:
198
+ if not platform:
199
+ return False
200
+ if intent == "path-lookup":
201
+ return True
202
+ normalized = _normalize_basic(query)
203
+ return any(keyword in normalized for keyword in PLATFORM_SENSITIVE_KEYWORDS)
204
+
205
+
206
+ def _parse_llms_index(text: str) -> list[HeadingCandidate]:
207
+ candidates: list[HeadingCandidate] = []
208
+ current_heading: Optional[str] = None
209
+ current_desc = ""
210
+ current_docs: list[HeadingDoc] = []
211
+
212
+ def flush() -> None:
213
+ nonlocal current_heading, current_desc, current_docs
214
+ if current_heading:
215
+ candidates.append(
216
+ HeadingCandidate(
217
+ heading=current_heading,
218
+ description=current_desc.strip(),
219
+ docs=tuple(current_docs),
220
+ )
221
+ )
222
+ current_heading = None
223
+ current_desc = ""
224
+ current_docs = []
225
+
226
+ for raw_line in text.splitlines():
227
+ line = raw_line.strip()
228
+ if not line:
229
+ continue
230
+ if line.startswith("## "):
231
+ flush()
232
+ current_heading = line[3:].strip()
233
+ continue
234
+ if current_heading is None:
235
+ continue
236
+ link_match = LINK_RE.search(line)
237
+ if link_match:
238
+ title = link_match.group(1).strip()
239
+ url = link_match.group(2).strip()
240
+ description = line[link_match.end() :].lstrip(" :-|\t")
241
+ if url and not url.startswith("http"):
242
+ url = f"{BASE_TRTC_URL}/{url.lstrip('/')}"
243
+ current_docs.append(HeadingDoc(title=title, url=url, description=description))
244
+ if not current_desc and description:
245
+ current_desc = description
246
+ continue
247
+ if not current_desc:
248
+ current_desc = line
249
+ flush()
250
+ return candidates
251
+
252
+
253
+ def _query_keyword_overlap(query: str, text: str) -> int:
254
+ normalized_query = _normalize_basic(query)
255
+ normalized_text = _normalize_basic(text)
256
+ return sum(1 for keyword in HIGH_VALUE_QUERY_KEYWORDS if keyword in normalized_query and keyword in normalized_text)
257
+
258
+
259
+ def _score_heading(query: str, candidate: HeadingCandidate, intent: str) -> int:
260
+ normalized_query = _normalize_basic(query)
261
+ query_tokens = _tokenize(normalized_query)
262
+ heading_text = _normalize_basic(candidate.heading)
263
+ desc_text = _normalize_basic(candidate.description)
264
+ heading_hits = len(query_tokens & _tokenize(heading_text))
265
+ desc_hits = len(query_tokens & _tokenize(desc_text))
266
+ combined = " ".join(part for part in [heading_text, desc_text] if part)
267
+ path_boost = 0
268
+ if intent == "path-lookup" and any(token in combined for token in ("migration", "upgrade", "compatibility", "迁移", "升级", "兼容")):
269
+ path_boost = 8
270
+ return (
271
+ heading_hits * 5
272
+ + desc_hits * 3
273
+ + _query_keyword_overlap(query, combined) * 2
274
+ + _cjk_overlap(normalized_query, combined)
275
+ + path_boost
276
+ )
277
+
278
+
279
+ def _score_chunk(query: str, title: str, body: str) -> int:
280
+ normalized_query = _normalize_basic(query)
281
+ query_tokens = _tokenize(normalized_query)
282
+ title_text = _normalize_basic(title)
283
+ body_text = _normalize_basic(body)
284
+ title_hits = len(query_tokens & _tokenize(title_text))
285
+ body_hits = len(query_tokens & _tokenize(body_text))
286
+ combined = " ".join(part for part in [title_text, body_text] if part)
287
+ return (
288
+ title_hits * 5
289
+ + body_hits * 3
290
+ + _query_keyword_overlap(query, combined) * 2
291
+ + _cjk_overlap(normalized_query, combined)
292
+ )
293
+
294
+
295
+ def _md_chunks(text: str, query: str) -> list[DocChunk]:
296
+ matches = list(MD_HEADING_RE.finditer(text))
297
+ if not matches:
298
+ body = text.strip()
299
+ score = _score_chunk(query, "document", body)
300
+ return [DocChunk(title="document", body=body[:800], score=score)] if score > 0 else []
301
+
302
+ chunks: list[DocChunk] = []
303
+ for idx, match in enumerate(matches):
304
+ title = match.group(2).strip()
305
+ start = match.end()
306
+ end = matches[idx + 1].start() if idx + 1 < len(matches) else len(text)
307
+ body = text[start:end].strip()
308
+ if not body:
309
+ continue
310
+ score = _score_chunk(query, title, body)
311
+ if score <= 0:
312
+ continue
313
+ chunks.append(DocChunk(title=title, body=body[:800], score=score))
314
+ chunks.sort(key=lambda item: item.score, reverse=True)
315
+ return chunks[:3]
316
+
317
+
318
+ def _ensure_md_url(url: str) -> str:
319
+ return url if url.endswith(".md") else f"{url}.md"
320
+
321
+
322
+ def _low_score_candidate(item: ScoredHeading) -> dict[str, str]:
323
+ first_doc = item.candidate.docs[0]
324
+ return {
325
+ "heading": item.candidate.heading,
326
+ "url": _ensure_md_url(first_doc.url),
327
+ }
328
+
329
+
330
+ def _score_heading_candidates(query: str, candidates: list[HeadingCandidate], intent: str) -> list[ScoredHeading]:
331
+ scored: list[ScoredHeading] = []
332
+ for candidate in candidates:
333
+ score = _score_heading(query, candidate, intent)
334
+ if score > 0 and candidate.docs:
335
+ scored.append(ScoredHeading(candidate=candidate, score=score))
336
+ scored.sort(key=lambda item: item.score, reverse=True)
337
+ return scored
338
+
339
+
340
+ def _build_source(kind: str, title: str, *, url: Optional[str], path: Optional[str], snippet: str, confidence: float, source_rank: int) -> dict[str, Any]:
341
+ return {
342
+ "kind": kind,
343
+ "title": title,
344
+ "url": url,
345
+ "path": path,
346
+ "snippet": snippet,
347
+ "confidence": max(0.0, min(1.0, confidence)),
348
+ "source_rank": source_rank,
349
+ }
350
+
351
+
352
+ def _empty_result(status: str, *, reason: str, ask_user: Optional[str] = None, retryable: bool = False) -> dict[str, Any]:
353
+ return {
354
+ "status": status,
355
+ "mode": "none" if status in {"need_product", "not_found"} else "docs",
356
+ "reason": reason,
357
+ "retryable": retryable,
358
+ "ask_user": ask_user,
359
+ "sources": [],
360
+ "debug": {
361
+ "used_product_index": None,
362
+ "used_platform_index": None,
363
+ "used_fallback_indexes": [],
364
+ "matched_headings": [],
365
+ "search_status": "skipped",
366
+ "docs_fetched": [],
367
+ "low_score_candidates": [],
368
+ },
369
+ }
370
+
371
+
372
+ class Docs:
373
+ """官方文档检索编排工具。"""
374
+
375
+ @classmethod
376
+ def resolve(
377
+ cls,
378
+ *,
379
+ product: Optional[str],
380
+ query: str,
381
+ intent: str,
382
+ platform: Optional[str] = None,
383
+ ) -> dict[str, Any]:
384
+ if intent not in KNOWN_INTENTS:
385
+ raise InvalidInputError(f"未知 intent:{intent}")
386
+ if not query or not query.strip():
387
+ raise InvalidInputError("query 不能为空")
388
+ if product is None:
389
+ return _empty_result(
390
+ "need_product",
391
+ reason="need_product",
392
+ ask_user="你想查哪个产品?conference / chat / call / live / rtc-engine",
393
+ )
394
+ if product not in KNOWN_PRODUCTS:
395
+ raise InvalidInputError(f"未知 product:{product}")
396
+ if platform is not None and platform not in KNOWN_PLATFORMS:
397
+ raise InvalidInputError(f"未知 platform:{platform}")
398
+
399
+ normalized_query = _normalize_basic(query)
400
+ if sum(1 for p in KNOWN_PRODUCTS if _normalize_basic(p) in normalized_query) >= 2:
401
+ return {
402
+ **_empty_result(
403
+ "need_product",
404
+ reason="cross_product_not_supported",
405
+ ask_user="当前 docs 查询只支持单产品。请先拆成一个产品的问题再查。",
406
+ ),
407
+ "mode": "none",
408
+ }
409
+
410
+ error_code = _extract_error_code(query) if intent == "slice-lookup" else None
411
+
412
+ debug = {
413
+ "used_product_index": None,
414
+ "used_platform_index": None,
415
+ "used_fallback_indexes": [],
416
+ "matched_headings": [],
417
+ "search_status": "skipped",
418
+ "docs_fetched": [],
419
+ "low_score_candidates": [],
420
+ }
421
+
422
+ slice_sources: list[dict[str, Any]] = []
423
+ if intent == "slice-lookup":
424
+ try:
425
+ if error_code is not None:
426
+ search_result = Search.slices(product=product, error_code=error_code, platform=platform)
427
+ else:
428
+ search_result = Search.slices(product=product, query=query, platform=platform)
429
+ debug["search_status"] = search_result.get("status") or "failed"
430
+ candidates = search_result.get("candidates") or []
431
+ if candidates:
432
+ top = candidates[0]
433
+ confidence = float(top.get("confidence") or 0.0)
434
+ slice_sources.append(
435
+ _build_source(
436
+ "slice",
437
+ top.get("title") or top.get("slice_id") or "slice",
438
+ url=None,
439
+ path=top.get("path"),
440
+ # mode=slice 时调用方应直接 Read slice 原文;这里不返回占位摘要,避免和真实 source of truth 冲突。
441
+ snippet="",
442
+ confidence=confidence,
443
+ source_rank=1,
444
+ )
445
+ )
446
+ if search_result.get("status") == "exact" and confidence >= 0.80:
447
+ return {
448
+ "status": "resolved",
449
+ "mode": "slice",
450
+ "reason": "slice_exact",
451
+ "retryable": False,
452
+ "ask_user": None,
453
+ "sources": slice_sources,
454
+ "debug": debug,
455
+ }
456
+ except (SearchError, SearchInvalidInputError):
457
+ debug["search_status"] = "failed"
458
+
459
+ product_index_url = _product_llms_url(product)
460
+ debug["used_product_index"] = product_index_url
461
+ try:
462
+ product_index_text = _fetch_text(product_index_url)
463
+ except (HTTPError, URLError) as exc:
464
+ retryable = not isinstance(exc, HTTPError) or exc.code >= 500
465
+ reason = "docs_fetch_timeout" if retryable else "docs_fetch_not_found"
466
+ return {
467
+ **_empty_result("fetch_failed", reason=reason, retryable=retryable),
468
+ "debug": debug,
469
+ }
470
+
471
+ heading_candidates = _parse_llms_index(product_index_text)
472
+
473
+ if _needs_platform_index(query, platform, intent):
474
+ platform_index_url = _platform_llms_url(product, platform or "")
475
+ try:
476
+ platform_index_text = _fetch_text(platform_index_url)
477
+ if not _looks_like_missing_platform_index(platform_index_text):
478
+ debug["used_platform_index"] = platform_index_url
479
+ heading_candidates.extend(_parse_llms_index(platform_index_text))
480
+ except (HTTPError, URLError):
481
+ pass
482
+
483
+ scored = _score_heading_candidates(query, heading_candidates, intent)
484
+ qualified = [item for item in scored if item.score >= HEADING_RESOLVE_MIN_SCORE]
485
+
486
+ # 这是第一版对“共享错误码落在公共索引”的兼容兜底,不是长期理想形态。
487
+ # 只有 numeric error code 且主产品 index 没形成可用 heading 命中时,才额外补抓 conference.txt。
488
+ if error_code is not None and not qualified:
489
+ try:
490
+ fallback_url = _conference_llms_url()
491
+ fallback_text = _fetch_text(fallback_url)
492
+ debug["used_fallback_indexes"].append(fallback_url)
493
+ heading_candidates.extend(_parse_llms_index(fallback_text))
494
+ scored = _score_heading_candidates(query, heading_candidates, intent)
495
+ qualified = [item for item in scored if item.score >= HEADING_RESOLVE_MIN_SCORE]
496
+ except (HTTPError, URLError):
497
+ pass
498
+
499
+ if not scored:
500
+ # 即使没有任何 heading 打上分,也尽量返回少量索引内真实存在的 heading,
501
+ # 让调用方把它们当作“可能相关、未验证”的候选给用户确认。
502
+ debug["low_score_candidates"] = [
503
+ {
504
+ "heading": candidate.heading,
505
+ "url": _ensure_md_url(candidate.docs[0].url),
506
+ }
507
+ for candidate in heading_candidates
508
+ if candidate.docs
509
+ ][:3]
510
+ reason = "slice_not_found" if intent == "slice-lookup" else "docs_no_heading"
511
+ return {
512
+ **_empty_result("not_found", reason=reason),
513
+ "debug": debug,
514
+ }
515
+
516
+ if not qualified:
517
+ debug["low_score_candidates"] = [_low_score_candidate(item) for item in scored[:3]]
518
+ reason = "slice_not_found" if intent == "slice-lookup" else "docs_no_heading"
519
+ return {
520
+ **_empty_result("not_found", reason=reason),
521
+ "debug": debug,
522
+ }
523
+
524
+ top_score = scored[0].score
525
+ selected: list[ScoredHeading]
526
+ ambiguous = False
527
+ if intent == "decision-lookup":
528
+ # decision-lookup 天然允许多个候选并列展示,不走 ambiguous ask-user 分支。
529
+ selected = [item for item in qualified if item.score >= top_score * 0.7][:3]
530
+ else:
531
+ selected = [qualified[0]]
532
+ if len(qualified) >= 2 and qualified[1].score >= top_score * 0.85:
533
+ ambiguous = True
534
+ selected = qualified[:2]
535
+
536
+ debug["matched_headings"] = [item.candidate.heading for item in selected]
537
+
538
+ if ambiguous and intent != "decision-lookup":
539
+ sources = []
540
+ for rank, item in enumerate(selected, start=1):
541
+ first_doc = item.candidate.docs[0]
542
+ sources.append(
543
+ _build_source(
544
+ "trtc_doc",
545
+ item.candidate.heading,
546
+ url=_ensure_md_url(first_doc.url),
547
+ path=None,
548
+ snippet=item.candidate.description,
549
+ confidence=min(0.95, 0.30 + item.score * 0.02),
550
+ source_rank=rank,
551
+ )
552
+ )
553
+ return {
554
+ "status": "ambiguous",
555
+ "mode": "docs",
556
+ "reason": "docs_heading_ambiguous",
557
+ "retryable": False,
558
+ "ask_user": "我找到了多个可能相关的官方文档方向,你想看哪一个?",
559
+ "sources": sources,
560
+ "debug": debug,
561
+ }
562
+
563
+ fetched_sources: list[dict[str, Any]] = []
564
+ fetch_errors: list[tuple[str, bool]] = []
565
+ docs_fetched = 0
566
+ for item in selected:
567
+ for doc in item.candidate.docs:
568
+ if docs_fetched >= 2:
569
+ break
570
+ md_url = _ensure_md_url(doc.url)
571
+ try:
572
+ md_text = _fetch_text(md_url)
573
+ except HTTPError as exc:
574
+ fetch_errors.append(("docs_fetch_not_found", False if exc.code == 404 else exc.code >= 500))
575
+ continue
576
+ except URLError:
577
+ fetch_errors.append(("docs_fetch_timeout", True))
578
+ continue
579
+
580
+ chunks = _md_chunks(md_text, query)
581
+ if not chunks:
582
+ continue
583
+ debug["docs_fetched"].append(md_url)
584
+ docs_fetched += 1
585
+ snippet = "\n\n".join(f"## {chunk.title}\n{chunk.body}" for chunk in chunks)
586
+ fetched_sources.append(
587
+ _build_source(
588
+ "trtc_doc",
589
+ doc.title or item.candidate.heading,
590
+ url=md_url,
591
+ path=None,
592
+ snippet=snippet,
593
+ confidence=min(0.95, 0.30 + item.score * 0.02),
594
+ source_rank=0,
595
+ )
596
+ )
597
+ if docs_fetched >= 2:
598
+ break
599
+
600
+ if slice_sources and fetched_sources:
601
+ sources = []
602
+ for rank, source in enumerate(slice_sources + fetched_sources, start=1):
603
+ source["source_rank"] = rank
604
+ sources.append(source)
605
+ return {
606
+ "status": "resolved",
607
+ "mode": "mixed",
608
+ "reason": "slice_mixed",
609
+ "retryable": False,
610
+ "ask_user": None,
611
+ "sources": sources,
612
+ "debug": debug,
613
+ }
614
+
615
+ if fetched_sources:
616
+ for rank, source in enumerate(fetched_sources, start=1):
617
+ source["source_rank"] = rank
618
+ return {
619
+ "status": "resolved",
620
+ "mode": "docs",
621
+ "reason": "docs_heading_exact",
622
+ "retryable": False,
623
+ "ask_user": None,
624
+ "sources": fetched_sources,
625
+ "debug": debug,
626
+ }
627
+
628
+ if fetch_errors:
629
+ reason, retryable = fetch_errors[0]
630
+ return {
631
+ **_empty_result("fetch_failed", reason=reason, retryable=retryable),
632
+ "debug": debug,
633
+ }
634
+
635
+ reason = "slice_not_found" if intent == "slice-lookup" else "docs_no_heading"
636
+ debug["low_score_candidates"] = [_low_score_candidate(item) for item in selected]
637
+ return {
638
+ **_empty_result("not_found", reason=reason),
639
+ "debug": debug,
640
+ }
641
+
642
+
643
+ def _parse_args(argv: list[str]) -> dict[str, Any]:
644
+ out: dict[str, Any] = {}
645
+ i = 0
646
+ while i < len(argv):
647
+ arg = argv[i]
648
+ if arg.startswith("--"):
649
+ key = arg[2:]
650
+ if i + 1 < len(argv) and not argv[i + 1].startswith("--"):
651
+ out[key] = argv[i + 1]
652
+ i += 2
653
+ else:
654
+ out[key] = True
655
+ i += 1
656
+ else:
657
+ i += 1
658
+ return out
659
+
660
+
661
+ def _cli_resolve(args: list[str]) -> int:
662
+ kv = _parse_args(args)
663
+ query = kv.get("query")
664
+ intent = kv.get("intent")
665
+ product = kv.get("product")
666
+ platform = kv.get("platform")
667
+ if not query or isinstance(query, bool):
668
+ print("ERROR: --query 必须提供", file=sys.stderr)
669
+ return 1
670
+ if not intent or isinstance(intent, bool):
671
+ print("ERROR: --intent 必须提供", file=sys.stderr)
672
+ return 1
673
+ if isinstance(product, bool):
674
+ print("ERROR: --product 值非法", file=sys.stderr)
675
+ return 1
676
+ if isinstance(platform, bool):
677
+ print("ERROR: --platform 值非法", file=sys.stderr)
678
+ return 1
679
+ try:
680
+ result = Docs.resolve(
681
+ product=str(product) if product else None,
682
+ platform=str(platform) if platform else None,
683
+ query=str(query),
684
+ intent=str(intent),
685
+ )
686
+ except InvalidInputError as exc:
687
+ print(f"ERROR: {exc}", file=sys.stderr)
688
+ return 1
689
+ except DependencyError as exc:
690
+ print(f"ERROR: {exc}", file=sys.stderr)
691
+ return 2
692
+ except Exception as exc:
693
+ print(f"ERROR: {exc}", file=sys.stderr)
694
+ return 3
695
+ print(json.dumps(result, ensure_ascii=False, indent=2))
696
+ return 0
697
+
698
+
699
+ def main() -> int:
700
+ argv = sys.argv[1:]
701
+ if not argv:
702
+ print(__doc__)
703
+ return 0
704
+ cmd, rest = argv[0], argv[1:]
705
+ if cmd != "resolve":
706
+ print(f"ERROR: 未知子命令 {cmd}", file=sys.stderr)
707
+ return 1
708
+ return _cli_resolve(rest)
709
+
710
+
711
+ if __name__ == "__main__":
712
+ raise SystemExit(main())