@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.
- package/.cursor/rules/main.mdc +12 -0
- package/AGENTS.md +14 -104
- package/CLAUDE.md +15 -127
- package/CODEBUDDY.md +32 -118
- package/README.md +7 -5
- package/README.zh.md +7 -5
- package/bin/cli.js +133 -41
- package/hooks/__pycache__/cursor-adapter.cpython-313.pyc +0 -0
- package/hooks/cursor-adapter.py +45 -18
- package/hooks/hooks-cursor.json +5 -18
- package/hooks/hooks.json +6 -31
- package/knowledge-base/conference/web/index.yaml +143 -0
- package/knowledge-base/platform-slice-template.md +1041 -133
- package/knowledge-base/products.yaml +76 -0
- package/knowledge-base/scenario-spec.md +1316 -115
- package/knowledge-base/scenarios/conference/base/general-conference.md +22 -2
- package/knowledge-base/scenarios/conference/base/webinar-conference.md +14 -0
- package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +19 -0
- package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +13 -0
- package/knowledge-base/scenarios/live/entertainment-live-room.md +12 -0
- package/knowledge-base/slice-spec.md +2377 -299
- package/knowledge-base/slices/conference/web/integration-audit.md +1 -1
- package/knowledge-base/slices/conference/web/login-auth.md +14 -1
- package/knowledge-base/tooling/aliases.yaml +92 -0
- package/knowledge-base/tooling/intent-signals.yaml +181 -0
- package/knowledge-base/tooling/symptom-keywords.yaml +21 -0
- package/package.json +1 -1
- package/skills/trtc/SKILL.md +202 -245
- package/skills/trtc/hooks/__pycache__/report_prompt.cpython-313.pyc +0 -0
- package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_read.py +12 -8
- package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_write.py +16 -12
- package/skills/{trtc-topic/guardrails → trtc/hooks}/stop_require_apply_evidence.py +22 -14
- package/skills/trtc/hooks/topic_phase_gate.py +161 -0
- package/skills/{trtc-topic → trtc}/runtime/README.md +2 -2
- package/skills/{trtc-onboarding/reference/reporting-protocol.md → trtc/runtime/REPORTING.md} +5 -5
- package/skills/{trtc-topic → trtc}/runtime/RUNTIME.md +5 -5
- package/skills/trtc/runtime/lib/__init__.py +0 -0
- package/skills/{trtc-topic → trtc}/runtime/package-lock.json +2 -2
- package/skills/{trtc-topic → trtc}/runtime/package.json +2 -2
- package/skills/{trtc-topic → trtc}/runtime/telemetry-bridge.mjs +1 -1
- package/skills/{trtc-topic/scripts → trtc/tools}/STATE-MACHINE-GUIDE.md +23 -23
- package/skills/trtc/tools/__init__.py +2 -0
- package/skills/trtc/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/query_classifier.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/reporting.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/search.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/session.cpython-313.pyc +0 -0
- package/skills/trtc/tools/apply.py +540 -0
- package/skills/trtc/tools/docs.py +712 -0
- package/skills/trtc/tools/docsbot.py +182 -0
- package/skills/trtc/tools/entry/render_ai_instructions.py +92 -0
- package/skills/trtc/tools/flow.py +1089 -0
- package/skills/{trtc-topic/scripts → trtc/tools}/init_slice_queue.py +5 -4
- package/skills/{trtc-topic/scripts → trtc/tools}/next_slice.py +5 -4
- package/skills/trtc/tools/query_classifier.py +301 -0
- package/skills/trtc/tools/reporting.py +447 -0
- package/skills/trtc/tools/search.py +817 -0
- package/skills/trtc/tools/session.py +1261 -0
- package/skills/trtc/tools/state_machine.py +690 -0
- package/skills/trtc-ai-service/README.md +195 -0
- package/skills/trtc-ai-service/README.zh-CN.md +193 -0
- package/skills/trtc-ai-service/SKILL.md +945 -0
- package/skills/trtc-ai-service/auto_adapters/README.md +40 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/README.md +27 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/angular/voice-agent.component.ts.tpl +131 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/manifest.yaml +57 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/react/VoiceAgent.tsx.tpl +142 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/vue/VoiceAgent.vue.tpl +121 -0
- package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-backend.md +45 -0
- package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-frontend.md +51 -0
- package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-rest-api.md +93 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/README.md +25 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/manifest.yaml +30 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/quarkus/VoiceAgentFilter.java.tpl +64 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/springboot/VoiceAgentFilter.java.tpl +91 -0
- package/skills/trtc-ai-service/auto_adapters/manifest.yaml +43 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/README.md +25 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/express.js.tpl +40 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/fastify.js.tpl +27 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/koa.js.tpl +31 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/manifest.yaml +47 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/README.md +22 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/django.py.tpl +32 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/fastapi.py.tpl +35 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/flask.py.tpl +31 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/manifest.yaml +45 -0
- package/skills/trtc-ai-service/capabilities/__init__.py +43 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/.env.example +29 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/INTEGRATION.md +134 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/INTERFACE_ADAPT.md +111 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/QUICK_START.md +62 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/manifest.yaml +250 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/requirements.txt +6 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/__init__.py +10 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/_capability_loader.py +218 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/agent.py +231 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/credentials.py +132 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/health.py +355 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/log_filter.py +76 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/modality.py +109 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/server.py +312 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/trtc_client.py +315 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/usersig.py +90 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/tests/test_skeleton.py +216 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/README.md +48 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/app.js +415 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/index.html +68 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/styles.css +136 -0
- package/skills/trtc-ai-service/capabilities/digital-human/README.md +31 -0
- package/skills/trtc-ai-service/capabilities/digital-human/manifest.yaml +64 -0
- package/skills/trtc-ai-service/capabilities/digital-human/src/__init__.py +2 -0
- package/skills/trtc-ai-service/capabilities/digital-human/src/router.py +43 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/INTERFACE_ADAPT.md +353 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/README.md +44 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/manifest.yaml +227 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/__init__.py +2 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/__init__.py +9 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/default_rest.py +242 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/factory.py +89 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/local_queue.py +258 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/mock.py +132 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/__init__.py +25 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/intent_detector.py +75 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/models.py +163 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/service.py +192 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/feedback_store.py +54 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/__init__.py +4 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/handoff_client.py +86 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/queue.py +62 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/router.py +201 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/summary_link.py +77 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/trigger.py +25 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/INTERFACE_ADAPT.md +297 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/README.md +51 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/data/faq.json +20 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/manifest.yaml +211 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/__init__.py +8 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/__init__.py +9 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/default_rest.py +209 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/factory.py +86 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/local_json.py +172 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/mock.py +91 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/__init__.py +12 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/models.py +77 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/scoring.py +73 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/service.py +78 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/__init__.py +4 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/kb_client.py +61 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/retriever.py +56 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/router.py +85 -0
- package/skills/trtc-ai-service/capabilities/session-summary/INTERFACE_ADAPT.md +99 -0
- package/skills/trtc-ai-service/capabilities/session-summary/README.md +47 -0
- package/skills/trtc-ai-service/capabilities/session-summary/data/test_session.json +18 -0
- package/skills/trtc-ai-service/capabilities/session-summary/manifest.yaml +165 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/__init__.py +2 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/__init__.py +5 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/base.py +31 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/default_rest.py +67 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/factory.py +51 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/local_json.py +42 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/mock.py +22 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/recorder.py +210 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/router.py +93 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/summarizer.py +163 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/INTERFACE_ADAPT.md +158 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/README.md +50 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/data/tools.yaml +58 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/examples/__init__.py +1 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/examples/local_tools.py +101 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/manifest.yaml +146 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/__init__.py +8 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/dispatcher.py +54 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/registry.py +219 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/router.py +50 -0
- package/skills/trtc-ai-service/references/business-contract-spec.md +263 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/README.md +86 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/output-templates/recipe.yaml.j2 +194 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q1-business-scenario.md +43 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q2-io-modality.md +57 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q3-ui-form.md +55 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q4-capabilities.md +78 -0
- package/skills/trtc-ai-service/scenarios/customer-service/README.md +114 -0
- package/skills/trtc-ai-service/scenarios/customer-service/recipe.yaml +154 -0
- package/skills/trtc-ai-service/scenarios/customer-service/sample-data/README.md +32 -0
- package/skills/trtc-ai-service/scenarios/customer-service/sample-data/faq-sample.json +37 -0
- package/skills/trtc-ai-service/scenarios/customer-service/system-prompt.template.md +94 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/app.js +347 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/index.html +125 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/styles.css +487 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/tokens.css +71 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/design-system/DESIGN_GUIDELINES.md +370 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/README.md +68 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/app.js +1307 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/data.js +40 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/index.html +233 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/mock-shop.json +21 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/styles.css +603 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/tokens.css +71 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/agent-link.js +323 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/app.js +458 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/index.html +109 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/styles.css +489 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/tokens.css +59 -0
- package/skills/trtc-ai-service/scripts/add-capability.py +364 -0
- package/skills/trtc-ai-service/scripts/contract-adapt.py +334 -0
- package/skills/trtc-ai-service/scripts/detect-stack.py +40 -0
- package/skills/trtc-ai-service/scripts/lib/__init__.py +20 -0
- package/skills/trtc-ai-service/scripts/lib/adapter_codegen.py +509 -0
- package/skills/trtc-ai-service/scripts/lib/arbitrator.py +152 -0
- package/skills/trtc-ai-service/scripts/lib/contract_resolver.py +519 -0
- package/skills/trtc-ai-service/scripts/lib/credential_validators.py +253 -0
- package/skills/trtc-ai-service/scripts/lib/curl_parser.py +303 -0
- package/skills/trtc-ai-service/scripts/lib/degrader.py +140 -0
- package/skills/trtc-ai-service/scripts/lib/injector.py +347 -0
- package/skills/trtc-ai-service/scripts/lib/manifest_resolver.py +288 -0
- package/skills/trtc-ai-service/scripts/lib/openapi_parser.py +289 -0
- package/skills/trtc-ai-service/scripts/lib/stack_detector.py +159 -0
- package/skills/trtc-ai-service/scripts/lib/tokens_compile.py +204 -0
- package/skills/trtc-ai-service/scripts/post-install-patch.py +225 -0
- package/skills/trtc-ai-service/scripts/setup-credentials.py +393 -0
- package/skills/trtc-ai-service/scripts/verify-credentials.py +108 -0
- package/skills/trtc-ai-service/start.sh +111 -0
- package/skills/trtc-ai-service/tests/__init__.py +1 -0
- package/skills/trtc-ai-service/tests/test_arbitrator.py +64 -0
- package/skills/trtc-ai-service/tests/test_capability_overlay.py +85 -0
- package/skills/trtc-ai-service/tests/test_contract_resolver.py +190 -0
- package/skills/trtc-ai-service/tests/test_handoff_ports.py +195 -0
- package/skills/trtc-ai-service/tests/test_kb_ports.py +195 -0
- package/skills/trtc-ai-service/tests/test_manifest_resolver.py +95 -0
- package/skills/trtc-ai-service/tests/test_recipe_assembly.py +175 -0
- package/skills/trtc-ai-service/tests/test_stack_and_degrader.py +101 -0
- package/skills/trtc-ai-service/tests/test_verify_credentials.py +285 -0
- package/skills/trtc-ai-service/triggers.yaml +29 -0
- package/skills/trtc-conference/SKILL.md +324 -0
- package/skills/trtc-conference/flows/onboarding.md +205 -0
- package/skills/trtc-conference/flows/topic.md +474 -0
- package/skills/trtc-conference/flows/troubleshoot.md +85 -0
- package/skills/trtc-conference/hooks/pretooluse_require_business_decisions.py +213 -0
- package/skills/trtc-conference/playbooks/medical-quickstart.md +84 -0
- package/skills/trtc-conference/playbooks/official-roomkit.md +97 -0
- package/skills/trtc-conference/references/local-usersig/basic-info-config.ts +39 -0
- package/skills/trtc-conference/references/usersig-handling.md +134 -0
- package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
- package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_onboarding_contract.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_topic_flow_contract.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-conference/tests/test_conference_index_contract.py +43 -0
- package/skills/trtc-conference/tests/test_conference_onboarding_contract.py +103 -0
- package/skills/trtc-conference/tests/test_conference_template_contract.py +25 -0
- package/skills/trtc-conference/tests/test_conference_topic_flow_contract.py +132 -0
- package/skills/trtc-conference/tools/apply_checks.py +328 -0
- package/skills/trtc-conference/verify_lib/__init__.py +0 -0
- package/skills/{trtc-apply/guardrails/apply_lib → trtc-conference/verify_lib}/rule_parser.py +1 -1
- package/skills/trtc-docs/SKILL.md +91 -119
- package/.cursor/rules/ui-mode.mdc +0 -99
- package/ai-instructions/base.md +0 -13
- package/ai-instructions/ui-mode.md +0 -93
- package/knowledge-base/index.yaml +0 -462
- package/skills/trtc/room-builder/SKILL.md +0 -133
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +0 -108
- package/skills/trtc/room-builder/tools/render_ai_instructions.py +0 -226
- package/skills/trtc-apply/SKILL.md +0 -97
- package/skills/trtc-onboarding/SKILL.md +0 -841
- package/skills/trtc-onboarding/reference/path-a1-demo.md +0 -103
- package/skills/trtc-onboarding/reference/path-a2-integrate.md +0 -737
- package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +0 -186
- package/skills/trtc-onboarding/reference/path-c-expand.md +0 -43
- package/skills/trtc-onboarding/reference/supported-matrix.md +0 -100
- package/skills/trtc-search/SKILL.md +0 -228
- package/skills/trtc-topic/SKILL.md +0 -622
- package/skills/trtc-topic/scripts/apply.py +0 -581
- package/skills/trtc-topic/scripts/lib/state_machine.py +0 -328
- package/skills/trtc-topic/tests/README.md +0 -70
- package/skills/trtc-topic/tests/conftest.py +0 -72
- package/skills/trtc-topic/tests/test_apply_cli.py +0 -480
- package/skills/trtc-topic/tests/test_end_to_end.py +0 -305
- package/skills/trtc-topic/tests/test_finalize_session.py +0 -51
- package/skills/trtc-topic/tests/test_gates.py +0 -316
- package/skills/trtc-topic/tests/test_session_resolver.py +0 -260
- package/skills/trtc-topic/tests/test_state_machine.py +0 -414
- package/skills/trtc-topic/tests/test_stop_require_apply.py +0 -99
- package/skills/trtc-topic/tests/test_topic_skill_invariants.py +0 -130
- /package/skills/{trtc-topic → trtc}/runtime/lib/platforms.py +0 -0
- /package/skills/{trtc-topic → trtc}/runtime/telemetry_collector.py +0 -0
- /package/skills/{trtc-onboarding/reference → trtc/runtime}/usersig-handling.md +0 -0
- /package/skills/{trtc-topic/scripts → trtc/tools}/finalize_session.py +0 -0
- /package/skills/{trtc-apply/guardrails/apply_lib → trtc-ai-service/capabilities/conversation-core/tests}/__init__.py +0 -0
- /package/skills/{trtc-topic → trtc-conference}/references/execution-units.yaml +0 -0
- /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
- /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
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/backend-contract.zh-CN.md +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/integration.zh-CN.md +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/theme.zh-CN.md +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/index.html +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/package.json +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/postcss.config.js +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/App.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/ConsultationManagePanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LanguageSwitch.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LoadingSpinner.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalAlert.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalBusinessPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalButton.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalConfirmDialog.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalDataPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalRecordPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/PrescriptionPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/basic-info-config.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/runtime-config.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/env.d.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/types.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationChat.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationDevices.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationParticipants.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationPermissions.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/utils.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/en-US/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/medicalTranslate.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/state.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/zh-CN/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/main.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/appointments.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/users.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/router/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/appointmentService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/authService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/launchContext.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/userService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/appointmentService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/authService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/userService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/types.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/shared/icons.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/index.css +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/tailwind.css +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/theme.css +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/auth.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/format.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/navigation.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/session.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorConsultationView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorDashboardView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/LoginView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationFinishedView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientSelectDoctorView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientWaitingView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.json +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.node.json +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/vite.config.ts +0 -0
- /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
- /package/skills/{trtc-topic/runtime/lib → trtc-conference/tools}/__init__.py +0 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""DefaultRestKbClient — call external FAQ / search API per business_contract default contract.
|
|
2
|
+
|
|
3
|
+
Corresponding contracts:
|
|
4
|
+
- POST /faq/search faq.search
|
|
5
|
+
- GET /faq faq.list
|
|
6
|
+
- POST /faq faq.upsert
|
|
7
|
+
- DELETE /faq/{entry_id} faq.delete
|
|
8
|
+
|
|
9
|
+
Environment variables:
|
|
10
|
+
- KB_REST_BASE_URL FAQ service base URL
|
|
11
|
+
- KB_REST_TOKEN Bearer Token (optional)
|
|
12
|
+
- KB_REST_TIMEOUT_MS Timeout (default 5000)
|
|
13
|
+
|
|
14
|
+
Security: consistent with human-handoff default_rest — only https / localhost; reject private networks.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
from typing import List, Optional
|
|
22
|
+
from urllib.parse import urlparse
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import requests # type: ignore
|
|
26
|
+
except ImportError: # pragma: no cover
|
|
27
|
+
requests = None # type: ignore
|
|
28
|
+
|
|
29
|
+
from ..core.models import FaqEntry, KbStats, SearchHit
|
|
30
|
+
from ..ports.kb_client import KnowledgeBaseClient
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_PRIVATE_PATTERNS = [
|
|
37
|
+
re.compile(r"^9\."),
|
|
38
|
+
re.compile(r"^10\."),
|
|
39
|
+
re.compile(r"^11\."),
|
|
40
|
+
re.compile(r"^21\."),
|
|
41
|
+
re.compile(r"^30\."),
|
|
42
|
+
re.compile(r"^169\.254\."),
|
|
43
|
+
re.compile(r"^172\.(1[6-9]|2[0-9]|3[01])\."),
|
|
44
|
+
re.compile(r"^192\.168\."),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _is_localhost(host: str) -> bool:
|
|
49
|
+
return host in {"localhost", "127.0.0.1", "::1"}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _is_private(host: str) -> bool:
|
|
53
|
+
return any(p.match(host) for p in _PRIVATE_PATTERNS)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _validate_base_url(url: str) -> str:
|
|
57
|
+
parsed = urlparse(url)
|
|
58
|
+
if parsed.scheme not in {"http", "https"}:
|
|
59
|
+
raise ValueError(f"unsupported scheme: {parsed.scheme}")
|
|
60
|
+
host = parsed.hostname or ""
|
|
61
|
+
if not host:
|
|
62
|
+
raise ValueError("empty host in KB_REST_BASE_URL")
|
|
63
|
+
if parsed.scheme == "http" and not _is_localhost(host):
|
|
64
|
+
raise ValueError(
|
|
65
|
+
"non-HTTPS KB_REST_BASE_URL only allowed for localhost"
|
|
66
|
+
)
|
|
67
|
+
if _is_private(host):
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"access to private network host '{host}' is denied"
|
|
70
|
+
)
|
|
71
|
+
return url.rstrip("/")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class DefaultRestKbClient(KnowledgeBaseClient):
|
|
75
|
+
"""Call external FAQ service per default REST contract."""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
*,
|
|
80
|
+
base_url: str,
|
|
81
|
+
token: Optional[str] = None,
|
|
82
|
+
timeout_ms: int = 5000,
|
|
83
|
+
) -> None:
|
|
84
|
+
if requests is None:
|
|
85
|
+
raise RuntimeError(
|
|
86
|
+
"requests library is required for DefaultRestKbClient"
|
|
87
|
+
)
|
|
88
|
+
self._base = _validate_base_url(base_url)
|
|
89
|
+
self._token = token
|
|
90
|
+
self._timeout = max(0.5, timeout_ms / 1000.0)
|
|
91
|
+
self._session = requests.Session()
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
def search(
|
|
95
|
+
self,
|
|
96
|
+
query: str,
|
|
97
|
+
*,
|
|
98
|
+
top_k: Optional[int] = None,
|
|
99
|
+
min_score: Optional[float] = None,
|
|
100
|
+
) -> List[SearchHit]:
|
|
101
|
+
if not query or not query.strip():
|
|
102
|
+
return []
|
|
103
|
+
payload: dict = {"query": query}
|
|
104
|
+
if top_k is not None:
|
|
105
|
+
payload["top_k"] = int(top_k)
|
|
106
|
+
if min_score is not None:
|
|
107
|
+
payload["min_score"] = float(min_score)
|
|
108
|
+
data = self._post("/faq/search", payload)
|
|
109
|
+
items = data if isinstance(data, list) else data.get("hits", [])
|
|
110
|
+
hits: List[SearchHit] = []
|
|
111
|
+
for it in items or []:
|
|
112
|
+
entry = it.get("entry") if isinstance(it, dict) else None
|
|
113
|
+
if entry is None and isinstance(it, dict) and "question" in it:
|
|
114
|
+
entry = it
|
|
115
|
+
score = float(it.get("score", 0.0))
|
|
116
|
+
else:
|
|
117
|
+
score = float(it.get("score", 0.0)) if isinstance(it, dict) else 0.0
|
|
118
|
+
if not entry:
|
|
119
|
+
continue
|
|
120
|
+
hits.append(
|
|
121
|
+
SearchHit(
|
|
122
|
+
entry=FaqEntry.from_dict({**entry, "source": "remote_api"}),
|
|
123
|
+
score=score,
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
return hits
|
|
127
|
+
|
|
128
|
+
def list_all(self) -> List[FaqEntry]:
|
|
129
|
+
data = self._get("/faq")
|
|
130
|
+
items = data if isinstance(data, list) else data.get("items", [])
|
|
131
|
+
return [FaqEntry.from_dict({**it, "source": "remote_api"}) for it in items]
|
|
132
|
+
|
|
133
|
+
def upsert(self, entry: FaqEntry) -> FaqEntry:
|
|
134
|
+
if not entry.id or not entry.question:
|
|
135
|
+
raise ValueError("id and question are required")
|
|
136
|
+
data = self._post("/faq", entry.to_dict())
|
|
137
|
+
return FaqEntry.from_dict({**data, "source": "remote_api"})
|
|
138
|
+
|
|
139
|
+
def delete(self, entry_id: str) -> bool:
|
|
140
|
+
url = self._base + f"/faq/{entry_id}"
|
|
141
|
+
resp = self._session.delete(
|
|
142
|
+
url, headers=self._headers(), timeout=self._timeout
|
|
143
|
+
)
|
|
144
|
+
if resp.status_code == 404:
|
|
145
|
+
return False
|
|
146
|
+
if resp.status_code >= 400:
|
|
147
|
+
raise RuntimeError(
|
|
148
|
+
f"remote kb service returned HTTP {resp.status_code}"
|
|
149
|
+
)
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
def stats(self) -> KbStats:
|
|
153
|
+
try:
|
|
154
|
+
items = self.list_all()
|
|
155
|
+
return KbStats(
|
|
156
|
+
backend="remote_api",
|
|
157
|
+
entry_count=len(items),
|
|
158
|
+
data_source=self._base,
|
|
159
|
+
)
|
|
160
|
+
except Exception: # noqa: BLE001
|
|
161
|
+
return KbStats(backend="remote_api", entry_count=-1, data_source=self._base)
|
|
162
|
+
|
|
163
|
+
# ------------------------------------------------------------------
|
|
164
|
+
def _headers(self) -> dict:
|
|
165
|
+
h = {"Content-Type": "application/json"}
|
|
166
|
+
if self._token:
|
|
167
|
+
h["Authorization"] = f"Bearer {self._token}"
|
|
168
|
+
return h
|
|
169
|
+
|
|
170
|
+
def _get(self, path: str):
|
|
171
|
+
resp = self._session.get(
|
|
172
|
+
self._base + path, headers=self._headers(), timeout=self._timeout
|
|
173
|
+
)
|
|
174
|
+
return self._handle(resp)
|
|
175
|
+
|
|
176
|
+
def _post(self, path: str, payload: dict):
|
|
177
|
+
resp = self._session.post(
|
|
178
|
+
self._base + path,
|
|
179
|
+
json=payload,
|
|
180
|
+
headers=self._headers(),
|
|
181
|
+
timeout=self._timeout,
|
|
182
|
+
)
|
|
183
|
+
return self._handle(resp)
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def _handle(resp):
|
|
187
|
+
if resp.status_code >= 400:
|
|
188
|
+
raise RuntimeError(
|
|
189
|
+
f"remote kb service returned HTTP {resp.status_code}"
|
|
190
|
+
)
|
|
191
|
+
try:
|
|
192
|
+
data = resp.json()
|
|
193
|
+
except ValueError as exc:
|
|
194
|
+
raise RuntimeError("remote kb service returned non-JSON") from exc
|
|
195
|
+
if isinstance(data, dict) and "data" in data and isinstance(data["data"], (dict, list)):
|
|
196
|
+
return data["data"]
|
|
197
|
+
return data
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
def from_env() -> Optional[DefaultRestKbClient]:
|
|
202
|
+
base = os.getenv("KB_REST_BASE_URL")
|
|
203
|
+
if not base:
|
|
204
|
+
return None
|
|
205
|
+
return DefaultRestKbClient(
|
|
206
|
+
base_url=base,
|
|
207
|
+
token=os.getenv("KB_REST_TOKEN"),
|
|
208
|
+
timeout_ms=int(os.getenv("KB_REST_TIMEOUT_MS", "5000")),
|
|
209
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""adapter factory: selects KnowledgeBaseClient implementation based on environment variable.
|
|
2
|
+
|
|
3
|
+
Environment variable `KB_ADAPTER`:
|
|
4
|
+
local_json Default local JSON file search (production-ready, zero dependencies)
|
|
5
|
+
mock Built-in demo FAQ (for Recipe video recording)
|
|
6
|
+
default_rest Call remote FAQ service per business_contract default contract
|
|
7
|
+
user_custom User integration wizard generated implementation
|
|
8
|
+
|
|
9
|
+
When not set or invalid, fall back to local_json (keeps behavior compatibility with Phase 2).
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from ..ports.kb_client import KnowledgeBaseClient
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_VALID = ("local_json", "mock", "default_rest", "user_custom")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _build(name: str) -> Optional[KnowledgeBaseClient]:
|
|
27
|
+
if name == "local_json":
|
|
28
|
+
from .local_json import from_env as build_local
|
|
29
|
+
return build_local()
|
|
30
|
+
if name == "mock":
|
|
31
|
+
from .mock import from_env as build_mock
|
|
32
|
+
return build_mock()
|
|
33
|
+
if name == "default_rest":
|
|
34
|
+
from .default_rest import from_env as build_rest
|
|
35
|
+
c = build_rest()
|
|
36
|
+
if c is None:
|
|
37
|
+
logger.warning(
|
|
38
|
+
"KB_ADAPTER=default_rest but KB_REST_BASE_URL is empty; "
|
|
39
|
+
"falling back to local_json"
|
|
40
|
+
)
|
|
41
|
+
return c
|
|
42
|
+
if name == "user_custom":
|
|
43
|
+
try:
|
|
44
|
+
from .user_custom import from_env as build_custom # type: ignore
|
|
45
|
+
except ImportError:
|
|
46
|
+
logger.warning(
|
|
47
|
+
"KB_ADAPTER=user_custom but src/adapters/user_custom.py is missing; "
|
|
48
|
+
"run scripts/contract-adapt.py knowledge-base to generate it"
|
|
49
|
+
)
|
|
50
|
+
return None
|
|
51
|
+
return build_custom()
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_default() -> KnowledgeBaseClient:
|
|
56
|
+
name = (os.getenv("KB_ADAPTER") or "local_json").strip().lower()
|
|
57
|
+
if name not in _VALID:
|
|
58
|
+
logger.warning("KB_ADAPTER=%s is not recognised; using local_json", name)
|
|
59
|
+
name = "local_json"
|
|
60
|
+
client = _build(name)
|
|
61
|
+
if client is None:
|
|
62
|
+
from .local_json import from_env as build_local
|
|
63
|
+
client = build_local()
|
|
64
|
+
return client
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
_singleton: Optional[KnowledgeBaseClient] = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_client() -> KnowledgeBaseClient:
|
|
72
|
+
global _singleton
|
|
73
|
+
if _singleton is None:
|
|
74
|
+
_singleton = build_default()
|
|
75
|
+
return _singleton
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def set_client(client: KnowledgeBaseClient) -> None:
|
|
79
|
+
"""For testing only: inject a custom client."""
|
|
80
|
+
global _singleton
|
|
81
|
+
_singleton = client
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def reset_client() -> None:
|
|
85
|
+
global _singleton
|
|
86
|
+
_singleton = None
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""LocalJsonKbClient — default local implementation.
|
|
2
|
+
|
|
3
|
+
Zero external dependencies. Reads FAQ from JSON file + TF-IDF scoring. Migrated from the original retriever.py implementation as the "default out-of-the-box" version.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from ..core.models import FaqEntry, KbStats, SearchHit
|
|
15
|
+
from ..core.scoring import build_df, tfidf_score, tokenize
|
|
16
|
+
from ..ports.kb_client import KnowledgeBaseClient
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LocalJsonKbClient(KnowledgeBaseClient):
|
|
20
|
+
"""FAQ retriever based on local JSON file."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
data_file: Optional[str | Path] = None,
|
|
25
|
+
*,
|
|
26
|
+
min_score: float = 0.1,
|
|
27
|
+
top_k: int = 3,
|
|
28
|
+
) -> None:
|
|
29
|
+
self._lock = threading.RLock()
|
|
30
|
+
self._entries: List[FaqEntry] = []
|
|
31
|
+
self._df: Dict[str, int] = {}
|
|
32
|
+
self._min_score = float(min_score)
|
|
33
|
+
self._top_k = int(top_k)
|
|
34
|
+
self._data_file: Optional[Path] = (
|
|
35
|
+
Path(data_file) if data_file else None
|
|
36
|
+
)
|
|
37
|
+
self._loaded_at: Optional[float] = None
|
|
38
|
+
if self._data_file and self._data_file.exists():
|
|
39
|
+
self.reload()
|
|
40
|
+
|
|
41
|
+
# ------------------------------------------------------------------
|
|
42
|
+
# KnowledgeBaseClient required implementations
|
|
43
|
+
# ------------------------------------------------------------------
|
|
44
|
+
def search(
|
|
45
|
+
self,
|
|
46
|
+
query: str,
|
|
47
|
+
*,
|
|
48
|
+
top_k: Optional[int] = None,
|
|
49
|
+
min_score: Optional[float] = None,
|
|
50
|
+
) -> List[SearchHit]:
|
|
51
|
+
if not query or not query.strip():
|
|
52
|
+
return []
|
|
53
|
+
k = top_k or self._top_k
|
|
54
|
+
threshold = float(min_score) if min_score is not None else self._min_score
|
|
55
|
+
q_tokens = tokenize(query)
|
|
56
|
+
if not q_tokens:
|
|
57
|
+
return []
|
|
58
|
+
with self._lock:
|
|
59
|
+
n = max(1, len(self._entries))
|
|
60
|
+
hits: List[SearchHit] = []
|
|
61
|
+
for entry in self._entries:
|
|
62
|
+
score = tfidf_score(entry, q_tokens, df=self._df, n_docs=n)
|
|
63
|
+
if score >= threshold:
|
|
64
|
+
hits.append(SearchHit(entry=entry, score=score))
|
|
65
|
+
hits.sort(key=lambda h: h.score, reverse=True)
|
|
66
|
+
return hits[:k]
|
|
67
|
+
|
|
68
|
+
def list_all(self) -> List[FaqEntry]:
|
|
69
|
+
with self._lock:
|
|
70
|
+
return [
|
|
71
|
+
FaqEntry(
|
|
72
|
+
id=e.id,
|
|
73
|
+
question=e.question,
|
|
74
|
+
answer=e.answer,
|
|
75
|
+
keywords=list(e.keywords),
|
|
76
|
+
source=e.source or "local_json",
|
|
77
|
+
)
|
|
78
|
+
for e in self._entries
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
def upsert(self, entry: FaqEntry) -> FaqEntry:
|
|
82
|
+
if not entry.id or not entry.question:
|
|
83
|
+
raise ValueError("id and question are required")
|
|
84
|
+
with self._lock:
|
|
85
|
+
for i, e in enumerate(self._entries):
|
|
86
|
+
if e.id == entry.id:
|
|
87
|
+
self._entries[i] = entry
|
|
88
|
+
self._rebuild_df()
|
|
89
|
+
self._persist()
|
|
90
|
+
return entry
|
|
91
|
+
self._entries.append(entry)
|
|
92
|
+
self._rebuild_df()
|
|
93
|
+
self._persist()
|
|
94
|
+
return entry
|
|
95
|
+
|
|
96
|
+
def delete(self, entry_id: str) -> bool:
|
|
97
|
+
with self._lock:
|
|
98
|
+
before = len(self._entries)
|
|
99
|
+
self._entries = [e for e in self._entries if e.id != entry_id]
|
|
100
|
+
removed = before != len(self._entries)
|
|
101
|
+
if removed:
|
|
102
|
+
self._rebuild_df()
|
|
103
|
+
self._persist()
|
|
104
|
+
return removed
|
|
105
|
+
|
|
106
|
+
def stats(self) -> KbStats:
|
|
107
|
+
with self._lock:
|
|
108
|
+
return KbStats(
|
|
109
|
+
backend="local_json",
|
|
110
|
+
entry_count=len(self._entries),
|
|
111
|
+
loaded_at=self._loaded_at,
|
|
112
|
+
data_source=str(self._data_file) if self._data_file else None,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def reload(self) -> int:
|
|
116
|
+
if not self._data_file or not self._data_file.exists():
|
|
117
|
+
return 0
|
|
118
|
+
raw = json.loads(self._data_file.read_text(encoding="utf-8"))
|
|
119
|
+
with self._lock:
|
|
120
|
+
self._entries = [FaqEntry.from_dict(item) for item in raw]
|
|
121
|
+
for e in self._entries:
|
|
122
|
+
e.source = e.source or "local_json"
|
|
123
|
+
self._rebuild_df()
|
|
124
|
+
self._loaded_at = time.time()
|
|
125
|
+
return len(self._entries)
|
|
126
|
+
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
@property
|
|
129
|
+
def data_file(self) -> Optional[Path]:
|
|
130
|
+
return self._data_file
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def min_score(self) -> float:
|
|
134
|
+
return self._min_score
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def top_k(self) -> int:
|
|
138
|
+
return self._top_k
|
|
139
|
+
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
# Internal
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
def _rebuild_df(self) -> None:
|
|
144
|
+
self._df = build_df(self._entries)
|
|
145
|
+
|
|
146
|
+
def _persist(self) -> None:
|
|
147
|
+
if not self._data_file:
|
|
148
|
+
return
|
|
149
|
+
# Ensure directory exists
|
|
150
|
+
self._data_file.parent.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
tmp = self._data_file.with_suffix(self._data_file.suffix + ".tmp")
|
|
152
|
+
tmp.write_text(
|
|
153
|
+
json.dumps(
|
|
154
|
+
[e.to_dict() for e in self._entries],
|
|
155
|
+
ensure_ascii=False,
|
|
156
|
+
indent=2,
|
|
157
|
+
),
|
|
158
|
+
encoding="utf-8",
|
|
159
|
+
)
|
|
160
|
+
os.replace(tmp, self._data_file)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# Factory
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
def from_env() -> LocalJsonKbClient:
|
|
167
|
+
default_file = Path(__file__).resolve().parent.parent.parent / "data" / "faq.json"
|
|
168
|
+
return LocalJsonKbClient(
|
|
169
|
+
data_file=os.getenv("KB_DATA_FILE", str(default_file)),
|
|
170
|
+
min_score=float(os.getenv("KB_MIN_SCORE", "0.1")),
|
|
171
|
+
top_k=int(os.getenv("KB_TOP_K", "3")),
|
|
172
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""MockKbClient — mock implementation for Recipe demo recording.
|
|
2
|
+
|
|
3
|
+
Inherits LocalJsonKbClient; seeds embedded demo FAQ into memory on construction (no disk file dependency).
|
|
4
|
+
|
|
5
|
+
Applicable scenarios:
|
|
6
|
+
- User's first Recipe launch works out of the box even before data/faq.json has been filled in
|
|
7
|
+
- Stable data for video demos, won't break due to external file changes
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
from ..core.models import FaqEntry
|
|
15
|
+
from .local_json import LocalJsonKbClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_DEMO_FAQ: List[dict] = [
|
|
19
|
+
{
|
|
20
|
+
"id": "demo_refund",
|
|
21
|
+
"question": "How do I request a refund?",
|
|
22
|
+
"answer": "Go to My Orders, pick the item, tap Request refund and fill in the reason. Once approved, the amount returns to your original payment within 1-3 business days.",
|
|
23
|
+
"keywords": ["refund", "money back", "return", "cancel order"],
|
|
24
|
+
"source": "demo_seed",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "demo_logistics",
|
|
28
|
+
"question": "How long does shipping take?",
|
|
29
|
+
"answer": "Standard items ship within 48 hours. Major cities receive in 3-5 days; remote areas 5-7 days. You can track shipments in real time under My Orders.",
|
|
30
|
+
"keywords": ["shipping", "delivery", "tracking", "where is my package", "logistics"],
|
|
31
|
+
"source": "demo_seed",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "demo_invoice",
|
|
35
|
+
"question": "How can I get an invoice?",
|
|
36
|
+
"answer": "Tick \"Need invoice\" at checkout and fill in your billing details. For existing orders, open the order in My Orders and tap Request invoice. E-invoices are emailed within 24 hours.",
|
|
37
|
+
"keywords": ["invoice", "receipt", "billing", "tax"],
|
|
38
|
+
"source": "demo_seed",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "demo_size",
|
|
42
|
+
"question": "What if the size doesn't fit?",
|
|
43
|
+
"answer": "We offer 7-day free size exchange — return shipping is on us. Open My Orders, tap Exchange size and follow the instructions. If your size is out of stock you can request a refund and re-order.",
|
|
44
|
+
"keywords": ["size", "exchange", "doesn't fit", "wrong size"],
|
|
45
|
+
"source": "demo_seed",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "demo_after_sale",
|
|
49
|
+
"question": "What is the warranty period?",
|
|
50
|
+
"answer": "All products carry a 1-year warranty. Electronics may be replaced within 30 days; after 30 days the manufacturer's warranty policy applies. Provide your order ID and a description of the issue and we'll open a ticket for you.",
|
|
51
|
+
"keywords": ["warranty", "after-sale", "repair", "broken", "defective"],
|
|
52
|
+
"source": "demo_seed",
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class MockKbClient(LocalJsonKbClient):
|
|
58
|
+
"""Mock implementation for demos."""
|
|
59
|
+
|
|
60
|
+
is_mock = True
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
*,
|
|
65
|
+
min_score: float = 0.05, # More lenient threshold for demos
|
|
66
|
+
top_k: int = 3,
|
|
67
|
+
seed_demo_data: bool = True,
|
|
68
|
+
) -> None:
|
|
69
|
+
# No data_file specified — data is entirely memory-resident
|
|
70
|
+
super().__init__(
|
|
71
|
+
data_file=None,
|
|
72
|
+
min_score=min_score,
|
|
73
|
+
top_k=top_k,
|
|
74
|
+
)
|
|
75
|
+
if seed_demo_data:
|
|
76
|
+
self._seed()
|
|
77
|
+
|
|
78
|
+
def _seed(self) -> None:
|
|
79
|
+
for raw in _DEMO_FAQ:
|
|
80
|
+
entry = FaqEntry.from_dict(raw)
|
|
81
|
+
self._entries.append(entry)
|
|
82
|
+
self._rebuild_df()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
def from_env() -> MockKbClient:
|
|
87
|
+
return MockKbClient(
|
|
88
|
+
min_score=float(os.getenv("KB_MIN_SCORE", "0.05")),
|
|
89
|
+
top_k=int(os.getenv("KB_TOP_K", "3")),
|
|
90
|
+
seed_demo_data=os.getenv("KB_MOCK_SEED", "1") not in ("0", "false", "False"),
|
|
91
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""knowledge-base core module."""
|
|
2
|
+
from .models import FaqEntry, KbStats, SearchHit
|
|
3
|
+
from .service import KbService, get_default_service, reset_default_service
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"FaqEntry",
|
|
7
|
+
"KbService",
|
|
8
|
+
"KbStats",
|
|
9
|
+
"SearchHit",
|
|
10
|
+
"get_default_service",
|
|
11
|
+
"reset_default_service",
|
|
12
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""knowledge-base core models.
|
|
2
|
+
|
|
3
|
+
Defines unified domain models:
|
|
4
|
+
- FaqEntry Knowledge entry (id / question / answer / keywords / source)
|
|
5
|
+
- SearchHit Search hit (with score)
|
|
6
|
+
- KbStats Knowledge base statistics (entry count / data source type / load time)
|
|
7
|
+
|
|
8
|
+
All adapters must use this module's data structures as transport objects.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import List, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class FaqEntry:
|
|
18
|
+
"""A single FAQ knowledge entry."""
|
|
19
|
+
|
|
20
|
+
id: str
|
|
21
|
+
question: str
|
|
22
|
+
answer: str
|
|
23
|
+
keywords: List[str] = field(default_factory=list)
|
|
24
|
+
# Optional: annotate entry source (local_json / remote_api / user_uploaded etc.), useful for dashboard display
|
|
25
|
+
source: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> dict:
|
|
28
|
+
return {
|
|
29
|
+
"id": self.id,
|
|
30
|
+
"question": self.question,
|
|
31
|
+
"answer": self.answer,
|
|
32
|
+
"keywords": list(self.keywords),
|
|
33
|
+
**({"source": self.source} if self.source else {}),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_dict(cls, raw: dict) -> "FaqEntry":
|
|
38
|
+
return cls(
|
|
39
|
+
id=str(raw.get("id") or raw.get("question", ""))[:64] or "auto",
|
|
40
|
+
question=str(raw.get("question", "")).strip(),
|
|
41
|
+
answer=str(raw.get("answer", "")).strip(),
|
|
42
|
+
keywords=[
|
|
43
|
+
str(k).strip()
|
|
44
|
+
for k in (raw.get("keywords") or [])
|
|
45
|
+
if str(k).strip()
|
|
46
|
+
],
|
|
47
|
+
source=raw.get("source"),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class SearchHit:
|
|
53
|
+
"""Search hit."""
|
|
54
|
+
|
|
55
|
+
entry: FaqEntry
|
|
56
|
+
score: float
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> dict:
|
|
59
|
+
return {"entry": self.entry.to_dict(), "score": round(float(self.score), 4)}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class KbStats:
|
|
64
|
+
"""Knowledge base statistics (dashboard use)."""
|
|
65
|
+
|
|
66
|
+
backend: str # "local_json" / "remote_api" / "mock" / "user_custom"
|
|
67
|
+
entry_count: int
|
|
68
|
+
loaded_at: Optional[float] = None
|
|
69
|
+
data_source: Optional[str] = None
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> dict:
|
|
72
|
+
return {
|
|
73
|
+
"backend": self.backend,
|
|
74
|
+
"entry_count": self.entry_count,
|
|
75
|
+
"loaded_at": self.loaded_at,
|
|
76
|
+
"data_source": self.data_source,
|
|
77
|
+
}
|