Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [0.0.8] - 2025-11-14
### Changed
- Disabled thinking for Google Models

## [0.0.8] - 2025-11-12
### Changed
- Navbar spacing rules
Expand Down
8 changes: 4 additions & 4 deletions core/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
from core.agents.generate_blog_post_content_agent import (
create_generate_blog_post_content_agent,
)
from core.agents.insert_internal_links_agent import create_insert_internal_links_agent
from core.agents.populate_competitor_details_agent import (
create_populate_competitor_details_agent,
)
from core.agents.summarize_page_agent import create_summarize_page_agent
from core.agents.title_suggestions_agent import create_title_suggestions_agent
from core.agents.validate_blog_post_ending_agent import (
create_validate_blog_post_ending_agent,
)
from core.agents.validate_blog_post_agent import create_validate_blog_post_agent

__all__ = [
"create_analyze_competitor_agent",
Expand All @@ -30,8 +29,9 @@
"create_extract_links_agent",
"create_find_competitors_agent",
"create_generate_blog_post_content_agent",
"create_insert_internal_links_agent",
"create_populate_competitor_details_agent",
"create_summarize_page_agent",
"create_title_suggestions_agent",
"create_validate_blog_post_ending_agent",
"create_validate_blog_post_agent",
]
4 changes: 2 additions & 2 deletions core/agents/analyze_competitor_agent.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from django.utils import timezone
from pydantic_ai import Agent, RunContext

from core.agents.models import get_default_ai_model
from core.agents.schemas import CompetitorAnalysis, CompetitorAnalysisContext
from core.choices import get_default_ai_model


def create_analyze_competitor_agent(model=None):
Expand All @@ -23,7 +23,7 @@ def create_analyze_competitor_agent(model=None):
"""
You are an expert marketer.
Based on the competitor details and homepage content provided,
extract and infer the requested information. Make reasonable inferences based
extract and infer the requested information. Make reasonable inferences based
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix extra whitespace in system prompt.

Line 26 contains multiple consecutive spaces: "Make reasonable inferences based". This appears to be a formatting error.

Apply this diff to fix the spacing:

-            extract and infer the requested information. Make     reasonable inferences based
+            extract and infer the requested information. Make reasonable inferences based
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
extract and infer the requested information. Make reasonable inferences based
extract and infer the requested information. Make reasonable inferences based
🤖 Prompt for AI Agents
In core/agents/analyze_competitor_agent.py around line 26, the system prompt
string contains extra consecutive spaces ("Make     reasonable inferences
based"); remove the extra spaces so the sentence uses single spacing ("Make
reasonable inferences based") and ensure surrounding punctuation/indentation
remains unchanged.

on available content, context, and industry knowledge.
"""
),
Expand Down
3 changes: 2 additions & 1 deletion core/agents/analyze_project_agent.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from pydantic_ai import Agent

from core.agents.models import get_default_ai_model
from core.agents.schemas import ProjectDetails, WebPageContent
from core.agents.system_prompts import add_webpage_content
from core.choices import get_default_ai_model


def create_analyze_project_agent(model=None):
Expand All @@ -25,6 +25,7 @@ def create_analyze_project_agent(model=None):
"on available content, context, and industry knowledge."
),
retries=2,
model_settings={"temperature": 0.8, "thinking_budget": 0},
)
agent.system_prompt(add_webpage_content)

Expand Down
4 changes: 2 additions & 2 deletions core/agents/competitor_vs_blog_post_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider

from core.agents.models import AIModel
from core.agents.schemas import CompetitorVsPostContext
from core.agents.system_prompts import add_project_pages, markdown_lists
from core.choices import AIModel


def create_competitor_vs_blog_post_agent(model=None):
Expand Down Expand Up @@ -64,7 +64,7 @@ def create_competitor_vs_blog_post_agent(model=None):
@agent.system_prompt
def output_format() -> str:
return """
IMPORTANT: Return only the text. Don't surround the text with ```markdown or ```.
Return only the text. Don't surround the text with ```markdown or ```.
"""

@agent.system_prompt
Expand Down
6 changes: 3 additions & 3 deletions core/agents/content_editor_agent.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pydantic_ai import Agent

