Compare commits

...
7 Commits
Author SHA1 Message Date
brian 5faf3ef2a7 added use of larger model for refining prompt
Formatting Check / formatting-check (push) Successful in 13s
Python Code Quality / python-code-quality (push) Failing after 21s
Python Test / python-test (push) Successful in 45s
Sync Label Studio Annotations / sync_storage (push) Failing after 2m20s
2025-09-26 19:08:56 +00:00
Brian Bjarke Jensen 007161a927 code quality fixes
Formatting Check / formatting-check (push) Successful in 9s
Python Code Quality / python-code-quality (push) Successful in 18s
Python Test / python-test (push) Successful in 30s
Sync Label Studio Annotations / sync_storage (push) Successful in 57s
2025-09-26 00:02:02 +02:00
Brian Bjarke Jensen d804cc4a13 installed requests types 2025-09-26 00:01:54 +02:00
Brian Bjarke Jensen 6075848c91 added code to log to mlflow
Python Code Quality / python-code-quality (push) Failing after 19s
Python Test / python-test (push) Successful in 40s
Formatting Check / formatting-check (push) Successful in 11s
2025-09-25 23:57:57 +02:00
Brian Bjarke Jensen cdde0131a7 installed mlflow 2025-09-25 23:57:49 +02:00
Brian Bjarke Jensen 9a3694fc98 Merge pull request 'add-script-to-query-AI-model' (#17) from add-script-to-query-AI-model into main
Formatting Check / formatting-check (push) Successful in 49s
Python Test / python-test (push) Successful in 43s
Sync Label Studio Annotations / sync_storage (push) Successful in 21s
Python Code Quality / python-code-quality (push) Successful in 15s
Reviewed-on: #17
2025-09-21 19:48:53 +02:00
Brian Bjarke Jensen cbac5ded35 ruff format fixes
Formatting Check / formatting-check (pull_request) Successful in 53s
Python Code Quality / python-code-quality (pull_request) Successful in 15s
Python Test / python-test (pull_request) Successful in 45s
2025-09-21 19:40:01 +02:00
4 changed files with 1566 additions and 85 deletions
@@ -60,17 +60,27 @@ class AnnotationRepository(MinioAdapter, AnnotationRepositoryInterface):
annotation = Annotation( annotation = Annotation(
angle=AngleEnum(data["angle"]) if data["angle"] is not None else None, angle=AngleEnum(data["angle"]) if data["angle"] is not None else None,
angle_uncertainty=bool(data["angle_uncertainty"]), angle_uncertainty=bool(data["angle_uncertainty"]),
colour=ColourEnum(data["colour"]) if data["colour"] is not None else None, colour=ColourEnum(data["colour"])
if data["colour"] is not None
else None,
colour_uncertainty=bool(data["colour_uncertainty"]), colour_uncertainty=bool(data["colour_uncertainty"]),
contact=ContactEnum(data["contact"]) if data["contact"] is not None else None, contact=ContactEnum(data["contact"])
if data["contact"] is not None
else None,
contact_uncertainty=bool(data["contact_uncertainty"]), contact_uncertainty=bool(data["contact_uncertainty"]),
depth=DepthEnum(data["depth"]) if data["depth"] is not None else None, depth=DepthEnum(data["depth"]) if data["depth"] is not None else None,
depth_uncertainty=bool(data["depth_uncertainty"]), depth_uncertainty=bool(data["depth_uncertainty"]),
distance=DistanceEnum(data["distance"]) if data["distance"] is not None else None, distance=DistanceEnum(data["distance"])
if data["distance"] is not None
else None,
distance_uncertainty=bool(data["distance_uncertainty"]), distance_uncertainty=bool(data["distance_uncertainty"]),
lighting=LightingEnum(data["lighting"]) if data["lighting"] is not None else None, lighting=LightingEnum(data["lighting"])
if data["lighting"] is not None
else None,
lighting_uncertainty=bool(data["lighting_uncertainty"]), lighting_uncertainty=bool(data["lighting_uncertainty"]),
point_of_view=PointOfViewEnum(data["point_of_view"]) if data["point_of_view"] is not None else None, point_of_view=PointOfViewEnum(data["point_of_view"])
if data["point_of_view"] is not None
else None,
point_of_view_uncertainty=bool(data["point_of_view_uncertainty"]), point_of_view_uncertainty=bool(data["point_of_view_uncertainty"]),
) )
except (KeyError, ValueError) as e: except (KeyError, ValueError) as e:
+2
View File
@@ -14,6 +14,7 @@ classifiers = [
"Operating System :: OS Independent", "Operating System :: OS Independent",
] ]
dependencies = [ dependencies = [
"mlflow>=3.4.0",
"pandas>=2.3.2", "pandas>=2.3.2",
"pillow>=11.3.0", "pillow>=11.3.0",
"pydantic>=2.11.9", "pydantic>=2.11.9",
@@ -82,4 +83,5 @@ dev = [
"ruff>=0.13.0", "ruff>=0.13.0",
"safety>=3.2.11", "safety>=3.2.11",
"testcontainers>=4.13.0", "testcontainers>=4.13.0",
"types-requests>=2.32.4.20250913",
] ]
+68 -32
View File
@@ -9,6 +9,8 @@ import requests
import structlog import structlog
from dotenv import load_dotenv from dotenv import load_dotenv
from PIL import Image from PIL import Image
from datetime import datetime
import mlflow
from python_utils import check_env from python_utils import check_env
from data_store.repositories import JobRepository from data_store.repositories import JobRepository
from data_store.dto import ( from data_store.dto import (
@@ -21,7 +23,9 @@ from data_store.dto import (
PointOfViewEnum, PointOfViewEnum,
) )
MODEL = "llava:13b" RUN_NAME = f"query-ai-model_{datetime.now().strftime('%Y%m%d-%H%M%S')}"
IMAGE_MODEL = "llava:13b"
PROMPT_MODEL = "mistral-small:22b-instruct-2409-q4_K_M"
CATEGORY_PROMPT_MAP = { CATEGORY_PROMPT_MAP = {
"angle": ( "angle": (
"In the context of visual angle in the Kress and van Leeuwen framework, " "In the context of visual angle in the Kress and van Leeuwen framework, "
@@ -58,7 +62,7 @@ def query_model_with_image(
prompt: str, prompt: str,
allowed_answers: list[str], allowed_answers: list[str],
image: Image.Image, image: Image.Image,
model: str = "llava:13b" model: str = "llava:13b",
) -> str: ) -> str:
""" """
Query an Ollama instance with a prompt, image, and constrained answers. Query an Ollama instance with a prompt, image, and constrained answers.
@@ -77,9 +81,9 @@ def query_model_with_image(
# Read environment variables # Read environment variables
ollama_endpoint = str(os.getenv("OLLAMA_ENDPOINT")) ollama_endpoint = str(os.getenv("OLLAMA_ENDPOINT"))
# Handle RGBA images by converting to RGB # Handle RGBA images by converting to RGB
if image.mode == 'RGBA': if image.mode == "RGBA":
# Convert RGBA to RGB by compositing against white background # Convert RGBA to RGB by compositing against white background
rgb_image = Image.new('RGB', image.size, (255, 255, 255)) rgb_image = Image.new("RGB", image.size, (255, 255, 255))
rgb_image.paste(image, mask=image.split()[-1]) # Use alpha as mask rgb_image.paste(image, mask=image.split()[-1]) # Use alpha as mask
image = rgb_image image = rgb_image
# Convert PIL Image to base64 # Convert PIL Image to base64
@@ -97,14 +101,12 @@ def query_model_with_image(
"model": model, "model": model,
"prompt": prompt_with_options, "prompt": prompt_with_options,
"images": [image_b64], "images": [image_b64],
"stream": False "stream": False,
} }
# Make request to Ollama # Make request to Ollama
# N.B. the generate endpoint does not retain context between calls # N.B. the generate endpoint does not retain context between calls
response = requests.post( response = requests.post(
f"{ollama_endpoint}/api/generate", f"{ollama_endpoint}/api/generate", json=payload, timeout=60
json=payload,
timeout=60
) )
response.raise_for_status() response.raise_for_status()
result = response.json() result = response.json()
@@ -197,11 +199,22 @@ def calculate_model_prompt_accuracy(
expected_answer = getattr(job.annotation, category.lower()) expected_answer = getattr(job.annotation, category.lower())
# Score the response # Score the response
score = score_response(response, expected_answer) score = score_response(response, expected_answer)
print(f"Job {job_id}: Response '{response}' - Expected answer '{expected_answer}' - Score: {score}") print(
f"Job {job_id}: Response '{response}' - Expected answer '{expected_answer}' - Score: {score}"
)
total_score += score total_score += score
# Calculate accuracy # Calculate accuracy
jobs_evaluated = total_jobs - jobs_skipped jobs_evaluated = total_jobs - jobs_skipped
print(f"Total Jobs: {total_jobs}, Jobs Skipped: {jobs_skipped}, Jobs Evaluated: {jobs_evaluated}") mlflow.log_metrics(
{
"total_jobs": total_jobs,
"jobs_skipped": jobs_skipped,
"jobs_evaluated": jobs_evaluated,
}
)
print(
f"Total Jobs: {total_jobs}, Jobs Skipped: {jobs_skipped}, Jobs Evaluated: {jobs_evaluated}"
)
if jobs_evaluated == 0: if jobs_evaluated == 0:
raise ValueError("No jobs were evaluated. All jobs were skipped.") raise ValueError("No jobs were evaluated. All jobs were skipped.")
accuracy = total_score / jobs_evaluated accuracy = total_score / jobs_evaluated
@@ -237,17 +250,11 @@ def refine_prompt(
"Respond with only the refined prompt." "Respond with only the refined prompt."
) )
# Prepare request payload # Prepare request payload
payload = { payload = {"model": model, "prompt": refinement_instruction, "stream": False}
"model": model,
"prompt": refinement_instruction,
"stream": False
}
# Make request to Ollama # Make request to Ollama
# N.B. the generate endpoint does not retain context between calls # N.B. the generate endpoint does not retain context between calls
response = requests.post( response = requests.post(
f"{ollama_endpoint}/api/generate", f"{ollama_endpoint}/api/generate", json=payload, timeout=60
json=payload,
timeout=60
) )
response.raise_for_status() response.raise_for_status()
result = response.json() result = response.json()
@@ -274,27 +281,56 @@ if __name__ == "__main__":
) )
# Set up basic logging configuration # Set up basic logging configuration
logging.basicConfig(level=logging.ERROR) logging.basicConfig(level=logging.ERROR)
# Connect to MLFlow
mlflow.set_tracking_uri("http://10.0.0.2:5000")
experiment = mlflow.get_experiment_by_name("visual-semiotic-ai-analysis")
experiment_id = experiment.experiment_id if experiment else None
# Calculate accuracy # Calculate accuracy
category_events = {}
for category, initial_prompt in CATEGORY_PROMPT_MAP.items(): for category, initial_prompt in CATEGORY_PROMPT_MAP.items():
step = 0
run = mlflow.start_run(
experiment_id=experiment_id,
run_name=RUN_NAME,
tags={
"image_model": IMAGE_MODEL,
"prompt_model": PROMPT_MODEL,
"category": category,
},
)
mlflow.log_param(f"step_{step}_prompt", initial_prompt)
print(f"Calculating accuracy for category: {category}") print(f"Calculating accuracy for category: {category}")
accuracy = calculate_model_prompt_accuracy(MODEL, initial_prompt, category) accuracy = calculate_model_prompt_accuracy(IMAGE_MODEL, initial_prompt, category)
mlflow.log_metric("accuracy", accuracy, step=step)
# Log to MLFlow
prompt_accuracy_map = {initial_prompt: accuracy} prompt_accuracy_map = {initial_prompt: accuracy}
print(f"Prompt accuracy for category '{category}': {accuracy:.2%}") print(f"Prompt accuracy for category '{category}': {accuracy:.2%}")
# loop to refine prompt based on accuracy # Loop to refine prompt based on accuracy
for i in range(30): for i in range(99):
refined_prompt = refine_prompt(MODEL, prompt_accuracy_map) step += 1
try:
# Generate refined prompt
refined_prompt = refine_prompt(PROMPT_MODEL, prompt_accuracy_map)
mlflow.log_param(f"step_{step}_prompt", refined_prompt)
print(f"Refined prompt: {refined_prompt}") print(f"Refined prompt: {refined_prompt}")
accuracy = calculate_model_prompt_accuracy(MODEL, refined_prompt, category) except Exception as e:
prompt_accuracy_map[refined_prompt] = accuracy print(f"Error refining prompt: {e}")
continue
try:
# Calculate accuracy for refined prompt
accuracy = calculate_model_prompt_accuracy(
IMAGE_MODEL, refined_prompt, category
)
mlflow.log_metric("accuracy", accuracy, step=step)
except Exception as e:
print(f"Error calculating accuracy: {e}")
continue
# Update prompt accuracy map
# prompt_accuracy_map[refined_prompt] = accuracy
print(f"Prompt accuracy for category '{category}': {accuracy:.2%}") print(f"Prompt accuracy for category '{category}': {accuracy:.2%}")
# Stop if accuracy is 100% # Stop if accuracy is 100%
if accuracy == 1.0: if accuracy == 1.0:
print(f"Achieved 100% accuracy for category '{category}'. Stopping refinement.") print(
f"Achieved 100% accuracy for category '{category}'. Stopping refinement."
)
break break
print(f"Final prompt accuracy map for category '{category}': {prompt_accuracy_map}") mlflow.end_run()
category_events[category] = prompt_accuracy_map
# save results to json file
with open("prompt_accuracy_results.json", "w", encoding="utf-8") as f:
json.dump(category_events, f, indent=2)
print("Prompt accuracy results saved to 'prompt_accuracy_results.json'.")
Generated
+1480 -47
View File
File diff suppressed because it is too large Load Diff