@tencent-rtc/trtc-agent-skills 0.1.7 → 0.1.8

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 (125) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +1 -1
  3. package/CODEBUDDY.md +1 -1
  4. package/README.md +10 -7
  5. package/README.zh.md +10 -7
  6. package/bin/cli.js +196 -44
  7. package/knowledge-base/conference/web/index.yaml +6 -6
  8. package/knowledge-base/slices/conference/web/official-roomkit-api.md +119 -8
  9. package/package.json +1 -1
  10. package/skills/trtc/SKILL.md +45 -10
  11. package/skills/trtc-ai-oral-coach/README.ja.md +3 -3
  12. package/skills/trtc-ai-oral-coach/README.md +3 -3
  13. package/skills/trtc-ai-oral-coach/README.zh-CN.md +3 -3
  14. package/skills/trtc-ai-oral-coach/SKILL.md +7 -4
  15. package/skills/trtc-ai-realtime-interpreter/README.ja.md +197 -0
  16. package/skills/trtc-ai-realtime-interpreter/README.md +197 -0
  17. package/skills/trtc-ai-realtime-interpreter/README.zh-CN.md +197 -0
  18. package/skills/trtc-ai-realtime-interpreter/SKILL.md +748 -0
  19. package/skills/trtc-ai-realtime-interpreter/auto_adapters/integration_templates/generic-rest-api.md +85 -0
  20. package/skills/trtc-ai-realtime-interpreter/auto_adapters/integration_templates/room-owner-authz-note.md +53 -0
  21. package/skills/trtc-ai-realtime-interpreter/auto_adapters/manifest.yaml +49 -0
  22. package/skills/trtc-ai-realtime-interpreter/auto_adapters/python/README.md +11 -0
  23. package/skills/trtc-ai-realtime-interpreter/auto_adapters/python/fastapi_reverse_proxy.py.tpl +84 -0
  24. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/.env.example +17 -0
  25. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/frontend/silent-listener.ts +91 -0
  26. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/manifest.yaml +110 -0
  27. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/requirements.txt +5 -0
  28. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/src/__init__.py +0 -0
  29. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/src/_capability_loader.py +89 -0
  30. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/src/agent.py +153 -0
  31. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/src/credentials.py +112 -0
  32. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/src/health.py +218 -0
  33. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/src/log_filter.py +33 -0
  34. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/src/server.py +266 -0
  35. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/src/trtc_client.py +188 -0
  36. package/skills/trtc-ai-realtime-interpreter/capabilities/conversation-core/src/usersig.py +53 -0
  37. package/skills/trtc-ai-realtime-interpreter/capabilities/meeting-ops/README.md +46 -0
  38. package/skills/trtc-ai-realtime-interpreter/capabilities/meeting-ops/manifest.yaml +63 -0
  39. package/skills/trtc-ai-realtime-interpreter/capabilities/meeting-ops/src/__init__.py +0 -0
  40. package/skills/trtc-ai-realtime-interpreter/capabilities/meeting-ops/src/fanout.py +149 -0
  41. package/skills/trtc-ai-realtime-interpreter/capabilities/meeting-ops/src/router.py +83 -0
  42. package/skills/trtc-ai-realtime-interpreter/capabilities/realtime-translation/frontend/subtitle-parser.ts +145 -0
  43. package/skills/trtc-ai-realtime-interpreter/capabilities/realtime-translation/manifest.yaml +68 -0
  44. package/skills/trtc-ai-realtime-interpreter/capabilities/realtime-translation/src/__init__.py +0 -0
  45. package/skills/trtc-ai-realtime-interpreter/capabilities/realtime-translation/src/modes.py +73 -0
  46. package/skills/trtc-ai-realtime-interpreter/capabilities/realtime-translation/src/router.py +83 -0
  47. package/skills/trtc-ai-realtime-interpreter/capabilities/realtime-translation/src/service.py +77 -0
  48. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/README.md +23 -0
  49. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/backend/app/__init__.py +0 -0
  50. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/backend/app/server.py +277 -0
  51. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/backend/requirements.txt +5 -0
  52. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/backend/start.sh +28 -0
  53. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/recipe.yaml +72 -0
  54. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/index.html +12 -0
  55. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/legacy/index.html +738 -0
  56. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/package-lock.json +4302 -0
  57. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/package.json +33 -0
  58. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/postcss.config.js +6 -0
  59. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/App.vue +18 -0
  60. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/api/backend.ts +82 -0
  61. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/SetupScreen.vue +362 -0
  62. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/SummaryScreen.vue +203 -0
  63. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/conference/ChatPanel.vue +188 -0
  64. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/conference/ConferenceRoom.vue +453 -0
  65. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/conference/ParticipantViewUI.vue +170 -0
  66. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/conference/PeoplePanel.vue +206 -0
  67. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/conference/SidePanel.vue +77 -0
  68. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/conference/Toolbar.vue +371 -0
  69. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/conference/TopBar.vue +310 -0
  70. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/components/conference/TranscriptPanel.vue +225 -0
  71. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/composables/useAiInterpreter.ts +263 -0
  72. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/composables/useConference.ts +98 -0
  73. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/env.d.ts +7 -0
  74. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/main.ts +5 -0
  75. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/store.ts +103 -0
  76. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/src/style.css +19 -0
  77. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/tailwind.config.js +25 -0
  78. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/tsconfig.json +25 -0
  79. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/tsconfig.node.json +10 -0
  80. package/skills/trtc-ai-realtime-interpreter/scenarios/meeting-interpreter/ui/vite.config.ts +26 -0
  81. package/skills/trtc-ai-realtime-interpreter/scripts/add-capability.py +163 -0
  82. package/skills/trtc-ai-realtime-interpreter/scripts/deploy-demo.sh +64 -0
  83. package/skills/trtc-ai-realtime-interpreter/scripts/lib/__init__.py +0 -0
  84. package/skills/trtc-ai-realtime-interpreter/scripts/lib/credential_validators.py +143 -0
  85. package/skills/trtc-ai-realtime-interpreter/scripts/lib/manifest_resolver.py +60 -0
  86. package/skills/trtc-ai-realtime-interpreter/scripts/lib/stack_detector.py +50 -0
  87. package/skills/trtc-ai-realtime-interpreter/scripts/post-install-patch.py +79 -0
  88. package/skills/trtc-ai-realtime-interpreter/scripts/verify-credentials.py +76 -0
  89. package/skills/trtc-ai-realtime-interpreter/start.sh +85 -0
  90. package/skills/trtc-ai-realtime-interpreter/triggers.yaml +29 -0
  91. package/skills/trtc-ai-service/README.ja.md +3 -3
  92. package/skills/trtc-ai-service/README.md +3 -3
  93. package/skills/trtc-ai-service/README.zh-CN.md +3 -3
  94. package/skills/trtc-ai-service/SKILL.md +9 -7
  95. package/skills/trtc-ai-service/capabilities/conversation-core/src/credentials.py +1 -1
  96. package/skills/trtc-chat/SKILL.md +1 -1
  97. package/skills/trtc-chat/docs/SKILL.md +1 -1
  98. package/skills/trtc-conference/flows/onboarding.md +6 -0
  99. package/skills/trtc-conference/flows/topic.md +6 -2
  100. package/skills/trtc-conference/playbooks/official-roomkit.md +3 -1
  101. package/skills/trtc-conference/tests/test_conference_index_contract.py +16 -0
  102. package/skills/trtc-conference/tests/test_conference_onboarding_contract.py +9 -0
  103. package/skills/trtc-conference/tests/test_conference_topic_flow_contract.py +16 -0
  104. package/skills/trtc-push/SKILL.md +118 -0
  105. package/skills/trtc-push/issues/ROUTER.json +429 -0
  106. package/skills/trtc-push/issues/cards/android/fcm-gms-domestic.md +53 -0
  107. package/skills/trtc-push/issues/cards/android/vendor-huawei.md +72 -0
  108. package/skills/trtc-push/issues/cards/common/console-certificate-quota.md +46 -0
  109. package/skills/trtc-push/issues/cards/common/registration-binding.md +67 -0
  110. package/skills/trtc-push/issues/cards/ios/aps-environment-3000.md +55 -0
  111. package/skills/trtc-push/issues/cards/ios/certificate-businessid.md +56 -0
  112. package/skills/trtc-push/issues/cards/ios/xcodegen-cocoapods-module.md +54 -0
  113. package/skills/trtc-push/issues/flows/android/delivered-not-displayed.md +52 -0
  114. package/skills/trtc-push/issues/flows/android/vendor-not-received.md +57 -0
  115. package/skills/trtc-push/issues/flows/common/badge.md +49 -0
  116. package/skills/trtc-push/issues/flows/common/console-product-limits.md +48 -0
  117. package/skills/trtc-push/issues/flows/common/server-api.md +51 -0
  118. package/skills/trtc-push/issues/flows/cross-platform/harmonyos.md +51 -0
  119. package/skills/trtc-push/issues/flows/cross-platform/uniapp-integration.md +57 -0
  120. package/skills/trtc-push/issues/flows/ios/offline-not-received.md +57 -0
  121. package/skills/trtc-push/references/code-templates.md +386 -0
  122. package/skills/trtc-push/references/hard-rules.md +112 -0
  123. package/skills/trtc-push/references/timpush-sdk-api.md +72 -0
  124. package/skills/trtc-push/references/workflow-protocol.md +82 -0
  125. /package/.cursor/rules/{main.mdc → ui-mode.mdc} +0 -0
