feat: integrate azure deepseek r1 (#591)

* fix several task & integrate deepseek R1

* fix CI

---------

Co-authored-by: Xu Yang <xuyang1@microsoft.com>
This commit is contained in:
Xu Yang
2025-02-13 22:20:17 +08:00
committed by GitHub
parent c76afb21b5
commit ed53af4c65
17 changed files with 164 additions and 59 deletions
+2 -1
View File
@@ -84,6 +84,7 @@ ignore = [
"ANN401",
"D",
"ERA001",
"EXE002",
"FIX",
"INP001",
"PGH",
@@ -91,7 +92,7 @@ ignore = [
"S101",
"S301",
"T20",
"TC003",
"TCH003",
"TD",
]
select = ["ALL"]
+11 -6
View File
@@ -5,10 +5,15 @@ import fire
from rdagent.app.data_science.conf import DS_RD_SETTING
from rdagent.components.coder.data_science.ensemble import EnsembleCoSTEER
from rdagent.components.coder.data_science.ensemble.exp import EnsembleTask
from rdagent.components.coder.data_science.feature import FeatureCoSTEER
from rdagent.components.coder.data_science.feature.exp import FeatureTask
from rdagent.components.coder.data_science.model import ModelCoSTEER
from rdagent.components.coder.data_science.model.exp import ModelTask
from rdagent.components.coder.data_science.raw_data_loader import DataLoaderCoSTEER
from rdagent.components.coder.data_science.raw_data_loader.exp import DataLoaderTask
from rdagent.components.coder.data_science.workflow import WorkflowCoSTEER
from rdagent.components.coder.data_science.workflow.exp import WorkflowTask
from rdagent.components.workflow.conf import BasePropSetting
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.exception import CoderError, RunnerError
@@ -70,15 +75,15 @@ class DataScienceRDLoop(RDLoop):
exp = prev_out["direct_exp_gen"]
for tasks in exp.pending_tasks_list:
exp.sub_tasks = tasks
if exp.hypothesis.component == "DataLoadSpec":
if isinstance(exp.sub_tasks[0], DataLoaderTask):
exp = self.data_loader_coder.develop(exp)
elif exp.hypothesis.component == "FeatureEng":
elif isinstance(exp.sub_tasks[0], FeatureTask):
exp = self.feature_coder.develop(exp)
elif exp.hypothesis.component == "Model":
elif isinstance(exp.sub_tasks[0], ModelTask):
exp = self.model_coder.develop(exp)
elif exp.hypothesis.component == "Ensemble":
elif isinstance(exp.sub_tasks[0], EnsembleTask):
exp = self.ensemble_coder.develop(exp)
elif exp.hypothesis.component == "Workflow":
elif isinstance(exp.sub_tasks[0], WorkflowTask):
exp = self.workflow_coder.develop(exp)
else:
raise NotImplementedError(f"Unsupported component in DataScienceRDLoop: {exp.hypothesis.component}")
@@ -118,7 +123,7 @@ class DataScienceRDLoop(RDLoop):
ExperimentFeedback.from_exception(e),
)
)
if len(self.trace.hist) >= DS_RD_SETTING.consecutive_errors:
if self.trace.sota_experiment() is None and len(self.trace.hist) >= DS_RD_SETTING.consecutive_errors:
trace_exp_next_component_list = [
exp.next_component_required() for exp, _ in self.trace.hist[-DS_RD_SETTING.consecutive_errors :]
]
@@ -50,7 +50,7 @@ class EnsembleCoSTEEREvaluator(CoSTEEREvaluator):
}
de = DockerEnv(conf=ds_docker_conf)
fname = "ensemble_test.txt"
fname = "test/ensemble_test.txt"
test_code = (DIRNAME / "eval_tests" / "ensemble_test.txt").read_text()
test_code = (
Environment(undefined=StrictUndefined)
@@ -7,4 +7,7 @@ from typing import Dict, Optional
from rdagent.components.coder.CoSTEER.task import CoSTEERTask
from rdagent.core.utils import cache_with_pickle
EnsembleTask = CoSTEERTask
# Because we use isinstance to distinguish between different types of tasks, we need to use sub classes to represent different types of tasks
class EnsembleTask(CoSTEERTask):
pass
@@ -52,7 +52,7 @@ class FeatureCoSTEEREvaluator(CoSTEEREvaluator):
de = DockerEnv(conf=ds_docker_conf)
# TODO: do we need to clean the generated temporary content?
fname = "feature_test.py"
fname = "test/feature_test.py"
test_code = (DIRNAME / "eval_tests" / "feature_test.txt").read_text()
implementation.inject_files(**{fname: test_code})
@@ -7,4 +7,7 @@ from typing import Dict, Optional
from rdagent.components.coder.CoSTEER.task import CoSTEERTask
from rdagent.core.utils import cache_with_pickle
FeatureTask = CoSTEERTask
# Because we use isinstance to distinguish between different types of tasks, we need to use sub classes to represent different types of tasks
class FeatureTask(CoSTEERTask):
pass
@@ -62,7 +62,7 @@ class ModelGeneralCaseSpecEvaluator(CoSTEEREvaluator):
}
de = DockerEnv(conf=ds_docker_conf)
fname = "model_test.py"
fname = "test/model_test.py"
test_code = (
(DIRNAME / "eval_tests" / "model_test.txt").read_text().replace("model01", target_task.name)
) # only check the model changed this time
@@ -11,6 +11,7 @@ from rdagent.oai.llm_utils import md5_hash
from rdagent.utils.env import DockerEnv, DSDockerConf
# Because we use isinstance to distinguish between different types of tasks, we need to use sub classes to represent different types of tasks
class ModelTask(CoSTEERTask):
def __init__(
self,
@@ -54,7 +54,7 @@ class DataLoaderCoSTEEREvaluator(CoSTEEREvaluator):
de = DockerEnv(conf=ds_docker_conf)
# TODO: do we need to clean the generated temporary content?
fname = "data_loader_test.py"
fname = "test/data_loader_test.py"
test_code = (DIRNAME / "eval_tests" / "data_loader_test.txt").read_text()
implementation.inject_files(**{fname: test_code})
stdout = implementation.execute(env=de, entry=f"python {fname}")
@@ -12,3 +12,8 @@ from rdagent.utils.agent.tpl import T
from rdagent.utils.env import DockerEnv, DSDockerConf
DataLoaderTask = CoSTEERTask
# Because we use isinstance to distinguish between different types of tasks, we need to use sub classes to represent different types of tasks
class DataLoaderTask(CoSTEERTask):
pass
@@ -98,9 +98,9 @@ class WorkflowGeneralCaseSpecEvaluator(CoSTEEREvaluator):
stdout += "\nSubmission file (submission.csv) is not generated."
else:
base_check_code = (DIRNAME / "eval_tests" / "submission_format_test.txt").read_text()
implementation.inject_files(**{"submission_format_test.py": base_check_code})
implementation.inject_files(**{"test/submission_format_test.py": base_check_code})
# stdout += "----Submission Check 1-----\n"
stdout += implementation.execute(env=de, entry="python submission_format_test.py")
stdout += implementation.execute(env=de, entry="python test/submission_format_test.py")
# MLEBench Check
# !!! Since we are running on a sampled dataset, mlebench check is not required.
@@ -109,9 +109,9 @@ class WorkflowGeneralCaseSpecEvaluator(CoSTEEREvaluator):
# .read_text()
# .replace("<competition_id>", self.scen.competition)
# )
# implementation.inject_files(**{"mle_submission_format_test.py": mle_check_code})
# implementation.inject_files(**{"test/mle_submission_format_test.py": mle_check_code})
# stdout += "----Submission Check 2-----\n"
# stdout += implementation.execute(env=mde, entry=f"python mle_submission_format_test.py")
# stdout += implementation.execute(env=mde, entry=f"python test/mle_submission_format_test.py")
system_prompt = T(".prompts:workflow_eval.system").r(
scenario=self.scen.get_scenario_all_desc(),
@@ -7,4 +7,7 @@ from typing import Dict, Optional
from rdagent.components.coder.CoSTEER.task import CoSTEERTask
from rdagent.core.utils import cache_with_pickle
WorkflowTask = CoSTEERTask
# Because we use isinstance to distinguish between different types of tasks, we need to use sub classes to represent different types of tasks
class WorkflowTask(CoSTEERTask):
pass
+115 -33
View File
@@ -44,6 +44,19 @@ except ImportError:
if LLM_SETTINGS.use_llama2:
logger.warning("llama is not installed.")
try:
from azure.ai.inference import ChatCompletionsClient
from azure.ai.inference.models import (
AssistantMessage,
ChatRequestMessage,
SystemMessage,
UserMessage,
)
from azure.core.credentials import AzureKeyCredential
except ImportError:
if LLM_SETTINGS.chat_use_azure_deepseek:
logger.warning("azure.ai.inference or azure.core.credentials is not installed.")
class ConvManager:
"""
@@ -299,6 +312,15 @@ class DeprecBackend(APIBackend):
self.chat_model_map = json.loads(LLM_SETTINGS.chat_model_map)
self.chat_model = LLM_SETTINGS.chat_model if chat_model is None else chat_model
self.encoder = None
elif LLM_SETTINGS.chat_use_azure_deepseek:
self.client = ChatCompletionsClient(
endpoint=LLM_SETTINGS.chat_azure_deepseek_endpoint,
credential=AzureKeyCredential(LLM_SETTINGS.chat_azure_deepseek_key),
)
self.chat_model_map = json.loads(LLM_SETTINGS.chat_model_map)
self.encoder = None
self.chat_model = "deepseek-R1"
self.chat_stream = LLM_SETTINGS.chat_stream
else:
self.chat_use_azure = LLM_SETTINGS.chat_use_azure or LLM_SETTINGS.use_azure
self.embedding_use_azure = LLM_SETTINGS.embedding_use_azure or LLM_SETTINGS.use_azure
@@ -389,6 +411,7 @@ class DeprecBackend(APIBackend):
# transfer the config to the class if the config is not supposed to change during the runtime
self.use_llama2 = LLM_SETTINGS.use_llama2
self.use_gcr_endpoint = LLM_SETTINGS.use_gcr_endpoint
self.chat_use_azure_deepseek = LLM_SETTINGS.chat_use_azure_deepseek
self.retry_wait_seconds = LLM_SETTINGS.retry_wait_seconds
def _get_encoder(self) -> tiktoken.Encoding:
@@ -510,25 +533,60 @@ class DeprecBackend(APIBackend):
return resp[0]
return resp
def _create_chat_completion_auto_continue(self, messages: list[dict[str, Any]], *args, **kwargs) -> str: # type: ignore[no-untyped-def]
def _create_chat_completion_auto_continue(
self,
messages: list[dict[str, Any]],
*args: Any,
json_mode: bool = False,
chat_cache_prefix: str = "",
seed: Optional[int] = None,
**kwargs: Any,
) -> str:
"""
Call the chat completion function and automatically continue the conversation if the finish_reason is length.
TODO: This function only continues once, maybe need to continue more than once in the future.
"""
response, finish_reason = self._create_chat_completion_inner_function(messages, *args, **kwargs)
if seed is None and LLM_SETTINGS.use_auto_chat_cache_seed_gen:
seed = LLM_CACHE_SEED_GEN.get_next_seed()
input_content_json = json.dumps(messages)
input_content_json = (
chat_cache_prefix + input_content_json + f"<seed={seed}/>"
) # FIXME this is a hack to make sure the cache represents the round index
if self.use_chat_cache:
cache_result = self.cache.chat_get(input_content_json)
if cache_result is not None:
if LLM_SETTINGS.log_llm_chat_content:
logger.info(f"{LogColors.CYAN}Response:{cache_result}{LogColors.END}", tag="llm_messages")
return cache_result
if finish_reason == "length":
new_message = deepcopy(messages)
new_message.append({"role": "assistant", "content": response})
new_message.append(
{
"role": "user",
"content": "continue the former output with no overlap",
},
)
new_response, finish_reason = self._create_chat_completion_inner_function(new_message, *args, **kwargs)
return response + new_response
return response
all_response = ""
new_messages = deepcopy(messages)
for _ in range(10):
if "json_mode" in kwargs:
del kwargs["json_mode"]
response, finish_reason = self._create_chat_completion_inner_function(
new_messages, json_mode=json_mode, *args, **kwargs
) # type: ignore[misc]
all_response += response
if finish_reason is None or finish_reason != "length":
if self.chat_use_azure_deepseek:
match = re.search(r"<think>(.*?)</think>(.*)", all_response, re.DOTALL)
think_part, all_response = match.groups() if match else ("", all_response)
if LLM_SETTINGS.log_llm_chat_content:
logger.info(f"{LogColors.CYAN}Think:{think_part}{LogColors.END}", tag="llm_messages")
logger.info(f"{LogColors.CYAN}Response:{all_response}{LogColors.END}", tag="llm_messages")
if json_mode:
try:
json.loads(all_response)
except:
match = re.search(r"```json(.*?)```", all_response, re.DOTALL)
all_response = match.groups()[0] if match else all_response
json.loads(all_response)
if self.dump_chat_cache:
self.cache.chat_set(input_content_json, all_response)
return all_response
new_messages.append({"role": "assistant", "content": response})
return all_response
def _try_create_chat_completion_or_embedding( # type: ignore[no-untyped-def]
self,
@@ -618,12 +676,10 @@ class DeprecBackend(APIBackend):
messages: list[dict[str, Any]],
temperature: float | None = None,
max_tokens: int | None = None,
chat_cache_prefix: str = "",
frequency_penalty: float | None = None,
presence_penalty: float | None = None,
json_mode: bool = False,
add_json_in_prompt: bool = False,
seed: Optional[int] = None,
*args,
**kwargs,
) -> tuple[str, str | None]:
@@ -633,23 +689,11 @@ class DeprecBackend(APIBackend):
To make retries useful, we need to enable a seed.
This seed is different from `self.chat_seed` for GPT. It is for the local cache mechanism enabled by RD-Agent locally.
"""
if seed is None and LLM_SETTINGS.use_auto_chat_cache_seed_gen:
seed = LLM_CACHE_SEED_GEN.get_next_seed()
# TODO: we can add this function back to avoid so much `self.cfg.log_llm_chat_content`
if LLM_SETTINGS.log_llm_chat_content:
logger.info(self._build_log_messages(messages), tag="llm_messages")
# TODO: fail to use loguru adaptor due to stream response
input_content_json = json.dumps(messages)
input_content_json = (
chat_cache_prefix + input_content_json + f"<seed={seed}/>"
) # FIXME this is a hack to make sure the cache represents the round index
if self.use_chat_cache:
cache_result = self.cache.chat_get(input_content_json)
if cache_result is not None:
if LLM_SETTINGS.log_llm_chat_content:
logger.info(f"{LogColors.CYAN}Response:{cache_result}{LogColors.END}", tag="llm_messages")
return cache_result, None
if temperature is None:
temperature = LLM_SETTINGS.chat_temperature
@@ -700,6 +744,46 @@ class DeprecBackend(APIBackend):
resp = json.loads(response.read().decode())["output"]
if LLM_SETTINGS.log_llm_chat_content:
logger.info(f"{LogColors.CYAN}Response:{resp}{LogColors.END}", tag="llm_messages")
elif self.chat_use_azure_deepseek:
azure_style_message: list[ChatRequestMessage] = []
for message in messages:
if message["role"] == "system":
azure_style_message.append(SystemMessage(content=message["content"]))
elif message["role"] == "user":
azure_style_message.append(UserMessage(content=message["content"]))
elif message["role"] == "assistant":
azure_style_message.append(AssistantMessage(content=message["content"]))
response = self.client.complete(
messages=azure_style_message,
stream=self.chat_stream,
temperature=temperature,
max_tokens=max_tokens,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
)
if self.chat_stream:
resp = ""
# TODO: with logger.config(stream=self.chat_stream): and add a `stream_start` flag to add timestamp for first message.
if LLM_SETTINGS.log_llm_chat_content:
logger.info(f"{LogColors.CYAN}Response:{LogColors.END}", tag="llm_messages")
for chunk in response:
content = (
chunk.choices[0].delta.content
if len(chunk.choices) > 0 and chunk.choices[0].delta.content is not None
else ""
)
if LLM_SETTINGS.log_llm_chat_content:
logger.info(LogColors.CYAN + content + LogColors.END, raw=True, tag="llm_messages")
resp += content
if len(chunk.choices) > 0 and chunk.choices[0].finish_reason is not None:
finish_reason = chunk.choices[0].finish_reason
else:
resp = response.choices[0].message.content
finish_reason = response.choices[0].finish_reason
if LLM_SETTINGS.log_llm_chat_content:
logger.info(f"{LogColors.CYAN}Response:{resp}{LogColors.END}", tag="llm_messages")
else:
call_kwargs = dict(
model=model,
@@ -758,13 +842,11 @@ class DeprecBackend(APIBackend):
),
tag="llm_messages",
)
if json_mode:
json.loads(resp)
if self.dump_chat_cache:
self.cache.chat_set(input_content_json, resp)
return resp, finish_reason
def _calculate_token_from_messages(self, messages: list[dict[str, Any]]) -> int:
if self.chat_use_azure_deepseek:
return 0
if self.encoder is None:
raise ValueError("Encoder is not initialized.")
if self.use_llama2 or self.use_gcr_endpoint:
+4
View File
@@ -100,6 +100,10 @@ class LLMSettings(ExtendedBaseSettings):
gcr_endpoint_do_sample: bool = False
gcr_endpoint_max_token: int = 100
chat_use_azure_deepseek: bool = False
chat_azure_deepseek_endpoint: str = ""
chat_azure_deepseek_key: str = ""
chat_model_map: str = "{}"
@@ -117,9 +117,9 @@ class DSCoSTEERRunner(CoSTEER):
.read_text()
.replace("<competition_id>", self.scen.competition)
)
exp.experiment_workspace.inject_files(**{"mle_submission_format_test.py": mle_check_code})
exp.experiment_workspace.inject_files(**{"test/mle_submission_format_test.py": mle_check_code})
exp.format_check_result = exp.experiment_workspace.execute(
env=mde, entry=f"python mle_submission_format_test.py"
env=mde, entry=f"python test/mle_submission_format_test.py"
)
return exp
@@ -67,18 +67,15 @@ class DSCoSTEERCoSTEEREvaluator(CoSTEEREvaluator):
.read_text()
.replace("<competition_id>", self.scen.competition)
)
implementation.inject_files(**{"mle_submission_format_test.py": mle_check_code})
implementation.inject_files(**{"test/mle_submission_format_test.py": mle_check_code})
stdout += f"\n MLEBench submission check:"
stdout += implementation.execute(env=mde, entry="python mle_submission_format_test.py")
stdout += implementation.execute(env=mde, entry="python test/mle_submission_format_test.py")
# remove unused files
implementation.execute(env=de, entry="coverage json -o coverage.json")
if Path(implementation.workspace_path / "coverage.json").exists():
with open(implementation.workspace_path / "coverage.json") as f:
used_files = set(json.load(f)["files"].keys()) | {
"submission_format_test.py",
"mle_submission_format_test.py",
}
used_files = set(json.load(f)["files"].keys())
logger.info("All used scripts: {}".format(used_files))
all_python_files = set(Path(implementation.workspace_path).rglob("*.py"))
unused_files = [
+1
View File
@@ -49,3 +49,4 @@ nbformat
# tool
setuptools-scm
seaborn
azure.ai.inference