Use FastGPT app opener for greetings

This commit is contained in:
Xin Wang
2026-05-29 15:34:33 +08:00
parent ce3c384b00
commit 15c98f21fe
9 changed files with 30 additions and 50 deletions

View File

@@ -45,9 +45,7 @@
"user_speech_timeout_sec": 0.2
},
"agent": {
"system_prompt": "FastGPT app owns the system prompt when send_system_prompt is false.",
"greeting": "您好,这里是无锡交警,我将为您远程处理交通事故。请将人员撤离至路侧安全区域,开启危险报警双闪灯、放置三角警告牌、做好安全防护,谨防二次事故伤害。若您已经准备好了,请点击继续办理,如需人工服务,请说转人工。",
"greeting_mode": "fixed"
"greeting_mode": "fastgpt_opener"
},
"services": {
"stt": {
@@ -72,8 +70,7 @@
"chat_id": null,
"variables": {},
"detail": false,
"timeout_sec": 60.0,
"send_system_prompt": false
"timeout_sec": 60.0
},
"tts": {
"provider": "xfyun",

View File

@@ -48,9 +48,7 @@
"idle_prompt_text": "你好,请问还在吗?"
},
"agent": {
"system_prompt": "FastGPT app owns the system prompt when send_system_prompt is false.",
"greeting": "您好,这里是无锡交警,我将为您远程处理交通事故。请将人员撤离至路侧安全区域,开启危险报警双闪灯、放置三角警告牌、做好安全防护,谨防二次事故伤害。若您已经准备好了,请点击继续办理,如需人工服务,请说转人工。",
"greeting_mode": "fixed",
"greeting_mode": "fastgpt_opener",
"response_state": {
"enabled": true,
"tag": "state",
@@ -81,8 +79,7 @@
"chat_id": null,
"variables": {},
"detail": false,
"timeout_sec": 60.0,
"send_system_prompt": false
"timeout_sec": 60.0
},
"tts": {
"provider": "xfyun_super",

View File

@@ -55,9 +55,7 @@
"user_speech_timeout_sec": 0.2
},
"agent": {
"system_prompt": "FastGPT app owns the system prompt when send_system_prompt is false.",
"greeting": "您好,这里是无锡交警,我将为您远程处理交通事故。请将人员撤离至路侧安全区域,开启危险报警双闪灯、放置三角警告牌、做好安全防护,谨防二次事故伤害。若您已经准备好了,请点击继续办理,如需人工服务,请说转人工。",
"greeting_mode": "fixed",
"greeting_mode": "fastgpt_opener",
"response_state": {
"enabled": false,
"tag": "state",
@@ -88,8 +86,7 @@
"chat_id": null,
"variables": {},
"detail": false,
"timeout_sec": 60.0,
"send_system_prompt": false
"timeout_sec": 60.0
},
"tts": {
"provider": "xfyun",

View File

@@ -45,9 +45,7 @@
"user_speech_timeout_sec": 0.2
},
"agent": {
"system_prompt": "FastGPT app owns the system prompt when send_system_prompt is false.",
"greeting": "您好,这里是无锡交警,我将为您远程处理交通事故。请将人员撤离至路侧安全区域,开启危险报警双闪灯、放置三角警告牌、做好安全防护,谨防二次事故伤害。若您已经准备好了,请点击继续办理,如需人工服务,请说转人工。",
"greeting_mode": "fixed",
"greeting_mode": "fastgpt_opener",
"response_state": {
"enabled": false,
"tag": "state",
@@ -78,8 +76,7 @@
"chat_id": null,
"variables": {},
"detail": false,
"timeout_sec": 60.0,
"send_system_prompt": false
"timeout_sec": 60.0
},
"tts": {
"provider": "xfyun",

View File

@@ -48,9 +48,7 @@
"idle_prompt_text": "你好,请问还在吗?"
},
"agent": {
"system_prompt": "FastGPT app owns the system prompt when send_system_prompt is false.",
"greeting": "您好,这里是无锡交警,我将为您远程处理交通事故。请将人员撤离至路侧安全区域,开启危险报警双闪灯、放置三角警告牌、做好安全防护,谨防二次事故伤害。若您已经准备好了,请点击继续办理,如需人工服务,请说转人工。",
"greeting_mode": "fixed",
"greeting_mode": "fastgpt_opener",
"response_state": {
"enabled": false,
"tag": "state",
@@ -81,8 +79,7 @@
"chat_id": null,
"variables": {},
"detail": false,
"timeout_sec": 60.0,
"send_system_prompt": false
"timeout_sec": 60.0
},
"tts": {
"provider": "xfyun_super",

View File

@@ -147,7 +147,6 @@ class LLMConfig:
variables: dict[str, str] = field(default_factory=dict)
detail: bool = False
timeout_sec: float = 60.0
send_system_prompt: bool = False
@property
def is_fastgpt(self) -> bool:
@@ -160,7 +159,7 @@ class LLMConfig:
@property
def uses_local_context_history(self) -> bool:
"""Whether the pipeline should seed and maintain local LLM context history."""
return not self.is_fastgpt or self.send_system_prompt
return not self.is_fastgpt
@dataclass(frozen=True)
@@ -233,9 +232,11 @@ def config_from_dict(data: dict) -> EngineConfig:
agent = _dict(data.get("agent"))
if agent.get("greeting") == "":
agent["greeting"] = None
if agent.get("greeting_mode") not in (None, "generated", "fixed", "off"):
raise ValueError("agent.greeting_mode must be one of: generated, fixed, off")
response_state = ResponseStateConfig(**_dict(agent.pop("response_state")))
if agent.get("greeting_mode") not in (None, "generated", "fixed", "off", "fastgpt_opener"):
raise ValueError(
"agent.greeting_mode must be one of: generated, fixed, off, fastgpt_opener"
)
response_state = ResponseStateConfig(**_dict(agent.pop("response_state", None)))
if response_state.max_prefix_chars < 1:
raise ValueError("agent.response_state.max_prefix_chars must be greater than 0")
if not response_state.tag:
@@ -255,6 +256,10 @@ def config_from_dict(data: dict) -> EngineConfig:
llm["app_id"] = None
if not isinstance(llm.get("variables"), dict):
llm["variables"] = {}
if agent.get("greeting_mode") == "fastgpt_opener" and llm["provider"] != "fastgpt":
raise ValueError(
"agent.greeting_mode='fastgpt_opener' requires services.llm.provider='fastgpt'"
)
turn = _dict(data.get("turn"))
vad = _dict(turn.get("vad"))

View File

@@ -170,7 +170,6 @@ class FastGPTLLMService(LLMService):
base_url: str,
chat_id: str | None = None,
app_id: str | None = None,
send_system_prompt: bool = False,
greeting_prompt: str | None = None,
timeout: float = 60.0,
settings: FastGPTLLMSettings | None = None,
@@ -183,7 +182,6 @@ class FastGPTLLMService(LLMService):
self._chat_id = chat_id or f"voice_{uuid.uuid4().hex[:16]}"
self._app_id = (app_id or "").strip()
self._send_system_prompt = send_system_prompt
self._greeting_prompt = (greeting_prompt or "你好").strip() or "你好"
self._client = AsyncChatClient(
api_key=api_key,
@@ -246,6 +244,8 @@ class FastGPTLLMService(LLMService):
return _first_nonempty_text(
chat_config.get("welcomeText"),
app_payload.get("welcomeText"),
app_payload.get("opener"),
app_payload.get("intro"),
)
async def fetch_welcome_text(self) -> str | None:
@@ -261,7 +261,7 @@ class FastGPTLLMService(LLMService):
response.raise_for_status()
text = self._welcome_text_from_init_payload(response.json())
if text:
logger.info(f"FastGPT welcomeText loaded for appId={self._app_id}")
logger.info(f"FastGPT app opener loaded for appId={self._app_id}")
return text or None
except FastGPTError as exc:
logger.warning(f"FastGPT chat init failed: {exc}")
@@ -279,26 +279,15 @@ class FastGPTLLMService(LLMService):
def _build_fastgpt_messages(self, context: LLMContext) -> list[dict[str, str]]:
raw_messages = context.get_messages()
messages: list[dict[str, str]] = []
if self._send_system_prompt:
for message in raw_messages:
if not isinstance(message, dict) or message.get("role") != "system":
continue
text = _message_text(message)
if text:
messages.append({"role": "system", "content": text})
for message in reversed(raw_messages):
if not isinstance(message, dict) or message.get("role") != "user":
continue
text = _message_text(message)
if text:
messages.append({"role": "user", "content": text})
return messages
return [{"role": "user", "content": text}]
messages.append({"role": "user", "content": self._greeting_prompt})
return messages
return [{"role": "user", "content": self._greeting_prompt}]
async def _process_context(self, context: LLMContext) -> None:
messages = self._build_fastgpt_messages(context)

View File

@@ -196,15 +196,17 @@ async def run_pipeline_with_serializer(
logger.info(f"{client_label} websocket client connected")
if config.agent.greeting_mode == "fixed" and config.agent.greeting:
await task.queue_frames([TTSSpeakFrame(config.agent.greeting)])
elif config.agent.greeting_mode == "generated":
elif config.agent.greeting_mode == "fastgpt_opener":
if isinstance(llm, FastGPTLLMService):
welcome = await llm.fetch_welcome_text()
if welcome:
await task.queue_frames([TTSSpeakFrame(welcome)])
else:
await task.queue_frames([LLMRunFrame()])
logger.warning("FastGPT opener requested but no opener text was returned")
else:
await task.queue_frames([LLMRunFrame()])
raise RuntimeError("agent.greeting_mode='fastgpt_opener' requires FastGPT LLM service")
elif config.agent.greeting_mode == "generated":
await task.queue_frames([LLMRunFrame()])
@transport.event_handler("on_client_disconnected")
async def on_client_disconnected(_transport, _client):

View File

@@ -63,7 +63,6 @@ def create_llm_service(
base_url=config.base_url or "http://localhost:3000",
chat_id=chat_id or config.chat_id,
app_id=config.app_id,
send_system_prompt=config.send_system_prompt,
greeting_prompt=greeting_prompt,
timeout=config.timeout_sec,
settings=FastGPTLLMSettings(