from core.agents.models import get_default_ai_model
from core.agents.schemas import BlogPostGenerationContext
from core.agents.system_prompts import (
add_language_specification,
Expand All @@ -8,7 +9,6 @@
add_target_keywords,
add_title_details,
)
from core.choices import get_default_ai_model


def create_content_editor_agent(model=None):
Expand All @@ -31,13 +31,13 @@ def create_content_editor_agent(model=None):
Your task is to edit the blog post content based on the requested changes.
""",
retries=2,
model_settings={"temperature": 0.3},
model_settings={"temperature": 0.3, "thinking_budget": 0},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the thinking_budget parameter is supported.

The thinking_budget: 0 parameter has been added to model_settings. Please confirm this is a valid parameter for the version of pydantic-ai in use.


🌐 Web query:

Does pydantic-ai Agent support a thinking_budget parameter in model_settings?

💡 Result:

Yes. Agent accepts model_settings and you can set a thinking_budget via a model-specific settings class — e.g., GoogleModelSettings supports google_thinking_config={'thinking_budget': ...} which can be passed to Agent (or at run time). [1][2]

Sources:
[1] Pydantic AI — Agents (model_settings description).
[2] Pydantic AI — Google model docs (example showing google_thinking_config with thinking_budget).


🏁 Script executed:

#!/bin/bash
# View the content_editor_agent.py file to see full context
cat core/agents/content_editor_agent.py | head -60

Length of output: 1381


🏁 Script executed:

#!/bin/bash
# Search for model initialization patterns in agents directory
rg -nP --type=py -C5 'model_settings.*thinking_budget' core/agents/

Length of output: 8860


🏁 Script executed:

#!/bin/bash
# Check what model is being used in this agent
rg -nP --type=py -C10 'class.*Agent|ClientModel|model=' core/agents/content_editor_agent.py

Length of output: 575


🏁 Script executed:

#!/bin/bash
# Find all Agent instantiations in the agents directory
rg -nP --type=py 'Agent\(' core/agents/ -C3

Length of output: 6583


🏁 Script executed:

#!/bin/bash
# Check what get_default_ai_model returns
cat core/agents/models.py

Length of output: 362


🏁 Script executed:

#!/bin/bash
# Check if there's requirements or setup to see pydantic-ai version
fd -e 'txt' -e 'toml' -e 'cfg' --type f | grep -E '(requirements|setup|pyproject|poetry)' | head -5

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Search for any imports or usage of model settings structures
rg -nP --type=py 'ModelSettings|model_settings' core/agents/ -B2 -A2 | head -50

Length of output: 3016


🏁 Script executed:

#!/bin/bash
# Check pydantic-ai version
grep -A5 -B5 'pydantic.ai\|pydantic-ai' pyproject.toml requirements.txt 2>/dev/null | head -20

Length of output: 1182


🌐 Web query:

pydantic-ai Agent model_settings thinking_budget parameter syntax

💡 Result:

Short answer — use the model-specific settings field (GoogleModelSettings.google_thinking_config) and pass a dict with key "thinking_budget". Example:

  • Enable / set budget:
    from pydantic_ai import Agent
    from pydantic_ai.models.google import GoogleModel, GoogleModelSettings

    settings = GoogleModelSettings(google_thinking_config={'thinking_budget': 2048})
    model = GoogleModel('gemini-2.5-flash')
    agent = Agent(model, model_settings=settings)

  • Disable thinking (budget 0):
    settings = GoogleModelSettings(google_thinking_config={'thinking_budget': 0})

Agent-level model_settings are merged with model-level and run-time settings (run-time has highest precedence). [1][2][3]

Sources:
[1] Pydantic AI — Google model docs (shows google_thinking_config & thinking_budget examples).
[2] Pydantic AI — Agents / ModelSettings docs (explains model_settings merging & usage).
[3] Pydantic AI — Thinking page (other model-specific thinking examples).


Incorrect parameter format for thinking_budget in model_settings.

For pydantic-ai 1.9.1 with Google Gemini models, thinking_budget must be passed via GoogleModelSettings(google_thinking_config={'thinking_budget': ...}), not as a flat dictionary key in model_settings.

The current code at line 34:

model_settings={"temperature": 0.3, "thinking_budget": 0}

Should be corrected to use the model-specific settings structure. This issue affects multiple agents in core/agents/: validate_blog_post_agent.py, title_suggestions_agent.py, summarize_page_agent.py, populate_competitor_details_agent.py, generate_blog_post_content_agent.py, extract_competitors_data_agent.py, extract_links_agent.py, insert_internal_links_agent.py, and analyze_project_agent.py.

🤖 Prompt for AI Agents
In core/agents/content_editor_agent.py around line 34, model_settings uses an
incorrect flat key for thinking_budget; for pydantic-ai 1.9.1 with Google Gemini
you must supply thinking_budget via GoogleModelSettings using the
google_thinking_config dict. Replace the current model_settings={"temperature":
0.3, "thinking_budget": 0} with a model-specific settings object that sets
temperature and uses
GoogleModelSettings(google_thinking_config={"thinking_budget": 0}) (or
equivalent in your codebase) so thinking_budget is passed under
google_thinking_config; apply the same pattern to the other listed agent files.

)

@agent.system_prompt
def only_return_the_edited_content() -> str:
return """
IMPORTANT: Only return the edited content, no other text.
Only return the edited content, no other text.
"""

agent.system_prompt(add_project_details)
Expand Down
3 changes: 2 additions & 1 deletion core/agents/extract_competitors_data_agent.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pydantic_ai import Agent, RunContext

from core.agents.models import get_default_ai_model
from core.agents.schemas import CompetitorDetails
from core.choices import get_default_ai_model


def create_extract_competitors_data_agent(model=None):
Expand All @@ -22,6 +22,7 @@ def create_extract_competitors_data_agent(model=None):
Extract all the data from the text provided.
""",
retries=2,
model_settings={"temperature": 0.2, "thinking_budget": 0},
)

