XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Fix audio response in API

9868a881
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

5 个文件 +38 -19
Modified g4f/Provider/template/OpenaiTemplate.py +1 -4
@@ -166,11 +166,8 @@ async def read_response(response: StreamResponse, stream: bool, prompt: str, pro
166 166 audio = message.get("audio", {})
167 167 if "data" in audio:
168 168 if download_media:
169 async for chunk in save_response_media(audio["data"], prompt, [model]):
169 async for chunk in save_response_media(audio, prompt, [model]):
170 170 yield chunk
171 if "transcript" in audio:
172 yield "\n\n"
173 yield audio["transcript"]
174 171 else:
175 172 yield AudioResponse(f"data:audio/mpeg;base64,{audio['data']}", transcript=audio.get("transcript"))
176 173 if choice and "finish_reason" in choice and choice["finish_reason"] is not None:
Modified g4f/api/__init__.py +7 -2
@@ -12,6 +12,7 @@ from email.utils import formatdate
12 12 import os.path
13 13 import hashlib
14 14 import asyncio
15 import base64
15 16 from contextlib import asynccontextmanager
16 17 from urllib.parse import quote_plus
17 18 from fastapi import FastAPI, Response, Request, UploadFile, Form, Depends, Header
@@ -679,7 +680,7 @@ class Api:
679 680 HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponseModel},
680 681 }
681 682 @self.app.post("/v1/audio/speech", responses=responses)
682 @self.app.post("/api/{path_provider}/audio/speech", responses=responses)
683 @self.app.post("/api/{provider}/audio/speech", responses=responses)
683 684 async def generate_speech(
684 685 config: AudioSpeechConfig,
685 686 provider: str = AppConfig.media_provider,
@@ -698,8 +699,12 @@ class Api:
698 699 prompt=config.input,
699 700 audio=filter_none(voice=config.voice, format=config.response_format, language=config.language),
700 701 api_key=api_key,
702 download_media=config.download_media,
701 703 )
702 if isinstance(response.choices[0].message.content, AudioResponse):
704 if response.choices[0].message.audio is not None:
705 response = base64.b64decode(response.choices[0].message.audio.data)
706 return Response(response, media_type=f"audio/{config.response_format.replace("mp3", "mpeg")}")
707 elif isinstance(response.choices[0].message.content, AudioResponse):
703 708 response = response.choices[0].message.content.data
704 709 response = response.replace("/media", get_media_dir())
705 710 def delete_file():
Modified g4f/api/stubs.py +2 -1
@@ -133,4 +133,5 @@ class AudioSpeechConfig(BaseModel):
133 133 voice: Optional[str] = None
134 134 instrcutions: str = "Speech this text in a natural way."
135 135 response_format: Optional[str] = None
136 language: Optional[str] = None
136 language: Optional[str] = None
137 download_media: bool = True
Modified g4f/client/stubs.py +23 -8
@@ -113,16 +113,9 @@ class ResponseMessage(BaseModel):
113 113 type: str = "message"
114 114 role: str
115 115 content: list[ResponseMessageContent]
116 audio: dict = None
117 116
118 117 @classmethod
119 118 def model_construct(cls, content: str):
120 if isinstance(content, AudioResponse):
121 return super().model_construct(
122 role="assistant",
123 audio={"data": content.data.split(",")[-1], "transcript": content.transcript},
124 content=[ResponseMessageContent.model_construct(content)]
125 )
126 119 return super().model_construct(role="assistant", content=[ResponseMessageContent.model_construct(content)])
127 120
128 121 class ResponseMessageContent(BaseModel):
@@ -137,14 +130,36 @@ class ResponseMessageContent(BaseModel):
137 130 def serialize_text(self, text: str):
138 131 return str(text)
139 132
133 class AudioResponseModel(BaseModel):
134 data: str
135 transcript: Optional[str] = None
136
137 @classmethod
138 def model_construct(cls, data: str, transcript: Optional[str] = None):
139 return super().model_construct(data=data, transcript=transcript)
140
140 141 class ChatCompletionMessage(BaseModel):
141 142 role: str
142 143 content: str
143 144 reasoning_content: Optional[str] = None
144 145 tool_calls: list[ToolCallModel] = None
145
146 audio: AudioResponseModel = None
147
148 @classmethod
149 def model_construct(cls, content: str):
150 return super().model_construct(role="assistant", content=[ResponseMessageContent.model_construct(content)])
151
146 152 @classmethod
147 153 def model_construct(cls, content: str, reasoning_content: list[Reasoning] = None, tool_calls: list = None):
154 if isinstance(content, AudioResponse) and content.data.startswith("data:"):
155 return super().model_construct(
156 role="assistant",
157 audio=AudioResponseModel.model_construct(
158 data=content.data.split(",")[-1],
159 transcript=content.transcript
160 ),
161 content=content
162 )
148 163 return super().model_construct(role="assistant", content=content, **filter_none(tool_calls=tool_calls, reasoning_content=reasoning_content))
149 164
150 165 @field_serializer('content')
Modified g4f/image/copy_images.py +5 -4
@@ -60,15 +60,16 @@ def update_filename(response, filename: str) -> str:
60 60 timestamp = datetime.strptime(date, '%a, %d %b %Y %H:%M:%S %Z').timestamp()
61 61 return str(int(timestamp)) + "_" + filename.split("_", maxsplit=1)[-1]
62 62
63 async def save_response_media(response, prompt: str, tags: list[str] = []) -> AsyncIterator:
63 async def save_response_media(response, prompt: str, tags: list[str] = [], transcript: str = None) -> AsyncIterator:
64 64 """Save media from response to local file and return URL"""
65 65 if isinstance(response, dict):
66 content_type = response.get("mimeType")
66 content_type = response.get("mimeType", "audio/mpeg")
67 transcript = response.get("transcript")
67 68 response = response.get("data")
68 69 elif hasattr(response, "headers"):
69 70 content_type = response.headers["content-type"]
70 71 else:
71 content_type = "audio/mpeg"
72 raise ValueError("Response must be a dict or have headers")
72 73
73 74 if isinstance(response, str):
74 75 response = base64.b64decode(response)
@@ -101,7 +102,7 @@ async def save_response_media(response, prompt: str, tags: list[str] = []) -> As
101 102 source_url = str(response.url)
102 103
103 104 if content_type.startswith("audio/"):
104 yield AudioResponse(media_url, text=prompt, source_url=source_url)
105 yield AudioResponse(media_url, transcript, source_url=source_url)
105 106 elif content_type.startswith("video/"):
106 107 yield VideoResponse(media_url, prompt, source_url=source_url)
107 108 else: