fix: support experimental support for Deepseek models and update docs about configuration (#1024)

* fix qlib running bug for deepseek and add configuration docs about deepseek
This commit is contained in:
amstrongzyf
2025-07-07 14:18:56 +08:00
committed by GitHub
parent 4bf16d03cf
commit ea86b537eb
5 changed files with 162 additions and 20 deletions
+19 -1
View File
@@ -153,7 +153,9 @@ Ensure the current user can run Docker commands **without using sudo**. You can
You can set your Chat Model and Embedding Model in the following ways:
- **Using LiteLLM (Default)**: We now support LiteLLM as a backend for integration with multiple LLM providers. You can configure in two ways:
> **🔥 Attention**: We now provide experimental support for **DeepSeek** models! You can use DeepSeek's official API for cost-effective and high-performance inference. See the configuration example below for DeepSeek setup.
- **Using LiteLLM (Default)**: We now support LiteLLM as a backend for integration with multiple LLM providers. You can configure in multiple ways:
**Option 1: Unified API base for both models**
```bash
@@ -185,6 +187,22 @@ Ensure the current user can run Docker commands **without using sudo**. You can
LITELLM_PROXY_API_BASE=https://api.siliconflow.cn/v1
```
**Configuration Example: DeepSeek Setup**:
>Since many users encounter configuration errors when setting up DeepSeek. Here's a complete working example for DeepSeek Setup:
```bash
cat << EOF > .env
# CHAT MODEL: Using DeepSeek Official API
CHAT_MODEL=deepseek/deepseek-chat
DEEPSEEK_API_KEY=<replace_with_your_deepseek_api_key>
# EMBEDDING MODEL: Using SiliconFlow for embedding since deepseek has no embedding model.
# Note: embedding requires litellm_proxy prefix
EMBEDDING_MODEL=litellm_proxy/BAAI/bge-m3
LITELLM_PROXY_API_KEY=<replace_with_your_siliconflow_api_key>
LITELLM_PROXY_API_BASE=https://api.siliconflow.cn/v1
```
Notice: If you are using reasoning models that include thought processes in their responses (such as \<think> tags), you need to set the following environment variable:
```bash
REASONING_THINK_RM=True
+20
View File
@@ -16,6 +16,9 @@ Ensure the current user can run Docker commands **without using sudo**. You can
LiteLLM Backend Configuration (Default)
=======================================
.. note::
🔥 **Attention**: We now provide experimental support for **DeepSeek** models! You can use DeepSeek's official API for cost-effective and high-performance inference. See the configuration example below for DeepSeek setup.
Option 1: Unified API base for both models
------------------------------------------
@@ -48,6 +51,23 @@ Option 2: Separate API bases for Chat and Embedding models
LITELLM_PROXY_API_KEY=<replace_with_your_siliconflow_api_key>
LITELLM_PROXY_API_BASE=https://api.siliconflow.cn/v1
Configuration Example: DeepSeek Setup
-------------------------------------
Many users encounter configuration errors when setting up DeepSeek. Here's a complete working example:
.. code-block:: Properties
# CHAT MODEL: Using DeepSeek Official API
CHAT_MODEL=deepseek/deepseek-chat
DEEPSEEK_API_KEY=<replace_with_your_deepseek_api_key>
# EMBEDDING MODEL: Using SiliconFlow for embedding since DeepSeek has no embedding model.
# Note: embedding requires litellm_proxy prefix
EMBEDDING_MODEL=litellm_proxy/BAAI/bge-m3
LITELLM_PROXY_API_KEY=<replace_with_your_siliconflow_api_key>
LITELLM_PROXY_API_BASE=https://api.siliconflow.cn/v1
Necessary parameters include:
- `CHAT_MODEL`: The model name of the chat model.
@@ -1,6 +1,7 @@
from __future__ import annotations
import json
import re
from typing import Dict
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERSingleFeedback
@@ -136,18 +137,28 @@ class FactorMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
queried_similar_error_knowledge_to_render = queried_similar_error_knowledge_to_render[:-1]
for _ in range(10):
try:
code = json.loads(
APIBackend(
use_chat_cache=FACTOR_COSTEER_SETTINGS.coder_use_cache
).build_messages_and_create_chat_completion(
user_prompt=user_prompt,
system_prompt=system_prompt,
json_mode=True,
json_target_type=Dict[str, str],
)
)["code"]
response = APIBackend(
use_chat_cache=FACTOR_COSTEER_SETTINGS.coder_use_cache
).build_messages_and_create_chat_completion(
user_prompt=user_prompt,
system_prompt=system_prompt,
json_mode=True,
json_target_type=Dict[str, str],
)
try:
code = json.loads(response)["code"]
except json.decoder.JSONDecodeError:
# extract python code block
match = re.search(r"```python(.*?)```", response, re.DOTALL)
if match:
code = match.group(1).strip()
else:
raise # continue to retry
return code
except json.decoder.JSONDecodeError:
except (json.decoder.JSONDecodeError, KeyError):
pass
else:
return "" # return empty code if failed to get code after 10 attempts
+100 -7
View File
@@ -1,15 +1,17 @@
from __future__ import annotations
import io
import json
import re
import sqlite3
import time
import tokenize
import uuid
from abc import ABC, abstractmethod
from copy import deepcopy
from datetime import datetime
from pathlib import Path
from typing import Any, Optional, cast
from typing import Any, Callable, List, Optional, Tuple, cast
import pytz
from pydantic import BaseModel, TypeAdapter
@@ -31,6 +33,100 @@ except ImportError:
openai_imported = False
class JSONParser:
"""JSON parser supporting multiple strategies"""
def __init__(self) -> None:
self.strategies: List[Callable[[str], str]] = [
self._direct_parse,
self._extract_from_code_block,
self._fix_python_syntax,
self._extract_with_fix_combined,
]
def parse(self, content: str) -> str:
"""Parse JSON content, automatically trying multiple strategies"""
original_content = content
for strategy in self.strategies:
try:
return strategy(original_content)
except json.JSONDecodeError:
continue
# All strategies failed
raise json.JSONDecodeError("Failed to parse JSON after all attempts", original_content, 0)
def _direct_parse(self, content: str) -> str:
"""Strategy 1: Direct parsing (including handling extra data)"""
try:
json.loads(content)
return content
except json.JSONDecodeError as e:
if "Extra data" in str(e):
return self._extract_first_json(content)
raise
def _extract_from_code_block(self, content: str) -> str:
"""Strategy 2: Extract JSON from code block"""
match = re.search(r"```json\s*(.*?)\s*```", content, re.DOTALL)
if not match:
raise json.JSONDecodeError("No JSON code block found", content, 0)
json_content = match.group(1).strip()
return self._direct_parse(json_content)
def _fix_python_syntax(self, content: str) -> str:
"""Strategy 3: Fix Python syntax before parsing"""
fixed = self._fix_python_booleans(content)
return self._direct_parse(fixed)
def _extract_with_fix_combined(self, content: str) -> str:
"""Strategy 4: Combined strategy - fix Python syntax first, then extract the first JSON object"""
fixed = self._fix_python_booleans(content)
# Try to extract code block from the fixed content
match = re.search(r"```json\s*(.*?)\s*```", fixed, re.DOTALL)
if match:
fixed = match.group(1).strip()
return self._direct_parse(fixed)
@staticmethod
def _fix_python_booleans(json_str: str) -> str:
"""Safely fix Python-style booleans to JSON standard format using tokenize"""
replacements = {"True": "true", "False": "false", "None": "null"}
try:
out = []
io_string = io.StringIO(json_str)
tokens = tokenize.generate_tokens(io_string.readline)
for toknum, tokval, _, _, _ in tokens:
if toknum == tokenize.NAME and tokval in replacements:
out.append(replacements[tokval])
else:
out.append(tokval)
result = "".join(out)
# Validate if the result is valid JSON
json.loads(result)
return result
except (tokenize.TokenError, json.JSONDecodeError):
# If tokenize fails, fallback to regex method
for python_val, json_val in replacements.items():
json_str = re.sub(rf"\\b{python_val}\\b", json_val, json_str)
return json_str
@staticmethod
def _extract_first_json(response: str) -> str:
"""Extract the first complete JSON object, ignoring extra content"""
decoder = json.JSONDecoder()
obj, _ = decoder.raw_decode(response)
return json.dumps(obj)
class SQliteLazyCache(SingletonBaseClass):
def __init__(self, cache_location: str) -> None:
super().__init__()
@@ -482,12 +578,9 @@ class APIBackend(ABC):
# 3) format checking
if json_mode:
try:
json.loads(all_response)
except json.decoder.JSONDecodeError:
match = re.search(r"```json(.*)```", all_response, re.DOTALL)
all_response = match.groups()[0] if match else all_response
json.loads(all_response)
parser = JSONParser()
all_response = parser.parse(all_response)
if json_target_type is not None:
TypeAdapter(json_target_type).validate_json(all_response)
if (response_format := kwargs.get("response_format")) is not None:
+1 -1
View File
@@ -78,7 +78,7 @@ hypothesis_output_format_with_action: |-
The output should follow JSON format. The schema is as follows:
{
"action": "If `hypothesis_specification` provides the action you need to take, please follow "hypothesis_specification" to choose the action. Otherwise, based on previous experimental results, suggest the action you believe is most appropriate at the moment. It should be one of [`factor`, `model`].",
"hypothesis": "The new hypothesis generated based on the information provided.",
"hypothesis": "The new hypothesis generated based on the information provided,should be a string.",
"reason": "The reason why you generate this hypothesis. It should be comprehensive and logical. It should cover the other keys below and extend them. Limit in two or three sentences.",
}