@agent.system_prompt
Expand Down
3 changes: 2 additions & 1 deletion core/agents/extract_links_agent.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pydantic_ai import Agent, RunContext

from core.choices import get_default_ai_model
from core.agents.models import get_default_ai_model


def create_extract_links_agent(model=None):
Expand All @@ -24,6 +24,7 @@ def create_extract_links_agent(model=None):
If the text contains no valid URLs, return an empty list.
""",
retries=2,
model_settings={"temperature": 0.2, "thinking_budget": 0},
)

@agent.system_prompt
Expand Down
16 changes: 4 additions & 12 deletions core/agents/find_competitors_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider

from core.agents.models import AIModel
from core.agents.schemas import ProjectDetails
from core.choices import AIModel


def create_find_competitors_agent(is_on_free_plan: bool):
Expand Down Expand Up @@ -70,22 +70,14 @@ def number_of_competitors(ctx: RunContext[ProjectDetails]) -> str:
@agent.system_prompt
def language_specification(ctx: RunContext[ProjectDetails]) -> str:
project = ctx.deps
return f"""
IMPORTANT: Be mindful that competitors are likely to speak in
{project.language} language.
"""
return f"Be mindful that competitors are likely to speak in {project.language} language." # noqa: E501
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused noqa directives.

The static analysis tool correctly identified that the # noqa: E501 comments are unnecessary since the E501 rule (line too long) is not enabled in your linter configuration.

Apply this diff to remove the unused directives:

-        return f"Be mindful that competitors are likely to speak in {project.language} language."  # noqa: E501
+        return f"Be mindful that competitors are likely to speak in {project.language} language."
-            return f"Only return competitors whose target audience is in {project.location}."  # noqa: E501
+            return f"Only return competitors whose target audience is in {project.location}."

Also applies to: 79-79

🧰 Tools
🪛 Ruff (0.14.4)

73-73: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)

🤖 Prompt for AI Agents
In core/agents/find_competitors_agent.py lines 73 and 79, remove the unnecessary
"# noqa: E501" comments trailing the return statements; simply delete the "#
noqa: E501" tokens so the lines read normally without the unused noqa
directives, ensuring no other changes are made.


@agent.system_prompt
def location_specification(ctx: RunContext[ProjectDetails]) -> str:
project = ctx.deps
if project.location != "Global":
return f"""
IMPORTANT: Only return competitors whose target audience is in
{project.location}.
"""
return f"Only return competitors whose target audience is in {project.location}." # noqa: E501
else:
return """
IMPORTANT: Return competitors from all over the world.
"""
return "Return competitors from all over the world."

return agent
12 changes: 8 additions & 4 deletions core/agents/generate_blog_post_content_agent.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
from pydantic_ai import Agent

from core.agents.models import get_default_ai_model
from core.agents.schemas import BlogPostGenerationContext, GeneratedBlogPostSchema
from core.agents.system_prompts import (
add_language_specification,
add_project_details,
add_project_pages,
add_target_keywords,
add_title_details,
add_todays_date,
add_validation_feedback,
filler_content,
markdown_lists,
post_structure,
valid_markdown_format,
)
from core.choices import ContentType, get_default_ai_model
from core.choices import ContentType
from core.prompts import GENERATE_CONTENT_SYSTEM_PROMPTS


Expand All @@ -23,6 +24,9 @@ def create_generate_blog_post_content_agent(
"""
Create an agent to generate blog post content.

