Genera, verifica e riranga funzioni Python con test unitari e controlli di sicurezza utilizzando CodeGen
Intro all'Uso di Salesforce CodeGen
In questo tutorial implementiamo un flusso di lavoro completo per utilizzare CodeGen di Salesforce. Caricheremo un modello CodeGen da Hugging Face, lo prepareremo per la generazione di codice e utilizziamo per generare funzioni Python da prompt in linguaggio naturale. Procederemo al di là dell'inferenza di base aggiungendo estrazione di funzioni, controllo della sintassi, controlli di sicurezza statici, validazione basata sui test unitari, rideterminazione dei candidati best-of-N, sintesi di programmi multipasso, esperimenti su stili di prompt, visualizzazione dei benchmark, e l'esportazione di artefatti.
Il flusso di lavoro ci insegnerà come usare CodeGen non solo come modello di completamento del codice ma anche come parte di un pipeline di generazione strutturata che valuta, filtra e organizza le soluzioni generate.
Caricamento del Modello CodeGen di Salesforce da Hugging Face
Per iniziare, installiamo le librerie necessarie e prepariamo l'ambiente esecutivo per eseguire CodeGen di Salesforce. Controlliamo l'ambiente, rileviamo la disponibilità del GPU, selezioniamo il modello CodeGen e carichiamo sia il tokenize che il modello stesso da Hugging Face. Definiamo anche delle funzioni di utilità per la generazione di testo e la visualizzazione formattata del codice, per facilitare l'esperienza del tutorial.
Il seguente codice esegue il caricamento delle librerie necessarie e prepara l'ambiente:
import os, sys, subprocess, textwrap, json, re, time, math, ast, tempfile, multiprocessing as mp
from pathlib import Path
def sh(cmd):
print(f"\n$ {cmd}")
subprocess.run(cmd, shell=True, check=True)
sh(f"{sys.executable} -m pip install -q -U transformers accelerate safetensors einops datasets evaluate pandas matplotlib tqdm rich radon tiktoken")
import torch
import pandas as pd
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
from rich import print
from rich.panel import Panel
from rich.syntax import Syntax
from transformers import AutoTokenizer, AutoModelForCausalLM, set_seed
from radon.complexity import cc_visit
Successivamente, verificheremo le informazioni sull'esecuzione, le informazioni sull'architettura CUDA, caricheremo il tokenizer, il modello e inizieremo ad utilizzare le funzioni:
OUTDIR = Path("/content/codegenadvanced_tutorial")
set_seed(42)
print(Panel.fit("Salesforce CodeGen Advanced Tutorial", style="bold green"))
print("\nRuntime information")
print("Python:", sys.version.split()[0])
print("Torch:", torch._version_)
print("CUDA available:", torch.cuda.is_available())
if torch.cuda.is_available():
print("GPU:", torch.cuda.getdevicename(0))
print("CUDA memoria in GB:", round(torch.cuda.getdeviceproperties(0).total_memory / 1e9, 2))
Implementiamo Strumenti per L'Validazione e la Sicurezza
Creiamo funzioni utili per estrarre la funzione da un testo, per controllare la sintassi, per effettuare controlli di sicurezza statici e per eseguire i test unitari. Queste utility saranno essenziali per il pipeline della generazione di codice avanzati.
Estrazione delle Funzioni dal Testo
La seguente funzione extractfunctionsource cerca nel testo fornito una funzione con un nome specifico:
def extractfunctionsource(fulltext, functionname):
text = full_text.replace("\r\n", "\n")
fence = re.search(r"```(?:python)?\n(.*?)```", text, flags=re.S | re.I)
if fence:
text = fence.group(1)
pattern = rf"^def\s+{re.escape(function_name)}\s*\("
match = re.search(pattern, text, flags=re.M)
if not match:
return ""
chunk = text[match.start():]
lines = chunk.splitlines()
collected = []
for i, line in enumerate(lines):
if i > 0:
if line.startswith("def ") or line.startswith("class "):
break
if line.startswith("if _name_"):
break
if line and not line.startswith((" ", "\t", "#")) and re.match(r"^[A-Za-z][A-Za-z0-9]\s=", line):
break
collected.append(line)
source = "\n".join(collected).rstrip()
try:
ast.parse(source)
return source
except SyntaxError:
fixed_lines = []
for line in collected:
fixed_lines.append(line)
candidate = "\n".join(fixed_lines).rstrip()
try:
ast.parse(candidate)
source = candidate
except SyntaxError:
pass
return source if source.strip().startswith("def ") else ""
Controllo della Sintassi
La funzione syntax_ok verifica se la funzione generata è esente da errori di sintassi:
def syntax_ok(source):
try:
ast.parse(source)
return True, ""
except SyntaxError as e:
return False, str(e)
Controlli di Sicurezza Statici
La funzione staticsafetycheck esegue un'analisi statica per verificare la sicurezza:
FORBIDDEN_NAMES = {
"eval", "exec", "compile", "open", "input", "_import_",
"globals", "locals", "vars", "dir", "getattr", "setattr", "delattr",
"help", "breakpoint", "exit", "quit"
}
FORBIDDEN_NODES = (
ast.Import,
ast.ImportFrom,
ast.Global,
ast.Nonlocal,
ast.With,
ast.AsyncWith,
ast.AsyncFunctionDef,
ast.ClassDef,
ast.Delete,
ast.Raise,
)
ALLOWED_BUILTINS = {
"abs": abs,
"all": all,
"any": any,
"bool": bool,
"...": "...",
}
def staticsafetycheck(source):
try:
tree = ast.parse(source)
except SyntaxError as e:
return False, f"SyntaxError: {e}"
for node in ast.walk(tree):
if isinstance(node, FORBIDDEN_NODES):
return False, f"Forbidden AST node: {type(node)._name_}"
if isinstance(node, ast.Name):
if node.id in FORBIDDENNAMES or node.id.startswith("_"):
return False, f"Forbidden name: {node.id}"
if isinstance(node, ast.Attribute):
if node.attr.startswith("__"):
return False, f"Forbidden attribute: {node.attr}"
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name) and node.func.id in FORBIDDEN_NAMES:
return False, f"Forbidden call: {node.func.id}"
return True, "passed"
Conclusione
Attraverso la combinazione delle utilità sopra descritte, possiamo creare una pipeline avanzata per CodeGen che non solo genera codice, ma lo valuta in termini di correttezza e sicurezza, rango i candidati e li presenta in modo organizzato. Tutto questo rende CodeGen uno strumento molto potente quando utilizzato in un contesto strutturato e avanzato di generazione di codice.