chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
<h1 align="center">CodeR: Towards A Generalist Code Embedding Model</h1>
|
||||
<p align="center">
|
||||
<a href="https://huggingface.co/datasets/nebula2025/CodeR-Pile">
|
||||
<img alt="Build" src="https://img.shields.io/badge/🤗 Dataset-CodeR Pile-yellow">
|
||||
</a>
|
||||
<a href="https://huggingface.co/nebula2025/CodeR-full">
|
||||
<img alt="Build" src="https://img.shields.io/badge/🤗 Model-CodeR Full-green">
|
||||
</a>
|
||||
<a href="https://huggingface.co/nebula2025/CodeR-synthetic">
|
||||
<img alt="Build" src="https://img.shields.io/badge/🤗 Model-CodeR Synthetic-blue">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
This repo contains the data, training, and evaluation pipeline for CodeR / [BGE-Code-v1](https://huggingface.co/BAAI/bge-code-v1)
|
||||
|
||||
**[BGE-Code-v1](https://huggingface.co/BAAI/bge-code-v1)** is an LLM-based code embedding model that supports code retrieval, text retrieval, and multilingual retrieval. It primarily demonstrates the following capabilities:
|
||||
|
||||
- Superior Code Retrieval Performance: The model demonstrates exceptional code retrieval capabilities, supporting natural language queries in both English and Chinese, as well as 20 programming languages.
|
||||
- Robust Text Retrieval Capabilities: The model maintains strong text retrieval capabilities comparable to text embedding models of similar scale.
|
||||
- Extensive Multilingual Support: BGE-Code-v1 offers comprehensive multilingual retrieval capabilities, excelling in languages such as English, Chinese, Japanese, French, and more.
|
||||
|
||||
## :bell: News:
|
||||
|
||||
- 🥳 5/15/2025: We have released the CodeR! :fire:
|
||||
|
||||
## Usage
|
||||
|
||||
### Using FlagEmbedding
|
||||
|
||||
```
|
||||
git clone https://github.com/FlagOpen/FlagEmbedding.git
|
||||
cd FlagEmbedding
|
||||
pip install -e .
|
||||
from FlagEmbedding import FlagLLMModel
|
||||
queries = [
|
||||
"Delete the record with ID 4 from the 'Staff' table.",
|
||||
'Delete all records in the "Livestock" table where age is greater than 5'
|
||||
]
|
||||
documents = [
|
||||
"DELETE FROM Staff WHERE StaffID = 4;",
|
||||
"DELETE FROM Livestock WHERE age > 5;"
|
||||
]
|
||||
model = FlagLLMModel('BAAI/bge-code-v1',
|
||||
query_instruction_format="<instruct>{}\n<query>{}",
|
||||
query_instruction_for_retrieval="Given a question in text, retrieve SQL queries that are appropriate responses to the question.",
|
||||
trust_remote_code=True,
|
||||
use_fp16=True) # Setting use_fp16 to True speeds up computation with a slight performance degradation
|
||||
embeddings_1 = model.encode_queries(queries)
|
||||
embeddings_2 = model.encode_corpus(documents)
|
||||
similarity = embeddings_1 @ embeddings_2.T
|
||||
print(similarity)
|
||||
```
|
||||
|
||||
By default, FlagLLMModel will use all available GPUs when encoding. Please set `os.environ["CUDA_VISIBLE_DEVICES"]` to select specific GPUs. You also can set `os.environ["CUDA_VISIBLE_DEVICES"]=""` to make all GPUs unavailable.
|
||||
|
||||
### Using Sentence Transformers
|
||||
|
||||
```python
|
||||
from sentence_transformers import SentenceTransformer
|
||||
import torch
|
||||
|
||||
# Load the model, optionally in float16 precision for faster inference
|
||||
model = SentenceTransformer(
|
||||
"BAAI/bge-code-v1",
|
||||
trust_remote_code=True,
|
||||
model_kwargs={"torch_dtype": torch.float16},
|
||||
)
|
||||
|
||||
# Prepare a prompt given an instruction
|
||||
instruction = 'Given a question in text, retrieve SQL queries that are appropriate responses to the question.'
|
||||
prompt = f'<instruct>{instruction}\n<query>'
|
||||
# Prepare queries and documents
|
||||
queries = [
|
||||
"Delete the record with ID 4 from the 'Staff' table.",
|
||||
'Delete all records in the "Livestock" table where age is greater than 5'
|
||||
]
|
||||
documents = [
|
||||
"DELETE FROM Staff WHERE StaffID = 4;",
|
||||
"DELETE FROM Livestock WHERE age > 5;"
|
||||
]
|
||||
|
||||
# Compute the query and document embeddings
|
||||
query_embeddings = model.encode(queries, prompt=prompt)
|
||||
document_embeddings = model.encode(documents)
|
||||
|
||||
# Compute the cosine similarity between the query and document embeddings
|
||||
similarities = model.similarity(query_embeddings, document_embeddings)
|
||||
print(similarities)
|
||||
```
|
||||
|
||||
### Using HuggingFace Transformers
|
||||
|
||||
```python
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from torch import Tensor
|
||||
from transformers import AutoTokenizer, AutoModel
|
||||
|
||||
|
||||
def last_token_pool(last_hidden_states: Tensor,
|
||||
attention_mask: Tensor) -> Tensor:
|
||||
left_padding = (attention_mask[:, -1].sum() == attention_mask.shape[0])
|
||||
if left_padding:
|
||||
return last_hidden_states[:, -1]
|
||||
else:
|
||||
sequence_lengths = attention_mask.sum(dim=1) - 1
|
||||
batch_size = last_hidden_states.shape[0]
|
||||
return last_hidden_states[torch.arange(batch_size, device=last_hidden_states.device), sequence_lengths]
|
||||
|
||||
|
||||
def get_detailed_instruct(task_description: str, query: str) -> str:
|
||||
return f'<instruct>{task_description}\n<query>{query}'
|
||||
|
||||
|
||||
instruction = 'Given a question in text, retrieve SQL queries that are appropriate responses to the question.'
|
||||
queries = [
|
||||
"Delete the record with ID 4 from the 'Staff' table.",
|
||||
'Delete all records in the "Livestock" table where age is greater than 5'
|
||||
]
|
||||
documents = [
|
||||
"DELETE FROM Staff WHERE StaffID = 4;",
|
||||
"DELETE FROM Livestock WHERE age > 5;"
|
||||
]
|
||||
input_texts = queries + documents
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained('BAAI/bge-code-v1', trust_remote_code=True)
|
||||
model = AutoModel.from_pretrained('BAAI/bge-code-v1', trust_remote_code=True)
|
||||
model.eval()
|
||||
|
||||
max_length = 4096
|
||||
# Tokenize the input texts
|
||||
batch_dict = tokenizer(input_texts, max_length=max_length, padding=True, truncation=True, return_tensors='pt', pad_to_multiple_of=8)
|
||||
|
||||
with torch.no_grad():
|
||||
outputs = model(**batch_dict)
|
||||
embeddings = last_token_pool(outputs.last_hidden_state, batch_dict['attention_mask'])
|
||||
|
||||
# normalize embeddings
|
||||
embeddings = F.normalize(embeddings, p=2, dim=1)
|
||||
scores = (embeddings[:2] @ embeddings[2:].T) * 100
|
||||
print(scores.tolist())
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
**BGE-Code-v1** achieves state-of-the-art performance on both the CoIR and CodeRAG benchmarks.
|
||||
|
||||
- CoIR
|
||||
|
||||
| | CodeXEmbed-2B | CodeXEmbed-7B | Voyage-Code-002 | Voyage-Code-003 | BGE-Code-v1 |
|
||||
| --------------------- | ------------- | ------------- | --------------- | --------------- | ----------- |
|
||||
| **Apps** | 76.86 | 85.38 | 26.52 | 93.62 | 98.08 |
|
||||
| **CosQA** | 40.47 | 42.47 | 29.79 | 34.45 | 46.72 |
|
||||
| **Text2SQL** | 78.42 | 78.94 | 69.26 | 62.87 | 64.35 |
|
||||
| **CSN** | 87.87 | 89.67 | 81.79 | 89.35 | 89.53 |
|
||||
| **CSN-CCR** | 97.66 | 97.95 | 73.45 | 90.05 | 98.30 |
|
||||
| **CodeTrans-Contest** | 90.30 | 94.45 | 72.77 | 94.96 | 94.38 |
|
||||
| **CodeTrans-DL** | 38.57 | 40.46 | 27.48 | 38.57 | 46.13 |
|
||||
| **StackOverFlow-QA** | 94.47 | 96.33 | 67.68 | 97.17 | 95.35 |
|
||||
| **CodeFeedBack-ST** | 86.36 | 87.53 | 65.35 | 90.67 | 90.56 |
|
||||
| **CodeFeedBack-MT** | 65.51 | 68.83 | 28.74 | 93.58 | 94.38 |
|
||||
| **AVG** | **75.65** | **78.20** | **56.26** | **78.53** | **81.77** |
|
||||
|
||||
- CodedRAG
|
||||
|
||||
| | HummanEval | MBPP | DS-1000 | ODEX | RepoEval | SWE-bench-Lite | AVG |
|
||||
| --------------- | ---------- | ---- | ------- | ---- | -------- | -------------- | -------- |
|
||||
| SFR | 100.0 | 99.0 | 19.3 | 37.1 | 83.8 | 62.7 | **67.0** |
|
||||
| Jina-v2-code | 100.0 | 97.7 | 26.2 | 19.9 | 90.5 | 58.3 | **65.4** |
|
||||
| CodeXEmbed-2B | 100.0 | 97.4 | 25.4 | 23.9 | 88.7 | 52.4 | **64.6** |
|
||||
| Voyage-Code-002 | 100.0 | 99.0 | 33.1 | 26.6 | 94.3 | 29.1 | **63.7** |
|
||||
| BGE-Code-v1 | 100.0 | 99.2 | 40.9 | 36.1 | 93.1 | 67.4 | **72.8** |
|
||||
|
||||
### Instructions for Evaluation
|
||||
|
||||
```python
|
||||
{
|
||||
"Apps": "Given a code contest problem description, retrieve relevant code that can help solve the problem.",
|
||||
"CosQA": "Given a web search query, retrieve relevant code that can help answer the query.",
|
||||
"Text2SQL": "Given a question in text, retrieve SQL queries that are appropriate responses to the question.",
|
||||
"CSN": "Given a piece of code, retrieve the document string that summarizes the code.",
|
||||
"CSN-CCR": "Given a piece of code segment, retrieve the code segment that is the latter part of the code.",
|
||||
"CodeTrans-DL": "Given a piece of code, retrieve code that is semantically equivalent to the input code.",
|
||||
"CodeTrans-Contest": "Given a piece of Python code, retrieve C++ code that is semantically equivalent to the input code.",
|
||||
"StackOverFlow-QA": "Given a question that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.",
|
||||
"CodeFeedBack-ST": "Given a question that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.",
|
||||
"CodeFeedBack-MT": "Given a multi-turn conversation history that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.",
|
||||
"HummanEval": "Given a question that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.",
|
||||
"MBPP": "Given a textual explanation of code functionality, retrieve the corresponding code implementation.",
|
||||
"DS-1000": "Given a question that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.",
|
||||
"ODEX": "Given a question, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.",
|
||||
"RepoEval": "Given a piece of code segment, retrieve the code segment that is the latter part of the code.",
|
||||
"SWE-bench-Lite": "Given a code snippet containing a bug and a natural language description of the bug or error, retrieve code snippets that demonstrate solutions or fixes for similar bugs or errors (the desired documents)."
|
||||
}
|
||||
```
|
||||
|
||||
### Evaluation script
|
||||
|
||||
#### CoIR
|
||||
|
||||
For CoIR, we use the [CoIR](https://github.com/CoIR-team/coir) evaluation script:
|
||||
|
||||
```shell
|
||||
cd ./evaluation/coir_eval
|
||||
### clone coir
|
||||
mkdir test
|
||||
cd ./test
|
||||
git clone https://github.com/CoIR-team/coir.git
|
||||
mv ./coir/coir ../
|
||||
cd ..
|
||||
rm -rf ./test
|
||||
### evaluate
|
||||
bash eval.sh
|
||||
```
|
||||
|
||||
### CodeRAG
|
||||
|
||||
For CodeRAG, we use the [CodeRAG](https://github.com/code-rag-bench/code-rag-bench) evaluation script:
|
||||
|
||||
```shell
|
||||
cd ./evaluation/coderag_eval
|
||||
### clone coderag
|
||||
git clone https://github.com/code-rag-bench/code-rag-bench.git
|
||||
## You need prepare environment according to README.md
|
||||
rm -rf ./code-rag-bench/retrieval/create
|
||||
cp -r ./test/* ./code-rag-bench/retrieval/
|
||||
### prepare data
|
||||
bash prepare_data.sh
|
||||
### evaluate
|
||||
bash eval.sh
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,104 @@
|
||||
import os
|
||||
import random
|
||||
import datasets
|
||||
from tqdm import tqdm
|
||||
from typing import List, Tuple
|
||||
|
||||
from utils import clean_code
|
||||
from constant import DocLength
|
||||
|
||||
|
||||
class CorpusGenerator:
|
||||
def __init__(
|
||||
self,
|
||||
cache_dir: str = None,
|
||||
):
|
||||
self.cache_dir = cache_dir
|
||||
|
||||
def _load_corpus(self, corpus_dir: str, doc_length: List[str], external_path: List[str],
|
||||
source_language: str, stop_threshold: int = -1):
|
||||
"""
|
||||
Load availavle documents for a given task from the CoIR-Retrieval dataset.
|
||||
"""
|
||||
|
||||
corpus_list = []
|
||||
|
||||
if corpus_dir is not None and os.path.exists(corpus_dir):
|
||||
file_list = os.listdir(corpus_dir)
|
||||
random.shuffle(file_list)
|
||||
|
||||
for file in file_list:
|
||||
flag = False
|
||||
if not file.endswith('.jsonl'):
|
||||
flag = False
|
||||
for d_length in doc_length:
|
||||
d_length = DocLength[d_length].value
|
||||
if d_length in file:
|
||||
flag = True
|
||||
if flag is False:
|
||||
continue
|
||||
file_path = os.path.join(corpus_dir, file)
|
||||
corpus = datasets.load_dataset('json', data_files=file_path, cache_dir=self.cache_dir)['train']
|
||||
for data in tqdm(corpus, desc="Loading corpus"):
|
||||
if source_language is None:
|
||||
lang = os.path.basename(corpus_dir)
|
||||
data['language'] = lang
|
||||
else:
|
||||
data['language'] = source_language
|
||||
|
||||
text = clean_code(data["text"], data["language"], length_threshold=200)
|
||||
data["text"] = text
|
||||
if text != '':
|
||||
corpus_list.append(data)
|
||||
|
||||
if stop_threshold > 0 and len(corpus_list) > stop_threshold:
|
||||
break
|
||||
break
|
||||
|
||||
for ep in external_path:
|
||||
if os.path.exists(ep):
|
||||
corpus = datasets.load_dataset('json', data_files=ep, cache_dir=self.cache_dir)['train']
|
||||
for data in tqdm(corpus, desc="Loading corpus"):
|
||||
if source_language is None:
|
||||
lang = os.path.basename(os.path.dirname(ep))
|
||||
data['language'] = lang
|
||||
else:
|
||||
data['language'] = source_language
|
||||
|
||||
# useful when the text is not present in the data
|
||||
if "text" not in data:
|
||||
data["text"] = data["pos"][0]
|
||||
|
||||
corpus_list.append(data)
|
||||
text = clean_code(data["text"], lang, length_threshold=200)
|
||||
data["text"] = text
|
||||
if text != '':
|
||||
corpus_list.append(data)
|
||||
|
||||
return corpus_list
|
||||
|
||||
def run(
|
||||
self,
|
||||
num_samples: int = -1,
|
||||
max_corpus: int = -1,
|
||||
corpus_dir: str = None,
|
||||
doc_length: List[str] = ["len_0_500"],
|
||||
external_path: List[str] = None,
|
||||
source_language: str = None
|
||||
) -> Tuple[List[dict], List[dict]]:
|
||||
stop_threshold = max(num_samples * 10, max_corpus * 2)
|
||||
corpus_list = self._load_corpus(
|
||||
corpus_dir, doc_length, external_path, source_language, stop_threshold
|
||||
)
|
||||
|
||||
if num_samples > 0 and num_samples < len(corpus_list):
|
||||
small_corpus_list = random.sample(corpus_list, num_samples)
|
||||
else:
|
||||
small_corpus_list = corpus_list
|
||||
|
||||
if max_corpus > 0 and max_corpus < len(corpus_list):
|
||||
corpus_list = random.sample(corpus_list, max_corpus)
|
||||
else:
|
||||
corpus_list = corpus_list
|
||||
|
||||
return small_corpus_list, corpus_list
|
||||
@@ -0,0 +1,127 @@
|
||||
import os
|
||||
import json
|
||||
from constant import Language, CodeLanguage, TaskType, CODE_TRANSLATION_RETRIEVAL_PAIRS, \
|
||||
get_pos_as_input_by_task_type
|
||||
|
||||
|
||||
def format_generated_examples(
|
||||
file_path: str,
|
||||
save_path: str,
|
||||
task_type: TaskType
|
||||
):
|
||||
if os.path.exists(save_path):
|
||||
return
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
print("====================================")
|
||||
print("Warning: file not found! Maybe need to generate it first.")
|
||||
print(f"file_path: {file_path}")
|
||||
return
|
||||
|
||||
pos_as_input = get_pos_as_input_by_task_type(task_type)
|
||||
|
||||
data_list = []
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
for line in f.readlines():
|
||||
data = json.loads(line)
|
||||
|
||||
if pos_as_input:
|
||||
_input = data["pos"][0]
|
||||
_output = data["query"]
|
||||
else:
|
||||
_input = data["query"]
|
||||
_output = data["pos"][0]
|
||||
|
||||
if 'provided' in _input:
|
||||
continue
|
||||
if len(_input) > 12000 or len(_output) > 12000:
|
||||
continue
|
||||
|
||||
data_list.append({
|
||||
"input": _input,
|
||||
"output": _output
|
||||
})
|
||||
|
||||
if len(data_list) == 0:
|
||||
print("====================================")
|
||||
print("Warning: no data found!")
|
||||
print(f"file_path: {file_path}")
|
||||
return
|
||||
|
||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||
with open(save_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data_list, f, indent=4, ensure_ascii=False)
|
||||
|
||||
|
||||
def main():
|
||||
original_gen_examples_dir = "./examples"
|
||||
|
||||
formatted_examples_dir = "./filtered_for_generation"
|
||||
|
||||
for language in Language:
|
||||
for task_type in TaskType:
|
||||
if task_type == TaskType.code_translation_retrieval:
|
||||
for code_language_pair in CODE_TRANSLATION_RETRIEVAL_PAIRS:
|
||||
code_language, tgt_code_language = code_language_pair
|
||||
|
||||
file_path = os.path.join(
|
||||
original_gen_examples_dir,
|
||||
language.name, task_type.name, f"{language.name}-{code_language.name}-to-{tgt_code_language.name}-triplets.jsonl"
|
||||
)
|
||||
save_path = os.path.join(
|
||||
formatted_examples_dir,
|
||||
language.name, task_type.name, f"{code_language.name}-to-{tgt_code_language.name}_sample_examples.json"
|
||||
)
|
||||
|
||||
format_generated_examples(file_path, save_path, task_type)
|
||||
|
||||
for code_language_pair in CODE_TRANSLATION_RETRIEVAL_PAIRS:
|
||||
tgt_code_language, code_language = code_language_pair
|
||||
|
||||
file_path = os.path.join(
|
||||
original_gen_examples_dir,
|
||||
language.name, task_type.name, f"{language.name}-{code_language.name}-to-{tgt_code_language.name}-triplets.jsonl"
|
||||
)
|
||||
save_path = os.path.join(
|
||||
formatted_examples_dir,
|
||||
language.name, task_type.name, f"{code_language.name}-to-{tgt_code_language.name}_sample_examples.json"
|
||||
)
|
||||
|
||||
format_generated_examples(file_path, save_path, task_type)
|
||||
|
||||
elif task_type == TaskType.text2sql_retrieval:
|
||||
file_path = os.path.join(
|
||||
original_gen_examples_dir,
|
||||
language.name, task_type.name, f"{language.name}-sql-triplets.jsonl"
|
||||
)
|
||||
save_path = os.path.join(
|
||||
formatted_examples_dir,
|
||||
language.name, task_type.name, "sql_sample_examples.json"
|
||||
)
|
||||
|
||||
format_generated_examples(file_path, save_path, task_type)
|
||||
|
||||
elif task_type == TaskType.code_context_retrieval:
|
||||
continue
|
||||
|
||||
else:
|
||||
for code_language in CodeLanguage:
|
||||
if code_language == CodeLanguage.null:
|
||||
continue
|
||||
|
||||
file_path = os.path.join(
|
||||
original_gen_examples_dir,
|
||||
language.name, task_type.name, f"{language.name}-{code_language.name}-triplets.jsonl"
|
||||
)
|
||||
save_path = os.path.join(
|
||||
formatted_examples_dir,
|
||||
language.name, task_type.name, f"{code_language.name}_sample_examples.json"
|
||||
)
|
||||
|
||||
format_generated_examples(file_path, save_path, task_type)
|
||||
|
||||
print("All done!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,134 @@
|
||||
import os
|
||||
import time
|
||||
import openai
|
||||
import random
|
||||
import tiktoken
|
||||
import threading
|
||||
from openai import OpenAI, AzureOpenAI
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class LLM:
|
||||
def __init__(
|
||||
self,
|
||||
model: str="Qwen2-5-Coder-32B-Instruct",
|
||||
model_type: str = "open-source",
|
||||
port: int = 8000,
|
||||
):
|
||||
if model_type == "open-source":
|
||||
self.client = OpenAI(
|
||||
api_key="EMPTY",
|
||||
base_url=f"http://localhost:{port}/v1/"
|
||||
)
|
||||
elif model_type == "azure":
|
||||
self.client = AzureOpenAI(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
api_version=os.getenv("AZURE_API_VERSION", "2024-02-01"),
|
||||
azure_endpoint=os.getenv("AZURE_ENDPOINT"),
|
||||
azure_deployment=os.getenv("OPENAI_DEPLOYMENT_NAME", 'gpt-35-turbo')
|
||||
)
|
||||
elif model_type == "openai":
|
||||
self.client = OpenAI(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
base_url=os.getenv("OPENAI_BASE_URL", None)
|
||||
)
|
||||
else:
|
||||
raise ValueError("model_type must be one of ['open-source', 'azure', 'openai']")
|
||||
|
||||
self.model = model
|
||||
self.tokenizer = tiktoken.get_encoding("o200k_base")
|
||||
|
||||
def split_text(self, text: str, anchor_points: Tuple[float, float] = (0.4, 0.7)):
|
||||
token_ids = self.tokenizer.encode(text)
|
||||
anchor_point = random.uniform(anchor_points[0], anchor_points[1])
|
||||
split_index = int(len(token_ids) * anchor_point)
|
||||
return self.tokenizer.decode(token_ids[:split_index]), self.tokenizer.decode(token_ids[split_index:])
|
||||
|
||||
def chat(
|
||||
self,
|
||||
prompt: str,
|
||||
max_tokens: int = 8192,
|
||||
logit_bais: dict = None,
|
||||
n: int = 1,
|
||||
temperature: float = 1.0,
|
||||
top_p: float = 0.6,
|
||||
repetition_penalty: float = 1.0,
|
||||
remove_thinking: bool = True,
|
||||
timeout: int = 90,
|
||||
):
|
||||
endure_time = 0
|
||||
endure_time_limit = timeout * 2
|
||||
|
||||
def create_completion(results):
|
||||
try:
|
||||
completion = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
max_tokens=max_tokens,
|
||||
logit_bias=logit_bais if logit_bais is not None else {},
|
||||
n=n,
|
||||
temperature=temperature,
|
||||
top_p=top_p,
|
||||
extra_body={'repetition_penalty': repetition_penalty},
|
||||
timeout=timeout,
|
||||
)
|
||||
results["content"] = [x.message.content for x in completion.choices[:n]]
|
||||
except openai.BadRequestError as e:
|
||||
# The response was filtered due to the prompt triggering Azure OpenAI's content management policy.
|
||||
results["content"] = [None for _ in range(n)]
|
||||
except openai.APIConnectionError as e:
|
||||
results["error"] = f'APIConnectionError({e})'
|
||||
except openai.RateLimitError as e:
|
||||
results["error"] = f'RateLimitError({e})'
|
||||
except Exception as e:
|
||||
results["error"] = f"Error: {e}"
|
||||
|
||||
while True:
|
||||
results = {"content": None, "error": None}
|
||||
completion_thread = threading.Thread(target=create_completion, args=(results,))
|
||||
completion_thread.start()
|
||||
|
||||
start_time = time.time()
|
||||
while completion_thread.is_alive():
|
||||
elapsed_time = time.time() - start_time
|
||||
if elapsed_time > endure_time_limit:
|
||||
print("Completion timeout exceeded. Aborting...")
|
||||
return [None for _ in range(n)]
|
||||
time.sleep(1)
|
||||
|
||||
# If an error occurred during result processing
|
||||
if results["error"]:
|
||||
if endure_time >= endure_time_limit:
|
||||
print(f'{results["error"]} - Skip this prompt.')
|
||||
return [None for _ in range(n)]
|
||||
print(f"{results['error']} - Waiting for 5 seconds...")
|
||||
endure_time += 5
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
content_list = results["content"]
|
||||
if remove_thinking:
|
||||
content_list = [x.split('</think>')[-1].strip('\n').strip() if x is not None else None for x in content_list]
|
||||
return content_list
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
llm = LLM(
|
||||
model="gpt-4o-mini-2024-07-18",
|
||||
model_type="openai"
|
||||
)
|
||||
|
||||
prompt = "hello, who are you?"
|
||||
response = llm.chat(prompt)[0]
|
||||
print(response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
llm = LLM(
|
||||
model="gpt-4o-mini-2024-07-18",
|
||||
model_type="openai"
|
||||
)
|
||||
|
||||
prompt = "hello, who are you?"
|
||||
response = llm.chat(prompt)[0]
|
||||
print(response)
|
||||
@@ -0,0 +1,368 @@
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import gc
|
||||
import torch
|
||||
import argparse
|
||||
import random
|
||||
from hashlib import md5
|
||||
import multiprocessing as mp
|
||||
from typing import List, Optional
|
||||
|
||||
from constant import TaskType, Language, CodeLanguage, NUM_HARD_NEGATIVES
|
||||
from corpus_generator import CorpusGenerator
|
||||
from triplet_generator import TripletGenerator
|
||||
from search import get_top1
|
||||
|
||||
|
||||
def compute_md5(text: str):
|
||||
return md5(text.encode()).hexdigest()
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'--task_type',
|
||||
type=str,
|
||||
required=True,
|
||||
help='The task type to generate data for',
|
||||
choices=[t.name for t in TaskType]
|
||||
)
|
||||
parser.add_argument(
|
||||
'--code_language',
|
||||
type=str,
|
||||
required=True,
|
||||
help='The code language to generate questions for.',
|
||||
choices=[c.name for c in CodeLanguage]
|
||||
)
|
||||
parser.add_argument(
|
||||
'--corpus_root',
|
||||
type=str,
|
||||
required=True,
|
||||
help='The root directory of the corpus data.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--save_dir',
|
||||
type=str,
|
||||
required=True,
|
||||
help='The path to save the generated data'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--examples_dir',
|
||||
type=str,
|
||||
default=None,
|
||||
help='The path to the examples directory. If not None, the examples will be used for few-shot generation.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--num_examples',
|
||||
type=int,
|
||||
default=3,
|
||||
help='The number of examples to use for few-shot generation. Default: 3'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--cache_dir',
|
||||
type=str,
|
||||
default=None,
|
||||
help='The cache directory'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--language',
|
||||
type=str,
|
||||
default='en',
|
||||
help='The language to generate for. ISO 639-1 code. Default: en',
|
||||
choices=[l.name for l in Language]
|
||||
)
|
||||
parser.add_argument(
|
||||
'--tgt_code_language',
|
||||
type=str,
|
||||
default=None,
|
||||
help='The target code language to generate code translations for.',
|
||||
choices=[c.name for c in CodeLanguage]
|
||||
)
|
||||
parser.add_argument(
|
||||
'--num_samples',
|
||||
type=int,
|
||||
default=-1,
|
||||
help='The number of examples to use for generation. Default: -1. Use all available examples.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--model',
|
||||
type=str,
|
||||
default='Qwen2.5-72B-Instruct',
|
||||
help='The model to use for generation. Default: Qwen2.5-72B-Instruct'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--model_type',
|
||||
type=str,
|
||||
default='open-source',
|
||||
help='The type of model to use for generation. Default: open-source',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--port',
|
||||
type=int,
|
||||
default=8000,
|
||||
help='The port for vllm.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--num_processes',
|
||||
type=int,
|
||||
default=1,
|
||||
help='The number of processes to use for generation. Default: 1'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--doc_length',
|
||||
type=str,
|
||||
default='len_0_500',
|
||||
help='The corpus length used to load dataset. Default: len_0_500'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--external_path',
|
||||
type=str,
|
||||
default='',
|
||||
help='The corpus length used to load dataset. Default: len_0_500'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sim_model_name',
|
||||
type=str,
|
||||
default=None,
|
||||
help='The language of source corpus.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--max_corpus',
|
||||
type=int,
|
||||
default=500000,
|
||||
help='The max num of corpus to load.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--overwrite',
|
||||
action='store_true',
|
||||
help='Whether to overwrite the existing data.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--debug_mode',
|
||||
action='store_true',
|
||||
help='Whether to open debug mode.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--gen_hard_neg',
|
||||
action='store_true',
|
||||
help='Whether to generate hard negatives.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--seed',
|
||||
type=int,
|
||||
default=None,
|
||||
help='Random seed for generating triplets using the same positive. Default: 42'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def gen_triplets(
|
||||
model: str,
|
||||
model_type: str,
|
||||
port: int,
|
||||
positives: List[dict],
|
||||
task_type: str,
|
||||
language: str,
|
||||
code_language: str,
|
||||
tgt_code_language: str,
|
||||
examples_pool: Optional[List[dict]] = None,
|
||||
num_examples: int = 3,
|
||||
tqdm_desc: str = "Generating triplets",
|
||||
thread_count: int = 1,
|
||||
gen_cache_dir: Optional[str] = None,
|
||||
debug_mode: bool = False,
|
||||
gen_hard_neg: bool = False,
|
||||
):
|
||||
triplet_generator = TripletGenerator(model, model_type, port, cache_dir=gen_cache_dir)
|
||||
triplets = triplet_generator.run(
|
||||
positives=positives,
|
||||
task_type=task_type,
|
||||
language=language,
|
||||
code_language=code_language,
|
||||
tgt_code_language=tgt_code_language,
|
||||
examples_pool=examples_pool,
|
||||
num_examples=num_examples,
|
||||
tqdm_desc=tqdm_desc,
|
||||
thread_count=thread_count,
|
||||
debug_mode=debug_mode,
|
||||
gen_hard_neg=gen_hard_neg,
|
||||
num_negatives=NUM_HARD_NEGATIVES,
|
||||
)
|
||||
return triplets
|
||||
|
||||
|
||||
def get_save_path(
|
||||
save_dir: str,
|
||||
task_type: str,
|
||||
language: str,
|
||||
code_language: str,
|
||||
tgt_code_language: Optional[str] = None
|
||||
):
|
||||
save_dir = os.path.join(save_dir, language, task_type)
|
||||
if tgt_code_language is not None:
|
||||
file_name = f"{language}-{code_language}-to-{tgt_code_language}-triplets.jsonl"
|
||||
else:
|
||||
file_name = f"{language}-{code_language}-triplets.jsonl"
|
||||
save_path = os.path.join(save_dir, file_name)
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
return save_path
|
||||
|
||||
|
||||
def save_triplets(
|
||||
triplets: list,
|
||||
save_dir: str,
|
||||
task_type: str,
|
||||
language: str,
|
||||
code_language: str,
|
||||
tgt_code_language: Optional[str] = None
|
||||
):
|
||||
if len(triplets) == 0:
|
||||
print(f"No triplets to save: {task_type} | {language} | {code_language} | {tgt_code_language}")
|
||||
return
|
||||
|
||||
save_path = get_save_path(save_dir, task_type, language, code_language, tgt_code_language)
|
||||
query_md5s = set()
|
||||
pos_md5s = set()
|
||||
old_triplets = []
|
||||
if os.path.exists(save_path):
|
||||
with open(save_path, "r", encoding="utf-8") as f:
|
||||
for line in f.readlines():
|
||||
triplet = json.loads(line)
|
||||
old_triplets.append(triplet)
|
||||
query_md5s.add(compute_md5(triplet['query']))
|
||||
pos_md5s.add(compute_md5(triplet['pos'][0]))
|
||||
|
||||
with open(save_path, 'w', encoding='utf-8') as f:
|
||||
for triplet in old_triplets:
|
||||
f.write(json.dumps(triplet, ensure_ascii=False) + '\n')
|
||||
|
||||
for triplet in triplets:
|
||||
_query_md5 = compute_md5(triplet['query'])
|
||||
_pos_md5 = compute_md5(triplet['pos'][0])
|
||||
if _query_md5 in query_md5s or _pos_md5 in pos_md5s:
|
||||
continue
|
||||
f.write(json.dumps(triplet, ensure_ascii=False) + '\n')
|
||||
print(f"Triplets saved to {save_path}")
|
||||
|
||||
|
||||
def main(args):
|
||||
# set seed
|
||||
seed = args.seed
|
||||
if seed is not None:
|
||||
print(f"------------------- Seed set to {seed} -------------------")
|
||||
random.seed(seed)
|
||||
|
||||
model = args.model
|
||||
model_type = args.model_type
|
||||
port = args.port
|
||||
|
||||
num_samples = args.num_samples
|
||||
|
||||
task_type = args.task_type
|
||||
language = args.language
|
||||
code_language = args.code_language
|
||||
tgt_code_language = args.tgt_code_language
|
||||
|
||||
corpus_root = args.corpus_root
|
||||
corpus_dir = os.path.join(corpus_root, code_language)
|
||||
doc_length = args.doc_length.split()
|
||||
external_path = args.external_path.split()
|
||||
|
||||
save_dir = args.save_dir
|
||||
cache_dir = args.cache_dir
|
||||
num_processes = min(args.num_processes, int(mp.cpu_count() * 0.8))
|
||||
overwrite = args.overwrite
|
||||
debug_mode = args.debug_mode
|
||||
gen_hard_neg = args.gen_hard_neg
|
||||
|
||||
save_path = get_save_path(save_dir, task_type, language, code_language, tgt_code_language)
|
||||
# if os.path.exists(save_path) and not overwrite:
|
||||
# data = []
|
||||
# with open(save_path) as f:
|
||||
# for line in f:
|
||||
# data.append(json.loads(line))
|
||||
# if len(data) >= num_samples * 0.8:
|
||||
# print(f"Triplets already exist at {save_path}. Skipping generation.")
|
||||
# return
|
||||
# else:
|
||||
# print(f"Triplets already exist at {save_path}. But samples is really small, continue generation.")
|
||||
# num_samples = int((num_samples - len(data)) * 1.25) # consider the filtered samples
|
||||
|
||||
corpus_generator = CorpusGenerator(cache_dir)
|
||||
|
||||
examples_dir = args.examples_dir
|
||||
num_examples = args.num_examples
|
||||
if examples_dir is not None:
|
||||
# if task_type in ["single_turn_code_qa", "multi_turn_code_qa"]:
|
||||
# examples_path = os.path.join(examples_dir, language, task_type, "sample_examples.json")
|
||||
if task_type in ["code_translation_retrieval"]:
|
||||
examples_path = os.path.join(examples_dir, language, task_type,
|
||||
f"{code_language}-to-{tgt_code_language}_sample_examples.json")
|
||||
else:
|
||||
examples_path = os.path.join(examples_dir, language, task_type, f"{code_language}_sample_examples.json")
|
||||
try:
|
||||
with open(examples_path, 'r', encoding='utf-8') as f:
|
||||
examples_pool = json.load(f)
|
||||
examples_pool = random.sample(examples_pool,
|
||||
min(30, len(examples_pool))) # sample 30 examples for few-shot generation
|
||||
except:
|
||||
print(f'Error for loading examples from {examples_path}')
|
||||
examples_pool = None
|
||||
else:
|
||||
examples_pool = None
|
||||
|
||||
positives, large_positives = corpus_generator.run(
|
||||
num_samples=num_samples,
|
||||
max_corpus=args.max_corpus,
|
||||
corpus_dir=corpus_dir,
|
||||
doc_length=doc_length,
|
||||
external_path=external_path,
|
||||
source_language=code_language
|
||||
)
|
||||
|
||||
if task_type in ["code_modification_retrieval", "code_comparison_retrieval"]:
|
||||
top1_docs = get_top1([e['text'] for e in positives], args.sim_model_name, [e['text'] for e in large_positives])
|
||||
for i in range(len(top1_docs)):
|
||||
positives[i]['similar'] = top1_docs[i]
|
||||
gc.collect()
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
print("=================== Generate training data ===================")
|
||||
print(f'Task Type: {task_type} | Language: {language} | Code Language: {code_language} | Target Code Language: {tgt_code_language}')
|
||||
start_time = time.time()
|
||||
triplets = gen_triplets(
|
||||
model=model,
|
||||
model_type=model_type,
|
||||
port=port,
|
||||
positives=positives,
|
||||
task_type=task_type,
|
||||
language=language,
|
||||
code_language=code_language,
|
||||
tgt_code_language=tgt_code_language,
|
||||
examples_pool=examples_pool,
|
||||
num_examples=num_examples,
|
||||
thread_count=num_processes,
|
||||
gen_cache_dir=os.path.join(save_dir, language, task_type, "gen_cache_dir"),
|
||||
debug_mode=debug_mode,
|
||||
gen_hard_neg=gen_hard_neg,
|
||||
)
|
||||
save_triplets(
|
||||
triplets=triplets,
|
||||
save_dir=save_dir,
|
||||
task_type=task_type,
|
||||
language=language,
|
||||
code_language=code_language,
|
||||
tgt_code_language=tgt_code_language
|
||||
)
|
||||
end_time = time.time()
|
||||
print("=============================================================")
|
||||
print(f"Time taken: {end_time - start_time:.2f} seconds")
|
||||
print("=============================================================")
|
||||
print("DONE!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = get_args()
|
||||
main(args)
|
||||
@@ -0,0 +1,71 @@
|
||||
from typing import Optional, List
|
||||
|
||||
import faiss
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from FlagEmbedding import FlagModel
|
||||
|
||||
def create_index(embeddings: np.ndarray, use_gpu: bool = False):
|
||||
index = faiss.IndexFlatIP(len(embeddings[0]))
|
||||
embeddings = np.asarray(embeddings, dtype=np.float32)
|
||||
if use_gpu:
|
||||
co = faiss.GpuMultipleClonerOptions()
|
||||
co.shard = True
|
||||
co.useFloat16 = True
|
||||
index = faiss.index_cpu_to_all_gpus(index, co=co)
|
||||
index.add(embeddings)
|
||||
return index
|
||||
|
||||
|
||||
def search(
|
||||
faiss_index: faiss.Index,
|
||||
k: int = 100,
|
||||
query_embeddings: Optional[np.ndarray] = None,
|
||||
load_path: Optional[str] = None
|
||||
):
|
||||
if query_embeddings is None:
|
||||
query_embeddings = np.load(load_path)
|
||||
|
||||
query_size = len(query_embeddings)
|
||||
|
||||
all_scores = []
|
||||
all_indices = []
|
||||
|
||||
for i in tqdm(range(0, query_size, 32), desc="Searching"):
|
||||
j = min(i + 32, query_size)
|
||||
query_embedding = query_embeddings[i: j]
|
||||
score, indice = faiss_index.search(query_embedding.astype(np.float32), k=k)
|
||||
all_scores.append(score)
|
||||
all_indices.append(indice)
|
||||
|
||||
all_scores = np.concatenate(all_scores, axis=0)
|
||||
all_indices = np.concatenate(all_indices, axis=0)
|
||||
return all_scores, all_indices
|
||||
|
||||
def get_top1(
|
||||
small_docs,
|
||||
encoder_name,
|
||||
docs: List[str],
|
||||
top: int = 1
|
||||
):
|
||||
encoder = FlagModel(encoder_name, trust_remote_code=True)
|
||||
doc_emb = encoder.encode_corpus(docs, max_length=512, batch_size=256)
|
||||
small_doc_emb = encoder.encode_corpus(small_docs, max_length=512, batch_size=256)
|
||||
faiss_index = create_index(doc_emb, True)
|
||||
all_scores, all_indices = search(faiss_index, 1000, small_doc_emb)
|
||||
return_docs = []
|
||||
for i in range(len(all_indices)):
|
||||
return_docs.append([])
|
||||
for idx, score in zip(all_indices[i][20:], all_scores[i][20:]):
|
||||
d1 = set(docs[idx].split())
|
||||
d2 = set(small_docs[i].split())
|
||||
if len(d1 & d2) / len(d1 | d2) > 0.95:
|
||||
continue
|
||||
return_docs[-1].append(docs[idx])
|
||||
if len(return_docs[-1]) >= top:
|
||||
break
|
||||
if len(return_docs[-1]) == 0:
|
||||
print(all_indices[i], all_scores[i])
|
||||
# print(return_docs)
|
||||
del faiss_index
|
||||
return return_docs
|
||||
@@ -0,0 +1,654 @@
|
||||
import os
|
||||
import json
|
||||
import random
|
||||
from tqdm import tqdm
|
||||
from hashlib import md5
|
||||
from warnings import warn
|
||||
from typing import List, Optional
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from llm import LLM
|
||||
from utils import clean_content
|
||||
from constant import TaskType, Task, SPECIAL_TASK_STEPS, \
|
||||
get_task, get_generation_prompt, get_quality_control_prompt, \
|
||||
get_gen_hard_neg_prompt
|
||||
|
||||
|
||||
def compute_md5(text: str):
|
||||
return md5(text.encode()).hexdigest()
|
||||
|
||||
|
||||
class TripletGenerator(LLM):
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "Qwen2-5-Coder-32B-Instruct",
|
||||
model_type: str = "open-source",
|
||||
port: int = 8000,
|
||||
cache_dir: Optional[str] = None
|
||||
):
|
||||
super().__init__(model, model_type, port)
|
||||
self.cache_dir = cache_dir
|
||||
if self.cache_dir is not None:
|
||||
os.makedirs(self.cache_dir, exist_ok=True)
|
||||
|
||||
def _gen_for_code_modification_retrieval(
|
||||
self,
|
||||
task: Task,
|
||||
text: str,
|
||||
text_b: Optional[str] = None,
|
||||
examples: Optional[List[dict]] = None,
|
||||
debug_mode: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=text,
|
||||
text_b=text_b,
|
||||
examples=examples,
|
||||
idx=0
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
diff = clean_content(response)
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=diff,
|
||||
examples=examples,
|
||||
idx=1
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
modification_instr = clean_content(response)
|
||||
|
||||
query = f"{modification_instr}\n```\n{text}\n```"
|
||||
pos = text_b
|
||||
|
||||
if debug_mode:
|
||||
result = {
|
||||
"generation_prompt": gen_prompt,
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
return result
|
||||
|
||||
def _gen_for_code_comparison_retrieval(
|
||||
self,
|
||||
task: Task,
|
||||
text: str,
|
||||
text_b: Optional[str] = None,
|
||||
examples: Optional[List[dict]] = None,
|
||||
debug_mode: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=text,
|
||||
text_b=text_b,
|
||||
examples=examples,
|
||||
idx=0
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
diff_question = clean_content(response)
|
||||
query = f"{diff_question}\n\nInput Code:\n```\n{text}\n```\n\nOutput Code:\n```\n{text_b}\n```"
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=query,
|
||||
examples=examples,
|
||||
idx=1
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
pos = clean_content(response)
|
||||
|
||||
if debug_mode:
|
||||
result = {
|
||||
"generation_prompt": gen_prompt,
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
return result
|
||||
|
||||
def _gen_for_code_context_retrieval(
|
||||
self,
|
||||
task: Task,
|
||||
text: str,
|
||||
anchor_points: Optional[tuple] = (0.4, 0.7),
|
||||
**kwargs
|
||||
):
|
||||
former_part, latter_part = self.split_text(
|
||||
text,
|
||||
anchor_points=anchor_points
|
||||
)
|
||||
result = {
|
||||
"prompt": task.task_instruction,
|
||||
"query": former_part,
|
||||
"pos": [latter_part],
|
||||
"neg": []
|
||||
}
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _arrange_query_and_pos(task: Task, input_text: str, response: str):
|
||||
"""
|
||||
Arrange the query and positive example based on the task type.
|
||||
|
||||
Args:
|
||||
- task: Task
|
||||
- input_text: str
|
||||
- response: str
|
||||
|
||||
Returns:
|
||||
- query: str
|
||||
- pos: str
|
||||
"""
|
||||
# TODO: support more task types, including some special task types.
|
||||
if task.main_task_type in ["text2code", "hybrid"]:
|
||||
query = clean_content(response)
|
||||
pos = input_text
|
||||
else:
|
||||
query = input_text
|
||||
pos = clean_content(response)
|
||||
return query, pos
|
||||
|
||||
def _gen_for_normal_task(
|
||||
self,
|
||||
task: Task,
|
||||
text: str,
|
||||
examples: Optional[List[dict]] = None,
|
||||
debug_mode: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=text,
|
||||
examples=examples
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
|
||||
# Arrange the query and positive example based on the task type.
|
||||
query, pos = self._arrange_query_and_pos(
|
||||
task=task,
|
||||
input_text=text,
|
||||
response=response
|
||||
)
|
||||
|
||||
if debug_mode:
|
||||
result = {
|
||||
"generation_prompt": gen_prompt,
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": [],
|
||||
"response": response
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
return result
|
||||
|
||||
def _gen_for_bug_desc_retrieval(
|
||||
self,
|
||||
task: Task,
|
||||
text: str,
|
||||
examples: Optional[List[dict]] = None,
|
||||
debug_mode: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=text,
|
||||
examples=examples,
|
||||
idx=0
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
if response is None:
|
||||
raise ValueError("Response is None.")
|
||||
buggy_code = response
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=buggy_code,
|
||||
examples=examples,
|
||||
idx=1
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
query = clean_content(response)
|
||||
pos = text
|
||||
|
||||
if debug_mode:
|
||||
result = {
|
||||
"generation_prompt": gen_prompt,
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
return result
|
||||
|
||||
def _gen_for_two_step_not_use_last(
|
||||
self,
|
||||
task: Task,
|
||||
text: str,
|
||||
examples: Optional[List[dict]] = None,
|
||||
debug_mode: bool = False,
|
||||
reverse_query_pos: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=text,
|
||||
idx=0
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
query = clean_content(response)
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=query,
|
||||
examples=examples,
|
||||
idx=1
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
pos = clean_content(response)
|
||||
if reverse_query_pos:
|
||||
query, pos = pos, query
|
||||
|
||||
if debug_mode:
|
||||
result = {
|
||||
"generation_prompt": gen_prompt,
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
return result
|
||||
|
||||
def _gen_for_two_step_use_last(
|
||||
self,
|
||||
task: Task,
|
||||
text: str,
|
||||
examples: Optional[List[dict]] = None,
|
||||
debug_mode: bool = False,
|
||||
reverse_query_pos: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=text,
|
||||
idx=0
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
query = clean_content(response) + f"\n```\n{text}\n```"
|
||||
gen_prompt = get_generation_prompt(
|
||||
task=task,
|
||||
text=query,
|
||||
examples=examples,
|
||||
idx=1
|
||||
)
|
||||
response = self.chat(gen_prompt, **kwargs)[0]
|
||||
pos = clean_content(response)
|
||||
if reverse_query_pos:
|
||||
query, pos = pos, query
|
||||
|
||||
if debug_mode:
|
||||
result = {
|
||||
"generation_prompt": gen_prompt,
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"prompt": task.task_instruction,
|
||||
"query": query,
|
||||
"pos": [pos],
|
||||
"neg": []
|
||||
}
|
||||
return result
|
||||
|
||||
def generate_triplets(
|
||||
self,
|
||||
data: dict,
|
||||
task: Task,
|
||||
examples_pool: Optional[List[dict]] = None,
|
||||
num_examples: int = 3,
|
||||
debug_mode: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
kwargs["remove_thinking"] = not debug_mode
|
||||
|
||||
result_list = []
|
||||
|
||||
examples = None
|
||||
if examples_pool is not None:
|
||||
examples = random.sample(examples_pool, min(num_examples, len(examples_pool)))
|
||||
|
||||
try:
|
||||
if task.task_type in SPECIAL_TASK_STEPS:
|
||||
text = data["text"]
|
||||
|
||||
if task.task_type == TaskType.code_modification_retrieval:
|
||||
text_b = data["similar"][0]
|
||||
|
||||
result = self._gen_for_code_modification_retrieval(
|
||||
task=task,
|
||||
text=text,
|
||||
text_b=text_b,
|
||||
examples=examples,
|
||||
debug_mode=debug_mode
|
||||
)
|
||||
elif task.task_type == TaskType.code_comparison_retrieval:
|
||||
text_b = data["similar"][0]
|
||||
|
||||
result = self._gen_for_code_comparison_retrieval(
|
||||
task=task,
|
||||
text=text,
|
||||
text_b=text_b,
|
||||
examples=examples,
|
||||
debug_mode=debug_mode
|
||||
)
|
||||
elif task.task_type == TaskType.bug_desc_retrieval:
|
||||
result = self._gen_for_bug_desc_retrieval(
|
||||
task=task,
|
||||
text=text,
|
||||
examples=examples,
|
||||
debug_mode=debug_mode
|
||||
)
|
||||
elif task.task_type in [
|
||||
# cf - updated
|
||||
TaskType.code_issue_discussion_retrieval,
|
||||
TaskType.code_version_update_retrieval,
|
||||
TaskType.code_bug_fix_example_retrieval,
|
||||
]:
|
||||
result = self._gen_for_two_step_not_use_last(
|
||||
task=task,
|
||||
text=text,
|
||||
examples=examples,
|
||||
debug_mode=debug_mode,
|
||||
reverse_query_pos=False
|
||||
)
|
||||
elif task.task_type in [
|
||||
# cf - updated
|
||||
TaskType.code_refactoring_pattern_retrieval,
|
||||
TaskType.code_style_guideline_example_retrieval,
|
||||
TaskType.code_migration_retrieval,
|
||||
# jl - updated
|
||||
TaskType.code_optimization_hybrid_retrieval,
|
||||
TaskType.code_best_practices_retrieval,
|
||||
TaskType.security_vulnerability_fix_retrieval,
|
||||
]:
|
||||
result = self._gen_for_two_step_use_last(
|
||||
task=task,
|
||||
text=text,
|
||||
examples=examples,
|
||||
debug_mode=debug_mode,
|
||||
reverse_query_pos=False
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(f"Task type {task.task_type} not implemented.")
|
||||
elif task.task_type == TaskType.code_context_retrieval:
|
||||
text = data["text"]
|
||||
|
||||
result = self._gen_for_code_context_retrieval(
|
||||
task=task,
|
||||
text=text,
|
||||
**kwargs
|
||||
)
|
||||
# NOTE: no need to do quality control for code context retrieval task
|
||||
result_list.append(result)
|
||||
return result_list
|
||||
else:
|
||||
text = data["text"]
|
||||
|
||||
result = self._gen_for_normal_task(
|
||||
task=task,
|
||||
text=text,
|
||||
examples=examples,
|
||||
debug_mode=debug_mode,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
# print(gen_prompt)
|
||||
# print('================================================')
|
||||
qc_prompt = get_quality_control_prompt(
|
||||
task=task,
|
||||
query=result["query"],
|
||||
pos=result["pos"][0]
|
||||
)
|
||||
# print(qc_prompt)
|
||||
# print('*********************************************************************')
|
||||
response = self.chat(qc_prompt, **kwargs)[0]
|
||||
judge = clean_content(response)
|
||||
# print(response, judge)
|
||||
if "1" in judge:
|
||||
if debug_mode:
|
||||
result["judge"] = judge
|
||||
result["judge_response"] = response
|
||||
result_list.append(result)
|
||||
else:
|
||||
if debug_mode:
|
||||
result["judge"] = judge
|
||||
result["judge_response"] = response
|
||||
result_list.append(result)
|
||||
except Exception as e:
|
||||
warn(f"Error: {e}")
|
||||
|
||||
return result_list
|
||||
|
||||
def gen_hard_negatives(self, result: dict, task: Task, num_negatives: int = 7, **kwargs):
|
||||
gen_hard_neg_prompt = get_gen_hard_neg_prompt(
|
||||
task=task,
|
||||
query=result["query"],
|
||||
pos=result["pos"][0]
|
||||
)
|
||||
response_list = self.chat(gen_hard_neg_prompt, n=num_negatives, **kwargs)
|
||||
for response in response_list:
|
||||
if response is None:
|
||||
continue
|
||||
hard_neg = clean_content(response)
|
||||
result["neg"].append(hard_neg)
|
||||
result["neg"] = list(set(result["neg"]))
|
||||
return result
|
||||
|
||||
def run_single(
|
||||
self,
|
||||
data: dict,
|
||||
task: Task,
|
||||
examples_pool: Optional[List[dict]] = None,
|
||||
num_examples: int = 3,
|
||||
debug_mode: bool = False,
|
||||
gen_hard_neg: bool = False,
|
||||
num_negatives: int = 7,
|
||||
**kwargs
|
||||
):
|
||||
result_list = []
|
||||
|
||||
docid = compute_md5(data["text"])
|
||||
if self.cache_dir is not None:
|
||||
gen_data_cache_path = os.path.join(self.cache_dir, f"{docid}.json")
|
||||
if os.path.exists(gen_data_cache_path):
|
||||
with open(gen_data_cache_path, "r", encoding="utf-8") as f:
|
||||
result_list = json.load(f)
|
||||
|
||||
if len(result_list) > 0:
|
||||
if gen_hard_neg:
|
||||
for i in range(len(result_list)):
|
||||
if len(result_list[i]["neg"]) == 0:
|
||||
result_list[i] = self.gen_hard_negatives(
|
||||
result=result_list[i],
|
||||
task=task,
|
||||
num_negatives=num_negatives,
|
||||
**kwargs
|
||||
)
|
||||
# overwrite the cache file
|
||||
with open(gen_data_cache_path, "w", encoding="utf-8") as f:
|
||||
json.dump(result_list, f, indent=4, ensure_ascii=False)
|
||||
return result_list
|
||||
|
||||
triplets = self.generate_triplets(
|
||||
data,
|
||||
task=task,
|
||||
examples_pool=examples_pool,
|
||||
num_examples=num_examples,
|
||||
debug_mode=debug_mode,
|
||||
**kwargs
|
||||
)
|
||||
if len(triplets) == 0:
|
||||
return []
|
||||
|
||||
result = triplets[0]
|
||||
if debug_mode:
|
||||
result["docid"] = docid
|
||||
|
||||
if gen_hard_neg:
|
||||
result = self.gen_hard_negatives(
|
||||
result,
|
||||
task=task,
|
||||
num_negatives=num_negatives,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
result_list.append(result)
|
||||
|
||||
if self.cache_dir is not None:
|
||||
gen_data_cache_path = os.path.join(self.cache_dir, f"{docid}.json")
|
||||
with open(gen_data_cache_path, "w", encoding="utf-8") as f:
|
||||
json.dump(result_list, f, indent=4, ensure_ascii=False)
|
||||
|
||||
return result_list
|
||||
|
||||
def run(
|
||||
self,
|
||||
positives: List[dict],
|
||||
task_type: str,
|
||||
language: str = "en",
|
||||
code_language: str = "python",
|
||||
tgt_code_language: Optional[str] = None,
|
||||
examples_pool: Optional[List[dict]] = None,
|
||||
num_examples: int = 3,
|
||||
tqdm_desc: str = "Generating triplets",
|
||||
debug_mode: bool = False,
|
||||
gen_hard_neg: bool = False,
|
||||
num_negatives: int = 7,
|
||||
thread_count: int = 1,
|
||||
**kwargs
|
||||
):
|
||||
task = get_task(
|
||||
task_type=task_type,
|
||||
language=language,
|
||||
code_language=code_language,
|
||||
tgt_code_language=tgt_code_language
|
||||
)
|
||||
|
||||
result_list = []
|
||||
|
||||
def process_positive(positive):
|
||||
return self.run_single(
|
||||
data=positive,
|
||||
task=task,
|
||||
examples_pool=examples_pool,
|
||||
num_examples=num_examples,
|
||||
debug_mode=debug_mode,
|
||||
gen_hard_neg=gen_hard_neg,
|
||||
num_negatives=num_negatives,
|
||||
**kwargs
|
||||
)
|
||||
# Use thread pool for parallel processing with tqdm progress bar.
|
||||
with ThreadPoolExecutor(max_workers=thread_count) as executor:
|
||||
results = list(tqdm(executor.map(
|
||||
process_positive,
|
||||
positives
|
||||
), total=len(positives), desc=tqdm_desc))
|
||||
|
||||
# Collect results into result_list.
|
||||
for res in results:
|
||||
if isinstance(res, list):
|
||||
result_list.extend(res)
|
||||
else:
|
||||
result_list.append(res)
|
||||
# result_list.extend(results)
|
||||
|
||||
return result_list
|
||||
|
||||
def run_for_gen_neg(
|
||||
self,
|
||||
pairs: List[dict],
|
||||
task_type: str,
|
||||
language: str = "en",
|
||||
code_language: str = "python",
|
||||
tgt_code_language: Optional[str] = None,
|
||||
examples_pool: Optional[List[dict]] = None,
|
||||
num_examples: int = 3,
|
||||
tqdm_desc: str = "Generating triplets",
|
||||
debug_mode: bool = False,
|
||||
gen_hard_neg: bool = False,
|
||||
num_negatives: int = 7,
|
||||
thread_count: int = 1,
|
||||
**kwargs
|
||||
):
|
||||
task = get_task(
|
||||
task_type=task_type,
|
||||
language=language,
|
||||
code_language=code_language,
|
||||
tgt_code_language=tgt_code_language
|
||||
)
|
||||
|
||||
result_list = []
|
||||
|
||||
def gen_single_negative(pair):
|
||||
result = self.gen_hard_negatives(
|
||||
pair,
|
||||
task=task,
|
||||
num_negatives=num_negatives,
|
||||
**kwargs
|
||||
)
|
||||
return [result]
|
||||
|
||||
# Use thread pool for parallel processing with tqdm progress bar.
|
||||
with ThreadPoolExecutor(max_workers=thread_count) as executor:
|
||||
results = list(tqdm(executor.map(
|
||||
gen_single_negative,
|
||||
pairs
|
||||
), total=len(pairs), desc=tqdm_desc))
|
||||
|
||||
# Collect results into result_list.
|
||||
for res in results:
|
||||
if isinstance(res, list):
|
||||
result_list.extend(res)
|
||||
else:
|
||||
result_list.append(res)
|
||||
# result_list.extend(results)
|
||||
|
||||
return result_list
|
||||
@@ -0,0 +1,128 @@
|
||||
import re
|
||||
|
||||
|
||||
def clean_content(content: str):
|
||||
if content is None:
|
||||
raise ValueError("content is None.")
|
||||
|
||||
content = content.split('</think>')[-1].strip('\n').strip()
|
||||
|
||||
if content.startswith('\"') and content.endswith('\"'):
|
||||
content = content[1:-1]
|
||||
|
||||
if content.startswith("```\n") and content.endswith("\n```"):
|
||||
content = content[4:-4]
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def clean_code(code: str, lang: str, length_threshold: int = 30) -> str:
|
||||
cleaned_code = code.strip('\ufeff').strip()
|
||||
if not cleaned_code:
|
||||
return ''
|
||||
|
||||
def clean_empty_lines(text: str) -> str:
|
||||
return re.sub(r'\n\s*\n', '\n', text).strip()
|
||||
|
||||
# 各语言函数/类定义检测正则表达式
|
||||
function_patterns = {
|
||||
"java": r"(?m)^(?!\s*(import|package)\b).*\b(public\s+class|class\s+\w+|void\s+main|new\s+\w+\(|@Override)\b",
|
||||
"python": r"(?m)^(?!\s*(import|from\s+\S+\s+import)\b).*\b(def\s+\w+|class\s+\w+|=\s*\S+|if\s+[:\w]|print\s+)",
|
||||
"javascript": r"(?m)^(?!\s*(import|require\(|export\s)).*\b(function\s+\w+|const\s+\w+|=>|\(\)\s*=>|console\.log)",
|
||||
"php": r"(?m)^(?!\s*(include|require|use)\b).*\b(function\s+\w+|echo\s+\S+|class\s+\w+)",
|
||||
"ruby": r"(?m)^(?!\s*(require|load)\b).*\b(class\s+\w+|def\s+\w+|puts\s+\S+)",
|
||||
"go": r"(?m)^(?!\s*import\b).*\bfunc\s+main\s*\(|type\s+\w+\s+struct",
|
||||
"c#": r"(?m)^(?!\s*using\b).*\b(class\s+\w+|void\s+Main\s*\()",
|
||||
"cplusplus": r"(?m)^(?!#include\b).*\b(int\s+main\s*\(|class\s+\w+|void\s+\w+\s*\(.*\)\s*{)",
|
||||
"c": r"(?m)^(?!#include\b).*\b(int\s+main\s*\(|void\s+\w+\s*\(.*\)\s*{)",
|
||||
"rust": r"(?m)^(?!\s*use\b).*\b(fn\s+main\s*\(|struct\s+\w+|impl\s+\w+)",
|
||||
"typescript": r"(?m)^(?!\s*(import|require\(|export\s)).*\b(interface\s+\w+|class\s+\w+|function\s+\w+)",
|
||||
"perl": r"(?m)^(?!\s*(use|require)\b).*\b(sub\s+\w+|my\s+\$\w+|print\s+\S+)",
|
||||
"shell": r"(?m)^(?!\s*(source|\.)\s).*\b(function\s+\w+|if\s+\[|\$\(|echo\s+\S+)",
|
||||
"sql": r"(?i)\b(CREATE\s+TABLE|SELECT\s+\*|INSERT\s+INTO|UPDATE\s+\w+|DELETE\s+FROM)\b",
|
||||
"batchfile": r"(?m)^(?!\s*@?call\b).*\b(echo\s+\S+|set\s+\w+|if\s+.*\s+==\s+)",
|
||||
"fortran": r"(?mi)^(?!\s*use\b).*\b(program\s+\w+|subroutine\s+\w+|do\s+\d+\s*,\s*\d+)",
|
||||
"haskell": r"(?m)^(?!\s*import\b).*\b(main\s*=\s*do|data\s+\w+|putStrLn\s+\S+)",
|
||||
"lua": r"(?m)^(?!\s*require\b).*\b(function\s+\w+|local\s+\w+|print\s*\()",
|
||||
"powershell": r"(?m)^(?!\s*Import-Module\b).*\b(function\s+\w+|Write-Host\s+\S+|\$\w+\s*=)",
|
||||
"visual_basic": r"(?m)^(?!\s*Imports\b).*\b(Module\s+\w+|Sub\s+Main|Class\s+\w+)"
|
||||
}
|
||||
|
||||
# 各语言注释处理规则
|
||||
comment_patterns = {
|
||||
'java': (r'//.*?$|/\*.*?\*/|\\/\\/.*?$|\\/\*.*?\*\\/', re.DOTALL | re.MULTILINE),
|
||||
'python': (r'#.*?$', re.MULTILINE),
|
||||
'javascript': (r'//.*?$|/\*.*?\*/|\\/\\/.*?$|\\/\*.*?\*\\/', re.DOTALL | re.MULTILINE),
|
||||
'php': (r'//.*?$|#.*?$|/\*.*?\*/|\\/\\/.*?$|#.*?$|\\/\*.*?\*\\/', re.DOTALL | re.MULTILINE),
|
||||
'ruby': (r'#.*', re.MULTILINE),
|
||||
'go': (r'//.*?$|/\*.*?\*/|\\/\\/.*?$|\\/\*.*?\*\\/', re.DOTALL | re.MULTILINE),
|
||||
'csharp': (r'//.*?$|/\*.*?\*/|\\/\\/.*?$|\\/\*.*?\*\\/', re.DOTALL | re.MULTILINE),
|
||||
'cplusplus': (r'//.*?$|/\*.*?\*/|\\/\\/.*?$|\\/\*.*?\*\\/', re.DOTALL | re.MULTILINE),
|
||||
'c': (r'//.*?$|/\*.*?\*/|\\/\\/.*?$|\\/\*.*?\*\\/', re.DOTALL | re.MULTILINE),
|
||||
'rust': (r'//.*?$|/\*.*?\*/|\\/\\/.*?$|\\/\*.*?\*\\/', re.DOTALL | re.MULTILINE),
|
||||
'typescript': (r'//.*?$|/\*.*?\*/|\\/\\/.*?$|\\/\*.*?\*\\/', re.DOTALL | re.MULTILINE),
|
||||
'perl': (r'#.*', re.MULTILINE),
|
||||
'shell': (r'#.*', re.MULTILINE),
|
||||
'sql': (r'--.*?$|/\*.*?\*/', re.DOTALL),
|
||||
'batchfile': (r'^\s*(REM|@REM|::).*', re.MULTILINE | re.IGNORECASE),
|
||||
'fortran': (r'!.*', re.MULTILINE),
|
||||
'haskell': (r'--.*', re.MULTILINE),
|
||||
'lua': (r'--.*?$|--\[\[.*?\]\]', re.DOTALL),
|
||||
'powershell': (r'<#.*?#>|#.*', re.DOTALL),
|
||||
'visual_basic': (r"'.*", re.MULTILINE),
|
||||
}
|
||||
|
||||
# 执行注释清理
|
||||
if lang in comment_patterns:
|
||||
pattern, flags = comment_patterns[lang]
|
||||
cleaned_code = re.sub(pattern, '', cleaned_code, flags=flags)
|
||||
cleaned_code = clean_empty_lines(cleaned_code)
|
||||
|
||||
# 特殊语言处理规则
|
||||
if lang == 'fortran':
|
||||
cleaned_code = re.sub(r'^[Cc*].*', '', cleaned_code, flags=re.MULTILINE)
|
||||
elif lang == 'sql':
|
||||
cleaned_code = re.sub(r'/\*.*?\*/', '', cleaned_code, flags=re.DOTALL)
|
||||
elif lang == 'python':
|
||||
cleaned_code = re.sub(r'^\s*#.*', '', cleaned_code, flags=re.MULTILINE)
|
||||
|
||||
# 函数定义检测及内容验证
|
||||
def has_valid_code(text: str, lang: str) -> bool:
|
||||
pattern = function_patterns.get(lang)
|
||||
if not pattern:
|
||||
return len(text.strip()) > 0
|
||||
|
||||
# 增强检测逻辑
|
||||
if lang == 'batchfile':
|
||||
return bool(re.search(r'^\s*@?echo\b|:\w+', text, re.MULTILINE))
|
||||
if lang == 'shell':
|
||||
return bool(re.search(r'^\s*(if|for|while|case|echo|export|shopt|source)\b', text, re.MULTILINE))
|
||||
if lang == 'python':
|
||||
if re.search(r'^\s*(def|class)\s+\w+', text, re.MULTILINE):
|
||||
return bool(re.search(r'^\s+[^\s#]', text, re.MULTILINE))
|
||||
return False
|
||||
if lang == 'ruby':
|
||||
return bool(re.search(r'(def\s+\w+|class\s+\w+).*?\n\s+[^\s#]', text, re.MULTILINE))
|
||||
return bool(re.search(pattern, text, re.DOTALL | re.MULTILINE))
|
||||
|
||||
# 最终有效性检查
|
||||
if not has_valid_code(cleaned_code, lang):
|
||||
return ''
|
||||
|
||||
cleaned_code = cleaned_code.strip('\ufeff').strip()
|
||||
|
||||
if len(cleaned_code) < length_threshold:
|
||||
return ''
|
||||
|
||||
return cleaned_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_text = "\/\/ ----------------------------------------------------------------------\n\/\/ ----------------------------------------------------------------------\n\/\/\n\/\/ File: StrMaxProjection.h\n\/\/ Author: mgrosso \n\/\/ Created: Mon Jul 17 14:39:22 PDT 2006 on caliban\n\/\/ Project: \n\/\/ Purpose: \n\/\/ \n\/\/ $Id$\n\/\/ ----------------------------------------------------------------------\n\/\/ ----------------------------------------------------------------------\n\n#ifndef STRMAXPROJECTION_H\n#define STRMAXPROJECTION_H 1\n\n#include \"StrMinProjection.h\"\n\nclass StrMaxProjection : public StrMinProjection\n{\n public:\n StrMaxProjection(ExpressionPtr &operand);\n virtual ~StrMaxProjection();\n virtual AbstractProjectionPtr copy();\n\n protected:\n int compare(const char *lhs, const char *rhs);\n\n private:\n \/\/not implemented\n StrMaxProjection();\n StrMaxProjection( const StrMaxProjection &rhs );\n StrMaxProjection &operator=( const StrMaxProjection &rhs );\n};\n\n#endif \/* STRMAXPROJECTION_H *\/"
|
||||
|
||||
result = clean_code(test_text, "c", 200)
|
||||
print(result)
|
||||
|
||||
test_text = "\/**\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for\n * license information.\n *\n * Code generated by Microsoft (R) AutoRest Code Generator.\n *\/\n\npackage com.microsoft.azure.management.datafactory.v2018_06_01;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.fasterxml.jackson.annotation.JsonTypeInfo;\nimport com.fasterxml.jackson.annotation.JsonTypeName;\n\n\/**\n * The location of Google Cloud Storage dataset.\n *\/\n@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = \"type\", defaultImpl = GoogleCloudStorageLocation.class)\n@JsonTypeName(\"GoogleCloudStorageLocation\")\npublic class GoogleCloudStorageLocation extends DatasetLocation {\n \/**\n * Specify the bucketName of Google Cloud Storage. Type: string (or\n * Expression with resultType string).\n *\/\n @JsonProperty(value = \"bucketName\")\n private Object bucketName;\n\n \/**\n * Specify the version of Google Cloud Storage. Type: string (or Expression\n * with resultType string).\n *\/\n @JsonProperty(value = \"version\")\n private Object version;\n\n \/**\n * Get specify the bucketName of Google Cloud Storage. Type: string (or Expression with resultType string).\n *\n * @return the bucketName value\n *\/\n public Object bucketName() {\n return this.bucketName;\n }\n\n \/**\n * Set specify the bucketName of Google Cloud Storage. Type: string (or Expression with resultType string).\n *\n * @param bucketName the bucketName value to set\n * @return the GoogleCloudStorageLocation object itself.\n *\/\n public GoogleCloudStorageLocation withBucketName(Object bucketName) {\n this.bucketName = bucketName;\n return this;\n }\n\n \/**\n * Get specify the version of Google Cloud Storage. Type: string (or Expression with resultType string).\n *\n * @return the version value\n *\/\n public Object version() {\n return this.version;\n }\n\n \/**\n * Set specify the version of Google Cloud Storage. Type: string (or Expression with resultType string).\n *\n * @param version the version value to set\n * @return the GoogleCloudStorageLocation object itself.\n *\/\n public GoogleCloudStorageLocation withVersion(Object version) {\n this.version = version;\n return this;\n }\n\n}"
|
||||
result = clean_code(test_text, "java", 200)
|
||||
print(result)
|
||||
@@ -0,0 +1,22 @@
|
||||
cd ./code-rag-bench/retrieval/
|
||||
|
||||
output_dir='result'
|
||||
|
||||
for dataset_name in "humaneval" "mbpp" "repoeval" "ds1000_all_completion" "odex_en" "swe-bench-lite"
|
||||
do
|
||||
echo "dataset_name: ${dataset_name}"
|
||||
python main.py \
|
||||
--embedder_name_or_path BAAI/bge-code-v1 \
|
||||
--embedder_model_class decoder-only-base \
|
||||
--query_instruction_format_for_retrieval '<instruct>{}\n<query>{}' \
|
||||
--embedder_query_max_length 2048 \
|
||||
--embedder_passage_max_length 2048 \
|
||||
--trust_remote_code True \
|
||||
--pooling_method last_token \
|
||||
--embedder_batch_size 64 \
|
||||
--devices cuda:0 cuda:1 cuda:2 cuda:3 cuda:4 cuda:5 cuda:6 cuda:7 \
|
||||
--cache_dir ./cache \
|
||||
--dataset $dataset_name \
|
||||
--output_file ../../${output_dir}/${dataset_name}_output.json \
|
||||
--results_file ../../${output_dir}/${dataset_name}_results.json
|
||||
done
|
||||
@@ -0,0 +1,7 @@
|
||||
cd ./code-rag-bench/retrieval/
|
||||
|
||||
for dataset_name in "humaneval" "mbpp" "live_code_bench" "ds1000" "odex" "repoeval_repo" "swebench_repo"
|
||||
do
|
||||
echo "dataset_name: ${dataset_name}"
|
||||
PYTHONPATH=./ python create/${dataset_name}.py
|
||||
done
|
||||
@@ -0,0 +1,34 @@
|
||||
from typing import List
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from FlagEmbedding.abc.evaluation import (
|
||||
AbsEvalModelArgs as CodeRAGEvalModelArgs,
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class CodeRAGEvalArgs:
|
||||
dataset: str = field(
|
||||
default='humaneval',
|
||||
metadata={
|
||||
"help": "Task to evaluate. Default: humaneval. Available tasks: "
|
||||
"['humaneval', 'mbpp', 'live_code_bench', 'ds1000', 'odex', 'repoeval_repo', 'swebench_repo', 'code_search_net']",
|
||||
}
|
||||
)
|
||||
max_length: int = field(
|
||||
default=2048, metadata={"help": "Max length to use for evaluation."}
|
||||
)
|
||||
batch_size: int = field(
|
||||
default=64, metadata={"help": "Batch size for evaluation."}
|
||||
)
|
||||
output_file: str = field(
|
||||
default="outputs.json",
|
||||
metadata={
|
||||
"help": "Specify the filepath if you want to save the retrieval (evaluation) results."
|
||||
},
|
||||
)
|
||||
results_file: str = field(
|
||||
default="results.json",
|
||||
metadata={
|
||||
"help": "Specify the filepath if you want to save the retrieval results."
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
import argparse
|
||||
import datasets
|
||||
import os
|
||||
from tqdm import tqdm
|
||||
import random
|
||||
from create.utils import save_tsv_dict, save_file_jsonl, load_jsonlines
|
||||
|
||||
def document2code(data, split="train"):
|
||||
data = data[split]
|
||||
code_search_net_data_queries = []
|
||||
code_search_net_data_docs = []
|
||||
code_search_net_data_qrels = []
|
||||
|
||||
for item in tqdm(data):
|
||||
doc = item["func_documentation_string"]
|
||||
code = item["func_code_string"]
|
||||
doc_id = "{repository_name}_{func_path_in_repository}_{func_name}_doc".format_map(item)
|
||||
code_id = "{repository_name}_{func_path_in_repository}_{func_name}_code".format_map(item)
|
||||
code_search_net_data_queries.append({"_id": doc_id, "text": doc, "metadata": {}})
|
||||
code_search_net_data_docs.append({"_id": code_id, "title": item["func_name"], "text": code, "metadata": {}})
|
||||
code_search_net_data_qrels.append({"query-id": doc_id, "corpus-id": code_id, "score": 1})
|
||||
|
||||
return code_search_net_data_queries, code_search_net_data_docs, code_search_net_data_qrels
|
||||
|
||||
def main():
|
||||
#### /print debug information to stdout
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--language", type=str, default="python", help="codesearch net language")
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
|
||||
args = parser.parse_args()
|
||||
dataset = datasets.load_dataset("code_search_net", args.language)
|
||||
|
||||
path = os.path.join(args.output_dir, "code_search_net_{}".format(args.language))
|
||||
os.makedirs(path)
|
||||
os.makedirs(os.path.join(path, "qrels"))
|
||||
|
||||
docs = []
|
||||
queries = []
|
||||
for split in ["train", "validation", "test"]:
|
||||
queries_split, docs_split, qrels_split = document2code(dataset, split)
|
||||
docs += docs_split
|
||||
queries += queries_split
|
||||
|
||||
save_tsv_dict(qrels_split, os.path.join(path, "qrels", "{}.tsv".format(split)), ["query-id", "corpus-id", "score"])
|
||||
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(docs, os.path.join(path, "corpus.jsonl"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,123 @@
|
||||
import io
|
||||
import os
|
||||
import fcntl
|
||||
import pathlib
|
||||
import zipfile
|
||||
import argparse
|
||||
import requests
|
||||
import warnings
|
||||
import itertools
|
||||
from tqdm import tqdm
|
||||
from datasets import load_dataset
|
||||
from create.utils import save_tsv_dict, save_file_jsonl
|
||||
|
||||
|
||||
# Load dataset
|
||||
def download_source(source_dir):
|
||||
src = source_dir / "ds1000.py"
|
||||
url = "https://github.com/HKUNLP/DS-1000/blob/49c1c543ada8b58138181333cdc62e613204efcf/ds1000.py?raw=true"
|
||||
lock = src.with_suffix(".lock")
|
||||
with open(lock, "w") as f_lock:
|
||||
fcntl.flock(f_lock, fcntl.LOCK_EX)
|
||||
if not src.exists():
|
||||
warnings.warn(f"DS-1000 source is being saved to {src}.")
|
||||
print("Downloading source code...")
|
||||
r = requests.get(url, stream=True)
|
||||
with open(src, "wb") as f_src:
|
||||
f_src.write(r.content)
|
||||
open(src.parent / "__init__.py", "w").close()
|
||||
print("Done.")
|
||||
fcntl.flock(f_lock, fcntl.LOCK_UN)
|
||||
|
||||
def download_dataset(source_dir):
|
||||
path = source_dir / "ds1000_data"
|
||||
url = "https://github.com/HKUNLP/DS-1000/blob/49c1c543ada8b58138181333cdc62e613204efcf/ds1000_data.zip?raw=true"
|
||||
lock = path.with_suffix(".lock")
|
||||
with open(lock, "w") as f_lock:
|
||||
fcntl.flock(f_lock, fcntl.LOCK_EX)
|
||||
if not path.exists():
|
||||
warnings.warn(f"DS-1000 data is being saved to {path}.")
|
||||
print("Downloading dataset...")
|
||||
r = requests.get(url, stream=True)
|
||||
z = zipfile.ZipFile(io.BytesIO(r.content))
|
||||
z.extractall(source_dir)
|
||||
print("Done.")
|
||||
fcntl.flock(f_lock, fcntl.LOCK_UN)
|
||||
|
||||
def get_dataset(source_dir, mode: str = "Completion", key: str = "All"):
|
||||
"""Returns dataset for the task or an iterable of any object, that get_prompt can handle"""
|
||||
from ds.ds1000 import DS1000Dataset
|
||||
|
||||
data = DS1000Dataset(source_dir / "ds1000_data", mode=mode).data
|
||||
if key == "All":
|
||||
if mode == "Insertion":
|
||||
warnings.warn(
|
||||
"Insertion not supported for Matplotlib. Only running others."
|
||||
)
|
||||
data = {k: v for k, v in data.items() if k != "Matplotlib"}
|
||||
dataset = list(itertools.chain(*data.values()))
|
||||
else:
|
||||
dataset = data[key]
|
||||
return dataset
|
||||
|
||||
|
||||
# Collect queries, docs, and relations
|
||||
def document2code(data: list):
|
||||
queries, docs, qrels = [], [], []
|
||||
|
||||
# collect doc corpus
|
||||
code_docs = load_dataset("neulab/docprompting-conala", "docs")["train"]
|
||||
for i in range(len(code_docs)):
|
||||
docs.append({
|
||||
"_id": str(i),
|
||||
"title": code_docs[i]["doc_id"],
|
||||
"text": code_docs[i]["doc_content"],
|
||||
"metadata": {}
|
||||
})
|
||||
|
||||
# load canonical docs
|
||||
ds1000 = load_dataset("json", data_files={"test": args.canonical_file})["test"]
|
||||
for idx,item in enumerate(tqdm(data)):
|
||||
example = item.data
|
||||
query = example["prompt"]
|
||||
query_id = f"{example['lib']}_{example['perturbation_origin_id']}"
|
||||
queries.append({"_id": query_id, "text": query, "metadata": {}})
|
||||
|
||||
doc_ids = [doc["title"] for doc in ds1000[idx]["docs"]]
|
||||
for doc_id in doc_ids:
|
||||
corpus_id = code_docs["doc_id"].index(doc_id)
|
||||
corpus_id = str(corpus_id)
|
||||
qrels.append({"query-id": query_id, "corpus-id": corpus_id, "score": 1})
|
||||
|
||||
return queries, docs, qrels
|
||||
|
||||
|
||||
def main():
|
||||
args.source_dir = pathlib.Path(__file__).parent.parent / args.source_dir
|
||||
os.makedirs(args.source_dir, exist_ok=True)
|
||||
download_source(args.source_dir)
|
||||
download_dataset(args.source_dir)
|
||||
dataset = get_dataset(args.source_dir, mode=args.mode, key=args.key)
|
||||
|
||||
path = os.path.join(args.output_dir, f"ds1000_{args.key.lower()}_{args.mode.lower()}")
|
||||
os.makedirs(path, exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "qrels"), exist_ok=True)
|
||||
|
||||
queries, docs, qrels = document2code(dataset)
|
||||
save_tsv_dict(qrels, os.path.join(path, "qrels", "test.tsv"), ["query-id", "corpus-id", "score"])
|
||||
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(docs, os.path.join(path, "corpus.jsonl"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--source_dir", type=str, default="ds")
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
parser.add_argument("--mode", type=str, default="Completion", choices=["Completion", "Insertion"])
|
||||
parser.add_argument("--key", type=str, default="All",
|
||||
choices=["All", "Numpy", "Pandas", "Scipy", "Matplotlib", "Sklearn", "Tensorflow", "Pytorch"])
|
||||
parser.add_argument("--canonical_file", type=str, default="datasets/canonical/ds1000_docs.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Aggregate all code-generation datasets."""
|
||||
|
||||
import os
|
||||
import json
|
||||
import datasets
|
||||
import argparse
|
||||
from create.utils import save_tsv_dict
|
||||
from create.humaneval import document2code as d2c_humaneval
|
||||
from create.mbpp import document2code as d2c_mbpp
|
||||
|
||||
D2C_FUNC_DICT = {
|
||||
"humaneval": d2c_humaneval,
|
||||
"mbpp": d2c_mbpp,
|
||||
}
|
||||
SPLIT_DICT = {
|
||||
"humaneval": ["test"],
|
||||
"mbpp": ["train", "test", "validation", "prompt"],
|
||||
}
|
||||
HF_NAME_DICT = {
|
||||
"humaneval": "openai_humaneval",
|
||||
"mbpp": "mbpp",
|
||||
}
|
||||
|
||||
|
||||
def save_file_jsonl(data, path):
|
||||
with open(path,'w') as fw:
|
||||
for item in data:
|
||||
fw.write(json.dumps(item) + '\n')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset_names", type=str, nargs='+', default=["humaneval", "mbpp"])
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
parser.add_argument("--output_name", type=str, default="general-programming")
|
||||
args = parser.parse_args()
|
||||
|
||||
path = os.path.join(args.output_dir, args.output_name)
|
||||
os.makedirs(path)
|
||||
os.makedirs(os.path.join(path, "qrels"), exist_ok=True)
|
||||
|
||||
split_dict = {}
|
||||
for dataset_name in args.dataset_names:
|
||||
for split in SPLIT_DICT[dataset_name]:
|
||||
if split not in split_dict:
|
||||
split_dict[split] = []
|
||||
split_dict[split].append(dataset_name)
|
||||
|
||||
dataset_dict = {
|
||||
dataset_name: datasets.load_dataset(HF_NAME_DICT[dataset_name])
|
||||
for dataset_name in args.dataset_names
|
||||
}
|
||||
docs, queries = [], []
|
||||
for split, ds_names in split_dict.items():
|
||||
for ds in ds_names:
|
||||
dataset = dataset_dict[ds]
|
||||
|
||||
queries_split, docs_split, qrels_split = D2C_FUNC_DICT[ds](dataset, split)
|
||||
docs += docs_split
|
||||
queries += queries_split
|
||||
|
||||
qrels_path = os.path.join(path, "qrels", f"{split}.tsv")
|
||||
save_tsv_dict(qrels_split, qrels_path, ["query-id", "corpus-id", "score"])
|
||||
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(docs, os.path.join(path, "corpus.jsonl"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,58 @@
|
||||
import os
|
||||
import argparse
|
||||
import datasets
|
||||
from tqdm import tqdm
|
||||
from create.utils import save_tsv_dict, save_file_jsonl
|
||||
|
||||
|
||||
def document2code(data, split="test"):
|
||||
data = data[split]
|
||||
queries, docs, qrels = [], [], []
|
||||
|
||||
for item in tqdm(data):
|
||||
doc = item["prompt"]
|
||||
code = item["prompt"] + '\n' + item["canonical_solution"]
|
||||
doc_id = "{task_id}_doc".format_map(item)
|
||||
code_id = "{task_id}_code".format_map(item)
|
||||
|
||||
queries.append({"_id": doc_id, "text": doc, "metadata": {}})
|
||||
docs.append({"_id": code_id, "title": item["entry_point"], "text": code, "metadata": {}})
|
||||
qrels.append({"query-id": doc_id, "corpus-id": code_id, "score": 1})
|
||||
|
||||
return queries, docs, qrels
|
||||
|
||||
|
||||
def main():
|
||||
dataset = datasets.load_dataset(args.dataset_name)
|
||||
|
||||
path = os.path.join(args.output_dir, args.output_name)
|
||||
os.makedirs(path, exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "qrels"), exist_ok=True)
|
||||
|
||||
queries, docs, qrels = document2code(dataset, split="test")
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(docs, os.path.join(path, "corpus.jsonl"))
|
||||
qrels_path = os.path.join(path, "qrels", "test.tsv")
|
||||
save_tsv_dict(qrels, qrels_path, ["query-id", "corpus-id", "score"])
|
||||
|
||||
# create canonical file if not existent yet
|
||||
if not os.path.exists(args.canonical_file):
|
||||
canonical_solutions = []
|
||||
for doc in docs:
|
||||
canonical_solutions.append([{
|
||||
"text": doc["text"], "title": doc["title"]
|
||||
}])
|
||||
canonical_dataset = dataset["test"].add_column("docs", canonical_solutions)
|
||||
canonical_dataset.to_json(args.canonical_file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset_name", type=str, default="openai_humaneval")
|
||||
parser.add_argument("--output_name", type=str, default="humaneval")
|
||||
parser.add_argument("--canonical_file", type=str,
|
||||
default="datasets/canonical/humaneval_solutions.json")
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
args = parser.parse_args()
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
import os
|
||||
import argparse
|
||||
import datasets
|
||||
from tqdm import tqdm
|
||||
from datasets import load_dataset
|
||||
from create.utils import save_tsv_dict, save_file_jsonl
|
||||
|
||||
|
||||
def get_queries(data, split="test") -> list[dict]:
|
||||
queries = [{
|
||||
"_id": item["question_id"] + '__' + item["contest_id"],
|
||||
"text": item["question_content"],
|
||||
"metadata": {}
|
||||
} for item in data[split]]
|
||||
return queries
|
||||
|
||||
def get_corpus(hf_name: str, cache_dir: str) -> list[dict]:
|
||||
dataset = load_dataset(hf_name, cache_dir=cache_dir)["train"]
|
||||
corpus = [
|
||||
{"_id": i, "text": item["text"], "title": item["title"]}
|
||||
for i,item in enumerate(dataset)
|
||||
]
|
||||
return corpus
|
||||
|
||||
|
||||
def main():
|
||||
dataset = datasets.load_dataset(args.dataset_name, cache_dir=args.cache_dir)
|
||||
|
||||
path = os.path.join(args.output_dir, args.output_name)
|
||||
os.makedirs(path, exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "qrels"), exist_ok=True)
|
||||
|
||||
queries = get_queries(dataset, split="test")
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
|
||||
docs = get_corpus(args.corpus_name, args.cache_dir)
|
||||
save_file_jsonl(docs, os.path.join(path, "corpus.jsonl"))
|
||||
|
||||
qrels = [] # no ground-truth solutions
|
||||
qrels_path = os.path.join(path, "qrels", "test.tsv")
|
||||
save_tsv_dict(qrels, qrels_path, ["query-id", "corpus-id", "score"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset_name", type=str, default="livecodebench/code_generation")
|
||||
parser.add_argument("--corpus_name", type=str, default="code-rag-bench/programming-solutions")
|
||||
parser.add_argument("--cache_dir", type=str, default="/scratch/zhiruow/data")
|
||||
parser.add_argument("--output_name", type=str, default="livecodebench")
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
args = parser.parse_args()
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,78 @@
|
||||
import os
|
||||
import argparse
|
||||
import datasets
|
||||
from tqdm import tqdm
|
||||
from create.utils import save_tsv_dict, save_file_jsonl
|
||||
|
||||
|
||||
def get_function_name(code: str) -> str:
|
||||
"""Parse the function name for a code snippet string."""
|
||||
lines = code.split('\n')
|
||||
for line in lines:
|
||||
if line.lstrip().startswith("def "):
|
||||
break
|
||||
func_name = line.lstrip()[4: ]
|
||||
func_name = func_name.split('(')[0]
|
||||
return func_name
|
||||
|
||||
|
||||
def document2code(data, split="test"):
|
||||
data = data[split]
|
||||
queries, docs, qrels = [], [], []
|
||||
|
||||
for item in tqdm(data):
|
||||
doc = item["text"]
|
||||
code = "# " + item["text"] + '\n' + item["code"]
|
||||
doc_id = "{task_id}_doc".format_map(item)
|
||||
code_id = "{task_id}_code".format_map(item)
|
||||
|
||||
queries.append({"_id": doc_id, "text": doc, "metadata": {}})
|
||||
docs.append({"_id": code_id, "title": get_function_name(item["code"]), "text": code, "metadata": {}})
|
||||
qrels.append({"query-id": doc_id, "corpus-id": code_id, "score": 1})
|
||||
|
||||
return queries, docs, qrels
|
||||
|
||||
|
||||
def main():
|
||||
dataset = datasets.load_dataset(args.dataset_name)
|
||||
|
||||
path = os.path.join(args.output_dir, args.output_name)
|
||||
os.makedirs(path, exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "qrels"), exist_ok=True)
|
||||
|
||||
docs, queries = [], []
|
||||
for split in args.splits:
|
||||
queries_split, docs_split, qrels_split = document2code(dataset, split)
|
||||
docs += docs_split
|
||||
queries += queries_split
|
||||
|
||||
qrels_path = os.path.join(path, "qrels", f"{split}.tsv")
|
||||
save_tsv_dict(qrels_split, qrels_path, ["query-id", "corpus-id", "score"])
|
||||
|
||||
# create canonical file for test split if not existent yet
|
||||
if split == "test" and (not os.path.exists(args.canonical_file)):
|
||||
canonical_solutions = []
|
||||
for doc in docs_split:
|
||||
canonical_solutions.append([{
|
||||
"text": doc["text"], "title": doc["title"]
|
||||
}])
|
||||
canonical_dataset = dataset["test"].add_column("docs", canonical_solutions)
|
||||
canonical_dataset.to_json(args.canonical_file)
|
||||
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(docs, os.path.join(path, "corpus.jsonl"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset_name", type=str, default="google-research-datasets/mbpp")
|
||||
# parser.add_argument("--dataset_name", type=str, default="code-rag-bench/mbpp")
|
||||
parser.add_argument("--splits", type=str, default=["train", "validation", "test"],
|
||||
choices=["train", "validation", "test", "prompt"])
|
||||
parser.add_argument("--output_name", type=str, default="mbpp")
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
parser.add_argument("--canonical_file", type=str,
|
||||
default="datasets/canonical/mbpp_solutions.json")
|
||||
|
||||
args = parser.parse_args()
|
||||
main()
|
||||
@@ -0,0 +1,76 @@
|
||||
import os
|
||||
import re
|
||||
import random
|
||||
import argparse
|
||||
import datasets
|
||||
from tqdm import tqdm
|
||||
from collections import Counter
|
||||
from datasets import load_dataset
|
||||
from create.utils import save_tsv_dict, save_file_jsonl
|
||||
|
||||
|
||||
def document2code(data, split="test"):
|
||||
data = data[split]
|
||||
queries, docs, qrels = [], [], []
|
||||
|
||||
# build doc corpus
|
||||
code_docs = load_dataset("neulab/docprompting-conala", "docs")["train"]
|
||||
for i in range(len(code_docs)):
|
||||
docs.append({
|
||||
"_id": str(i),
|
||||
"title": code_docs[i]["doc_id"],
|
||||
"text": code_docs[i]["doc_content"],
|
||||
"metadata": {}
|
||||
})
|
||||
|
||||
# load canonical docs
|
||||
odex = load_dataset("json", data_files={"test": args.canonical_file})["test"]
|
||||
# collect queries and query-doc matching
|
||||
for idx,item in enumerate(tqdm(data)):
|
||||
query = item["intent"]
|
||||
query_id = f"{idx}_{item['task_id']}"
|
||||
queries.append({"_id": query_id, "text": query, "metadata": {}})
|
||||
|
||||
doc_ids = [doc["title"] for doc in odex[idx]["docs"]]
|
||||
for doc_id in doc_ids:
|
||||
corpus_id = code_docs["doc_id"].index(doc_id)
|
||||
corpus_id = str(corpus_id)
|
||||
qrels.append({"query-id": query_id, "corpus-id": corpus_id, "score": 1})
|
||||
|
||||
return queries, docs, qrels
|
||||
|
||||
|
||||
def main():
|
||||
if '_' in args.dataset_name:
|
||||
dataset_name = args.dataset_name.split('_')[0]
|
||||
language = args.dataset_name.split('_')[1]
|
||||
else:
|
||||
dataset_name = args.dataset_name
|
||||
language = 'en'
|
||||
dataset = datasets.load_dataset(dataset_name, language) # english version by default
|
||||
|
||||
path = os.path.join(args.output_dir, args.output_name.replace('en', language))
|
||||
os.makedirs(path, exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "qrels"), exist_ok=True)
|
||||
|
||||
docs, queries = [], []
|
||||
for split in ["test"]:
|
||||
queries_split, docs_split, qrels_split = document2code(dataset, split)
|
||||
docs += docs_split
|
||||
queries += queries_split
|
||||
|
||||
save_tsv_dict(qrels_split, os.path.join(path, "qrels", "{}.tsv".format(split)), ["query-id", "corpus-id", "score"])
|
||||
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(docs, os.path.join(path, "corpus.jsonl"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset_name", type=str, default="neulab/odex")
|
||||
parser.add_argument("--output_name", type=str, default="odex_en")
|
||||
parser.add_argument("--canonical_file", type=str, default="datasets/canonical/odex_docs.json")
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
args = parser.parse_args()
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,306 @@
|
||||
import io
|
||||
import os
|
||||
import glob
|
||||
import json
|
||||
import argparse
|
||||
import requests
|
||||
import zipfile
|
||||
from collections import defaultdict
|
||||
from create.utils import save_tsv_dict, save_file_jsonl
|
||||
|
||||
REPOs_line_and_api = [
|
||||
'huggingface_diffusers',
|
||||
'nerfstudio-project_nerfstudio',
|
||||
'awslabs_fortuna',
|
||||
'huggingface_evaluate',
|
||||
'google_vizier',
|
||||
'alibaba_FederatedScope',
|
||||
'pytorch_rl',
|
||||
'opendilab_ACE',
|
||||
]
|
||||
|
||||
REPOs_function = [
|
||||
"amazon-science_patchcore-inspection",
|
||||
"deepmind_tracr",
|
||||
"facebookresearch_omnivore",
|
||||
"google_lightweight_mmm",
|
||||
"lucidrains_imagen-pytorch",
|
||||
"maxhumber_redframes",
|
||||
]
|
||||
|
||||
REPO_DIRs = {
|
||||
"api": "repositories/line_and_api_level",
|
||||
"line": "repositories/line_and_api_level",
|
||||
"function": "repositories/function_level",
|
||||
}
|
||||
|
||||
|
||||
def iterate_repository(base_dir: str, repo: str) -> dict:
|
||||
pattern = os.path.join(f'{base_dir}/{repo}', "**", "*.py")
|
||||
files = glob.glob(pattern, recursive=True)
|
||||
|
||||
skipped_files = []
|
||||
loaded_code_files = dict()
|
||||
base_dir_list = os.path.normpath(base_dir).split(os.sep)
|
||||
for fname in files:
|
||||
try:
|
||||
code = open(fname, 'r', encoding='utf8').read()
|
||||
fpath_tuple = tuple(os.path.normpath(fname).split(os.sep)[len(base_dir_list):])
|
||||
loaded_code_files[fpath_tuple]= code
|
||||
except Exception as e:
|
||||
skipped_files.append((fname, e))
|
||||
continue
|
||||
|
||||
if len(skipped_files) > 0:
|
||||
print(f"Skipped {len(skipped_files)} out of {len(files)} files due to I/O errors")
|
||||
for fname, e in skipped_files:
|
||||
print(f"{fname}: {e}")
|
||||
return loaded_code_files
|
||||
|
||||
|
||||
def window_overlap(span: tuple, target_span: tuple) -> bool:
|
||||
if span[0] >= target_span[1] or span[1] <= target_span[0]:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class RepoWindowMaker:
|
||||
def __init__(self, base_dir, repo, tasks, window_size, slice_size):
|
||||
self.base_dir = base_dir
|
||||
self.repo = repo
|
||||
self.window_size = window_size
|
||||
self.slice_size = slice_size
|
||||
self.slice_step = 1 if window_size // slice_size == 0 else window_size // slice_size
|
||||
self.tasks = tasks
|
||||
self.source_code_files = iterate_repository(base_dir, repo)
|
||||
|
||||
def _buid_windows_for_a_file(self, fpath_tuple, code):
|
||||
code_windows = []
|
||||
code_lines = code.splitlines()
|
||||
delta_size = self.window_size // 2
|
||||
for line_no in range(0, len(code_lines), self.slice_step): # line_no starts from 0
|
||||
start_line_no = max(0, line_no - delta_size)
|
||||
end_line_no = min(len(code_lines), line_no + self.window_size - delta_size)
|
||||
window_lines = [i for i in code_lines[start_line_no:end_line_no]]
|
||||
if not window_lines: # all empty lines
|
||||
continue
|
||||
window_text = '\n'.join(window_lines)
|
||||
code_windows.append({
|
||||
'context': window_text,
|
||||
'metadata': {
|
||||
'fpath_tuple': fpath_tuple,
|
||||
'line_no': line_no,
|
||||
'start_line_no': start_line_no,
|
||||
'end_line_no': end_line_no,
|
||||
'window_size': self.window_size,
|
||||
'repo': self.repo,
|
||||
'slice_size': self.slice_size,
|
||||
}
|
||||
})
|
||||
return code_windows
|
||||
|
||||
def _merge_windows_with_same_context(self, code_windows):
|
||||
merged_code_windows = defaultdict(list)
|
||||
for code_window in code_windows:
|
||||
context = code_window['context']
|
||||
metadata = code_window['metadata']
|
||||
merged_code_windows[context].append(metadata)
|
||||
json_lines = []
|
||||
for context, metadata_list in merged_code_windows.items():
|
||||
json_lines.append({
|
||||
'context': context,
|
||||
'metadata': metadata_list
|
||||
})
|
||||
return json_lines
|
||||
|
||||
def build_windows(self):
|
||||
all_code_windows = []
|
||||
for fpath_tuple, code in self.source_code_files.items():
|
||||
all_code_windows += self._buid_windows_for_a_file(fpath_tuple, code)
|
||||
merged_code_windows = self._merge_windows_with_same_context(all_code_windows)
|
||||
print(f'build {len(merged_code_windows)} windows for {self.repo} with window size {self.window_size} and slice {self.slice_size}')
|
||||
ground_truth_indices = {}
|
||||
for task in self.tasks:
|
||||
fpath_tuple = tuple(task['metadata']['fpath_tuple'])
|
||||
line_no = task['metadata']['line_no']
|
||||
start_line_no = task['metadata']['context_start_lineno']
|
||||
for i, window in enumerate(merged_code_windows):
|
||||
if window["metadata"][0]["fpath_tuple"] != fpath_tuple:
|
||||
continue
|
||||
if any([
|
||||
window_overlap(
|
||||
(sub_window["start_line_no"], sub_window["end_line_no"]),
|
||||
(start_line_no, line_no + 1)
|
||||
)
|
||||
for sub_window in window["metadata"]
|
||||
]):
|
||||
if i not in ground_truth_indices:
|
||||
ground_truth_indices[i] = []
|
||||
ground_truth_indices[i].append(task["metadata"]["task_id"])
|
||||
|
||||
return merged_code_windows, ground_truth_indices
|
||||
|
||||
|
||||
def download_data(directory: str = "repoeval"):
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
|
||||
datasets_dir = os.path.join(directory, "datasets")
|
||||
repos_lineapi_dir = os.path.join(directory, "repositories", "line_and_api_level")
|
||||
repos_function_dir = os.path.join(directory, "repositories", "function_level")
|
||||
|
||||
print(f"Start downloading the necessary `datasets` and `repositories` files.")
|
||||
if not os.path.exists(datasets_dir):
|
||||
print(f"Start downloading the `datasets`.")
|
||||
datasets_url = "https://github.com/microsoft/CodeT/raw/main/RepoCoder/datasets/datasets.zip"
|
||||
r = requests.get(datasets_url, stream=True)
|
||||
z = zipfile.ZipFile(io.BytesIO(r.content))
|
||||
z.extractall(datasets_dir)
|
||||
print("Finished downloading the `datasets` files.")
|
||||
|
||||
if not os.path.exists(repos_lineapi_dir):
|
||||
print(f"Start downloading the `repositories` (line_and_api).")
|
||||
repos_lineapi_url = "https://github.com/microsoft/CodeT/raw/main/RepoCoder/repositories/line_and_api_level.zip"
|
||||
r = requests.get(repos_lineapi_url, stream=True)
|
||||
z = zipfile.ZipFile(io.BytesIO(r.content))
|
||||
z.extractall(repos_lineapi_dir)
|
||||
|
||||
if not os.path.exists(repos_function_dir):
|
||||
print(f"Start downloading the `repositories` (function).")
|
||||
# repos_function_url = "https://github.com/microsoft/CodeT/raw/main/RepoCoder/repositories/function_level.zip"
|
||||
repos_function_url = "https://github.com/Veronicium/repoeval_debug/raw/main/function_level.zip"
|
||||
r = requests.get(repos_function_url, stream=True)
|
||||
z = zipfile.ZipFile(io.BytesIO(r.content))
|
||||
z.extractall(repos_function_dir)
|
||||
print("Finished downloading the `repositories` files.")
|
||||
|
||||
|
||||
def repo2code(
|
||||
repo: str, data_cache_dir: str,
|
||||
split: str, context_length: str,
|
||||
window_size: int, slice_size: int
|
||||
):
|
||||
# load test examples
|
||||
file_name = f"{split}_level_completion_{context_length}_context_codex.test.jsonl"
|
||||
if split == 'function':
|
||||
file_name = file_name.replace('.test.jsonl', '.test.clean.jsonl')
|
||||
|
||||
task_path = os.path.join(data_cache_dir, "datasets", file_name)
|
||||
tasks = [json.loads(l.rstrip()) for l in open(task_path, 'r')]
|
||||
tasks = [task for task in tasks if repo == task['metadata']['task_id'].split('/')[0]]
|
||||
|
||||
# collect queries
|
||||
queries = []
|
||||
for task in tasks:
|
||||
query_id = task["metadata"]["task_id"]
|
||||
# text = '\n'.join(task["prompt"].split('\n')[-2:])
|
||||
text = task["prompt"]
|
||||
metadata = task["metadata"]
|
||||
queries.append({"_id": query_id, "text": text, "metadata": metadata})
|
||||
|
||||
base_dir = os.path.join(data_cache_dir, REPO_DIRs[split])
|
||||
repo_window_maker = RepoWindowMaker(base_dir, repo, tasks, window_size, slice_size)
|
||||
windows, ground_truth_indices = repo_window_maker.build_windows()
|
||||
corpus, qrels = [], []
|
||||
query_id2gt = {task['metadata']['task_id']:[] for task in tasks}
|
||||
for i, window in enumerate(windows):
|
||||
path = '-'.join(window["metadata"][0]["fpath_tuple"])
|
||||
line = f"{window['metadata'][0]['start_line_no']}-{window['metadata'][-1]['end_line_no']}"
|
||||
corpus_id = f"{repo}_{path}_{line}"
|
||||
corpus.append({
|
||||
"_id": corpus_id, "title": path,
|
||||
"text": window["context"], "metadata": window["metadata"]
|
||||
})
|
||||
if i in ground_truth_indices:
|
||||
for query_id in ground_truth_indices[i]:
|
||||
qrels.append({"query-id": query_id, "corpus-id": corpus_id, "score": 1})
|
||||
query_id2gt[query_id].append({"title": corpus_id.replace('_', '/'), "text": window["context"]})
|
||||
|
||||
return queries, corpus, qrels, query_id2gt
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
parser.add_argument("--results_dir", type=str, default="results")
|
||||
parser.add_argument("--split", type=str, required=True, choices=["api", "line", "function"])
|
||||
parser.add_argument("--context_length", type=str, default="1k", choices=["1k", "2k", "4k"])
|
||||
parser.add_argument("--data_cache_dir", type=str, default="output/repoeval")
|
||||
parser.add_argument("--window_size", type=int, default=20)
|
||||
parser.add_argument("--slice_size", type=int, default=2)
|
||||
args = parser.parse_args()
|
||||
|
||||
download_data(args.data_cache_dir)
|
||||
|
||||
path = os.path.join(args.output_dir, "repoeval", args.split)
|
||||
os.makedirs(path, exist_ok=True)
|
||||
REPOs = REPOs_function if args.split == "function" else REPOs_line_and_api
|
||||
|
||||
file_name = f"{args.split}_level_completion_{args.context_length}_context_codex.test.jsonl"
|
||||
data_path = os.path.join(args.data_cache_dir, "datasets", file_name)
|
||||
data = [json.loads(l.rstrip()) for l in open(data_path, 'r')]
|
||||
|
||||
# preprocess function completion data (the data in the RepoCoder repo isn't correctly formatted)
|
||||
if args.split == 'function':
|
||||
repo2idx = {}
|
||||
for task in data:
|
||||
repo = task['metadata']['task_id'].replace('--', '_').split('/')[0]
|
||||
if repo not in repo2idx:
|
||||
repo2idx[repo] = 0
|
||||
task['metadata']['task_id'] = task['metadata']['task_id'].replace('--', '_').replace('idx', str(repo2idx[repo]))
|
||||
task['metadata']['line_no'] = task['metadata']['lineno']
|
||||
repo2idx[repo] += 1
|
||||
|
||||
new_data_path = data_path.replace('.test.jsonl', '.test.clean.jsonl')
|
||||
with open(new_data_path, 'w') as f:
|
||||
for task in data:
|
||||
repo = task['metadata']['task_id'].split('/')[0]
|
||||
if repo not in REPOs:
|
||||
continue
|
||||
f.write(json.dumps(task) + '\n')
|
||||
|
||||
data = [json.loads(l.rstrip()) for l in open(new_data_path, 'r')]
|
||||
|
||||
# build query, docs, and qrels for each repository
|
||||
queries, corpus, qrels = [], [], []
|
||||
query_id2gt = {}
|
||||
for repo in REPOs:
|
||||
repo_queries, repo_corpus, repo_qrels, repo_query_id2gt = repo2code(
|
||||
repo, args.data_cache_dir,
|
||||
args.split, args.context_length,
|
||||
args.window_size, args.slice_size
|
||||
)
|
||||
queries += repo_queries
|
||||
corpus += repo_corpus
|
||||
qrels += repo_qrels
|
||||
query_id2gt.update(repo_query_id2gt)
|
||||
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(corpus, os.path.join(path, "corpus.jsonl"))
|
||||
save_tsv_dict(qrels, os.path.join(path, "qrels", "test.tsv"), ["query-id", "corpus-id", "score"])
|
||||
|
||||
gt_data = []
|
||||
for example in data:
|
||||
query_id = example['metadata']['task_id']
|
||||
gt = query_id2gt[query_id]
|
||||
new_example = {
|
||||
"prompt": example["prompt"],
|
||||
"reference": example["metadata"]["ground_truth"],
|
||||
"docs": gt[:10],
|
||||
"metadata": {k:v for k,v in example["metadata"].items() if k != "ground_truth"},
|
||||
}
|
||||
gt_data.append(new_example)
|
||||
|
||||
results_file = os.path.join(args.results_dir, f"repoeval-{args.split}-{args.context_length}-gt.jsonl")
|
||||
with open(results_file, "w") as fw:
|
||||
for ex in gt_data:
|
||||
fw.write(json.dumps(ex) + "\n")
|
||||
|
||||
results_file = os.path.join(args.results_dir, f"repoeval-{args.split}-{args.context_length}-infile.jsonl")
|
||||
with open(results_file, "w") as fw:
|
||||
for ex in gt_data:
|
||||
ex = {k:v for k,v in ex.items() if k != "docs"}
|
||||
ex["docs"] = []
|
||||
fw.write(json.dumps(ex) + "\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,306 @@
|
||||
import io
|
||||
import os
|
||||
import glob
|
||||
import json
|
||||
import argparse
|
||||
import requests
|
||||
import zipfile
|
||||
from collections import defaultdict
|
||||
from create.utils import save_tsv_dict, save_file_jsonl
|
||||
|
||||
REPOs_line_and_api = [
|
||||
'huggingface_diffusers',
|
||||
'nerfstudio-project_nerfstudio',
|
||||
'awslabs_fortuna',
|
||||
'huggingface_evaluate',
|
||||
'google_vizier',
|
||||
'alibaba_FederatedScope',
|
||||
'pytorch_rl',
|
||||
'opendilab_ACE',
|
||||
]
|
||||
|
||||
REPOs_function = [
|
||||
"amazon-science_patchcore-inspection",
|
||||
"deepmind_tracr",
|
||||
"facebookresearch_omnivore",
|
||||
"google_lightweight_mmm",
|
||||
"lucidrains_imagen-pytorch",
|
||||
"maxhumber_redframes",
|
||||
]
|
||||
|
||||
REPO_DIRs = {
|
||||
"api": "repositories/line_and_api_level",
|
||||
"line": "repositories/line_and_api_level",
|
||||
"function": "repositories/function_level",
|
||||
}
|
||||
|
||||
|
||||
def iterate_repository(base_dir: str, repo: str) -> dict:
|
||||
pattern = os.path.join(f'{base_dir}/{repo}', "**", "*.py")
|
||||
files = glob.glob(pattern, recursive=True)
|
||||
|
||||
skipped_files = []
|
||||
loaded_code_files = dict()
|
||||
base_dir_list = os.path.normpath(base_dir).split(os.sep)
|
||||
for fname in files:
|
||||
try:
|
||||
code = open(fname, 'r', encoding='utf8').read()
|
||||
fpath_tuple = tuple(os.path.normpath(fname).split(os.sep)[len(base_dir_list):])
|
||||
loaded_code_files[fpath_tuple]= code
|
||||
except Exception as e:
|
||||
skipped_files.append((fname, e))
|
||||
continue
|
||||
|
||||
if len(skipped_files) > 0:
|
||||
print(f"Skipped {len(skipped_files)} out of {len(files)} files due to I/O errors")
|
||||
for fname, e in skipped_files:
|
||||
print(f"{fname}: {e}")
|
||||
return loaded_code_files
|
||||
|
||||
|
||||
def window_overlap(span: tuple, target_span: tuple) -> bool:
|
||||
if span[0] >= target_span[1] or span[1] <= target_span[0]:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class RepoWindowMaker:
|
||||
def __init__(self, base_dir, repo, tasks, window_size, slice_size):
|
||||
self.base_dir = base_dir
|
||||
self.repo = repo
|
||||
self.window_size = window_size
|
||||
self.slice_size = slice_size
|
||||
self.slice_step = 1 if window_size // slice_size == 0 else window_size // slice_size
|
||||
self.tasks = tasks
|
||||
self.source_code_files = iterate_repository(base_dir, repo)
|
||||
|
||||
def _buid_windows_for_a_file(self, fpath_tuple, code):
|
||||
code_windows = []
|
||||
code_lines = code.splitlines()
|
||||
delta_size = self.window_size // 2
|
||||
for line_no in range(0, len(code_lines), self.slice_step): # line_no starts from 0
|
||||
start_line_no = max(0, line_no - delta_size)
|
||||
end_line_no = min(len(code_lines), line_no + self.window_size - delta_size)
|
||||
window_lines = [i for i in code_lines[start_line_no:end_line_no]]
|
||||
if not window_lines: # all empty lines
|
||||
continue
|
||||
window_text = '\n'.join(window_lines)
|
||||
code_windows.append({
|
||||
'context': window_text,
|
||||
'metadata': {
|
||||
'fpath_tuple': fpath_tuple,
|
||||
'line_no': line_no,
|
||||
'start_line_no': start_line_no,
|
||||
'end_line_no': end_line_no,
|
||||
'window_size': self.window_size,
|
||||
'repo': self.repo,
|
||||
'slice_size': self.slice_size,
|
||||
}
|
||||
})
|
||||
return code_windows
|
||||
|
||||
def _merge_windows_with_same_context(self, code_windows):
|
||||
merged_code_windows = defaultdict(list)
|
||||
for code_window in code_windows:
|
||||
context = code_window['context']
|
||||
metadata = code_window['metadata']
|
||||
merged_code_windows[context].append(metadata)
|
||||
json_lines = []
|
||||
for context, metadata_list in merged_code_windows.items():
|
||||
json_lines.append({
|
||||
'context': context,
|
||||
'metadata': metadata_list
|
||||
})
|
||||
return json_lines
|
||||
|
||||
def build_windows(self):
|
||||
all_code_windows = []
|
||||
for fpath_tuple, code in self.source_code_files.items():
|
||||
all_code_windows += self._buid_windows_for_a_file(fpath_tuple, code)
|
||||
merged_code_windows = self._merge_windows_with_same_context(all_code_windows)
|
||||
print(f'build {len(merged_code_windows)} windows for {self.repo} with window size {self.window_size} and slice {self.slice_size}')
|
||||
ground_truth_indices = {}
|
||||
for task in self.tasks:
|
||||
fpath_tuple = tuple(task['metadata']['fpath_tuple'])
|
||||
line_no = task['metadata']['line_no']
|
||||
start_line_no = task['metadata']['context_start_lineno']
|
||||
for i, window in enumerate(merged_code_windows):
|
||||
# print(window["metadata"][0]["fpath_tuple"], fpath_tuple)
|
||||
if window["metadata"][0]["fpath_tuple"] != fpath_tuple and ' '.join(list(window["metadata"][0]["fpath_tuple"])) != ' '.join(list(fpath_tuple)):
|
||||
continue
|
||||
# print(1)
|
||||
if any([
|
||||
window_overlap(
|
||||
(sub_window["start_line_no"], sub_window["end_line_no"]),
|
||||
(start_line_no, line_no + 1)
|
||||
)
|
||||
for sub_window in window["metadata"]
|
||||
]):
|
||||
# print('test')
|
||||
if i not in ground_truth_indices:
|
||||
ground_truth_indices[i] = []
|
||||
ground_truth_indices[i].append(task["metadata"]["task_id"])
|
||||
# sys.exit()
|
||||
return merged_code_windows, ground_truth_indices
|
||||
|
||||
|
||||
def download_data(directory: str = "repoeval"):
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
|
||||
datasets_dir = os.path.join(directory, "datasets")
|
||||
repos_function_dir = os.path.join(directory, "repositories", "function_level")
|
||||
|
||||
print(f"Start downloading the necessary `datasets` and `repositories` files.")
|
||||
if not os.path.exists(datasets_dir):
|
||||
print(f"Start downloading the `datasets`.")
|
||||
datasets_url = "https://github.com/microsoft/CodeT/raw/main/RepoCoder/datasets/datasets.zip"
|
||||
r = requests.get(datasets_url, stream=True)
|
||||
z = zipfile.ZipFile(io.BytesIO(r.content))
|
||||
z.extractall(datasets_dir)
|
||||
print("Finished downloading the `datasets` files.")
|
||||
|
||||
import shutil
|
||||
shutil.rmtree(repos_function_dir)
|
||||
if not os.path.exists(repos_function_dir):
|
||||
print(f"Start downloading the `repositories` (function).")
|
||||
repos_function_url = "https://github.com/microsoft/CodeT/raw/main/RepoCoder/repositories/function_level.zip"
|
||||
# repos_function_url = "https://github.com/Veronicium/repoeval_debug/raw/main/function_level.zip"
|
||||
r = requests.get(repos_function_url, stream=True)
|
||||
z = zipfile.ZipFile(io.BytesIO(r.content))
|
||||
z.extractall(repos_function_dir)
|
||||
print("Finished downloading the `repositories` files.")
|
||||
|
||||
|
||||
def repo2code(
|
||||
repo: str, tasks: list[dict], data_cache_dir: str,
|
||||
split: str, context_length: str,
|
||||
window_size: int, slice_size: int
|
||||
):
|
||||
# collect queries
|
||||
queries = []
|
||||
for task in tasks:
|
||||
query_id = task["metadata"]["task_id"]
|
||||
# text = '\n'.join(task["prompt"].split('\n')[-2:])
|
||||
text = task["prompt"]
|
||||
metadata = task["metadata"]
|
||||
queries.append({"_id": query_id, "text": text, "metadata": metadata})
|
||||
|
||||
base_dir = os.path.join(data_cache_dir, REPO_DIRs[split])
|
||||
repo_window_maker = RepoWindowMaker(base_dir, repo, tasks, window_size, slice_size)
|
||||
windows, ground_truth_indices = repo_window_maker.build_windows()
|
||||
corpus, qrels = [], []
|
||||
query_id2gt = {task['metadata']['task_id']:[] for task in tasks}
|
||||
for i, window in enumerate(windows):
|
||||
path = '-'.join(window["metadata"][0]["fpath_tuple"])
|
||||
line = f"{window['metadata'][0]['start_line_no']}-{window['metadata'][-1]['end_line_no']}"
|
||||
corpus_id = f"{repo}_{path}_{line}"
|
||||
corpus.append({
|
||||
"_id": corpus_id, "title": path,
|
||||
"text": window["context"], "metadata": window["metadata"]
|
||||
})
|
||||
# print(windows, ground_truth_indices)
|
||||
if i in ground_truth_indices:
|
||||
for query_id in ground_truth_indices[i]:
|
||||
qrels.append({"query-id": query_id, "corpus-id": corpus_id, "score": 1})
|
||||
query_id2gt[query_id].append({"title": corpus_id.replace('_', '/'), "text": window["context"]})
|
||||
|
||||
return queries, corpus, qrels, query_id2gt
|
||||
|
||||
|
||||
def main():
|
||||
download_data(args.data_cache_dir)
|
||||
|
||||
REPOs = REPOs_function if args.split == "function" else REPOs_line_and_api
|
||||
|
||||
file_name = f"{args.split}_level_completion_{args.context_length}_context_codex.test.jsonl"
|
||||
data_path = os.path.join(args.data_cache_dir, "datasets", file_name)
|
||||
data = [json.loads(l.rstrip()) for l in open(data_path, 'r')]
|
||||
|
||||
# preprocess function completion data (the data in the RepoCoder repo isn't correctly formatted)
|
||||
if args.split == 'function':
|
||||
repo2idx = {}
|
||||
for task in data:
|
||||
repo = task['metadata']['task_id'].replace('--', '_').split('/')[0]
|
||||
if repo not in repo2idx:
|
||||
repo2idx[repo] = 0
|
||||
task['metadata']['task_id'] = task['metadata']['task_id'].replace('--', '_').replace('idx', str(repo2idx[repo]))
|
||||
task['metadata']['line_no'] = task['metadata']['lineno']
|
||||
repo2idx[repo] += 1
|
||||
|
||||
new_data_path = data_path.replace('.test.jsonl', '.test.clean.jsonl')
|
||||
with open(new_data_path, 'w') as f:
|
||||
for task in data:
|
||||
repo = task['metadata']['task_id'].split('/')[0]
|
||||
if repo not in REPOs:
|
||||
continue
|
||||
f.write(json.dumps(task) + '\n')
|
||||
|
||||
data = [json.loads(l.rstrip()) for l in open(new_data_path, 'r')]
|
||||
|
||||
# group data instances by repository
|
||||
data_dict = {}
|
||||
for ex in data:
|
||||
repo_name = ex["metadata"]["task_id"]
|
||||
repo_name = repo_name.split('/')[0]
|
||||
if repo_name not in data_dict:
|
||||
data_dict[repo_name] = []
|
||||
data_dict[repo_name].append(ex)
|
||||
|
||||
# build query, docs, and qrels for each repository
|
||||
for repo in REPOs:
|
||||
queries, corpus, qrels, query_id2gt = repo2code(
|
||||
repo, data_dict[repo], args.data_cache_dir,
|
||||
args.split, args.context_length,
|
||||
args.window_size, args.slice_size
|
||||
)
|
||||
print(len(queries))
|
||||
if len(qrels) == 0:
|
||||
print(repo)
|
||||
# sys.exit()
|
||||
continue
|
||||
# sys.exit()
|
||||
path = os.path.join(args.output_dir, f"repoeval__{repo}")
|
||||
os.makedirs(path, exist_ok=True)
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(corpus, os.path.join(path, "corpus.jsonl"))
|
||||
save_tsv_dict(qrels, os.path.join(path, "qrels", "test.tsv"), ["query-id", "corpus-id", "score"])
|
||||
|
||||
gt_data = []
|
||||
for example in data_dict[repo]:
|
||||
query_id = example['metadata']['task_id']
|
||||
gt = query_id2gt[query_id]
|
||||
new_example = {
|
||||
"prompt": example["prompt"],
|
||||
"reference": example["metadata"]["ground_truth"],
|
||||
"docs": gt[:10],
|
||||
"metadata": {k:v for k,v in example["metadata"].items() if k != "ground_truth"},
|
||||
}
|
||||
gt_data.append(new_example)
|
||||
|
||||
os.makedirs(args.results_dir, exist_ok=True)
|
||||
|
||||
results_file = os.path.join(args.results_dir, f"repoeval-{args.split}-{repo}-{args.context_length}-gt.jsonl")
|
||||
with open(results_file, "w") as fw:
|
||||
for ex in gt_data:
|
||||
fw.write(json.dumps(ex) + "\n")
|
||||
|
||||
results_file = os.path.join(args.results_dir, f"repoeval-{args.split}-{repo}-{args.context_length}-infile.jsonl")
|
||||
with open(results_file, "w") as fw:
|
||||
for ex in gt_data:
|
||||
ex = {k:v for k,v in ex.items() if k != "docs"}
|
||||
ex["docs"] = []
|
||||
fw.write(json.dumps(ex) + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
parser.add_argument("--results_dir", type=str, default="results")
|
||||
parser.add_argument("--split", type=str, default="function", choices=["function"])
|
||||
parser.add_argument("--context_length", type=str, default="2k", choices=["1k", "2k", "4k"])
|
||||
parser.add_argument("--data_cache_dir", type=str, default="output/repoeval")
|
||||
parser.add_argument("--window_size", type=int, default=50)
|
||||
parser.add_argument("--slice_size", type=int, default=5)
|
||||
args = parser.parse_args()
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,247 @@
|
||||
import os
|
||||
import re
|
||||
import chardet
|
||||
import unidiff
|
||||
import argparse
|
||||
import datasets
|
||||
import traceback
|
||||
import subprocess
|
||||
from git import Repo
|
||||
from tqdm import tqdm
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from create.utils import save_tsv_dict, save_file_jsonl
|
||||
|
||||
# %% Get oracle file contents
|
||||
|
||||
# get oracle file contents from the repo
|
||||
class ContextManager:
|
||||
def __init__(self, repo_path, base_commit, verbose=False):
|
||||
self.repo_path = Path(repo_path).resolve().as_posix()
|
||||
self.old_dir = os.getcwd()
|
||||
self.base_commit = base_commit
|
||||
self.verbose = verbose
|
||||
|
||||
def __enter__(self):
|
||||
os.chdir(self.repo_path)
|
||||
cmd = f"git reset --hard {self.base_commit} && git clean -fdxq"
|
||||
if self.verbose:
|
||||
subprocess.run(cmd, shell=True, check=True)
|
||||
else:
|
||||
subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
return self
|
||||
|
||||
def get_environment(self):
|
||||
raise NotImplementedError() # TODO: activate conda environment and return the environment file
|
||||
|
||||
def get_readme_files(self):
|
||||
files = os.listdir(self.repo_path)
|
||||
files = list(filter(lambda x: os.path.isfile(x), files))
|
||||
files = list(filter(lambda x: x.lower().startswith("readme"), files))
|
||||
return files
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
os.chdir(self.old_dir)
|
||||
|
||||
|
||||
class AutoContextManager(ContextManager):
|
||||
"""Automatically clones the repo if it doesn't exist"""
|
||||
|
||||
def __init__(self, instance, root_dir=None, verbose=False, token=None):
|
||||
if token is None:
|
||||
token = os.environ.get("GITHUB_TOKEN", "git")
|
||||
self.tempdir = None
|
||||
if root_dir is None:
|
||||
self.tempdir = TemporaryDirectory()
|
||||
root_dir = self.tempdir.name
|
||||
self.root_dir = root_dir
|
||||
repo_dir = os.path.join(self.root_dir, instance["repo"].replace("/", "__"))
|
||||
if not os.path.exists(repo_dir):
|
||||
repo_url = (
|
||||
f"https://{token}@github.com/swe-bench/"
|
||||
+ instance["repo"].replace("/", "__")
|
||||
+ ".git"
|
||||
)
|
||||
if verbose:
|
||||
print(f"Cloning {instance['repo']} to {root_dir}")
|
||||
Repo.clone_from(repo_url, repo_dir)
|
||||
super().__init__(repo_dir, instance["base_commit"], verbose=verbose)
|
||||
self.instance = instance
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.tempdir is not None:
|
||||
self.tempdir.cleanup()
|
||||
return super().__exit__(exc_type, exc_val, exc_tb)
|
||||
|
||||
|
||||
def ingest_files(filenames):
|
||||
files_dict = dict()
|
||||
for filename in filenames:
|
||||
with open(filename) as f:
|
||||
content = f.read()
|
||||
files_dict[filename] = content
|
||||
return files_dict
|
||||
|
||||
def get_oracle_filenames(instance):
|
||||
"""
|
||||
Returns the filenames that are changed in the patch
|
||||
"""
|
||||
source_files = {
|
||||
patch_file.source_file.split("a/", 1)[-1]
|
||||
for patch_file in unidiff.PatchSet(instance["patch"])
|
||||
}
|
||||
gold_docs = set()
|
||||
for source_file in source_files:
|
||||
gold_docs.add(source_file)
|
||||
return gold_docs
|
||||
|
||||
|
||||
# get all file contents from the repo
|
||||
def is_test(name, test_phrases=None):
|
||||
if test_phrases is None:
|
||||
test_phrases = ["test", "tests", "testing"]
|
||||
words = set(re.split(r" |_|\/|\.", name.lower()))
|
||||
return any(word in words for word in test_phrases)
|
||||
|
||||
def list_files(root_dir, include_tests=False):
|
||||
files = []
|
||||
for filename in Path(root_dir).rglob("*.py"):
|
||||
if not include_tests and is_test(filename.as_posix()):
|
||||
continue
|
||||
files.append(filename.relative_to(root_dir).as_posix())
|
||||
return files
|
||||
|
||||
def detect_encoding(filename):
|
||||
"""
|
||||
Detect the encoding of a file
|
||||
"""
|
||||
with open(filename, "rb") as file:
|
||||
rawdata = file.read()
|
||||
return chardet.detect(rawdata)["encoding"]
|
||||
|
||||
def ingest_directory_contents(root_dir, include_tests=False):
|
||||
files_content = {}
|
||||
for relative_path in list_files(root_dir, include_tests=include_tests):
|
||||
filename = os.path.join(root_dir, relative_path)
|
||||
encoding = detect_encoding(filename)
|
||||
if encoding is None:
|
||||
content = "[BINARY DATA FILE]"
|
||||
else:
|
||||
try:
|
||||
with open(filename, encoding=encoding) as file:
|
||||
content = file.read()
|
||||
except (UnicodeDecodeError, LookupError):
|
||||
content = "[BINARY DATA FILE]"
|
||||
files_content[relative_path] = content
|
||||
return files_content
|
||||
|
||||
def get_file_contents(input_instances, verbose: bool = False, tmp_dir: str = "/scratch"):
|
||||
orig_dir = os.getcwd()
|
||||
with TemporaryDirectory(dir=tmp_dir if os.path.exists(tmp_dir) else "/tmp") as root_dir:
|
||||
for instance_id, instance in tqdm(
|
||||
input_instances.items(),
|
||||
total=len(input_instances),
|
||||
desc="Getting file contents",
|
||||
):
|
||||
try:
|
||||
with AutoContextManager(instance, root_dir, verbose=verbose) as cm:
|
||||
readmes = cm.get_readme_files()
|
||||
instance["readmes"] = ingest_files(readmes)
|
||||
instance["oracle_file_contents"] = ingest_files(get_oracle_filenames(instance))
|
||||
instance["file_contents"] = ingest_directory_contents(cm.repo_path)
|
||||
assert all([
|
||||
okey in instance["file_contents"]
|
||||
for okey in instance["oracle_file_contents"].keys()
|
||||
])
|
||||
except Exception as e:
|
||||
print(f"Failed on instance {instance_id}", e)
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
# if AutoContextManager fails to exit properly future exits will return the wrong directory
|
||||
os.chdir(orig_dir)
|
||||
os.chdir(orig_dir)
|
||||
|
||||
|
||||
# %% Get queries, docs, and qrels
|
||||
|
||||
def document2code(data, split: str = "test"):
|
||||
subset = data[split]
|
||||
if args.num_examples is not None:
|
||||
import random
|
||||
indices = random.sample([i for i in range(len(subset))], args.num_examples)
|
||||
subset = subset.select(indices)
|
||||
print(subset)
|
||||
|
||||
# get queries for each example
|
||||
queries = [
|
||||
{
|
||||
"_id": item["instance_id"],
|
||||
"text": item["problem_statement"],
|
||||
"metadata": {}
|
||||
}
|
||||
for item in subset
|
||||
]
|
||||
|
||||
subset_dict = {x["instance_id"]: x for x in subset}
|
||||
get_file_contents(subset_dict, tmp_dir=args.tmp_dir)
|
||||
|
||||
# collect all docs, i.e., code chunks from the repo
|
||||
docs = []
|
||||
for instance_id, instance in subset_dict.items():
|
||||
print(f"Instance #{instance_id}: {len(instance['oracle_file_contents'])} oracle / {len(instance['file_contents'])} files")
|
||||
for filename, content in instance["file_contents"].items():
|
||||
docs.append({
|
||||
"_id": f"{instance_id}_{filename}",
|
||||
"title": filename,
|
||||
"text": content,
|
||||
"metadata": {},
|
||||
})
|
||||
|
||||
# find ground-truth docs for each example
|
||||
qrels = []
|
||||
for instance_id, instance in subset_dict.items():
|
||||
for filename, content in instance["oracle_file_contents"].items():
|
||||
qrels.append({
|
||||
"query-id": instance_id,
|
||||
"corpus-id": f"{instance_id}_{filename}",
|
||||
"score": 1
|
||||
})
|
||||
|
||||
return queries, docs, qrels
|
||||
|
||||
|
||||
def main():
|
||||
dataset = datasets.load_dataset(args.dataset_name, cache_dir=args.cache_dir)
|
||||
|
||||
name = "swe-bench"
|
||||
if "lite" in args.dataset_name.lower():
|
||||
name += "-lite"
|
||||
|
||||
path = os.path.join(args.output_dir, name)
|
||||
os.makedirs(path, exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "qrels"), exist_ok=True)
|
||||
|
||||
queries, docs, qrels = document2code(dataset, split="test")
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(docs, os.path.join(path, "corpus.jsonl"))
|
||||
qrels_path = os.path.join(path, "qrels", "test.tsv")
|
||||
save_tsv_dict(qrels, qrels_path, ["query-id", "corpus-id", "score"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset_name", type=str, default="princeton-nlp/SWE-bench_Lite",
|
||||
choices=["princeton-nlp/SWE-bench", "princeton-nlp/SWE-bench_Lite"])
|
||||
parser.add_argument("--cache_dir", type=str, default="/scratch/zhiruow/data")
|
||||
parser.add_argument("--tmp_dir", type=str, default="/scratch/zhiruow/tmp")
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
parser.add_argument("--num_examples", type=int, default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,263 @@
|
||||
import os
|
||||
import re
|
||||
import chardet
|
||||
import unidiff
|
||||
import argparse
|
||||
import datasets
|
||||
import traceback
|
||||
import subprocess
|
||||
from git import Repo
|
||||
from tqdm import tqdm
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from create.utils import save_tsv_dict, save_file_jsonl
|
||||
|
||||
# %% Get oracle file contents
|
||||
|
||||
# get oracle file contents from the repo
|
||||
class ContextManager:
|
||||
def __init__(self, repo_path, base_commit, verbose=False):
|
||||
self.repo_path = Path(repo_path).resolve().as_posix()
|
||||
self.old_dir = os.getcwd()
|
||||
self.base_commit = base_commit
|
||||
self.verbose = verbose
|
||||
|
||||
def __enter__(self):
|
||||
os.chdir(self.repo_path)
|
||||
cmd = f"git reset --hard {self.base_commit} && git clean -fdxq"
|
||||
if self.verbose:
|
||||
subprocess.run(cmd, shell=True, check=True)
|
||||
else:
|
||||
subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
return self
|
||||
|
||||
def get_environment(self):
|
||||
raise NotImplementedError() # TODO: activate conda environment and return the environment file
|
||||
|
||||
def get_readme_files(self):
|
||||
files = os.listdir(self.repo_path)
|
||||
files = list(filter(lambda x: os.path.isfile(x), files))
|
||||
files = list(filter(lambda x: x.lower().startswith("readme"), files))
|
||||
return files
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
os.chdir(self.old_dir)
|
||||
|
||||
|
||||
class AutoContextManager(ContextManager):
|
||||
"""Automatically clones the repo if it doesn't exist"""
|
||||
|
||||
def __init__(self, instance, root_dir=None, verbose=False, token=None):
|
||||
if token is None:
|
||||
token = os.environ.get("GITHUB_TOKEN", "git")
|
||||
self.tempdir = None
|
||||
if root_dir is None:
|
||||
self.tempdir = TemporaryDirectory()
|
||||
root_dir = self.tempdir.name
|
||||
self.root_dir = root_dir
|
||||
repo_dir = os.path.join(self.root_dir, instance["repo"].replace("/", "__"))
|
||||
if not os.path.exists(repo_dir):
|
||||
repo_url = (
|
||||
f"https://{token}@github.com/swe-bench/"
|
||||
+ instance["repo"].replace("/", "__")
|
||||
+ ".git"
|
||||
)
|
||||
if verbose:
|
||||
print(f"Cloning {instance['repo']} to {root_dir}")
|
||||
Repo.clone_from(repo_url, repo_dir)
|
||||
super().__init__(repo_dir, instance["base_commit"], verbose=verbose)
|
||||
self.instance = instance
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.tempdir is not None:
|
||||
self.tempdir.cleanup()
|
||||
return super().__exit__(exc_type, exc_val, exc_tb)
|
||||
|
||||
|
||||
def ingest_files(filenames):
|
||||
files_dict = dict()
|
||||
for filename in filenames:
|
||||
with open(filename) as f:
|
||||
content = f.read()
|
||||
files_dict[filename] = content
|
||||
return files_dict
|
||||
|
||||
def get_oracle_filenames(instance):
|
||||
"""
|
||||
Returns the filenames that are changed in the patch
|
||||
"""
|
||||
source_files = {
|
||||
patch_file.source_file.split("a/", 1)[-1]
|
||||
for patch_file in unidiff.PatchSet(instance["patch"])
|
||||
}
|
||||
gold_docs = set()
|
||||
for source_file in source_files:
|
||||
gold_docs.add(source_file)
|
||||
return gold_docs
|
||||
|
||||
|
||||
# get all file contents from the repo
|
||||
def is_test(name, test_phrases=None):
|
||||
if test_phrases is None:
|
||||
test_phrases = ["test", "tests", "testing"]
|
||||
words = set(re.split(r" |_|\/|\.", name.lower()))
|
||||
return any(word in words for word in test_phrases)
|
||||
|
||||
def list_files(root_dir, include_tests=False):
|
||||
files = []
|
||||
for filename in Path(root_dir).rglob("*.py"):
|
||||
if not include_tests and is_test(filename.as_posix()):
|
||||
continue
|
||||
files.append(filename.relative_to(root_dir).as_posix())
|
||||
return files
|
||||
|
||||
def detect_encoding(filename):
|
||||
"""
|
||||
Detect the encoding of a file
|
||||
"""
|
||||
with open(filename, "rb") as file:
|
||||
rawdata = file.read()
|
||||
return chardet.detect(rawdata)["encoding"]
|
||||
|
||||
def ingest_directory_contents(root_dir, include_tests=False):
|
||||
files_content = {}
|
||||
for relative_path in list_files(root_dir, include_tests=include_tests):
|
||||
filename = os.path.join(root_dir, relative_path)
|
||||
encoding = detect_encoding(filename)
|
||||
if encoding is None:
|
||||
content = "[BINARY DATA FILE]"
|
||||
else:
|
||||
try:
|
||||
with open(filename, encoding=encoding) as file:
|
||||
content = file.read()
|
||||
except (UnicodeDecodeError, LookupError):
|
||||
content = "[BINARY DATA FILE]"
|
||||
files_content[relative_path] = content
|
||||
return files_content
|
||||
|
||||
def get_file_contents(input_instances, verbose: bool = False, tmp_dir: str = "/scratch"):
|
||||
orig_dir = os.getcwd()
|
||||
with TemporaryDirectory(dir=tmp_dir if os.path.exists(tmp_dir) else "/tmp") as root_dir:
|
||||
for instance_id, instance in tqdm(
|
||||
input_instances.items(),
|
||||
total=len(input_instances),
|
||||
desc="Getting file contents",
|
||||
):
|
||||
try:
|
||||
with AutoContextManager(instance, root_dir, verbose=verbose) as cm:
|
||||
readmes = cm.get_readme_files()
|
||||
instance["readmes"] = ingest_files(readmes)
|
||||
instance["oracle_file_contents"] = ingest_files(get_oracle_filenames(instance))
|
||||
instance["file_contents"] = ingest_directory_contents(cm.repo_path)
|
||||
assert all([
|
||||
okey in instance["file_contents"]
|
||||
for okey in instance["oracle_file_contents"].keys()
|
||||
])
|
||||
except Exception as e:
|
||||
print(f"Failed on instance {instance_id}", e)
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
# if AutoContextManager fails to exit properly future exits will return the wrong directory
|
||||
os.chdir(orig_dir)
|
||||
os.chdir(orig_dir)
|
||||
|
||||
|
||||
import multiprocessing as mp
|
||||
from functools import partial
|
||||
|
||||
def process_single_item(item, args):
|
||||
"""处理单个数据项的函数"""
|
||||
name = "swe-bench"
|
||||
if "lite" in args.dataset_name.lower():
|
||||
name += "-lite"
|
||||
|
||||
queries = [{
|
||||
"_id": item["instance_id"],
|
||||
"text": item["problem_statement"],
|
||||
"metadata": {}
|
||||
}]
|
||||
item_dict = {item["instance_id"]: item}
|
||||
|
||||
output_path = os.path.join(args.output_dir, f"{name}_{item['instance_id']}", "qrels", "test.tsv")
|
||||
if os.path.exists(output_path):
|
||||
return
|
||||
|
||||
try:
|
||||
get_file_contents(item_dict, tmp_dir=args.tmp_dir)
|
||||
|
||||
docs = []
|
||||
for instance_id, instance in item_dict.items():
|
||||
print(f"Instance #{instance_id}: {len(instance['oracle_file_contents'])} oracle / {len(instance['file_contents'])} files")
|
||||
for filename, content in instance["file_contents"].items():
|
||||
docs.append({
|
||||
"_id": f"{instance_id}_{filename}",
|
||||
"title": filename,
|
||||
"text": content,
|
||||
"metadata": {},
|
||||
})
|
||||
|
||||
qrels = []
|
||||
for instance_id, instance in item_dict.items():
|
||||
for filename, content in instance["oracle_file_contents"].items():
|
||||
qrels.append({
|
||||
"query-id": instance_id,
|
||||
"corpus-id": f"{instance_id}_{filename}",
|
||||
"score": 1
|
||||
})
|
||||
|
||||
path = os.path.join(args.output_dir, f"{name}_{instance_id}")
|
||||
os.makedirs(path, exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "qrels"), exist_ok=True)
|
||||
|
||||
save_file_jsonl(queries, os.path.join(path, "queries.jsonl"))
|
||||
save_file_jsonl(docs, os.path.join(path, "corpus.jsonl"))
|
||||
qrels_path = os.path.join(path, "qrels", "test.tsv")
|
||||
save_tsv_dict(qrels, qrels_path, ["query-id", "corpus-id", "score"])
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing item {item['instance_id']}: {str(e)}")
|
||||
|
||||
def main():
|
||||
dataset = datasets.load_dataset(args.dataset_name, cache_dir=args.cache_dir)["test"]
|
||||
if args.num_examples is not None:
|
||||
import random
|
||||
indices = random.sample([i for i in range(len(dataset))], args.num_examples)
|
||||
dataset = dataset.select(indices)
|
||||
print(dataset)
|
||||
|
||||
# 创建进程池
|
||||
num_processes = mp.cpu_count() - 1 # 留一个CPU核心
|
||||
pool = mp.Pool(processes=num_processes)
|
||||
|
||||
# 使用partial固定args参数
|
||||
process_func = partial(process_single_item, args=args)
|
||||
|
||||
# 使用进程池并行处理
|
||||
list(tqdm(
|
||||
pool.imap_unordered(process_func, dataset),
|
||||
total=len(dataset),
|
||||
desc="Processing items"
|
||||
))
|
||||
|
||||
# 关闭进程池
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset_name", type=str, default="princeton-nlp/SWE-bench_Lite",
|
||||
choices=["princeton-nlp/SWE-bench", "princeton-nlp/SWE-bench_Lite"])
|
||||
parser.add_argument("--cache_dir", type=str, default="/scratch/zhiruow/data")
|
||||
parser.add_argument("--tmp_dir", type=str, default="/scratch/zhiruow/tmp")
|
||||
parser.add_argument("--output_dir", type=str, default="datasets")
|
||||
parser.add_argument("--num_examples", type=int, default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,37 @@
|
||||
import jsonlines
|
||||
import csv
|
||||
import os
|
||||
|
||||
def load_jsonlines(file):
|
||||
with jsonlines.open(file, 'r') as jsonl_f:
|
||||
lst = [obj for obj in jsonl_f]
|
||||
return lst
|
||||
|
||||
def save_file_jsonl(data, fp):
|
||||
with jsonlines.open(fp, mode='w') as writer:
|
||||
writer.write_all(data)
|
||||
|
||||
def save_tsv_dict(data, fp, fields):
|
||||
# build dir
|
||||
dir_path = os.path.dirname(fp)
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
|
||||
# writing to csv file
|
||||
with open(fp, 'w') as csvfile:
|
||||
writer = csv.DictWriter(csvfile, fieldnames=fields, delimiter='\t',)
|
||||
writer.writeheader()
|
||||
writer.writerows(data)
|
||||
|
||||
def cost_esitmate(path):
|
||||
corpus = load_jsonlines(os.path.join(path, "corpus.jsonl"))
|
||||
queries = load_jsonlines(os.path.join(path, "queries.jsonl"))
|
||||
num_corpus_words = 0
|
||||
num_queries_words = 0
|
||||
for item in tqdm(corpus):
|
||||
num_corpus_words += len(item["text"].split(" "))
|
||||
for item in tqdm(queries):
|
||||
num_queries_words += len(item["text"].split(" "))
|
||||
print(len(corpus))
|
||||
print(len(queries))
|
||||
print(num_corpus_words)
|
||||
print(num_queries_words)
|
||||
@@ -0,0 +1,313 @@
|
||||
import os
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
import pathlib
|
||||
import argparse
|
||||
import numpy as np
|
||||
from time import time
|
||||
from datasets import load_dataset
|
||||
from beir import util, LoggingHandler
|
||||
from beir.retrieval import models
|
||||
from beir.datasets.data_loader import GenericDataLoader
|
||||
from beir.retrieval.evaluation import EvaluateRetrieval
|
||||
from beir.retrieval.search.dense import DenseRetrievalExactSearch as DRES
|
||||
from tqdm import tqdm
|
||||
from transformers import HfArgumentParser
|
||||
|
||||
from arguments import CodeRAGEvalArgs, CodeRAGEvalModelArgs
|
||||
from prompts import get_task_def_by_task_name
|
||||
from FlagEmbedding import FlagLLMModel, FlagModel
|
||||
|
||||
|
||||
def get_model(model_args: CodeRAGEvalModelArgs):
|
||||
embedder_name_or_path = model_args.embedder_name_or_path
|
||||
|
||||
if model_args.embedder_model_class == "encoder-only-base":
|
||||
embedder = FlagModel(
|
||||
model_name_or_path=embedder_name_or_path,
|
||||
normalize_embeddings=model_args.normalize_embeddings,
|
||||
pooling_method=model_args.pooling_method,
|
||||
use_fp16=model_args.use_fp16,
|
||||
query_instruction_for_retrieval=model_args.query_instruction_for_retrieval,
|
||||
query_instruction_format=model_args.query_instruction_format_for_retrieval,
|
||||
devices=model_args.devices,
|
||||
trust_remote_code=model_args.trust_remote_code,
|
||||
cache_dir=model_args.cache_dir,
|
||||
batch_size=model_args.embedder_batch_size,
|
||||
query_max_length=model_args.embedder_query_max_length,
|
||||
passage_max_length=model_args.embedder_passage_max_length,
|
||||
)
|
||||
elif model_args.embedder_model_class == "decoder-only-base":
|
||||
embedder = FlagLLMModel(
|
||||
model_name_or_path=embedder_name_or_path,
|
||||
normalize_embeddings=model_args.normalize_embeddings,
|
||||
pooling_method=model_args.pooling_method,
|
||||
use_fp16=model_args.use_fp16,
|
||||
query_instruction_for_retrieval=model_args.query_instruction_for_retrieval,
|
||||
query_instruction_format=model_args.query_instruction_format_for_retrieval,
|
||||
devices=model_args.devices,
|
||||
examples_for_task=model_args.examples_for_task,
|
||||
examples_instruction_format=model_args.examples_instruction_format,
|
||||
trust_remote_code=model_args.trust_remote_code,
|
||||
cache_dir=model_args.cache_dir,
|
||||
batch_size=model_args.embedder_batch_size,
|
||||
query_max_length=model_args.embedder_query_max_length,
|
||||
passage_max_length=model_args.embedder_passage_max_length,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Invalid model class: {model_args.embedder_model_class}")
|
||||
embedder.model.config._name_or_path = model_args.embedder_name_or_path
|
||||
|
||||
class CustomFlagModel:
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
|
||||
def encode_queries(self, queries, show_progress_bar, convert_to_tensor, **kwargs):
|
||||
if isinstance(queries, str):
|
||||
queries = [queries]
|
||||
|
||||
if isinstance(queries[0], dict):
|
||||
queries = [(e.get('title') + ' ' + e['text']).strip() for e in queries]
|
||||
|
||||
return self.model.encode_queries(queries, **kwargs)
|
||||
|
||||
def encode_corpus(self, corpus, show_progress_bar, convert_to_tensor, **kwargs):
|
||||
if isinstance(corpus, str):
|
||||
corpus = [corpus]
|
||||
|
||||
if isinstance(corpus[0], dict):
|
||||
corpus = [(e.get('title') + ' ' + e['text']).strip() for e in corpus]
|
||||
|
||||
return self.model.encode_corpus(corpus, **kwargs)
|
||||
|
||||
def encode(self, corpus, show_progress_bar, convert_to_tensor, **kwargs):
|
||||
if isinstance(corpus, str):
|
||||
corpus = [corpus]
|
||||
|
||||
if isinstance(corpus[0], dict):
|
||||
corpus = [(e.get('title') + ' ' + e['text']).strip() for e in corpus]
|
||||
|
||||
return self.model.encode(corpus, **kwargs)
|
||||
|
||||
return CustomFlagModel(embedder)
|
||||
|
||||
#### Just some code to print debug information to stdout
|
||||
logging.basicConfig(format='%(asctime)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
level=logging.INFO,
|
||||
handlers=[LoggingHandler()])
|
||||
|
||||
|
||||
def get_top_docs(results: dict, corpus: dict, task_id: str, topk: int = 10) -> list[str]:
|
||||
if task_id not in results: return []
|
||||
doc_scores = results[task_id]
|
||||
doc_scores_sorted = sorted(doc_scores.items(), key=lambda item: item[1], reverse=True)
|
||||
doc_scores_sorted = doc_scores_sorted[:topk]
|
||||
doc_code_snippets = [corpus[code_id] for code_id, score in doc_scores_sorted]
|
||||
return doc_code_snippets
|
||||
|
||||
|
||||
def main(
|
||||
eval_args: CodeRAGEvalArgs,
|
||||
model_args: CodeRAGEvalModelArgs
|
||||
):
|
||||
args = eval_args
|
||||
|
||||
embedder = get_model(model_args)
|
||||
model = DRES(
|
||||
embedder,
|
||||
batch_size=args.batch_size,
|
||||
corpus_chunk_size=512 * 9999
|
||||
)
|
||||
retriever = EvaluateRetrieval(model, score_function="dot")
|
||||
|
||||
if args.dataset.startswith("swe-bench") or args.dataset.startswith("repoeval"):
|
||||
all_eval_results = []
|
||||
|
||||
if args.dataset.startswith("swe-bench"):
|
||||
swebench = load_dataset("princeton-nlp/SWE-bench_Lite")["test"]
|
||||
all_top_docs = [[] for _ in swebench]
|
||||
|
||||
instance_list = [i for i in os.listdir("datasets") if i.startswith(f"{args.dataset}_")]
|
||||
instance_list_filtered = []
|
||||
|
||||
for ins_dir in tqdm(instance_list):
|
||||
logging.info("Instance Repo: {}".format(ins_dir))
|
||||
# load data and perform retrieval
|
||||
corpus, queries, qrels = GenericDataLoader(
|
||||
data_folder=os.path.join("datasets", ins_dir)
|
||||
).load(split="test")
|
||||
logging.info(f"Instance #{ins_dir}: #{len(corpus)} corpus, #{len(queries)} queries")
|
||||
|
||||
start_time = time()
|
||||
if len(queries) == 1:
|
||||
queries.update({"dummy": "dummy"})
|
||||
results = retriever.retrieve(corpus, queries)
|
||||
if "dummy" in queries:
|
||||
queries.pop("dummy")
|
||||
results.pop("dummy")
|
||||
end_time = time()
|
||||
logging.info("Time taken to retrieve: {:.2f} seconds".format(end_time - start_time))
|
||||
|
||||
# get topk retrieved docs
|
||||
if args.dataset.startswith("swe-bench"):
|
||||
indices = [i for i, ex in enumerate(swebench) if ex["instance_id"] in queries]
|
||||
for index in indices:
|
||||
instance_id = swebench[index]["instance_id"]
|
||||
all_top_docs[index] = get_top_docs(results, corpus, instance_id)
|
||||
elif args.dataset.startswith("repoeval"):
|
||||
args.dataset_path = "output/repoeval/datasets/function_level_completion_2k_context_codex.test.clean.jsonl"
|
||||
tasks = [json.loads(line.strip()) for line in open(args.dataset_path, 'r')]
|
||||
prompts, references, docs, metadatas = [], [], [], []
|
||||
for task in tasks:
|
||||
if task["metadata"]["task_id"] not in queries: continue
|
||||
prompts.append(task["prompt"]) # save full prompt
|
||||
references.append(task["metadata"]["ground_truth"])
|
||||
docs.append(get_top_docs(
|
||||
results=results, corpus=corpus, task_id=task["metadata"]["task_id"],
|
||||
))
|
||||
metadatas.append(task["metadata"])
|
||||
assert len(prompts) == len(references) == len(docs)
|
||||
dataset = [
|
||||
{"prompt": p, "reference": r, "docs": d, "metadata": m}
|
||||
for p, r, d, m in zip(prompts, references, docs, metadatas)
|
||||
]
|
||||
with open(args.results_file, "a") as fout:
|
||||
for curr in dataset:
|
||||
fout.write(json.dumps(curr) + "\n")
|
||||
else:
|
||||
raise ValueError(f"`dataset` should starts with either 'swe-bench' or 'repoeval'.")
|
||||
|
||||
# evaluate retrieval results
|
||||
if len(qrels) == 0:
|
||||
logging.info("No qrels found for this dataset.")
|
||||
return
|
||||
logging.info("Retriever evaluation for k in: {}".format(retriever.k_values))
|
||||
ndcg, _map, recall, precision = retriever.evaluate(qrels, results, retriever.k_values)
|
||||
mrr = retriever.evaluate_custom(qrels, results, retriever.k_values, metric="mrr")
|
||||
eval_results = {
|
||||
"ndcg": ndcg, "mrr": mrr,
|
||||
"recall": recall, "precision": precision,
|
||||
"time": end_time - start_time
|
||||
}
|
||||
logging.info(f"Instance #{ins_dir}: {eval_results}")
|
||||
all_eval_results.append(eval_results)
|
||||
|
||||
with open(args.output_file + "_all", "w") as f:
|
||||
json.dump(all_eval_results, f)
|
||||
|
||||
if args.dataset.startswith("swe-bench"):
|
||||
swebench = swebench.add_column("docs", all_top_docs)
|
||||
swebench.to_json(args.results_file)
|
||||
|
||||
avg_eval_results = {}
|
||||
for k, v_dict in all_eval_results[0].items():
|
||||
if isinstance(v_dict, dict):
|
||||
avg_v_dict = {}
|
||||
for vk, vv in v_dict.items():
|
||||
avg_vv = sum([e[k][vk] for e in all_eval_results]) / len(all_eval_results)
|
||||
avg_v_dict[vk] = avg_vv
|
||||
avg_eval_results.update(avg_v_dict)
|
||||
elif isinstance(v_dict, float):
|
||||
avg_v = sum([e[k] for e in all_eval_results]) / len(all_eval_results)
|
||||
avg_eval_results[k] = avg_v
|
||||
else:
|
||||
raise ValueError
|
||||
print("Average Eval Results: ", avg_eval_results)
|
||||
with open(args.output_file, "w") as f:
|
||||
json.dump(avg_eval_results, f)
|
||||
else:
|
||||
dataset = args.dataset
|
||||
corpus, queries, qrels = GenericDataLoader(data_folder=os.path.join("datasets", args.dataset)).load(
|
||||
split="test")
|
||||
#### Retrieve dense results (format of results is identical to qrels)
|
||||
start_time = time()
|
||||
results = retriever.retrieve(corpus, queries)
|
||||
end_time = time()
|
||||
print("Time taken to retrieve: {:.2f} seconds".format(end_time - start_time))
|
||||
|
||||
if args.dataset in ["humaneval", "mbpp", "apps"]:
|
||||
if args.dataset == "humaneval":
|
||||
ds = load_dataset("openai_humaneval")
|
||||
id_key = "task_id"
|
||||
elif args.dataset == "mbpp":
|
||||
ds = load_dataset("mbpp")
|
||||
id_key = "task_id"
|
||||
elif args.dataset == "apps":
|
||||
ds = load_dataset("codeparrot/apps")
|
||||
id_key = "problem_id"
|
||||
all_top_docs = []
|
||||
for task_id in ds["test"][id_key]:
|
||||
all_top_docs.append(get_top_docs(results, corpus, f"{task_id}_doc"))
|
||||
ds["test"] = ds["test"].add_column("docs", all_top_docs)
|
||||
ds["test"].to_json(args.results_file) # this outputs to arrow format and read as .jsonl
|
||||
elif args.dataset.startswith("odex"):
|
||||
lang = args.dataset.split("_")[-1]
|
||||
ds = load_dataset("neulab/odex", lang, trust_remote_code=True)
|
||||
all_top_docs = []
|
||||
for idx, task_id in enumerate(ds["test"]["task_id"]):
|
||||
all_top_docs.append(get_top_docs(results, corpus, f"{idx}_{task_id}"))
|
||||
ds["test"] = ds["test"].add_column("docs", all_top_docs)
|
||||
ds["test"].to_json(args.results_file) # this outputs to arrow format and read as .jsonl
|
||||
elif args.dataset.startswith("ds1000"):
|
||||
_, key, mode = args.dataset.split("_")
|
||||
key = key.capitalize()
|
||||
mode = mode.capitalize()
|
||||
from create.ds1000 import get_dataset
|
||||
source_dir = pathlib.Path(__file__).parent / "ds"
|
||||
data = get_dataset(source_dir, mode=mode, key=key)
|
||||
all_docs = []
|
||||
example_ids = []
|
||||
for item in data:
|
||||
example = item.data
|
||||
example_id = f"{example['lib']}_{example['perturbation_origin_id']}"
|
||||
all_docs.append(get_top_docs(results, corpus, example_id))
|
||||
example_ids.append(example_id)
|
||||
assert len(all_docs) == len(
|
||||
example_ids), f"length of all_docs should be {len(example_ids)}, now is {len(all_docs)}"
|
||||
with open(args.results_file, "w+") as fout:
|
||||
for idx, all_doc in enumerate(all_docs):
|
||||
fout.write(json.dumps({"example_id": example_id,
|
||||
"docs": all_doc}) + "\n")
|
||||
else:
|
||||
with open(args.results_file, 'w+') as fw:
|
||||
for curr in results:
|
||||
fw.write(json.dumps({curr: results[curr]}) + "\n")
|
||||
|
||||
#### Evaluate your retrieval using NDCG@k, MAP@K ...
|
||||
if len(qrels) == 0:
|
||||
logging.info("No qrels found for this dataset.")
|
||||
return
|
||||
logging.info("Retriever evaluation for k in: {}".format(retriever.k_values))
|
||||
ndcg, _map, recall, precision = retriever.evaluate(qrels, results, retriever.k_values)
|
||||
|
||||
mrr = retriever.evaluate_custom(qrels, results, retriever.k_values, metric="mrr")
|
||||
recall_cap = retriever.evaluate_custom(qrels, results, retriever.k_values, metric="r_cap")
|
||||
hole = retriever.evaluate_custom(qrels, results, retriever.k_values, metric="hole")
|
||||
|
||||
all_results = {"ndcg": ndcg, "mrr": mrr, "recall": recall, "precision": precision,
|
||||
"time": end_time - start_time}
|
||||
with open(args.output_file, "w") as f:
|
||||
json.dump(all_results, f)
|
||||
#### Print top-k documents retrieved ####
|
||||
top_k = 3
|
||||
|
||||
query_id, ranking_scores = random.choice(list(results.items()))
|
||||
scores_sorted = sorted(ranking_scores.items(), key=lambda item: item[1], reverse=True)
|
||||
logging.info("Query : %s\n" % queries[query_id])
|
||||
|
||||
for rank in range(top_k):
|
||||
doc_id = scores_sorted[rank][0]
|
||||
# Format: Rank x: ID [Title] Body
|
||||
logging.info(
|
||||
"Rank %d: %s [%s] - %s\n" % (rank + 1, doc_id, corpus[doc_id].get("title"), corpus[doc_id].get("text")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = HfArgumentParser((
|
||||
CodeRAGEvalArgs,
|
||||
CodeRAGEvalModelArgs
|
||||
))
|
||||
eval_args, model_args = parser.parse_args_into_dataclasses()
|
||||
main(eval_args, model_args)
|
||||
@@ -0,0 +1,18 @@
|
||||
from typing import Dict
|
||||
|
||||
|
||||
def get_task_def_by_task_name(task_name: str) -> str:
|
||||
task_name_to_instruct: Dict[str, str] = {
|
||||
'humaneval': 'Given a question that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.',
|
||||
'mbpp': 'Given a textual explanation of code functionality, retrieve the corresponding code implementation.',
|
||||
'ds1000_all_completion': 'Given a question that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.',
|
||||
'odex_en': 'Given a question, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.',
|
||||
'odex_es': 'Given a question, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.',
|
||||
'odex_ja': 'Given a question, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.',
|
||||
'odex_ru': 'Given a question, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.',
|
||||
'repoeval': 'Given a code snippet and a new function name, retrieve the implementation of the function.',
|
||||
# 'repoeval': 'Given a piece of code segment, retrieve the code segment that is the latter part of the code.',
|
||||
'swe-bench-lite': 'Given a code snippet containing a bug and a natural language description of the bug or error, retrieve code snippets that demonstrate solutions or fixes for similar bugs or errors (the desired documents).'
|
||||
}
|
||||
|
||||
return task_name_to_instruct[task_name]
|
||||
@@ -0,0 +1,69 @@
|
||||
from typing import List
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from FlagEmbedding.abc.evaluation import (
|
||||
AbsEvalModelArgs as COIREvalModelArgs,
|
||||
)
|
||||
|
||||
|
||||
def coir_tasks():
|
||||
return [
|
||||
"apps",
|
||||
"codefeedback-mt",
|
||||
"codefeedback-st",
|
||||
"CodeSearchNet-ccr-go",
|
||||
"CodeSearchNet-ccr-java",
|
||||
"CodeSearchNet-ccr-javascript",
|
||||
"CodeSearchNet-ccr-php",
|
||||
"CodeSearchNet-ccr-python",
|
||||
"CodeSearchNet-ccr-ruby",
|
||||
"CodeSearchNet-go",
|
||||
"CodeSearchNet-java",
|
||||
"CodeSearchNet-javascript",
|
||||
"CodeSearchNet-php",
|
||||
"CodeSearchNet-python",
|
||||
"CodeSearchNet-ruby",
|
||||
"codetrans-contest",
|
||||
"codetrans-dl",
|
||||
"cosqa",
|
||||
"stackoverflow-qa",
|
||||
"synthetic-text2sql"
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class COIREvalArgs:
|
||||
output_dir: str = field(
|
||||
default="./results", metadata={"help": "Path to save results."}
|
||||
)
|
||||
tasks: List[str] = field(
|
||||
default_factory=coir_tasks,
|
||||
metadata={
|
||||
"help": "Tasks to evaluate. Default: None. Available tasks: ['apps', 'codefeedback-mt', 'codefeedback-st', 'CodeSearchNet-ccr-go', 'CodeSearchNet-ccr-java', 'CodeSearchNet-ccr-javascript', 'CodeSearchNet-ccr-php', 'CodeSearchNet-ccr-python', 'CodeSearchNet-ccr-ruby', 'CodeSearchNet-go', 'CodeSearchNet-java', 'CodeSearchNet-javascript', 'CodeSearchNet-php', 'CodeSearchNet-python', 'CodeSearchNet-ruby', 'codetrans-contest', 'codetrans-dl', 'cosqa', 'stackoverflow-qa', 'synthetic-text2sql']",
|
||||
"choices": [
|
||||
"apps",
|
||||
"codefeedback-mt",
|
||||
"codefeedback-st",
|
||||
"CodeSearchNet-ccr-go",
|
||||
"CodeSearchNet-ccr-java",
|
||||
"CodeSearchNet-ccr-javascript",
|
||||
"CodeSearchNet-ccr-php",
|
||||
"CodeSearchNet-ccr-python",
|
||||
"CodeSearchNet-ccr-ruby",
|
||||
"CodeSearchNet-go",
|
||||
"CodeSearchNet-java",
|
||||
"CodeSearchNet-javascript",
|
||||
"CodeSearchNet-php",
|
||||
"CodeSearchNet-python",
|
||||
"CodeSearchNet-ruby",
|
||||
"codetrans-contest",
|
||||
"codetrans-dl",
|
||||
"cosqa",
|
||||
"stackoverflow-qa",
|
||||
"synthetic-text2sql"
|
||||
]
|
||||
}
|
||||
)
|
||||
use_special_instructions: bool = field(
|
||||
default=False, metadata={"help": "Whether to use specific instructions in `prompts.py` for evaluation. Default: False"}
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
output_dir=result
|
||||
|
||||
python main.py \
|
||||
--output_dir ${output_dir} \
|
||||
--use_special_instructions True \
|
||||
--embedder_name_or_path BAAI/bge-code-v1 \
|
||||
--embedder_model_class decoder-only-base \
|
||||
--query_instruction_format_for_retrieval '<instruct>{}\n<query>{}' \
|
||||
--embedder_query_max_length 2048 \
|
||||
--embedder_passage_max_length 2048 \
|
||||
--trust_remote_code True \
|
||||
--pooling_method last_token \
|
||||
--embedder_batch_size 64 \
|
||||
--devices cuda:0 cuda:1 cuda:2 cuda:3 cuda:4 cuda:5 cuda:6 cuda:7 \
|
||||
--tasks apps codetrans-contest codetrans-dl cosqa synthetic-text2sql stackoverflow-qa codefeedback-mt codefeedback-st CodeSearchNet-ccr-go CodeSearchNet-ccr-java CodeSearchNet-ccr-javascript CodeSearchNet-ccr-php CodeSearchNet-ccr-python CodeSearchNet-ccr-ruby CodeSearchNet-go CodeSearchNet-java CodeSearchNet-javascript CodeSearchNet-php CodeSearchNet-python CodeSearchNet-ruby \
|
||||
--cache_dir ./cache
|
||||
@@ -0,0 +1,188 @@
|
||||
import os
|
||||
import json
|
||||
import coir
|
||||
from transformers import HfArgumentParser
|
||||
|
||||
from arguments import COIREvalArgs, COIREvalModelArgs
|
||||
from prompts import get_task_def_by_task_name
|
||||
from FlagEmbedding import FlagLLMModel, FlagModel, FlagPseudoMoEModel
|
||||
|
||||
|
||||
def get_model(model_args: COIREvalModelArgs):
|
||||
embedder_name_or_path = model_args.embedder_name_or_path
|
||||
|
||||
if model_args.embedder_model_class == "encoder-only-base":
|
||||
embedder = FlagModel(
|
||||
model_name_or_path=embedder_name_or_path,
|
||||
normalize_embeddings=model_args.normalize_embeddings,
|
||||
pooling_method=model_args.pooling_method,
|
||||
use_fp16=model_args.use_fp16,
|
||||
use_bf16=model_args.use_bf16,
|
||||
query_instruction_for_retrieval=model_args.query_instruction_for_retrieval,
|
||||
query_instruction_format=model_args.query_instruction_format_for_retrieval,
|
||||
devices=model_args.devices,
|
||||
trust_remote_code=model_args.trust_remote_code,
|
||||
cache_dir=model_args.cache_dir,
|
||||
batch_size=model_args.embedder_batch_size,
|
||||
query_max_length=model_args.embedder_query_max_length,
|
||||
passage_max_length=model_args.embedder_passage_max_length,
|
||||
)
|
||||
elif model_args.embedder_model_class == "decoder-only-base":
|
||||
embedder = FlagLLMModel(
|
||||
model_name_or_path=embedder_name_or_path,
|
||||
normalize_embeddings=model_args.normalize_embeddings,
|
||||
pooling_method=model_args.pooling_method,
|
||||
use_fp16=model_args.use_fp16,
|
||||
use_bf16=model_args.use_bf16,
|
||||
query_instruction_for_retrieval=model_args.query_instruction_for_retrieval,
|
||||
query_instruction_format=model_args.query_instruction_format_for_retrieval,
|
||||
devices=model_args.devices,
|
||||
examples_for_task=model_args.examples_for_task,
|
||||
examples_instruction_format=model_args.examples_instruction_format,
|
||||
trust_remote_code=model_args.trust_remote_code,
|
||||
cache_dir=model_args.cache_dir,
|
||||
batch_size=model_args.embedder_batch_size,
|
||||
query_max_length=model_args.embedder_query_max_length,
|
||||
passage_max_length=model_args.embedder_passage_max_length,
|
||||
)
|
||||
elif model_args.embedder_model_class == "decoder-only-pseudo_moe":
|
||||
embedder = FlagPseudoMoEModel(
|
||||
model_name_or_path=embedder_name_or_path,
|
||||
normalize_embeddings=model_args.normalize_embeddings,
|
||||
pooling_method=model_args.pooling_method,
|
||||
use_fp16=model_args.use_fp16,
|
||||
use_bf16=model_args.use_bf16,
|
||||
query_instruction_for_retrieval=model_args.query_instruction_for_retrieval,
|
||||
query_instruction_format=model_args.query_instruction_format_for_retrieval,
|
||||
devices=model_args.devices,
|
||||
examples_for_task=model_args.examples_for_task,
|
||||
examples_instruction_format=model_args.examples_instruction_format,
|
||||
trust_remote_code=model_args.trust_remote_code,
|
||||
cache_dir=model_args.cache_dir,
|
||||
batch_size=model_args.embedder_batch_size,
|
||||
query_max_length=model_args.embedder_query_max_length,
|
||||
passage_max_length=model_args.embedder_passage_max_length,
|
||||
domain_for_pseudo_moe=model_args.domain_for_pseudo_moe,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Invalid model class: {model_args.embedder_model_class}")
|
||||
embedder.model.config._name_or_path = model_args.embedder_name_or_path
|
||||
|
||||
class CustomFlagModel:
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
|
||||
def encode_queries(self, queries, show_progress_bar, convert_to_tensor, **kwargs):
|
||||
if isinstance(queries, str):
|
||||
queries = [queries]
|
||||
|
||||
if isinstance(queries[0], dict):
|
||||
queries = [(e.get('title') + ' ' + e['text']).strip() for e in queries]
|
||||
|
||||
return self.model.encode_queries(queries, **kwargs)
|
||||
|
||||
def encode_corpus(self, corpus, show_progress_bar, convert_to_tensor, **kwargs):
|
||||
if isinstance(corpus, str):
|
||||
corpus = [corpus]
|
||||
|
||||
if isinstance(corpus[0], dict):
|
||||
corpus = [(e.get('title') + ' ' + e['text']).strip() for e in corpus]
|
||||
|
||||
return self.model.encode_corpus(corpus, **kwargs)
|
||||
|
||||
def encode(self, corpus, show_progress_bar, convert_to_tensor, **kwargs):
|
||||
if isinstance(corpus, str):
|
||||
corpus = [corpus]
|
||||
|
||||
if isinstance(corpus[0], dict):
|
||||
corpus = [(e.get('title') + ' ' + e['text']).strip() for e in corpus]
|
||||
|
||||
return self.model.encode(corpus, **kwargs)
|
||||
|
||||
return CustomFlagModel(embedder)
|
||||
|
||||
|
||||
def main(
|
||||
eval_args: COIREvalArgs,
|
||||
model_args: COIREvalModelArgs
|
||||
):
|
||||
model = get_model(model_args)
|
||||
|
||||
output_folder = os.path.join(eval_args.output_dir, os.path.basename(model.model.model.config._name_or_path))
|
||||
|
||||
all_task = eval_args.tasks
|
||||
if not isinstance(all_task, list):
|
||||
all_task = [all_task]
|
||||
|
||||
all_results = {}
|
||||
for task_name in all_task:
|
||||
save_path = os.path.join(output_folder, f"{task_name}.json")
|
||||
if os.path.exists(save_path):
|
||||
with open(save_path, "r", encoding="utf-8") as f:
|
||||
results = json.load(f)
|
||||
all_results[task_name] = results['metrics']
|
||||
continue
|
||||
|
||||
tmp_task = coir.get_tasks(tasks=[task_name])
|
||||
evaluation = coir.COIR(tasks=tmp_task,
|
||||
batch_size=model_args.embedder_batch_size)
|
||||
|
||||
model.model.stop_self_pool()
|
||||
|
||||
if eval_args.use_special_instructions:
|
||||
model.model.query_instruction_for_retrieval = get_task_def_by_task_name(task_name)
|
||||
|
||||
results = evaluation.run(model, output_folder=output_folder)
|
||||
all_results[task_name] = results[task_name]
|
||||
|
||||
csn_result = 0
|
||||
csn_num = 0
|
||||
csn_ccr_result = 0
|
||||
csn_ccr_num = 0
|
||||
pop_keys = []
|
||||
all_result = 0
|
||||
all_num = 0
|
||||
for k in all_results.keys():
|
||||
if 'CodeSearchNet-ccr' in k:
|
||||
csn_ccr_result += all_results[k]['NDCG']['NDCG@10']
|
||||
csn_ccr_num += 1
|
||||
pop_keys.append(k)
|
||||
elif 'CodeSearchNet' in k:
|
||||
csn_result += all_results[k]['NDCG']['NDCG@10']
|
||||
csn_num += 1
|
||||
pop_keys.append(k)
|
||||
else:
|
||||
all_result += all_results[k]['NDCG']['NDCG@10']
|
||||
all_num += 1
|
||||
if csn_num > 0:
|
||||
print('Using CodeSearchNet')
|
||||
all_result += csn_result / csn_num
|
||||
all_num += 1
|
||||
if csn_ccr_num > 0:
|
||||
print('Using CodeSearchNet-ccr')
|
||||
all_result += csn_ccr_result / csn_ccr_num
|
||||
all_num += 1
|
||||
new_results = {}
|
||||
for k in all_results:
|
||||
if k in pop_keys:
|
||||
continue
|
||||
new_results[k] = all_results[k]['NDCG']['NDCG@10']
|
||||
if csn_num > 0:
|
||||
new_results['CodeSearchNet'] = csn_result / csn_num
|
||||
if csn_ccr_num > 0:
|
||||
new_results['CodeSearchNet_CCR'] = csn_ccr_result / csn_ccr_num
|
||||
new_results['all'] = all_result / all_num
|
||||
|
||||
print(new_results)
|
||||
|
||||
with open(os.path.join(output_folder, 'OVERALL-results.json'), 'w') as f:
|
||||
json.dump(new_results, f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = HfArgumentParser((
|
||||
COIREvalArgs,
|
||||
COIREvalModelArgs
|
||||
))
|
||||
eval_args, model_args = parser.parse_args_into_dataclasses()
|
||||
main(eval_args, model_args)
|
||||
@@ -0,0 +1,38 @@
|
||||
from typing import Dict
|
||||
|
||||
|
||||
def get_task_def_by_task_name(task_name: str) -> str:
|
||||
task_name_to_instruct: Dict[str, str] = {
|
||||
# Text-to-Code Retrieval
|
||||
## Code Contest Retrieval
|
||||
'apps': 'Given a code contest problem description, retrieve relevant code that can help solve the problem.',
|
||||
## Web Query to Code Retrieval
|
||||
'cosqa': 'Given a web search query, retrieve relevant code that can help answer the query.',
|
||||
## Text-to-SQL Retrieval
|
||||
'synthetic-text2sql': 'Given a question in text, retrieve SQL queries that are appropriate responses to the question.',
|
||||
|
||||
# Code-to-Text Retrieval
|
||||
## Code Summary Retrieval
|
||||
'CodeSearchNet-': 'Given a piece of code, retrieve the document string that summarizes the code.',
|
||||
|
||||
# Code-to-Code Retrieval
|
||||
## Code Context Retrieval
|
||||
'CodeSearchNet-ccr-': 'Given a piece of code segment, retrieve the code segment that is the latter part of the code.',
|
||||
## Similar Code Retrieval
|
||||
'codetrans-dl': 'Given a piece of code, retrieve code that is semantically equivalent to the input code.',
|
||||
'codetrans-contest': 'Given a piece of Python code, retrieve C++ code that is semantically equivalent to the input code.',
|
||||
|
||||
# Hybrid Code Retrieval
|
||||
## Single-turn Code QA
|
||||
'stackoverflow-qa': 'Given a question that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.',
|
||||
'codefeedback-st': 'Given a question that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.',
|
||||
## Multi-turn Code QA
|
||||
'codefeedback-mt': 'Given a multi-turn conversation history that consists of a mix of text and code snippets, retrieve relevant answers that also consist of a mix of text and code snippets, and can help answer the question.',
|
||||
}
|
||||
|
||||
special_task_names = ['CodeSearchNet-ccr-', 'CodeSearchNet-']
|
||||
for special_task_name in special_task_names:
|
||||
if special_task_name in task_name:
|
||||
return task_name_to_instruct[special_task_name]
|
||||
|
||||
return task_name_to_instruct[task_name]
|
||||
Binary file not shown.
Reference in New Issue
Block a user