Files
patchy631--ai-engineering-hub/o3-vs-claude-code/Opik for LLM evaluation.ipynb
T
2026-07-13 12:37:47 +08:00

386 lines
13 KiB
Plaintext

{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import opik\n",
"opik.configure(use_local=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from dotenv import load_dotenv\n",
"load_dotenv()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import re\n",
"import glob\n",
"import subprocess\n",
"\n",
"from IPython.display import Markdown, display\n",
"\n",
"from llama_index.core import Settings\n",
"from llama_index.llms.openai import OpenAI\n",
"\n",
"from llama_index.core import PromptTemplate\n",
"from llama_index.embeddings.huggingface import HuggingFaceEmbedding\n",
"from llama_index.core import VectorStoreIndex, ServiceContext, SimpleDirectoryReader\n",
"\n",
"\n",
"from llama_index.core import Settings\n",
"from llama_index.core import PromptTemplate\n",
"from llama_index.core import SimpleDirectoryReader\n",
"from llama_index.core import VectorStoreIndex\n",
"from llama_index.core.storage.storage_context import StorageContext\n",
"from llama_index.core.node_parser import CodeSplitter, MarkdownNodeParser\n",
"from llama_index.llms.openai import OpenAI\n",
"from llama_index.llms.anthropic import Anthropic\n",
"from llama_index.core.indices.vector_store.base import VectorStoreIndex\n",
"from llama_index.vector_stores.qdrant import QdrantVectorStore\n",
"from llama_index.embeddings.fastembed import FastEmbedEmbedding\n",
"from llama_index.core import Settings"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Trace RAG calls "
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"from llama_index.core import Settings\n",
"from llama_index.core.callbacks import CallbackManager\n",
"from opik.integrations.llama_index import LlamaIndexCallbackHandler\n",
"\n",
"# A callback handler tp automatically log all LlamaIndex operations to Opik\n",
"opik_callback_handler = LlamaIndexCallbackHandler()\n",
"\n",
"# Integrate handler into LlamaIndex's settings\n",
"Settings.callback_manager = CallbackManager([opik_callback_handler])"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [],
"source": [
"\n",
"# Step 2: Define helper functions\n",
"def parse_github_url(url):\n",
" \"\"\"Extract owner and repo name from GitHub URL\"\"\"\n",
" pattern = r\"https://github\\.com/([^/]+)/([^/]+)\"\n",
" match = re.match(pattern, url)\n",
" return match.groups() if match else (None, None)\n",
"\n",
"def clone_repo(repo_url):\n",
" \"\"\"Clone a GitHub repository\"\"\"\n",
" return subprocess.run([\"git\", \"clone\", repo_url], check=True, text=True, capture_output=True)\n",
"\n",
"def parse_docs_by_file_types(ext, language, input_dir_path):\n",
" \"\"\"Parse documents based on file extension\"\"\"\n",
" files = glob.glob(f\"{input_dir_path}/**/*{ext}\", recursive=True)\n",
" \n",
" if len(files) > 0:\n",
" print(f\"Found {len(files)} files with extension {ext}\")\n",
" loader = SimpleDirectoryReader(\n",
" input_dir=input_dir_path, required_exts=[ext], recursive=True\n",
" )\n",
" docs = loader.load_data()\n",
" parser = (\n",
" MarkdownNodeParser()\n",
" if ext == \".md\"\n",
" else CodeSplitter.from_defaults(language=language)\n",
" )\n",
" nodes = parser.get_nodes_from_documents(docs)\n",
" print(f\"Processed {len(nodes)} nodes from {ext} files\")\n",
" return nodes\n",
" return []\n",
"\n",
"def setup_chat_engine(github_url, model_provider=\"OpenAI o3-mini\"):\n",
" \"\"\"\n",
" Set up the chat engine for a GitHub repository\n",
" Args:\n",
" github_url: URL of the GitHub repository\n",
" model_provider: 'openai' or 'anthropic'\n",
" \"\"\"\n",
" # Step 3: Process GitHub URL\n",
" owner, repo = parse_github_url(github_url)\n",
" if not owner or not repo:\n",
" raise ValueError(\"Invalid GitHub URL\")\n",
" \n",
" print(f\"\\nProcessing repository: {owner}/{repo}\")\n",
" input_dir_path = f\"./{repo}\"\n",
"\n",
" # Step 4: Clone repository if it doesn't exist\n",
" if not os.path.exists(input_dir_path):\n",
" print(\"\\nCloning repository...\")\n",
" clone_repo(github_url)\n",
"\n",
" # Step 5: Define file types to process\n",
" file_types = {\n",
" \".md\": \"markdown\",\n",
" \".py\": \"python\",\n",
" \".ipynb\": \"python\",\n",
" \".js\": \"javascript\",\n",
" \".ts\": \"typescript\"\n",
" }\n",
"\n",
" # Step 6: Process all files\n",
" print(\"\\nProcessing files...\")\n",
" nodes = []\n",
" for ext, language in file_types.items():\n",
" nodes += parse_docs_by_file_types(ext, language, input_dir_path)\n",
"\n",
" if not nodes:\n",
" raise ValueError(\"No files were processed from the repository\")\n",
"\n",
" # Step 7: Setup embedding model\n",
" print(\"\\nSetting up embedding model...\")\n",
" # Settings.embed_model = FastEmbedEmbedding(model_name=\"BAAI/bge-base-en-v1.5\")\n",
"\n",
" # Step 8: Create index\n",
" print(\"Creating vector index...\")\n",
" index = VectorStoreIndex(nodes=nodes)\n",
"\n",
" # Step 9: Setup LLM and query engine\n",
" if model_provider == \"OpenAI o3-mini\":\n",
" Settings.llm = OpenAI(model=\"o3-mini\")\n",
" elif model_provider == \"Claude 3.7 Sonnet\":\n",
" Settings.llm = Anthropic(model=\"claude-3-7-sonnet-20250219\")\n",
" elif model_provider == \"Claude 3.5 Sonnet\":\n",
" Settings.llm = Anthropic(model=\"claude-3-5-sonnet-20240620\")\n",
"\n",
" query_engine = index.as_query_engine(streaming=True, similarity_top_k=4)\n",
"\n",
" # Step 10: Setup custom prompt template\n",
" qa_prompt_tmpl_str = (\n",
" \"Context information is below.\\n\"\n",
" \"---------------------\\n\"\n",
" \"{context_str}\\n\"\n",
" \"---------------------\\n\"\n",
" \"Given the context information above, you must always include a code snippet in your response.\\n\"\n",
" \"Think step by step to answer the query, and then provide a relevant code example that demonstrates the concept.\\n\"\n",
" \"Even if the question seems conceptual, translate your answer into a practical code example.\\n\"\n",
" \"If you don't know the answer, say 'I don't know!' but still provide a minimal code example of what you think might work.\\n\"\n",
" \"Query: {query_str}\\n\"\n",
" \"Answer: \"\n",
" )\n",
" qa_prompt_tmpl = PromptTemplate(qa_prompt_tmpl_str)\n",
" query_engine.update_prompts(\n",
" {\"response_synthesizer:text_qa_template\": qa_prompt_tmpl}\n",
" )\n",
"\n",
" print(\"\\nChat engine setup complete! Ready for questions.\")\n",
" return query_engine"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model_name = 'Claude 3.7 Sonnet'\n",
"github_url = \"https://github.com/Lightning-AI/LitServe\"\n",
"query_engine = setup_chat_engine(github_url, model_provider=model_name)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = query_engine.query(\"What is this repo about?\") \n",
"print(response)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Evaluation"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [],
"source": [
"from opik import Opik\n",
"\n",
"client = Opik()\n",
"dataset = client.get_or_create_dataset(name=\"Eval Code Generation\")"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [],
"source": [
"from opik import track\n",
"\n",
"@track\n",
"def my_llm_application(input: str) -> str:\n",
" response = query_engine.query(input)\n",
" return str(response)\n",
"\n",
"def evaluation_task(x):\n",
" return {\n",
" \"output\": my_llm_application(x['input'])\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [],
"source": [
"from opik.evaluation.metrics import base_metric, score_result\n",
"from openai import OpenAI\n",
"from typing import Any\n",
"import json\n",
"\n",
"class LLMJudgeMetric(base_metric.BaseMetric):\n",
" def __init__(self, name: str = \"Code Quality Evaluation\", model_name: str = \"gpt-4o\"):\n",
" self.name = name\n",
" self.llm_client = OpenAI()\n",
" self.model_name = model_name\n",
" self.prompt_template = \"\"\"\n",
" You are an expert judge tasked with evaluating the quality of code generation by comparing the AI-generated code to the ground truth code.\n",
" \n",
" Evaluate how well the AI-generated code matches the ground truth code in terms of:\n",
" 1. Correctness: Does the generated code implement the same functionality?\n",
" 2. Completeness: Does the generated code include all necessary components?\n",
" 3. Efficiency: Is the generated code similarly efficient in its approach?\n",
" 4. If the generated code is not exactly the same as the ground truth, but the functionality is similar, then still give a high score.\n",
" 5. Only focus on the code and the functionality, ignore the text.\n",
" \n",
" The format of your response should be a JSON object with no additional text or backticks that follows the format:\n",
" {{\n",
" \"score\": <score between 0 and 1>\n",
" }}\n",
" \n",
" Where:\n",
" - 0 means the generated code is completely different or incorrect\n",
" - 1 means the generated code is functionally equivalent to the ground truth\n",
" \n",
" AI-generated code: {output}\n",
" \n",
" Response:\n",
" \"\"\"\n",
" def score(self, output: str, **ignored_kwargs: Any):\n",
" \"\"\"\n",
" Score the output of an LLM.\n",
"\n",
" Args:\n",
" output: The output of an LLM to score.\n",
" **ignored_kwargs: Any additional keyword arguments. This is important so that the metric can be used in the `evaluate` function.\n",
" \"\"\"\n",
" # Construct the prompt based on the output of the LLM\n",
" prompt = self.prompt_template.format(output=output)\n",
" # Generate and parse the response from the LLM\n",
" response = self.llm_client.chat.completions.create(\n",
" model=self.model_name,\n",
" messages=[{\"role\": \"user\", \"content\": prompt}]\n",
" )\n",
" response_dict = json.loads(response.choices[0].message.content)\n",
"\n",
" response_score = float(response_dict[\"score\"])\n",
"\n",
" return score_result.ScoreResult(\n",
" name=self.name,\n",
" value=response_score\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [],
"source": [
"code_quality_metric = LLMJudgeMetric()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from opik.evaluation import evaluate\n",
"\n",
"evaluation = evaluate(\n",
" dataset=dataset,\n",
" task=evaluation_task,\n",
" experiment_name = model_name,\n",
" scoring_metrics=[code_quality_metric],\n",
" experiment_config={\n",
" \"model\": \"gpt-3.5-turbo\"\n",
" }\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "env_gen",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.11"
}
},
"nbformat": 4,
"nbformat_minor": 4
}