This commit is contained in:
Chingfeng Li
2025-11-03 10:47:09 +08:00
20 changed files with 132 additions and 108 deletions
@@ -68,6 +68,9 @@ def get_config_from_api(config):
"vision_explain": config["server"].get("vision_explain", ""),
"auth_key": config["server"].get("auth_key", ""),
}
# 如果服务器没有prompt_template,则从本地配置读取
if not config_data.get("prompt_template"):
config_data["prompt_template"] = config.get("prompt_template")
return config_data
+1 -1
View File
@@ -5,7 +5,7 @@ from config.config_loader import load_config
from config.settings import check_config_file
from datetime import datetime
SERVER_VERSION = "0.8.5"
SERVER_VERSION = "0.8.6"
_logger_initialized = False
+3 -1
View File
@@ -22,4 +22,6 @@ manager-api:
# 如果使用docker部署,请使用填写成 http://xiaozhi-esp32-server-web:8002/xiaozhi
url: http://127.0.0.1:8002/xiaozhi
# 你的manager-api的token,就是刚才复制出来的server.secret
secret: 你的server.secret值
secret: 你的server.secret值
# 默认系统提示词模板文件
prompt_template: agent-base-prompt.txt
@@ -94,37 +94,40 @@ async def sendAudio(conn, audios, frame_duration=60):
send_delay = conn.config.get("tts_audio_send_delay", -1) / 1000.0
if isinstance(audios, bytes):
if conn.client_abort:
return
conn.last_activity_time = time.time() * 1000
# 获取或初始化流控状态
if not hasattr(conn, "audio_flow_control"):
# 重置流控状态,第一次读取和会话发生转变时
if not hasattr(conn, "audio_flow_control") or conn.audio_flow_control.get("sentence_id") != conn.sentence_id:
conn.audio_flow_control = {
"last_send_time": 0,
"packet_count": 0,
"start_time": time.perf_counter(),
"sequence": 0, # 添加序列号
"sentence_id": conn.sentence_id,
}
if conn.client_abort:
return
conn.last_activity_time = time.time() * 1000
# 预缓冲:前5个包直接发送,不做延迟
pre_buffer_count = 5
flow_control = conn.audio_flow_control
current_time = time.perf_counter()
if send_delay > 0:
if flow_control["packet_count"] < pre_buffer_count:
# 预缓冲阶段,直接发送不延迟
pass
elif send_delay > 0:
# 使用固定延迟
await asyncio.sleep(send_delay)
else:
# 计算预期发送时间
effective_packet = flow_control["packet_count"] - pre_buffer_count
expected_time = flow_control["start_time"] + (
flow_control["packet_count"] * frame_duration / 1000
effective_packet * frame_duration / 1000
)
delay = expected_time - current_time
if delay > 0:
await asyncio.sleep(delay)
else:
# 纠正误差
flow_control["start_time"] += abs(delay)
if conn.conn_from_mqtt_gateway:
# 计算时间戳和序列号
@@ -150,7 +153,7 @@ async def sendAudio(conn, audios, frame_duration=60):
play_position = 0
# 执行预缓冲
pre_buffer_frames = min(3, len(audios))
pre_buffer_frames = min(5, len(audios))
for i in range(pre_buffer_frames):
if conn.conn_from_mqtt_gateway:
# 计算时间戳和序列号
@@ -17,7 +17,6 @@ class LLMProvider(LLMProviderBase):
self.base_url = config.get("base_url")
else:
self.base_url = config.get("url")
# 增加timeout的配置项,单位为秒
timeout = config.get("timeout", 300)
self.timeout = int(timeout) if timeout else 300
@@ -48,8 +47,18 @@ class LLMProvider(LLMProviderBase):
logger.bind(tag=TAG).error(model_key_msg)
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=httpx.Timeout(self.timeout))
@staticmethod
def normalize_dialogue(dialogue):
"""自动修复 dialogue 中缺失 content 的消息"""
for msg in dialogue:
if "role" in msg and "content" not in msg:
msg["content"] = ""
return dialogue
def response(self, session_id, dialogue, **kwargs):
try:
dialogue = self.normalize_dialogue(dialogue)
responses = self.client.chat.completions.create(
model=self.model_name,
messages=dialogue,
@@ -65,17 +74,11 @@ class LLMProvider(LLMProviderBase):
is_active = True
for chunk in responses:
try:
# 检查是否存在有效的choice且content不为空
delta = (
chunk.choices[0].delta
if getattr(chunk, "choices", None)
else None
)
content = delta.content if hasattr(delta, "content") else ""
delta = chunk.choices[0].delta if getattr(chunk, "choices", None) else None
content = getattr(delta, "content", "") if delta else ""
except IndexError:
content = ""
if content:
# 处理标签跨多个chunk的情况
if "<think>" in content:
is_active = False
content = content.split("<think>")[0]
@@ -90,17 +93,18 @@ class LLMProvider(LLMProviderBase):
def response_with_functions(self, session_id, dialogue, functions=None):
try:
dialogue = self.normalize_dialogue(dialogue)
stream = self.client.chat.completions.create(
model=self.model_name, messages=dialogue, stream=True, tools=functions
)
for chunk in stream:
# 检查是否存在有效的choice且content不为空
if getattr(chunk, "choices", None):
yield chunk.choices[0].delta.content, chunk.choices[
0
].delta.tool_calls
# 存在 CompletionUsage 消息时,生成 Token 消耗 log
delta = chunk.choices[0].delta
content = getattr(delta, "content", "")
tool_calls = getattr(delta, "tool_calls", None)
yield content, tool_calls
elif isinstance(getattr(chunk, "usage", None), CompletionUsage):
usage_info = getattr(chunk, "usage", None)
logger.bind(tag=TAG).info(
@@ -225,6 +225,7 @@ class PromptManager:
weather_info=weather_info,
emojiList=EMOJI_List,
device_id=device_id,
client_ip=client_ip,
*args, **kwargs
)
device_cache_key = f"device_prompt:{device_id}"