Note: This agent generates content WITHOUT internal links. Links will be inserted
in a separate step using the insert_internal_links_agent.

Args:
content_type: The type of content to generate (SHARING, ACTIONABLE, THOUGHT_LEADERSHIP).
model: Optional AI model to use. Defaults to the default AI model.
Expand All @@ -36,15 +40,15 @@ def create_generate_blog_post_content_agent(
deps_type=BlogPostGenerationContext,
system_prompt=GENERATE_CONTENT_SYSTEM_PROMPTS[content_type],
retries=2,
model_settings={"max_tokens": 65500, "temperature": 0.8},
model_settings={"max_tokens": 65500, "temperature": 0.3, "thinking_budget": 0},
)

agent.system_prompt(add_project_details)
agent.system_prompt(add_project_pages)
agent.system_prompt(add_title_details)
agent.system_prompt(add_todays_date)
agent.system_prompt(add_language_specification)
agent.system_prompt(add_target_keywords)
agent.system_prompt(add_validation_feedback)
agent.system_prompt(valid_markdown_format)
agent.system_prompt(markdown_lists)
agent.system_prompt(post_structure)
Expand Down
85 changes: 85 additions & 0 deletions core/agents/insert_internal_links_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from pydantic_ai import Agent, RunContext

from core.agents.models import get_default_ai_model
from core.agents.schemas import InsertedLinksOutput, InsertInternalLinksContext


def create_insert_internal_links_agent(model=None):
"""
Create an agent to insert internal links into blog post content.
The agent takes existing blog post content and a list of internal pages,
then intelligently inserts links where they are contextually relevant.
Args:
model: Optional AI model to use. Defaults to the default AI model.
Returns:
Configured Agent instance
"""
system_prompt = """
You are an expert content editor specializing in internal linking strategies for SEO optimization.
Your task is to take blog post content and intelligently insert internal links to relevant pages
where they naturally fit and add value to the reader.
## Guidelines for Link Insertion:
1. **Natural Integration**: Insert links only where they enhance the reader's understanding or provide valuable additional context. Links should feel organic and not forced.
2. **Contextual Relevance**: Match the page's topic with the surrounding content. Only link when there's a clear topical connection.
3. **Anchor Text**: Use descriptive, natural anchor text that clearly indicates what the reader will find when clicking the link. Avoid generic phrases like "click here" or "this page".
4. **Strategic Placement**:
- Prefer linking in the main body content rather than introductions or conclusions
- Space out links appropriately - avoid clustering multiple links in a single paragraph
- Link to each page at most once or twice in the entire article
5. **Must-Use Pages**: These are high-priority pages that should be linked if there's any reasonable contextual fit. Be creative in finding natural places to mention and link to these pages.
6. **Optional Pages**: Only link to these if they are highly relevant to the specific section of content where they would be inserted.
7. **Markdown Format**: Use proper Markdown link syntax: `[anchor text](URL)`
8. **Preserve Content**: Do not modify the existing content except to add links. Maintain all formatting, structure, and tone.
9. **Quality Over Quantity**: It's better to have fewer, highly relevant links than many loosely related ones.
## Output Requirements:
Return the complete blog post content with internal links inserted. The content should be in Markdown format with proper link syntax.
""" # noqa: E501
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused noqa directive.

The static analysis tool correctly identified that the # noqa: E501 comment is unnecessary since the E501 rule is not enabled in your linter configuration.

Apply this diff:

 ## Output Requirements:
 Return the complete blog post content with internal links inserted. The content should be in Markdown format with proper link syntax.
