[Dify] Speech2Text 관련 모델 파라미터 찾아보기 (진행중)

2025. 3. 29. 09:53꿀팁 분석 환경 설정

728x90

 

문제점

dify에 open-source 모델에 vllm openai-compatible 하게 서빙을 한 stt 모델을 테스트하는 데, 자꾸 영어로 변환되는 문제가 발생했다.

 

그래서 vllm 로그를 보니 다음과 같이 나왔다.

vllm 파라미터를 찾아봤지만 저 부분을 <|ko|> 로 바꾸는 파라미터를 찾을 수가 없었다.

prompt: '<|startoftranscript|><|en|><|transcribe|><|notimestamps|>'

 

그렇다고 실제로 dify에서도 STT 모델에 대해서 Language를 바꾸는 기능을 제공하지 않았다.

 

코드 분석해보기

Repository : dify

 

그래서 우선 코드에서 찾아보기로 하였다.

결론적으로 직접적으로 수정하는 부분을 찾지 못했지만 다음과 같은 부분을 찾았다.

앞에 front에서부터 관련된 부분을 찾았다./

web\app\components\base\voice-input\index.tsx
    let url = ''
    let isPublic = false

    if (params.token) {
      url = '/audio-to-text'
      isPublic = true
    }
    else if (params.appId) {
      if (pathname.search('explore/installed') > -1)
        url = `/installed-apps/${params.appId}/audio-to-text`
      else
        url = `/apps/${params.appId}/audio-to-text`
    }

    try {
      const audioResponse = await audioToText(url, isPublic, formData)
      onConverted(audioResponse.text)
      onCancel()
    }
    catch (e) {
      onConverted('')
      onCancel()
    }

그 다음에 api에서 연결된 부분을 찾아보고 있었다.

class AudioService:
    @classmethod
    def transcript_asr(cls, app_model: App, file: FileStorage, end_user: Optional[str] = None):
        if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
            workflow = app_model.workflow
            if workflow is None:
                raise ValueError("Speech to text is not enabled")

            features_dict = workflow.features_dict
            if "speech_to_text" not in features_dict or not features_dict["speech_to_text"].get("enabled"):
                raise ValueError("Speech to text is not enabled")
        else:
            app_model_config: AppModelConfig = app_model.app_model_config

            if not app_model_config.speech_to_text_dict["enabled"]:
                raise ValueError("Speech to text is not enabled")

        if file is None:
            raise NoAudioUploadedServiceError()

        extension = file.mimetype
        if extension not in [f"audio/{ext}" for ext in AUDIO_EXTENSIONS]:
            raise UnsupportedAudioTypeServiceError()

        file_content = file.read()
        file_size = len(file_content)

        if file_size > FILE_SIZE_LIMIT:
            message = f"Audio size larger than {FILE_SIZE} mb"
            raise AudioTooLargeServiceError(message)

        model_manager = ModelManager()
        model_instance = model_manager.get_default_model_instance(
            tenant_id=app_model.tenant_id, model_type=ModelType.SPEECH2TEXT
        )
        if model_instance is None:
            raise ProviderNotSupportSpeechToTextServiceError()

        buffer = io.BytesIO(file_content)
        buffer.name = "temp.mp3"

        return {"text": model_instance.invoke_speech2text(file=buffer, user=end_user)}
api\core\model_runtime\model_providers\__base\speech2text_model.py
class Speech2TextModel(AIModel):
    """
    Model class for speech2text model.
    """

    model_type: ModelType = ModelType.SPEECH2TEXT

    # pydantic configs
    model_config = ConfigDict(protected_namespaces=())

    def invoke(self, model: str, credentials: dict, file: IO[bytes], user: Optional[str] = None) -> str:
        """
        Invoke speech to text model

        :param model: model name
        :param credentials: model credentials
        :param file: audio file
        :param user: unique user id
        :return: text for given audio file
        """
        try:
            plugin_model_manager = PluginModelManager()
            return plugin_model_manager.invoke_speech_to_text(
                tenant_id=self.tenant_id,
                user_id=user or "unknown",
                plugin_id=self.plugin_id,
                provider=self.provider_name,
                model=model,
                credentials=credentials,
                file=file,
            )
        except Exception as e:
            raise self._transform_invoke_error(e)

여기에 보면 pluginmanager라는 부분에서 모든 모델에 대해서 상속받아서 처리하고 있었다.

그래서 plugin manager에 다시 해당하는 코드 확인하였다.

api\core\plugin\manager\model.py
    def invoke_speech_to_text(
        self,
        tenant_id: str,
        user_id: str,
        plugin_id: str,
        provider: str,
        model: str,
        credentials: dict,
        file: IO[bytes],
    ) -> str:
        """
        Invoke speech to text
        """
        response = self._request_with_plugin_daemon_response_stream(
            method="POST",
            path=f"plugin/{tenant_id}/dispatch/speech2text/invoke",
            type=PluginStringResultResponse,
            data=jsonable_encoder(
                {
                    "user_id": user_id,
                    "data": {
                        "provider": provider,
                        "model_type": "speech2text",
                        "model": model,
                        "credentials": credentials,
                        "file": binascii.hexlify(file.read()).decode(),
                    },
                }
            ),
            headers={
                "X-Plugin-ID": plugin_id,
                "Content-Type": "application/json",
            },
        )

        for resp in response:
            return resp.result

        raise ValueError("Failed to invoke speech to text")

 

