From 66057cc6a7fc23ae8762a575c5fbb36bbc3cc521 Mon Sep 17 00:00:00 2001 From: lujia Date: Wed, 26 Nov 2025 11:36:31 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Dashscope=20=E5=9C=A8=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E4=B8=AD=E7=9A=84=E6=B5=81=E5=BC=8F=E9=9B=86?= =?UTF-8?q?=E6=88=90=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 websocket_wiki 和 simple_chat 增加 Dashscope 提供方的显式处理分支 - 使用 DashscopeClient.acall(ModelType.LLM, stream=True) 并将文本分片流式转发给前端 - 在 DashscopeClient.acall 中为 AsyncStream 封装异步文本生成器 - 修复 "'DashscopeClient' object has no attribute 'generate_content'" 和 "'AsyncStream' object is not iterable" 等运行时错误 - 修复使用 provider="dashscope" 时前端出现的 "No valid XML found in response" 错误 --- api/dashscope_client.py | 17 +++++++- api/simple_chat.py | 92 ++++++++++++++++++++++++++++++++++------- api/websocket_wiki.py | 74 ++++++++++++++++++++++++++++----- 3 files changed, 156 insertions(+), 27 deletions(-) diff --git a/api/dashscope_client.py b/api/dashscope_client.py index c7279547..5c917443 100644 --- a/api/dashscope_client.py +++ b/api/dashscope_client.py @@ -511,8 +511,23 @@ async def acall( completion = await self.async_client.chat.completions.create(**api_kwargs) + # For async calls with streaming enabled, wrap the AsyncStream + # into an async generator of plain text chunks so that callers + # can simply `async for text in response`. if api_kwargs.get("stream", False): - return handle_streaming_response(completion) + + async def async_stream_generator(): + async for chunk in completion: + log.debug(f"Raw async chunk completion: {chunk}") + try: + parsed_content = parse_stream_response(chunk) + except Exception as e: + log.error(f"Error parsing async stream chunk: {e}") + parsed_content = None + if parsed_content: + yield parsed_content + + return async_stream_generator() else: return self.parse_chat_completion(completion) elif model_type == ModelType.EMBEDDER: diff --git a/api/simple_chat.py b/api/simple_chat.py index 06d329a2..41a184ed 100644 --- a/api/simple_chat.py +++ b/api/simple_chat.py @@ -17,6 +17,7 @@ from api.openrouter_client import OpenRouterClient from api.bedrock_client import BedrockClient from api.azureai_client import AzureAIClient +from api.dashscope_client import DashscopeClient from api.rag import RAG from api.prompts import ( DEEP_RESEARCH_FIRST_ITERATION_PROMPT, @@ -63,7 +64,7 @@ class ChatCompletionRequest(BaseModel): type: Optional[str] = Field("github", description="Type of repository (e.g., 'github', 'gitlab', 'bitbucket')") # model parameters - provider: str = Field("google", description="Model provider (google, openai, openrouter, ollama, bedrock, azure)") + provider: str = Field("google", description="Model provider (google, openai, openrouter, ollama, bedrock, azure, dashscope)") model: Optional[str] = Field(None, description="Model name for the specified provider") language: Optional[str] = Field("en", description="Language for content generation (e.g., 'en', 'ja', 'zh', 'es', 'kr', 'vi')") @@ -432,15 +433,31 @@ async def chat_completions_stream(request: ChatCompletionRequest): model_kwargs=model_kwargs, model_type=ModelType.LLM ) + elif request.provider == "dashscope": + logger.info(f"Using Dashscope with model: {request.model}") + + model = DashscopeClient() + model_kwargs = { + "model": request.model, + "stream": True, + "temperature": model_config["temperature"], + "top_p": model_config["top_p"], + } + + api_kwargs = model.convert_inputs_to_api_kwargs( + input=prompt, + model_kwargs=model_kwargs, + model_type=ModelType.LLM, + ) else: - # Initialize Google Generative AI model + # Initialize Google Generative AI model (default provider) model = genai.GenerativeModel( model_name=model_config["model"], generation_config={ "temperature": model_config["temperature"], "top_p": model_config["top_p"], - "top_k": model_config["top_k"] - } + "top_k": model_config["top_k"], + }, ) # Create a streaming response @@ -514,12 +531,29 @@ async def response_stream(): except Exception as e_azure: logger.error(f"Error with Azure AI API: {str(e_azure)}") yield f"\nError with Azure AI API: {str(e_azure)}\n\nPlease check that you have set the AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, and AZURE_OPENAI_VERSION environment variables with valid values." + elif request.provider == "dashscope": + try: + logger.info("Making Dashscope API call") + response = await model.acall( + api_kwargs=api_kwargs, model_type=ModelType.LLM + ) + # DashscopeClient.acall with stream=True returns an async + # generator of text chunks + async for text in response: + if text: + yield text + except Exception as e_dashscope: + logger.error(f"Error with Dashscope API: {str(e_dashscope)}") + yield ( + f"\nError with Dashscope API: {str(e_dashscope)}\n\n" + "Please check that you have set the DASHSCOPE_API_KEY (and optionally " + "DASHSCOPE_WORKSPACE_ID) environment variables with valid values." + ) else: - # Generate streaming response + # Google Generative AI (default provider) response = model.generate_content(prompt, stream=True) - # Stream the response for chunk in response: - if hasattr(chunk, 'text'): + if hasattr(chunk, "text"): yield chunk.text except Exception as e_outer: @@ -648,23 +682,51 @@ async def response_stream(): except Exception as e_fallback: logger.error(f"Error with Azure AI API fallback: {str(e_fallback)}") yield f"\nError with Azure AI API fallback: {str(e_fallback)}\n\nPlease check that you have set the AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, and AZURE_OPENAI_VERSION environment variables with valid values." + elif request.provider == "dashscope": + try: + # Create new api_kwargs with the simplified prompt + fallback_api_kwargs = model.convert_inputs_to_api_kwargs( + input=simplified_prompt, + model_kwargs=model_kwargs, + model_type=ModelType.LLM, + ) + + logger.info("Making fallback Dashscope API call") + fallback_response = await model.acall( + api_kwargs=fallback_api_kwargs, model_type=ModelType.LLM + ) + + # DashscopeClient.acall (stream=True) returns an async + # generator of text chunks + async for text in fallback_response: + if text: + yield text + except Exception as e_fallback: + logger.error( + f"Error with Dashscope API fallback: {str(e_fallback)}" + ) + yield ( + f"\nError with Dashscope API fallback: {str(e_fallback)}\n\n" + "Please check that you have set the DASHSCOPE_API_KEY (and optionally " + "DASHSCOPE_WORKSPACE_ID) environment variables with valid values." + ) else: - # Initialize Google Generative AI model + # Google Generative AI fallback (default provider) model_config = get_model_config(request.provider, request.model) fallback_model = genai.GenerativeModel( - model_name=model_config["model"], + model_name=model_config["model_kwargs"]["model"], generation_config={ "temperature": model_config["model_kwargs"].get("temperature", 0.7), "top_p": model_config["model_kwargs"].get("top_p", 0.8), - "top_k": model_config["model_kwargs"].get("top_k", 40) - } + "top_k": model_config["model_kwargs"].get("top_k", 40), + }, ) - # Get streaming response using simplified prompt - fallback_response = fallback_model.generate_content(simplified_prompt, stream=True) - # Stream the fallback response + fallback_response = fallback_model.generate_content( + simplified_prompt, stream=True + ) for chunk in fallback_response: - if hasattr(chunk, 'text'): + if hasattr(chunk, "text"): yield chunk.text except Exception as e2: logger.error(f"Error in fallback streaming response: {str(e2)}") diff --git a/api/websocket_wiki.py b/api/websocket_wiki.py index 2a7cce9e..6556bdf1 100644 --- a/api/websocket_wiki.py +++ b/api/websocket_wiki.py @@ -612,14 +612,36 @@ async def handle_websocket_chat(websocket: WebSocket): await websocket.send_text(error_msg) # Close the WebSocket connection after sending the error message await websocket.close() + elif request.provider == "dashscope": + try: + # Get the response and handle it properly using the previously created api_kwargs + logger.info("Making Dashscope API call") + response = await model.acall( + api_kwargs=api_kwargs, model_type=ModelType.LLM + ) + # DashscopeClient.acall with stream=True returns an async + # generator of plain text chunks + async for text in response: + if text: + await websocket.send_text(text) + # Explicitly close the WebSocket connection after the response is complete + await websocket.close() + except Exception as e_dashscope: + logger.error(f"Error with Dashscope API: {str(e_dashscope)}") + error_msg = ( + f"\nError with Dashscope API: {str(e_dashscope)}\n\n" + "Please check that you have set the DASHSCOPE_API_KEY (and optionally " + "DASHSCOPE_WORKSPACE_ID) environment variables with valid values." + ) + await websocket.send_text(error_msg) + # Close the WebSocket connection after sending the error message + await websocket.close() else: - # Generate streaming response + # Google Generative AI (default provider) response = model.generate_content(prompt, stream=True) - # Stream the response for chunk in response: if hasattr(chunk, 'text'): await websocket.send_text(chunk.text) - # Explicitly close the WebSocket connection after the response is complete await websocket.close() except Exception as e_outer: @@ -729,23 +751,52 @@ async def handle_websocket_chat(websocket: WebSocket): logger.error(f"Error with Azure AI API fallback: {str(e_fallback)}") error_msg = f"\nError with Azure AI API fallback: {str(e_fallback)}\n\nPlease check that you have set the AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, and AZURE_OPENAI_VERSION environment variables with valid values." await websocket.send_text(error_msg) + elif request.provider == "dashscope": + try: + # Create new api_kwargs with the simplified prompt + fallback_api_kwargs = model.convert_inputs_to_api_kwargs( + input=simplified_prompt, + model_kwargs=model_kwargs, + model_type=ModelType.LLM, + ) + + logger.info("Making fallback Dashscope API call") + fallback_response = await model.acall( + api_kwargs=fallback_api_kwargs, model_type=ModelType.LLM + ) + + # DashscopeClient.acall (stream=True) returns an async + # generator of text chunks + async for text in fallback_response: + if text: + await websocket.send_text(text) + except Exception as e_fallback: + logger.error( + f"Error with Dashscope API fallback: {str(e_fallback)}" + ) + error_msg = ( + f"\nError with Dashscope API fallback: {str(e_fallback)}\n\n" + "Please check that you have set the DASHSCOPE_API_KEY (and optionally " + "DASHSCOPE_WORKSPACE_ID) environment variables with valid values." + ) + await websocket.send_text(error_msg) else: - # Initialize Google Generative AI model + # Google Generative AI fallback (default provider) model_config = get_model_config(request.provider, request.model) fallback_model = genai.GenerativeModel( - model_name=model_config["model"], + model_name=model_config["model_kwargs"]["model"], generation_config={ "temperature": model_config["model_kwargs"].get("temperature", 0.7), "top_p": model_config["model_kwargs"].get("top_p", 0.8), - "top_k": model_config["model_kwargs"].get("top_k", 40) - } + "top_k": model_config["model_kwargs"].get("top_k", 40), + }, ) - # Get streaming response using simplified prompt - fallback_response = fallback_model.generate_content(simplified_prompt, stream=True) - # Stream the fallback response + fallback_response = fallback_model.generate_content( + simplified_prompt, stream=True + ) for chunk in fallback_response: - if hasattr(chunk, 'text'): + if hasattr(chunk, "text"): await websocket.send_text(chunk.text) except Exception as e2: logger.error(f"Error in fallback streaming response: {str(e2)}") @@ -767,3 +818,4 @@ async def handle_websocket_chat(websocket: WebSocket): await websocket.close() except: pass + \ No newline at end of file