"""Execute in Dify API Python with PAYLOAD supplied by private host wrapper. Uses deployed services/controllers for credential encryption and app config. Never print this process output to public logs: create returns a new app key to its private caller; the wrapper writes it 0600 and emits only public metadata. """ import hashlib import inspect import json import logging import sys logging.disable(logging.CRITICAL) from app_factory import create_app app = create_app() from sqlalchemy import select from extensions.ext_database import db from models.account import Account, Tenant, TenantAccountJoin from models.model import App, ApiToken from models.provider import ProviderModel, ProviderModelCredential, TenantDefaultModel from services.app_service import AppService from services.model_provider_service import ModelProviderService PROVIDER = 'langgenius/openai_api_compatible/openai_api_compatible' ASR = 'Qwen/Qwen3-ASR-1.7B' APP_NAME = 'followup-audio-isolated-20261009' ENDPOINT = 'http://followup-asr-bridge:8080/v1' CONFIG = { 'model': {'provider': PROVIDER, 'name': 'qwen3.6-35b', 'mode': 'chat', 'completion_params': {'max_tokens': 8192, 'temperature': 0.0, 'top_p': 1.0, 'enable_thinking': False}}, 'pre_prompt': '仅依据本次用户请求处理提供的文本。遵循用户给定的 JSON 格式,只返回最终 JSON,不输出思考过程、解释或 Markdown。不得把示例当成事实。', 'prompt_type': 'simple', 'user_input_form': [], 'speech_to_text': {'enabled': True}, 'text_to_speech': {'enabled': False}, 'suggested_questions_after_answer': {'enabled': False}, 'retriever_resource': {'enabled': False}, 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'enabled': False}, } with app.app_context(): # Anchor only to the existing default Qwen workspace. Fail if ambiguous. tenant_ids = db.session.scalars(select(TenantDefaultModel.tenant_id).where( TenantDefaultModel.model_type == 'text-generation', TenantDefaultModel.model_name == 'qwen3.6-35b')).all() assert len(tenant_ids) == 1, 'AMBIGUOUS_WORKSPACE' tenant_id = tenant_ids[0] tenant = db.session.get(Tenant, tenant_id) owner = db.session.scalars(select(Account).join(TenantAccountJoin, Account.id == TenantAccountJoin.account_id).where( TenantAccountJoin.tenant_id == tenant_id, TenantAccountJoin.role == 'owner')).one() owner.current_tenant = tenant def current_app_owned(): resource = PAYLOAD['resources'] obj = db.session.get(App, resource['app_id']) assert obj and obj.name == APP_NAME and obj.tenant_id == tenant_id, 'APP_IDENTITY_MISMATCH' return obj def public_state(obj): model = db.session.scalars(select(ProviderModel).where(ProviderModel.tenant_id == tenant_id, ProviderModel.provider_name == PROVIDER, ProviderModel.model_name == ASR, ProviderModel.model_type == 'speech2text')).one() default = db.session.scalars(select(TenantDefaultModel).where(TenantDefaultModel.tenant_id == tenant_id, TenantDefaultModel.model_type == 'speech2text')).one() config = obj.app_model_config.to_dict() return {'app_id':obj.id,'app_name':obj.name,'tenant_id':tenant_id,'app_mode':obj.mode, 'enable_api':obj.enable_api,'enable_site':obj.enable_site,'config_id':obj.app_model_config_id, 'speech_to_text':obj.app_model_config.speech_to_text_dict, 'model':obj.app_model_config.model_dict,'pre_prompt_sha256':hashlib.sha256(obj.app_model_config.pre_prompt.encode()).hexdigest(), 'config_sha256':hashlib.sha256(json.dumps(config,ensure_ascii=False,sort_keys=True,default=str).encode()).hexdigest(), 'asr_model_id':model.id,'asr_credential_id':model.credential_id,'asr_default_id':default.id, 'asr_model':model.model_name,'asr_default':default.model_name,'provider':PROVIDER,'endpoint':ENDPOINT, 'base_url':'https://ai.zhenyangtang.com.cn/v1','max_active_requests':obj.max_active_requests} result = {} with app.test_request_context('/', method='POST', json=CONFIG): app.login_manager._update_request_context_with_user(owner) svc = AppService() action = PAYLOAD['action'] if action == 'create': assert not db.session.scalar(select(App.id).where(App.name == APP_NAME)), 'APP_NAME_ALREADY_EXISTS' assert not db.session.scalar(select(ProviderModel.id).where(ProviderModel.tenant_id == tenant_id,ProviderModel.model_type == 'speech2text')), 'SPEECH2TEXT_NOT_EMPTY' assert not db.session.scalar(select(TenantDefaultModel.id).where(TenantDefaultModel.tenant_id == tenant_id,TenantDefaultModel.model_type == 'speech2text')), 'DEFAULT_NOT_EMPTY' ModelProviderService().create_model_credential(tenant_id,PROVIDER,'speech2text',ASR, {'api_key':PAYLOAD['asr_key'],'endpoint_url':ENDPOINT,'endpoint_model_name':ASR,'language':'zh','initial_prompt':'','display_name':'Followup ASR isolated'}, 'followup-asr-isolated-20261009') ModelProviderService().update_default_model_of_model_type(tenant_id,'speech2text',PROVIDER,ASR) obj=svc.create_app(tenant_id,{'mode':'chat','name':APP_NAME,'description':'Isolated synthetic-verified ASR and JSON extraction. Preview only; no clinical writes.','icon_type':'emoji','icon':'🎙️','icon_background':'#E4FBCC'},owner) svc.update_app_site_status(obj,False) from controllers.console.app.model_config import ModelConfigResource inspect.unwrap(ModelConfigResource.post)(ModelConfigResource(),obj) svc.update_app(obj, {'name':APP_NAME,'description':obj.description,'icon_type':obj.icon_type,'icon':obj.icon,'icon_background':obj.icon_background,'use_icon_as_answer_icon':False,'max_active_requests':1}) svc.update_app_api_status(obj,True) from controllers.console.apikey import AppApiKeyListResource, BaseApiKeyListResource token,status=inspect.unwrap(BaseApiKeyListResource.post)(AppApiKeyListResource(),obj.id) assert status == 201 result={'public':public_state(obj),'private':{'api_key':token.token,'key_id':token.id}} elif action in ('status','disable','enable'): obj=current_app_owned() if action != 'status':svc.update_app_api_status(obj,action == 'enable') result={'public':public_state(obj)} elif action == 'remove': resource=PAYLOAD['resources'];obj=current_app_owned() credential=db.session.get(ProviderModelCredential,resource['asr_credential_id']) recorded_default=db.session.get(TenantDefaultModel,resource['asr_default_id']) assert credential and credential.tenant_id==tenant_id and credential.model_name==ASR and credential.model_type=='speech2text', 'CREDENTIAL_IDENTITY_MISMATCH' assert recorded_default and recorded_default.tenant_id==tenant_id and recorded_default.model_name==ASR and recorded_default.model_type=='speech2text', 'DEFAULT_IDENTITY_MISMATCH' for other in db.session.scalars(select(App).where(App.tenant_id==tenant_id,App.id!=obj.id)).all(): features=(other.workflow.features_dict if other.workflow else {}) if other.mode in ('advanced-chat','workflow') else ({'speech_to_text':other.app_model_config.speech_to_text_dict} if other.app_model_config else {}) assert not features.get('speech_to_text',{}).get('enabled'), 'ANOTHER_APP_NOW_USES_ASR' svc.update_app_api_status(obj,False) # Only the precise new app key, via the deployed API controller. from controllers.console.apikey import AppApiKeyResource, BaseApiKeyResource key_id=resource['key_id'] if db.session.get(ApiToken,key_id): inspect.unwrap(BaseApiKeyResource.delete)(AppApiKeyResource(),obj.id,key_id) svc.delete_app(obj) default=db.session.get(TenantDefaultModel,resource['asr_default_id']) assert default and default.tenant_id==tenant_id and default.model_type=='speech2text' and default.model_name==ASR, 'DEFAULT_IDENTITY_MISMATCH' # No service offers reset-to-empty; remove only this recorded, new, non-secret default row. db.session.delete(default);db.session.commit() ModelProviderService().remove_model_credential(tenant_id,PROVIDER,'speech2text',ASR,resource['asr_credential_id']) assert not db.session.scalar(select(App.id).where(App.id==resource['app_id'])) assert not db.session.scalar(select(ProviderModel.id).where(ProviderModel.id==resource['asr_model_id'])) result={'public':{'removed_app_id':resource['app_id'],'speech2text_restored_empty':True}} else:raise ValueError('UNKNOWN_ACTION') print('OPERATOR_RESULT='+json.dumps(result,ensure_ascii=False,default=str))