-
-
Couldn't load subscription status.
- Fork 245
Creating chunks for OWASP chapters #1693
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
Changes from all commits
44aa23c
1429824
bf7f676
eac3b50
85bb4c5
996d9a7
70dd534
8834fd6
0951b19
50599e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| ai-create-chapter-chunks: | ||
| @echo "Creating chapter chunks" | ||
| @CMD="python manage.py ai_create_chapter_chunks" $(MAKE) exec-backend-command | ||
|
|
||
| ai-create-slack-message-chunks: | ||
| @echo "Creating Slack message chunks" | ||
| @CMD="python manage.py ai_create_slack_message_chunks" $(MAKE) exec-backend-command | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| """AI app constants.""" | ||
|
|
||
| DEFAULT_LAST_REQUEST_OFFSET_SECONDS = 2 | ||
| DELIMITER = "\n\n" | ||
| MIN_REQUEST_INTERVAL_SECONDS = 1.2 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,211 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """A command to create chunks of OWASP chapter data for RAG.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from datetime import UTC, datetime, timedelta | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import openai | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from django.core.management.base import BaseCommand | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.ai.common.constants import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DEFAULT_LAST_REQUEST_OFFSET_SECONDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DELIMITER, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MIN_REQUEST_INTERVAL_SECONDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.ai.models.chunk import Chunk | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.owasp.models.chapter import Chapter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class Command(BaseCommand): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| help = "Create chunks for OWASP chapter data" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def add_arguments(self, parser): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "--chapter", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type=str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| help="Process only the chapter with this key", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "--all", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| action="store_true", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| help="Process all the chapters", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+29
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Clarify that --all includes inactive chapters. The help text should explicitly mention that this option processes both active and inactive chapters to avoid confusion. parser.add_argument(
"--all",
action="store_true",
- help="Process all the chapters",
+ help="Process all chapters (including inactive)",
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "--batch-size", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type=int, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default=50, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| help="Number of chapters to process in each batch", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Add validation for batch-size argument. Consider adding validation to ensure batch-size is positive. parser.add_argument(
"--batch-size",
type=int,
default=50,
help="Number of chapters to process in each batch",
)
+ parser.add_argument(
+ "--batch-size",
+ type=int,
+ default=50,
+ help="Number of chapters to process in each batch",
+ choices=range(1, 1001),
+ metavar="{1..1000}",
+ )
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def handle(self, *args, **options): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not (openai_api_key := os.getenv("DJANGO_OPEN_AI_SECRET_KEY")): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.stdout.write( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.style.ERROR("DJANGO_OPEN_AI_SECRET_KEY environment variable not set") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.openai_client = openai.OpenAI(api_key=openai_api_key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter := options["chapter"]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryset = Chapter.objects.filter(key=chapter) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif options["all"]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryset = Chapter.objects.all() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryset = Chapter.objects.filter(is_active=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not (total_chapters := queryset.count()): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.stdout.write("No chapters found to process") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.stdout.write(f"Found {total_chapters} chapters to process") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| batch_size = options["batch_size"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for offset in range(0, total_chapters, batch_size): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| batch_chapters = queryset[offset : offset + batch_size] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| batch_chunks = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for chapter in batch_chapters: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| batch_chunks.extend(self.create_chunks(chapter)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if batch_chunks: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Chunk.bulk_save(batch_chunks) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.stdout.write(f"Saved {len(batch_chunks)} chunks") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+63
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Add progress tracking for better user feedback. Consider adding progress indicators showing which batch is being processed. batch_size = options["batch_size"]
+ batch_count = 0
for offset in range(0, total_chapters, batch_size):
+ batch_count += 1
+ self.stdout.write(f"Processing batch {batch_count}/{(total_chapters + batch_size - 1) // batch_size}...")
batch_chapters = queryset[offset : offset + batch_size]📝 Committable suggestion
Suggested change
🧰 Tools🪛 Flake8 (7.2.0)[error] 64-64: whitespace before ':' (E203) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.stdout.write(f"Completed processing all {total_chapters} chapters") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def create_chunks(self, chapter: Chapter) -> list[Chunk]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Create chunks from a chapter's data.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prose_content, metadata_content = self.extract_chapter_content(chapter) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| all_chunk_texts = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if metadata_content.strip(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| all_chunk_texts.append(metadata_content) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if prose_content.strip(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| all_chunk_texts.extend(Chunk.split_text(prose_content)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not all_chunk_texts: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.stdout.write(f"No content to chunk for chapter {chapter.key}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time_since_last_request = datetime.now(UTC) - getattr( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "last_request_time", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| datetime.now(UTC) - timedelta(seconds=DEFAULT_LAST_REQUEST_OFFSET_SECONDS), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if time_since_last_request < timedelta(seconds=MIN_REQUEST_INTERVAL_SECONDS): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time.sleep(MIN_REQUEST_INTERVAL_SECONDS - time_since_last_request.total_seconds()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response = self.openai_client.embeddings.create( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| input=all_chunk_texts, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model="text-embedding-3-small", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.last_request_time = datetime.now(UTC) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chunk | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for text, embedding in zip( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| all_chunk_texts, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [d.embedding for d in response.data], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strict=True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chunk := Chunk.update_data( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text=text, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| content_object=chapter, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| embedding=embedding, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| save=False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Dishant1804 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except openai.OpenAIError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.stdout.write(self.style.ERROR(f"OpenAI API error for chapter {chapter.key}: {e}")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def extract_chapter_content(self, chapter: Chapter) -> tuple[str, str]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Extract and separate prose content from metadata for a chapter. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tuple[str, str]: (prose_content, metadata_content) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prose_parts = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.description: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prose_parts.append(f"Description: {chapter.description}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.summary: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prose_parts.append(f"Summary: {chapter.summary}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if hasattr(chapter, "owasp_repository") and chapter.owasp_repository: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repo = chapter.owasp_repository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if repo.description: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prose_parts.append(f"Repository Description: {repo.description}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if repo.topics: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Repository Topics: {', '.join(repo.topics)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
arkid15r marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.name: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Chapter Name: {chapter.name}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location_parts = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.country: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location_parts.append(f"Country: {chapter.country}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.region: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location_parts.append(f"Region: {chapter.region}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.postal_code: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location_parts.append(f"Postal Code: {chapter.postal_code}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.suggested_location: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location_parts.append(f"Location: {chapter.suggested_location}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if location_parts: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Location Information: {', '.join(location_parts)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.level: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
arkid15r marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Chapter Level: {chapter.level}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.currency: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Currency: {chapter.currency}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.meetup_group: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Meetup Group: {chapter.meetup_group}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.tags: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Tags: {', '.join(chapter.tags)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.topics: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Topics: {', '.join(chapter.topics)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.leaders_raw: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| leaders_info = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for leader in chapter.leaders_raw: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(leader, dict): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| leader_name = leader.get("name", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| leader_email = leader.get("email", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if leader_name: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| leader_text = f"Leader: {leader_name}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if leader_email: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| leader_text += f" ({leader_email})" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| leaders_info.append(leader_text) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if leaders_info: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Chapter Leaders: {', '.join(leaders_info)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chapter.related_urls: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| valid_urls = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for url in chapter.related_urls | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if url and url not in (chapter.invalid_urls or []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if valid_urls: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Related URLs: {', '.join(valid_urls)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_parts.append(f"Active Chapter: {'Yes' if chapter.is_active else 'No'}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DELIMITER.join(filter(None, prose_parts)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DELIMITER.join(filter(None, metadata_parts)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Generated by Django 5.2.3 on 2025-07-01 10:39 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("ai", "0003_alter_chunk_options_alter_chunk_embedding_and_more"), | ||
| ("contenttypes", "0002_remove_content_type_name"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AlterUniqueTogether( | ||
| name="chunk", | ||
| unique_together=set(), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="chunk", | ||
| name="content_type", | ||
| field=models.ForeignKey( | ||
| blank=True, | ||
| null=True, | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| to="contenttypes.contenttype", | ||
| ), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="chunk", | ||
| name="object_id", | ||
| field=models.PositiveIntegerField(default=0), | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name="chunk", | ||
| name="message", | ||
| ), | ||
| # we need to remove the unique constraint before adding the new one | ||
| # message needs to be removed first and then we can add the new unique constraint | ||
| migrations.AlterUniqueTogether( | ||
| name="chunk", | ||
| unique_together={("content_type", "object_id", "text")}, | ||
| ), | ||
| ] |
Uh oh!
There was an error while loading. Please reload this page.