-"""  # noqa: E501
+"""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
""" # noqa: E501
## Output Requirements:
Return the complete blog post content with internal links inserted. The content should be in Markdown format with proper link syntax.
"""
🧰 Tools
🪛 Ruff (0.14.4)

51-51: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)

🤖 Prompt for AI Agents
In core/agents/insert_internal_links_agent.py around line 51, the trailing "#
noqa: E501" on the docstring is unnecessary; remove the unused noqa directive by
deleting " # noqa: E501" from the line (leave the docstring content intact) and
run linting to confirm no other violations remain.


agent = Agent(
model or get_default_ai_model(),
output_type=InsertedLinksOutput,
deps_type=InsertInternalLinksContext,
system_prompt=system_prompt,
retries=2,
model_settings={"max_tokens": 65500, "temperature": 0.3, "thinking_budget": 0},
)

@agent.system_prompt
def add_must_use_and_optional_pages(ctx: RunContext[InsertInternalLinksContext]) -> str:
return f"""
Must-use pages:
{ctx.deps.must_use_pages}
--------------------------------
Optional pages:
{ctx.deps.optional_pages}
"""

@agent.system_prompt
def add_content(ctx: RunContext[InsertInternalLinksContext]) -> str:
return f"""
Content:
--------------------------------
{ctx.deps.content}
--------------------------------
"""

@agent.system_prompt
def output() -> str:
return "Return only the post that I gave you, but with links."

return agent
14 changes: 14 additions & 0 deletions core/agents/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.db import models


class AIModel(models.TextChoices):
GEMINI_FLASH = "google-gla:gemini-2.5-flash", "Gemini 2.5 Flash"
PERPLEXITY_SONAR = "sonar", "Perplexity Sonar"


DEFAULT_AI_MODEL = AIModel.GEMINI_FLASH


def get_default_ai_model() -> str:
"""Returns the default AI model to use across the application."""
return DEFAULT_AI_MODEL
Comment on lines +1 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

LGTM: Clean centralization of AI model configuration.

The new module provides a clean, centralized location for AI model definitions. The structure follows Django conventions with TextChoices and provides a clear default model selection mechanism.

However, please verify the model identifier "google-gla:gemini-2.5-flash" is correct. The "google-gla" prefix appears unusual for Gemini models.


🌐 Web query:

What is the correct model identifier for Google Gemini 2.5 Flash in pydantic-ai?

💡 Result:

The correct model identifier is "gemini-2.5-flash". [1][2]

Sources:
[1] Pydantic AI — Google models list (LatestGoogleModelNames includes "gemini-2.5-flash").
[2] Google Gemini models documentation (model code "gemini-2.5-flash").


Fix incorrect model identifier for Google Gemini 2.5 Flash.

The correct model identifier for Google Gemini 2.5 Flash in pydantic-ai is "gemini-2.5-flash", not "google-gla:gemini-2.5-flash". Update line 5 in core/agents/models.py:

GEMINI_FLASH = "gemini-2.5-flash", "Gemini 2.5 Flash"

The incorrect prefix will cause runtime failures when pydantic-ai attempts to load the model.

🤖 Prompt for AI Agents
In core/agents/models.py lines 1-14, the AIModel.GEMINI_FLASH enum value is
incorrect and will fail at runtime; change the enum member's stored string to
"gemini-2.5-flash" (i.e., GEMINI_FLASH = "gemini-2.5-flash", "Gemini 2.5 Flash")
and also ensure the default/exported value matches the hinted str type by
setting DEFAULT_AI_MODEL to AIModel.GEMINI_FLASH.value or by returning
DEFAULT_AI_MODEL.value from get_default_ai_model().

3 changes: 2 additions & 1 deletion core/agents/populate_competitor_details_agent.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pydantic_ai import Agent, RunContext

from core.agents.models import get_default_ai_model
from core.agents.schemas import CompetitorDetails, WebPageContent
from core.choices import get_default_ai_model


def create_populate_competitor_details_agent(model=None):
Expand All @@ -27,6 +27,7 @@ def create_populate_competitor_details_agent(model=None):
"""
),
retries=2,
model_settings={"temperature": 0.5, "thinking_budget": 0},
)

@agent.system_prompt
Expand Down
Loading