Repository : dify-plugin-daemon 

저게 어디있을까 하고 다른 코드를 찾아보니 dify-plugin-daemon에서 볼 수 있었다.

근데 내가 GO를 잘 몰라서 여기에서 찾아봤을 때 그냥 API만 있을 뿐 더 디테일한 부분은 못 찾았다.

dify-plugin-daemon/internal/server/http_server.go

	group.POST("/speech2text/invoke", controllers.InvokeSpeech2Text(config))
dify-plugin-daemon/internal/core/dify_invocation/real/http_request.go
func (i *RealBackwardsInvocation) InvokeSpeech2Text(payload *dify_invocation.InvokeSpeech2TextRequest) (*model_entities.Speech2TextResult, error) {
	return Request[model_entities.Speech2TextResult](i, "POST", "invoke/speech2text", http_requests.HttpPayloadJson(payload))
}

 

하지만 더이상 dify repoisotry에서는 plugin별로 정보를 처리하느 것을 찾을 수 없어서 다른 repository를 확인해봐야했다.

그래서 다른 방향으로 한 것은 Plugin이다 보니 Plugin 관련 repostiory를 보기로 했다.

 

그래서 이름이 dify-official-plugins 라는 repository가 있어서 그 중에 openai_api_compatible한 곳에 speech2text를 확인하였다. 

그러니 또 dify_plugin이라는 라이브러리가 있어서 다시 그 부분을 찾아보았다.

Repository : dify_plugin

dify-official-plugins/models/openai_api_compatible/models/speech2text/speech2text.py
from typing import Optional
from dify_plugin.entities.model import AIModelEntity, FetchFrom, I18nObject, ModelType
from dify_plugin.interfaces.model.openai_compatible.speech2text import (
    OAICompatSpeech2TextModel,
)


class OpenAISpeech2TextModel(OAICompatSpeech2TextModel):
    def get_customizable_model_schema(self, model: str, credentials: dict) -> Optional[AIModelEntity]:
        """
        used to define customizable model schema
        """
        entity = AIModelEntity(
            model=model,
            label=I18nObject(en_US=model),
            fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
            model_type=ModelType.SPEECH2TEXT,
            model_properties={},
            parameter_rules=[],
        )

        if "display_name" in credentials and credentials["display_name"] != "":
            entity.label= I18nObject(
                en_US=credentials["display_name"],
                zh_Hans=credentials["display_name"]
            )

        return entity

 

 

그 dify_plugin이라는 라이브러리는 dify-plugin-sdks 라는 repository에 있는 것을 확인했고 거기서도 또 speech2text를 찾아봤다.

그래서 아래와 같은 경로에 보니 다음과 같은 코드를 찾았다.

아래에 보니 결국 모델만 payload로 받고 나머지는 무시하게 되어있는 것을 확인했다.

dify-plugin-sdks\python\dify_plugin\interfaces\model\openai_compatible\speech2text.py
class OAICompatSpeech2TextModel(_CommonOaiApiCompat, Speech2TextModel):
    """
    Model class for OpenAI Compatible Speech to text model.
    """

    def _invoke(self, model: str, credentials: dict, file: IO[bytes], user: Optional[str] = None) -> str:
        """
        Invoke speech2text model

        :param model: model name
        :param credentials: model credentials
        :param file: audio file
        :param user: unique user id
        :return: text for given audio file
        """
        headers = {}

        api_key = credentials.get("api_key")
        if api_key:
            headers["Authorization"] = f"Bearer {api_key}"

        endpoint_url = credentials.get("endpoint_url", "https://api.openai.com/v1/")
        if not endpoint_url.endswith("/"):
            endpoint_url += "/"
        endpoint_url = urljoin(endpoint_url, "audio/transcriptions")

        payload = {"model": model}
        files = [("file", file)]
        response = requests.post(endpoint_url, headers=headers, data=payload, files=files)  # noqa: S113

        if response.status_code != 200:
            raise InvokeBadRequestError(response.text)
        response_data = response.json()
        return response_data["text"]

 

Openai API Site

아래에 openai api를 보니 다른 것도 많이 있지만 language를 추가하는 부분이 있는 것을 확인했다.

 

 

TODO...

하지만 이 offical-plugin-sdk랑 실제 코드에서 연동되는 부분인 dify-plugin-daemon 과의 관계를 아직 찾지 못했다. 이 부분을 좀 더 찾아봐야 연결이 되는 지 확인하고, 그 다음에 수정을 해서 고칠 수 있는 지 확인해봐야겠다.

참조링크

 

 

728x90

데이터분석뉴비님의
글이 좋았다면 응원을 보내주세요!