@@ -0,0 +1,266 @@
1
+ # -*- coding: utf-8 -*-
2
+ """FastAPI 入口:骨架 REST API + 能力包路由挂载 + 静态 Web Demo 托管。
3
+
4
+ 骨架路由:
5
+ GET /api/v1/health —— 三把钥匙连通性自检
6
+ POST /api/v1/usersig —— 给任意 user_id 签发 UserSig(通用工具,供旁听/多身份场景使用)
7
+ POST /api/v1/agent/start —— 起一路 Conversational AI 会话(单目标)
8
+ POST /api/v1/agent/stop —— 停一路会话
9
+ POST /api/v1/agent/control —— 文本注入 / 打断
10
+ GET /api/v1/sessions —— 内存态会话列表(调试用)
11
+ GET / —— Web Demo 静态页
12
+
13
+ 设计原则:
14
+ - 骨架只做协议编排,不内置任何行业 prompt / 语言对 / 权限逻辑。
15
+ - 通过 app.state.conversation_agent 把 Agent 单例暴露给能力包路由(比模块级单例更适合
16
+ FastAPI 的依赖注入习惯,能力包路由用 `request.app.state.conversation_agent` 取用)。
17
+ - 能力包路由通过 _capability_loader 动态加载,未安装时静默跳过,不影响骨架启动。
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ import os
23
+ import sys
24
+ from pathlib import Path
25
+ from typing import Any, Dict, Optional
26
+
27
+ from dotenv import load_dotenv
28
+
29
+ _BASE_DIR = Path(__file__).resolve().parent.parent # capabilities/conversation-core/
30
+ _SRC_DIR = Path(__file__).resolve().parent
31
+ if str(_SRC_DIR) not in sys.path:
32
+ sys.path.insert(0, str(_SRC_DIR))
33
+
34
+ load_dotenv(_BASE_DIR / ".env.local")
35
+ load_dotenv(_BASE_DIR / ".env")
36
+
37
+ from fastapi import APIRouter, FastAPI, HTTPException, Request
38
+ from fastapi.middleware.cors import CORSMiddleware
39
+ from fastapi.responses import FileResponse
40
+ from fastapi.staticfiles import StaticFiles
41
+ from pydantic import BaseModel, Field
42
+
43
+ from agent import ConversationAgent
44
+ from credentials import load_from_env
45
+ from health import check_all
46
+ from log_filter import install_redacting_filter
47
+ from trtc_client import AgentLifecycleConfig
48
+
49
+ logger = logging.getLogger("conversation_core")
50
+ logging.basicConfig(level=os.getenv("LOG_LEVEL", "INFO"), format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
51
+ install_redacting_filter(logging.getLogger())
52
+
53
+ _credentials = load_from_env()
54
+ _agent: Optional[ConversationAgent] = None
55
+ _init_error: Optional[str] = None
56
+ try:
57
+ _agent = ConversationAgent(_credentials)
58
+ logger.info("ConversationAgent initialized")
59
+ except Exception as exc: # noqa: BLE001
60
+ _init_error = str(exc)
61
+ logger.warning("ConversationAgent not initialized: %s", _init_error)
62
+
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # Pydantic Models
66
+ # ---------------------------------------------------------------------------
67
+ class UserSigRequest(BaseModel):
68
+ user_id: str
69
+
70
+
71
+ class AgentStartRequest(BaseModel):
72
+ room_id: str
73
+ room_id_type: int = 0
74
+ target_user_id: str
75
+ agent_user_id: Optional[str] = None
76
+ instructions: Optional[str] = None
77
+ greeting: Optional[str] = None
78
+ language: Optional[str] = "zh"
79
+ voice_id: Optional[str] = None
80
+ max_idle_time: Optional[int] = 60
81
+
82
+
83
+ class AgentStopRequest(BaseModel):
84
+ session_id: str
85
+
86
+
87
+ class ControlRequest(BaseModel):
88
+ session_id: str
89
+ text: str
90
+ interrupt: bool = True
91
+
92
+
93
+ # ---------------------------------------------------------------------------
94
+ # FastAPI App
95
+ # ---------------------------------------------------------------------------
96
+ app = FastAPI(
97
+ title="conversation-core",
98
+ version="1.0.0",
99
+ description="TRTC Conversational AI 通用骨架(拉起单路会话并连通,无行业假设)",
100
+ )
101
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
102
+
103
+ # 把 Agent 单例挂到 app.state,供能力包路由通过 request.app.state.conversation_agent 取用
104
+ app.state.conversation_agent = _agent
105
+ app.state.credentials = _credentials
106
+
107
+ api = APIRouter(prefix="/api/v1")
108
+
109
+
110
+ def _to_http_error(exc: Exception) -> HTTPException:
111
+ if isinstance(exc, ValueError):
112
+ return HTTPException(status_code=400, detail=str(exc))
113
+ if isinstance(exc, RuntimeError):
114
+ return HTTPException(status_code=500, detail=str(exc))
115
+ return HTTPException(status_code=500, detail=f"internal: {exc}")
116
+
117
+
118
+ def require_agent(request: Request) -> ConversationAgent:
119
+ """能力包路由可直接复用这个依赖函数:request.app.state.conversation_agent。"""
120
+ agent = getattr(request.app.state, "conversation_agent", None)
121
+ if agent is None:
122
+ raise HTTPException(
123
+ status_code=503,
124
+ detail={
125
+ "code": "credentials_missing",
126
+ "message": _init_error or "credentials not configured",
127
+ "hint": "在 .env 里配好三把钥匙(TRTC / 腾讯云 / LLM)后重启",
128
+ },
129
+ )
130
+ return agent
131
+
132
+
133
+ # ---------------------------------------------------------------------------
134
+ # Health
135
+ # ---------------------------------------------------------------------------
136
+ @api.get("/health")
137
+ def health() -> Dict[str, Any]:
138
+ cred = load_from_env()
139
+ tc, trtc, llm = check_all(cred.tencent_cloud, cred.trtc, cred.llm)
140
+ overall = "ok" if tc.ok and trtc.ok and llm.ok else "partial_failure"
141
+ return {
142
+ "status": overall,
143
+ "checks": {"tencent_cloud": tc.to_dict(), "trtc": trtc.to_dict(), "llm": llm.to_dict()},
144
+ "configured": cred.fully_configured,
145
+ "missing": cred.missing(),
146
+ }
147
+
148
+
149
+ # ---------------------------------------------------------------------------
150
+ # UserSig(通用签发,供旁听客户端 / 多身份场景使用)
151
+ # ---------------------------------------------------------------------------
152
+ @api.post("/usersig")
153
+ def issue_usersig(req: UserSigRequest, request: Request) -> Dict[str, Any]:
154
+ agent = require_agent(request)
155
+ if not req.user_id or not req.user_id.strip():
156
+ raise HTTPException(status_code=400, detail="user_id is required")
157
+ user_id = req.user_id.strip()[:32]
158
+ sig = agent.issue_user_sig(user_id)
159
+ return {"sdk_app_id": agent.sdk_app_id, "user_id": user_id, "user_sig": sig}
160
+
161
+
162
+ # ---------------------------------------------------------------------------
163
+ # 单路会话生命周期
164
+ # ---------------------------------------------------------------------------
165
+ @api.post("/agent/start")
166
+ def agent_start(req: AgentStartRequest, request: Request) -> Dict[str, Any]:
167
+ agent = require_agent(request)
168
+ try:
169
+ defaults = AgentLifecycleConfig()
170
+ cfg = AgentLifecycleConfig(
171
+ instructions=req.instructions or defaults.instructions,
172
+ greeting=req.greeting or defaults.greeting,
173
+ language=req.language or "zh",
174
+ voice_id=req.voice_id or "",
175
+ max_idle_time=req.max_idle_time or 60,
176
+ )
177
+ info = agent.start(
178
+ room_id=req.room_id,
179
+ target_user_id=req.target_user_id,
180
+ config=cfg,
181
+ room_id_type=req.room_id_type,
182
+ agent_user_id=req.agent_user_id,
183
+ )
184
+ return {
185
+ "code": 0, "msg": "success",
186
+ "data": {
187
+ "session_id": info.session_id, "task_id": info.task_id, "request_id": info.request_id,
188
+ "agent_user_id": info.agent_user_id, "status": "started",
189
+ },
190
+ }
191
+ except Exception as exc: # noqa: BLE001
192
+ raise _to_http_error(exc)
193
+
194
+
195
+ @api.post("/agent/stop")
196
+ def agent_stop(req: AgentStopRequest, request: Request) -> Dict[str, Any]:
197
+ agent = require_agent(request)
198
+ try:
199
+ agent.stop(req.session_id)
200
+ return {"code": 0, "msg": "success", "data": {"session_id": req.session_id, "status": "stopped"}}
201
+ except Exception as exc: # noqa: BLE001
202
+ raise _to_http_error(exc)
203
+
204
+
205
+ @api.post("/agent/control")
206
+ def agent_control(req: ControlRequest, request: Request) -> Dict[str, Any]:
207
+ agent = require_agent(request)
208
+ try:
209
+ return {"code": 0, "msg": "success", "data": agent.control(req.session_id, req.text, req.interrupt)}
210
+ except Exception as exc: # noqa: BLE001
211
+ raise _to_http_error(exc)
212
+
213
+
214
+ @api.get("/sessions")
215
+ def sessions_list(request: Request) -> Dict[str, Any]:
216
+ agent = require_agent(request)
217
+ return {"code": 0, "data": agent.list_sessions()}
218
+
219
+
220
+ app.include_router(api)
221
+
222
+ # ---------------------------------------------------------------------------
223
+ # 能力包路由挂载(可选;动态加载,未安装时静默跳过)
224
+ # ---------------------------------------------------------------------------
225
+ from _capability_loader import try_load_capability as _try_load_capability # noqa: E402
226
+
227
+ # [realtime-translation] 挂子路由
228
+ _rt_router_mod = _try_load_capability("realtime-translation", "src/router.py")
229
+ if _rt_router_mod is not None and hasattr(_rt_router_mod, "router"):
230
+ app.include_router(_rt_router_mod.router, prefix="/api/v1/translation", tags=["realtime-translation"])
231
+
232
+ # [meeting-ops] 挂子路由
233
+ _mo_router_mod = _try_load_capability("meeting-ops", "src/router.py")
234
+ if _mo_router_mod is not None and hasattr(_mo_router_mod, "router"):
235
+ app.include_router(_mo_router_mod.router, prefix="/api/v1/meeting", tags=["meeting-ops"])
236
+
237
+
238
+ # ---------------------------------------------------------------------------
239
+ # 静态前端:可用 WEB_DEMO_DIR 覆盖(Path A 场景层会把它指向自己的 UI 构建产物)
240
+ # ---------------------------------------------------------------------------
241
+ _DEMO_DIR = Path(os.getenv("WEB_DEMO_DIR", str(_BASE_DIR / "web-demo")))
242
+ if _DEMO_DIR.exists():
243
+ app.mount("/static", StaticFiles(directory=str(_DEMO_DIR), html=True), name="static")
244
+
245
+ @app.get("/")
246
+ def index() -> FileResponse:
247
+ return FileResponse(str(_DEMO_DIR / "index.html"))
248
+
249
+
250
+ def main() -> None:
251
+ import uvicorn
252
+
253
+ port = int(os.getenv("PORT", "8020"))
254
+ host = os.getenv("HOST", "0.0.0.0")
255
+ cert_file = _BASE_DIR.parent.parent / "cert.pem"
256
+ key_file = _BASE_DIR.parent.parent / "key.pem"
257
+ if cert_file.exists() and key_file.exists():
258
+ logger.info("Starting HTTPS server (cert=%s)", cert_file)
259
+ uvicorn.run(app, host=host, port=port, ssl_certfile=str(cert_file), ssl_keyfile=str(key_file))
260
+ else:
261
+ logger.info("Starting HTTP server (no cert.pem/key.pem found)")
262
+ uvicorn.run(app, host=host, port=port)
263
+
264
+
265
+ if __name__ == "__main__":
266
+ main()
@@ -0,0 +1,188 @@
1
+ # -*- coding: utf-8 -*-
2
+ """TRTC Conversational AI 控制面客户端(使用 tencentcloud-sdk-python 官方 SDK)。
3
+
4
+ 封装 StartAIConversation / StopAIConversation / ControlAIConversation 三个接口。
5
+ 骨架层只做「协议封装 + 密钥签名」,不内置任何 system_prompt / 语言对 / 行业知识——
6
+ 这些由上层业务能力包(如 realtime-translation)通过 AgentLifecycleConfig 参数传入。
7
+
8
+ 使用官方 SDK 而非手写 REST,确保请求格式与腾讯云服务端完全兼容
9
+ (手写 REST 在某些参数组合下可能触发 UserSig 校验异常)。
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import logging
15
+ from dataclasses import dataclass, field
16
+ from typing import Any, Dict, Optional
17
+
18
+ from tencentcloud.common import credential
19
+ from tencentcloud.common.profile.client_profile import ClientProfile
20
+ from tencentcloud.common.profile.http_profile import HttpProfile
21
+ from tencentcloud.trtc.v20190722 import trtc_client, models
22
+
23
+ from credentials import LlmCredential, TencentCloudCredential, TrtcCredential
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ @dataclass
29
+ class AgentLifecycleConfig:
30
+ """单路会话的启动参数(业务无关;具体 system_prompt/语言对由上层能力包填入)。"""
31
+
32
+ instructions: str = "You are a helpful voice assistant. Reply briefly."
33
+ greeting: str = "Hello, how can I help you?"
34
+ max_idle_time: int = 60
35
+ welcome_message: str = ""
36
+ language: str = "zh" # STT 识别语种
37
+ voice_id: str = "" # 空 -> DEFAULT_VOICE_IDS 按 language 选取
38
+ tts_model: str = "flow_01_turbo"
39
+ tts_enabled: bool = True # False 时跳过 TTSConfig(只有字幕,不播报语音)
40
+ llm_extra: Dict[str, Any] = field(default_factory=dict) # 额外透传给 LLMConfig 的字段(如 Temperature)
41
+
42
+
43
+ # TRTC FlowTTS 内置音色(trtc.io/document/79682)
44
+ DEFAULT_VOICE_IDS = {
45
+ ("en", "female"): "v-female-p9Xy7Q1L",
46
+ ("en", "male"): "v-male-A4b9KqP2",
47
+ ("zh", "female"): "female-kefu-xiaoyue",
48
+ ("zh", "male"): "male-kefu-xiaoxu",
49
+ }
50
+
51
+
52
+ class TrtcConversationClient:
53
+ """TRTC Conversational AI 控制面的轻量封装(使用官方 SDK)。"""
54
+
55
+ def __init__(self, tencent: TencentCloudCredential, trtc: TrtcCredential, llm: LlmCredential) -> None:
56
+ if not tencent.configured:
57
+ raise ValueError("tencent cloud credential not configured")
58
+ if not trtc.configured:
59
+ raise ValueError("trtc credential not configured")
60
+ if not llm.configured:
61
+ raise ValueError("llm credential not configured")
62
+ self.tencent = tencent
63
+ self.trtc = trtc
64
+ self.llm = llm
65
+ # 用官方 SDK 创建 client(跟原 demo translator_agent.py 完全一致的方式)
66
+ cred = credential.Credential(tencent.secret_id, tencent.secret_key)
67
+ http = HttpProfile()
68
+ http.endpoint = trtc.trtc_endpoint
69
+ cp = ClientProfile()
70
+ cp.httpProfile = http
71
+ self._api = trtc_client.TrtcClient(cred, trtc.trtc_region, cp)
72
+
73
+ # ------------------------------------------------------------------
74
+ # StartAIConversation
75
+ # ------------------------------------------------------------------
76
+ def start(
77
+ self,
78
+ room_id: str,
79
+ agent_user_id: str,
80
+ agent_user_sig: str,
81
+ target_user_id: str,
82
+ config: Optional[AgentLifecycleConfig] = None,
83
+ room_id_type: int = 0,
84
+ ) -> Dict[str, Any]:
85
+ cfg = config or AgentLifecycleConfig()
86
+ voice_id = cfg.voice_id or DEFAULT_VOICE_IDS.get((cfg.language, "female"), DEFAULT_VOICE_IDS[("en", "female")])
87
+
88
+ llm_cfg: Dict[str, Any] = {
89
+ "LLMType": self.llm.llm_type,
90
+ "Model": self.llm.model,
91
+ "APIKey": self.llm.api_key,
92
+ "APIUrl": self.llm.api_url,
93
+ "Streaming": True,
94
+ "SystemPrompt": cfg.instructions,
95
+ "History": 6,
96
+ "Temperature": 0.2,
97
+ }
98
+ llm_cfg.update(cfg.llm_extra or {})
99
+
100
+ # AgentConfig 参数对齐原 demo translator_agent.py(已验证跑通的参数组合)
101
+ agent_cfg = {
102
+ "UserId": agent_user_id,
103
+ "UserSig": agent_user_sig,
104
+ "TargetUserId": target_user_id,
105
+ "MaxIdleTime": cfg.max_idle_time,
106
+ "WelcomeMessage": cfg.welcome_message or cfg.greeting,
107
+ "WelcomeMessagePriority": 1, # 高优先级,欢迎语不会被打断
108
+ "TurnDetectionMode": 0, # 服务端检测一句话说完 -> 自动触发新一轮
109
+ "InterruptMode": 0,
110
+ "InterruptSpeechDuration": 500,
111
+ "SubtitleMode": 0, # 全量累积字幕
112
+ }
113
+ stt_cfg = {"Language": cfg.language}
114
+ tts_cfg = {
115
+ "TTSType": "flow",
116
+ "VoiceId": voice_id,
117
+ "Model": cfg.tts_model,
118
+ "Speed": 2.0 if not cfg.tts_enabled else 1.0,
119
+ "Volume": 0.001 if not cfg.tts_enabled else 1.0,
120
+ }
121
+ params = {
122
+ "SdkAppId": self.trtc.sdk_app_id,
123
+ "RoomId": str(room_id),
124
+ "RoomIdType": room_id_type,
125
+ "AgentConfig": agent_cfg,
126
+ "STTConfig": stt_cfg,
127
+ "LLMConfig": json.dumps(llm_cfg, ensure_ascii=False),
128
+ "TTSConfig": json.dumps(tts_cfg, ensure_ascii=False),
129
+ }
130
+
131
+ logger.info(
132
+ "StartAIConversation: endpoint=%s region=%s SdkAppId=%s RoomId=%s "
133
+ "agent=%s target=%s userSig=%s...%s(len=%d) lang=%s voice=%s",
134
+ self.trtc.trtc_endpoint, self.trtc.trtc_region, self.trtc.sdk_app_id,
135
+ room_id, agent_user_id, target_user_id,
136
+ agent_user_sig[:6], agent_user_sig[-4:], len(agent_user_sig),
137
+ cfg.language, voice_id,
138
+ )
139
+
140
+ req = models.StartAIConversationRequest()
141
+ req.from_json_string(json.dumps(params, ensure_ascii=False))
142
+ try:
143
+ resp = self._api.StartAIConversation(req)
144
+ except Exception as exc: # noqa: BLE001
145
+ err_str = str(exc)
146
+ if "UserSig" in err_str:
147
+ raise RuntimeError(
148
+ f"TRTC UserSig 校验失败({err_str})。"
149
+ f"常见原因:TRTC_REGION 与应用所在站点不匹配"
150
+ f"(当前 region={self.trtc.region}, endpoint={self.trtc.trtc_endpoint})。"
151
+ f"请检查 .env 中 TRTC_REGION:国际站应用用 intl。"
152
+ ) from exc
153
+ raise
154
+ result = json.loads(resp.to_json_string())
155
+ return {
156
+ "task_id": result.get("TaskId"),
157
+ "request_id": result.get("RequestId"),
158
+ }
159
+
160
+ # ------------------------------------------------------------------
161
+ # StopAIConversation
162
+ # ------------------------------------------------------------------
163
+ def stop(self, task_id: str) -> None:
164
+ if not task_id:
165
+ raise ValueError("task_id is required")
166
+ req = models.StopAIConversationRequest()
167
+ req.from_json_string(json.dumps({"TaskId": task_id}))
168
+ try:
169
+ self._api.StopAIConversation(req)
170
+ except Exception as exc: # noqa: BLE001
171
+ if "TaskNotExist" in str(exc):
172
+ logger.info("StopAIConversation: task already stopped (%s)", task_id)
173
+ return
174
+ raise
175
+
176
+ # ------------------------------------------------------------------
177
+ # ControlAIConversation:文本注入 / 打断
178
+ # ------------------------------------------------------------------
179
+ def control(self, task_id: str, command: str, text: Optional[str] = None, interrupt: bool = True) -> Dict[str, Any]:
180
+ if not task_id or not command:
181
+ raise ValueError("task_id and command are required")
182
+ payload: Dict[str, Any] = {"TaskId": task_id, "Command": command}
183
+ if text is not None:
184
+ payload["ServerPushText"] = {"Text": text, "Interrupt": interrupt}
185
+ req = models.ControlAIConversationRequest()
186
+ req.from_json_string(json.dumps(payload, ensure_ascii=False))
187
+ resp = self._api.ControlAIConversation(req)
188
+ return json.loads(resp.to_json_string())
@@ -0,0 +1,53 @@
1
+ # -*- coding: utf-8 -*-
2
+ """TLS-SIG-API-v2 UserSig 签发(纯 Python 实现,无第三方依赖)。
3
+
4
+ TRTC 进房鉴权:用 SDKAppID + SDKSecretKey 对 UserId 做 HMAC-SHA256 签名,
5
+ 再 zlib 压缩 + base64url 编码得到 UserSig。
6
+
7
+ 参考:https://cloud.tencent.com/document/product/647/17275
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import base64
12
+ import hashlib
13
+ import hmac
14
+ import json
15
+ import time
16
+ import zlib
17
+
18
+
19
+ def _base64_encode(data: bytes) -> str:
20
+ s = base64.b64encode(data).decode("utf-8")
21
+ return s.replace("+", "*").replace("/", "-").replace("=", "_")
22
+
23
+
24
+ def _hmac_sha256(sdk_app_id: int, user_id: str, secret_key: str, current_ts: int, expire: int) -> str:
25
+ raw_to_sign = (
26
+ f"TLS.identifier:{user_id}\n"
27
+ f"TLS.sdkappid:{sdk_app_id}\n"
28
+ f"TLS.time:{current_ts}\n"
29
+ f"TLS.expire:{expire}\n"
30
+ )
31
+ digest = hmac.new(secret_key.encode("utf-8"), raw_to_sign.encode("utf-8"), hashlib.sha256).digest()
32
+ return base64.b64encode(digest).decode("utf-8")
33
+
34
+
35
+ def gen_user_sig(sdk_app_id: int, sdk_secret_key: str, user_id: str, expire_seconds: int = 86400) -> str:
36
+ """签发一个 UserSig,供 TRTC Web/Native SDK 进房使用。"""
37
+ if not sdk_app_id or not sdk_secret_key:
38
+ raise ValueError("sdk_app_id and sdk_secret_key are required")
39
+ if not user_id:
40
+ raise ValueError("user_id is required")
41
+
42
+ current_ts = int(time.time())
43
+ sig = _hmac_sha256(sdk_app_id, user_id, sdk_secret_key, current_ts, expire_seconds)
44
+ payload = {
45
+ "TLS.ver": "2.0",
46
+ "TLS.identifier": str(user_id),
47
+ "TLS.sdkappid": int(sdk_app_id),
48
+ "TLS.expire": int(expire_seconds),
49
+ "TLS.time": int(current_ts),
50
+ "TLS.sig": sig,
51
+ }
52
+ compressed = zlib.compress(json.dumps(payload).encode("utf-8"))
53
+ return _base64_encode(compressed)
@@ -0,0 +1,46 @@
1
+ # meeting-ops
2
+
3
+ 多人房间场景的扇出编排能力:给定一组 `targetUserId`,批量起停 [conversation-core](../conversation-core) 会话,并维护房间级状态(当前跑的是什么业务、有哪些 bot 在线)。
4
+
5
+ ## 这个能力包不做什么
6
+
7
+ **不做权限校验。** `session/start` / `session/stop` 是特权操作(会触发真实的云服务调用,产生费用),但"谁能调用"完全交给集成方自己的业务系统判断——你的会议室/直播间/App 大概率已经有一套用户角色体系了,不需要我们重新发明一套。
8
+
9
+ 集成时请遵循:
10
+
11
+ ```
12
+ [你的前端] -> [你的后端:校验调用者是房主/管理员] -> 通过才转发 -> [meeting-ops /session/start]
13
+ ```
14
+
15
+ Path A 默认场景(`scenarios/meeting-interpreter`)里能看到一个具体实现:用内存字典记录"谁建的房就是谁的房主",仅供演示参考,**不要直接搬到生产环境**——生产场景请接入你自己系统里真实的房主/管理员判断逻辑。
16
+
17
+ ## 这个能力包做什么
18
+
19
+ - 按参会人列表批量调用 `conversation-core` 起一路会话(每个人一路,各自独立的 `agent_user_id`)
20
+ - 维护 `room_id -> {capability, params, tasks[]}` 的内存态,供状态查询/幂等重启使用
21
+ - 与具体业务(比如"翻译")解耦:通过 `capability` 字段 + 动态加载约定接口连接,不直接依赖 `realtime-translation`
22
+
23
+ ## API
24
+
25
+ | Method | Path | 说明 |
26
+ |---|---|---|
27
+ | POST | `/api/v1/meeting/session/start` | 批量起会话(特权操作) |
28
+ | POST | `/api/v1/meeting/session/stop` | 批量停会话(特权操作) |
29
+ | GET | `/api/v1/meeting/session/state` | 查询房间扇出状态(只读) |
30
+
31
+ 请求示例(起翻译扇出):
32
+
33
+ ```json
34
+ POST /api/v1/meeting/session/start
35
+ {
36
+ "room_id": "12345",
37
+ "room_id_type": 1,
38
+ "participants": ["user_a", "user_b"],
39
+ "capability": "realtime-translation",
40
+ "params": { "mode": "zh-en" }
41
+ }
42
+ ```
43
+
44
+ ## 复用到其他场景
45
+
46
+ 只要新业务能力包在 `src/service.py` 里实现 `build_lifecycle_config(params) -> AgentLifecycleConfig`,就可以直接把 `capability` 字段换成新能力包的目录名,无需修改 `meeting-ops` 任何代码。
@@ -0,0 +1,63 @@
1
+ # meeting-ops 能力自描述 manifest
2
+ # 类型:业务能力(可选安装)——多人房间的扇出编排,跟"翻译"这个具体业务无关
3
+
4
+ name: "meeting-ops"
5
+ version: "1.0.0"
6
+ type: "capability"
7
+ description: "按参会人列表批量起停 conversation-core 会话(扇出编排)+ 房间级会话状态存储。不含任何权限/AuthZ 逻辑——调用方必须自行完成权限校验后再转发请求。"
8
+
9
+ dependencies:
10
+ - name: "conversation-core"
11
+ version: ">=1.0.0,<2.0.0"
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # 与业务能力的连接方式:不直接 import,而是通过 capability 参数 + 动态加载约定接口
15
+ # ---------------------------------------------------------------------------
16
+ integration_contract:
17
+ description: >
18
+ session/start 请求体里的 `capability` 字段指定要扇出哪种业务(默认 "realtime-translation")。
19
+ 本能力包通过 conversation-core 的 _capability_loader 动态加载
20
+ capabilities/<capability>/src/service.py 里的 build_lifecycle_config(params) 函数,
21
+ 换取 conversation-core 的 AgentLifecycleConfig。因此 meeting-ops 可以脱离"翻译"
22
+ 这个具体业务单独复用——未来任何"按参会人批量起会话"的场景(如会议纪要 bot)
23
+ 只要实现同样的约定接口,就能直接换用本能力包,无需修改 meeting-ops 代码。
24
+ required_function: "build_lifecycle_config(params: dict) -> AgentLifecycleConfig"
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # 对外暴露的 API
28
+ # ---------------------------------------------------------------------------
29
+ endpoints:
30
+ - method: POST
31
+ path: /api/v1/meeting/session/start
32
+ description: "按参会人列表批量起会话(特权操作,无内置权限校验,见安全声明)"
33
+ - method: POST
34
+ path: /api/v1/meeting/session/stop
35
+ description: "批量停止该房间当前所有会话(特权操作,无内置权限校验)"
36
+ - method: GET
37
+ path: /api/v1/meeting/session/state
38
+ description: "查询房间当前扇出状态(只读,无需权限)"
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # 业务契约:无外部业务系统依赖
42
+ # ---------------------------------------------------------------------------
43
+ business_contract:
44
+ external_apis: []
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # 安全声明(务必遵守)
48
+ # ---------------------------------------------------------------------------
49
+ security:
50
+ authz:
51
+ enforced_by_this_capability: false
52
+ note: >
53
+ 本能力包的 session/start、session/stop 是特权操作端点,**不做任何调用者身份/权限校验**。
54
+ 集成方必须在自己的后端完成"调用者是否有权触发批量起会话"的校验后,才转发请求到这里。
55
+ 切勿把这些端点直接暴露给未经身份校验的公网客户端。
56
+ network:
57
+ enforce_https: true
58
+
59
+ acceptance:
60
+ - "meeting-ops 不 import 任何具体业务能力包(如 realtime-translation),只通过 capability 参数 + 动态加载约定接口连接"
61
+ - "session/start 对已有活跃扇出会先全部停掉再重开(幂等)"
62
+ - "不做权限校验;README/manifest 明确声明该职责边界,避免误用为'内置安全'"
63
+ - "单目标场景(不装本能力包)依然可以正常使用 conversation-core + realtime-translation"