-
Notifications
You must be signed in to change notification settings - Fork 2
Ask Qwen Task #406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Ask Qwen Task #406
Changes from 5 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
73aa90b
build: Add `huggingface_hub` to tasks dep group
annehaley d625323
feat: Add new task to ask qwen a question about imagery
annehaley c8a7611
chore: Configure terraform to manage HF token secret
annehaley c9ad678
feat: Add markdown renderer to analytics panel outputs display
annehaley 892eff7
test: Update analysis types test to check for enabled tasks
annehaley 815e5f5
fix: Update typing of HF token setting
annehaley 78c87a5
fix: Apply review suggestions
annehaley d70f3ea
fix: Send system prompt instead of prefix to user prompt
annehaley 89a690a
Pin version of `huggingface-hub`
annehaley 3511794
fix: Update system prompt
annehaley 8a4ab65
fix: Limit thumbnail width and height to 4000
annehaley ca1ef8a
fix: Validate prompt length
annehaley 1ef13dc
refactor: Move HF config to env vars
annehaley 959c5ef
fix: Use brackets to get endpoint name
annehaley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import base64 | ||
|
|
||
| from celery import shared_task | ||
| from django.conf import settings | ||
| from django_large_image import utilities | ||
| import large_image | ||
|
|
||
| from uvdat.core.models import RasterData, TaskResult | ||
|
|
||
| from .analysis_type import AnalysisInputError, AnalysisTask, AnalysisType | ||
|
|
||
| ENDPOINT_NAMESPACE = "Kitware" | ||
| ENDPOINT_NAME = "qwen3-5-9b-gguf-ulh" | ||
|
annehaley marked this conversation as resolved.
Outdated
|
||
| MODEL_CARD_URL = "https://huggingface.co/unsloth/Qwen3.5-9B-GGUF" | ||
| PROMPT_PREFIX = ( | ||
| "You are a geospatial analyst. Answer the following question about the provided image." | ||
| ) | ||
| TOKEN_RANGE = {"min": 1000, "max": 10000, "step": 1000} | ||
| THUMBNAIL_WIDTH = 2000 | ||
|
|
||
|
|
||
| class ImageryAskQwen(AnalysisType): | ||
| def __init__(self): | ||
| super().__init__() | ||
| self.name = "Imagery: Ask Qwen" | ||
| self.description = "Select an imagery layer and ask Qwen 3.5 about it." | ||
| self.details = ( | ||
| "Inferencing with unsloth/Qwen3.5-9B-GGUF provided by a " | ||
| "Kitware-hosted Huggingface Inference Endpoint. " | ||
| f"See the model card at {MODEL_CARD_URL}. " | ||
| f'Prompts will be prefixed with "{PROMPT_PREFIX}".' | ||
| "Responses may cut off mid-sentence if max_tokens is reached." | ||
| ) | ||
| self.db_value = "imagery_ask_qwen" | ||
| self.input_types = { | ||
| "imagery": "RasterData", | ||
| "text_prompt": "string", | ||
| "max_tokens": "number", | ||
| } | ||
| self.output_types = { | ||
| "response": "markdown", | ||
| } | ||
| self.attribution = "Unsloth AI, Kitware Inc." | ||
|
|
||
| @classmethod | ||
| def is_enabled(cls): | ||
| return settings.UVDAT_ENABLE_IMAGERY_ASK_QWEN and settings.UVDAT_HF_TOKEN | ||
|
annehaley marked this conversation as resolved.
Outdated
|
||
|
|
||
| def get_input_options(self): | ||
| return { | ||
| "imagery": RasterData.objects.filter(dataset__category="imagery"), | ||
| "text_prompt": [], | ||
| "max_tokens": [TOKEN_RANGE], | ||
| } | ||
|
|
||
| def validate_inputs(self, inputs): | ||
| super().validate_inputs(inputs) | ||
| try: | ||
| imagery = RasterData.objects.get(id=inputs.get("imagery")) | ||
| except RasterData.DoesNotExist as e: | ||
| err_msg = "Imagery raster does not exist." | ||
| raise AnalysisInputError(err_msg) from e | ||
| if imagery.dataset.category != "imagery": | ||
| err_msg = 'Selected raster is not categorized as "imagery".' | ||
| raise AnalysisInputError(err_msg) | ||
| max_tokens = int(inputs.get("max_tokens")) | ||
| if max_tokens < TOKEN_RANGE["min"] or max_tokens > TOKEN_RANGE["max"]: | ||
| err_msg = f"max_tokens must be between {TOKEN_RANGE['min']} and {TOKEN_RANGE['max']}." | ||
| raise AnalysisInputError(err_msg) | ||
|
|
||
| def run_task(self, *, project, **inputs): | ||
| text_prompt = inputs.get("text_prompt") | ||
| result = TaskResult.objects.create( | ||
| name=text_prompt[:250], | ||
| task_type=self.db_value, | ||
| inputs=inputs, | ||
| project=project, | ||
| status="Initializing Task...", | ||
| ) | ||
| imagery_ask_qwen.delay(result.id) | ||
| return result | ||
|
|
||
| def finalize(self, result): | ||
| pass | ||
|
|
||
|
|
||
| @shared_task(base=AnalysisTask) | ||
| def imagery_ask_qwen(result_id): | ||
| # Only available with [tasks] extra | ||
| from huggingface_hub import get_inference_endpoint # noqa: PLC0415 | ||
|
|
||
| result = TaskResult.objects.get(id=result_id) | ||
| imagery = RasterData.objects.get(id=result.inputs.get("imagery")) | ||
| text_prompt = result.inputs.get("text_prompt") | ||
| max_tokens = int(result.inputs.get("max_tokens")) | ||
|
brianhelba marked this conversation as resolved.
|
||
|
|
||
| result.write_status("Encoding imagery...") | ||
| imagery_path = utilities.field_file_to_local_path(imagery.cloud_optimized_geotiff) | ||
| src = large_image.open(imagery_path) | ||
| thumbnail_bytes, _ = src.getThumbnail(THUMBNAIL_WIDTH, encoding="PNG") | ||
|
annehaley marked this conversation as resolved.
Outdated
|
||
| thumbnail_b64 = base64.b64encode(thumbnail_bytes).decode("utf-8") | ||
| thumbnail_uri = f"data:image/jpeg;base64,{thumbnail_b64}" | ||
|
|
||
| result.write_status("Starting inference endpoint...") | ||
| endpoint = get_inference_endpoint( | ||
| name=ENDPOINT_NAME, | ||
| namespace=ENDPOINT_NAMESPACE, | ||
| token=settings.UVDAT_HF_TOKEN, | ||
| ) | ||
| endpoint.resume() | ||
| endpoint.wait() | ||
|
brianhelba marked this conversation as resolved.
Outdated
|
||
|
|
||
| result.write_status("Sending question to Qwen...") | ||
| messages = [ | ||
| { | ||
| "role": "user", | ||
| "content": [ | ||
| {"type": "image_url", "image_url": {"url": thumbnail_uri}}, | ||
| {"type": "text", "text": f"{PROMPT_PREFIX} {text_prompt}"}, | ||
|
annehaley marked this conversation as resolved.
Outdated
|
||
| ], | ||
| } | ||
| ] | ||
|
|
||
| result.write_status("Awaiting Qwen's response...") | ||
| chat = endpoint.client.chat.completions.create( | ||
|
brianhelba marked this conversation as resolved.
Outdated
|
||
| model="unsloth/Qwen3.5-9B-GGUF", | ||
| messages=messages, | ||
| stream=False, | ||
|
brianhelba marked this conversation as resolved.
Outdated
|
||
| max_tokens=max_tokens, | ||
|
annehaley marked this conversation as resolved.
|
||
| ) | ||
| response = "" | ||
| for choice in chat.choices: | ||
| if choice.finish_reason == "length": | ||
| # max tokens exceeded, use reasoning content | ||
| response += choice.message.reasoning_content | ||
| else: | ||
| response += choice.message.content | ||
| result.write_outputs({"response": response}) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.