Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ jobs:
with:
python-version: "3.11"
- name: Install ruff
run: pip install ruff
run: pip install ruff==0.16.1
- name: Lint
run: ruff check .
56 changes: 28 additions & 28 deletions agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,32 @@
import logging
import os
import sys
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import AsyncGenerator

import asyncpg
from dotenv import load_dotenv
from openai import AsyncOpenAI
from pydantic import BaseModel, Field
from pydantic_ai import Agent, RunContext
from pydantic_ai.capabilities import Capability, Thinking
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider
from dotenv import load_dotenv

from config import (
DEFAULT_DATABASE_URL,
DEFAULT_MODEL,
EMBEDDING_MODEL,
HYBRID_SEARCH_ALPHA,
LLM_MAX_TOKENS,
LLM_TEMPERATURE,
MMR_LAMBDA,
OPENROUTER_BASE_URL,
OPENROUTER_DEFAULT_MODEL,
DEFAULT_DATABASE_URL,
HYBRID_SEARCH_ALPHA,
SIMILARITY_THRESHOLD,
RETRIEVAL_CANDIDATES,
RERANK_TOP_N,
MMR_LAMBDA,
RETRIEVAL_CANDIDATES,
SIMILARITY_THRESHOLD,
THINKING_EFFORT,
)
from rag_utils import hybrid_retrieve
Expand All @@ -59,15 +59,12 @@
# API key helper (unchanged from v1)
# ---------------------------------------------------------------------------


def get_openai_api_key() -> str:
"""Get OpenAI API key with validation and interactive prompt if needed."""
openai_api_key = os.getenv("OPENAI_API_KEY")

if (
not openai_api_key
or "YOUR_" in openai_api_key
or openai_api_key == "sk-..."
):
if not openai_api_key or "YOUR_" in openai_api_key or openai_api_key == "sk-...":
logger.warning("OPENAI_API_KEY is not set or has a placeholder value.")
print("Please enter your OpenAI API key:")
openai_api_key = input("> ")
Expand All @@ -78,6 +75,7 @@ def get_openai_api_key() -> str:
env_path = os.path.join(os.path.dirname(__file__), ".env")
try:
from dotenv import set_key

set_key(env_path, "OPENAI_API_KEY", openai_api_key)
logger.info("Updated OPENAI_API_KEY in %s", env_path)
except ImportError:
Expand All @@ -90,6 +88,7 @@ def get_openai_api_key() -> str:
# Output schema (unchanged)
# ---------------------------------------------------------------------------


class PineScriptResult(BaseModel):
query: str = Field(description="The original query")
response: str = Field(description="The generated response")
Expand All @@ -101,9 +100,11 @@ class PineScriptResult(BaseModel):
# fields that were never read by any tool.
# ---------------------------------------------------------------------------


@dataclass
class Dependencies:
"""Runtime dependencies injected per run."""

openai: AsyncOpenAI
pool: asyncpg.Pool

Expand Down Expand Up @@ -180,12 +181,11 @@ async def retrieve(ctx: RunContext[Dependencies], search_query: str) -> str:
logger.debug("Hybrid retrieval returned %d documents", len(docs))

return "\n\n".join(
f"# {doc.title}\nDocumentation URL: {doc.url}\n\n{doc.content}\n"
for doc in docs
f"# {doc.title}\nDocumentation URL: {doc.url}\n\n{doc.content}\n" for doc in docs
)
except Exception as e:
logger.error("Error in retrieve tool: %s", e)
return f"Error retrieving documentation: {str(e)}"
return f"Error retrieving documentation: {e!s}"


# ---------------------------------------------------------------------------
Expand All @@ -199,16 +199,16 @@ async def retrieve(ctx: RunContext[Dependencies], search_query: str) -> str:
# ---------------------------------------------------------------------------

pinescript_agent = Agent(
DEFAULT_MODEL, # "openai:gpt-4o-mini"
DEFAULT_MODEL, # "openai:gpt-4o-mini"
deps_type=Dependencies,
output_type=PineScriptResult, # v2 naming (was result_type in old v1)
output_type=PineScriptResult, # v2 naming (was result_type in old v1)
capabilities=[
Thinking(effort=THINKING_EFFORT), # extended reasoning, unified across providers
pinescript_rag, # retrieve tool + expert instructions
Thinking(effort=THINKING_EFFORT), # extended reasoning, unified across providers
pinescript_rag, # retrieve tool + expert instructions
],
model_settings={
"temperature": LLM_TEMPERATURE, # 0.2 — now actually applied every run
"max_tokens": LLM_MAX_TOKENS, # 2000
"temperature": LLM_TEMPERATURE, # 0.2 — now actually applied every run
"max_tokens": LLM_MAX_TOKENS, # 2000
},
)

Expand All @@ -217,6 +217,7 @@ async def retrieve(ctx: RunContext[Dependencies], search_query: str) -> str:
# Database helper (unchanged)
# ---------------------------------------------------------------------------


@asynccontextmanager
async def database_connect(create_db: bool = False) -> AsyncGenerator[asyncpg.Pool, None]:
"""Connect to the pgvector database."""
Expand All @@ -242,6 +243,7 @@ async def database_connect(create_db: bool = False) -> AsyncGenerator[asyncpg.Po
# v2: resolve_model() → agent.run(model=...) (no context manager needed)
# ---------------------------------------------------------------------------


def resolve_model(preset: str | None = None) -> OpenAIChatModel | None:
"""Resolve a preset name or raw model ID to a Pydantic AI model.

Expand All @@ -261,19 +263,15 @@ def resolve_model(preset: str | None = None) -> OpenAIChatModel | None:
logger.info("Using raw model ID: %s", model_id)

if openrouter_api_key:
provider = OpenAIProvider(
base_url=OPENROUTER_BASE_URL, api_key=openrouter_api_key
)
provider = OpenAIProvider(base_url=OPENROUTER_BASE_URL, api_key=openrouter_api_key)
return OpenAIChatModel(model_id, provider=provider)

logger.warning("No OPENROUTER_API_KEY for preset '%s', using default", preset)
return None

# No preset: use OpenRouter default if key exists, else agent default
if openrouter_api_key:
provider = OpenAIProvider(
base_url=OPENROUTER_BASE_URL, api_key=openrouter_api_key
)
provider = OpenAIProvider(base_url=OPENROUTER_BASE_URL, api_key=openrouter_api_key)
return OpenAIChatModel(OPENROUTER_DEFAULT_MODEL, provider=provider)

return None # agent default (OpenAI)
Expand All @@ -283,6 +281,7 @@ def resolve_model(preset: str | None = None) -> OpenAIChatModel | None:
# Run helper — now accepts message_history for proper multi-turn
# ---------------------------------------------------------------------------


async def run_agent(question: str, preset: str | None = None, message_history=None):
"""Run the agent with a specific question.

Expand Down Expand Up @@ -322,6 +321,7 @@ async def run_agent(question: str, preset: str | None = None, message_history=No
# CLI entry point
# ---------------------------------------------------------------------------


async def main():
"""Run the agent from the command line."""
if len(sys.argv) > 1:
Expand All @@ -334,7 +334,7 @@ async def main():

if result:
print("\nResponse:")
print(result.output.response) # v2: .output (was .data)
print(result.output.response) # v2: .output (was .data)
print(f"\nSnippets used: {result.output.snippets_used}")
else:
print("No response received from the agent.")
Expand Down
72 changes: 30 additions & 42 deletions api_debug.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,46 @@
different aspects of the initialization and request process.
"""

import os
import asyncio
import os

from dotenv import load_dotenv
from openai import OpenAI, AsyncOpenAI
from pydantic_ai.models.openai import OpenAIModel
from openai import AsyncOpenAI, OpenAI
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider

# Load environment variables
load_dotenv(override=True)


def print_section(title):
"""Print a section title"""
print("\n" + "=" * 80)
print(f" {title} ".center(80, "="))
print("=" * 80)


async def main():
"""Main function to debug API key issues"""
print_section("Environment Variables")

# Check if API key is set in environment
api_key = os.getenv("OPENAI_API_KEY")
masked_key = f"{api_key[:4]}...{api_key[-4:]}" if api_key and len(api_key) > 8 else "None"
print(f"OPENAI_API_KEY from environment: {masked_key}")

# Check for malformed placeholders
if api_key in ["YOUR_OPENAI_API_KEY", "sk-...", "YOUR_OPE***_API"] or not api_key:
print("WARNING: API key appears to be a placeholder or is missing!")

# Prompt for key
print("Enter your OpenAI API key for testing:")
api_key = input("> ").strip()
os.environ["OPENAI_API_KEY"] = api_key
masked_key = f"{api_key[:4]}...{api_key[-4:]}" if api_key and len(api_key) > 8 else "None"
print(f"Using API key: {masked_key}")

# Test standard OpenAI client
print_section("Standard OpenAI Client Test")
try:
Expand All @@ -51,7 +55,7 @@ async def main():
print(f"Found {len(models.data)} models")
except Exception as e:
print(f"❌ Standard OpenAI client failed: {e}")

# Test AsyncOpenAI client
print_section("Async OpenAI Client Test")
try:
Expand All @@ -61,50 +65,33 @@ async def main():
print(f"Found {len(models.data)} models")
except Exception as e:
print(f"❌ Async OpenAI client failed: {e}")

# Test Pydantic AI OpenAIModel
print_section("Pydantic AI OpenAIModel Test")

# Test Pydantic AI OpenAIChatModel
# v2: the API key travels on the provider, not as a model kwarg.
print_section("Pydantic AI OpenAIChatModel Test")
openai_model = None
try:
openai_model = OpenAIModel("gpt-4o", api_key=api_key)
print("✅ OpenAIModel initialized successfully!")
openai_model = OpenAIChatModel("gpt-4o", provider=OpenAIProvider(api_key=api_key))
print("✅ OpenAIChatModel initialized successfully!")
print(f"Model name: {openai_model.model_name}")

# Test making a request with the model
response = await openai_model("Hello, world!")
print("✅ Model request successful!")
print(f"Response: {response[:50]}...")
except Exception as e:
print(f"❌ OpenAIModel failed: {e}")
print(f"❌ OpenAIChatModel failed: {e}")

# Test Pydantic AI Agent
print_section("Pydantic AI Agent Test")
try:
# Try creating a minimal agent
agent = Agent(
model="openai:gpt-4o",
model_settings={
"api_key": api_key
}
)
# v2: pass the configured model object; api_key is not a model_setting.
agent = Agent(openai_model) if openai_model else Agent("openai:gpt-4o")
print("✅ Agent created successfully!")

# Print the agent's model settings
print("Agent model settings:")
if hasattr(agent, 'model_settings'):
for key, value in agent.model_settings.items():
if key == "api_key" and value:
print(f" api_key: {value[:4]}...{value[-4:]}")
else:
print(f" {key}: {value}")


# Try making a request with the agent
try:
result = agent.run_sync("Hello, world!", model_settings={"api_key": api_key})
result = agent.run_sync("Hello, world!")
print("✅ Agent request successful!")
print(f"Response: {result.data[:50]}...")
print(f"Response: {str(result.output)[:50]}...")
except Exception as e:
print(f"❌ Agent request failed: {e}")

# Check if the error is related to API key
error_str = str(e).lower()
if "api key" in error_str or "openai" in error_str:
Expand All @@ -115,7 +102,7 @@ async def main():
print("3. Modify the retrieve tool to use a separate OpenAI client")
except Exception as e:
print(f"❌ Agent creation failed: {e}")

print_section("Summary")
print("This debug information should help identify where the API key issue is occurring.")
print("Look for any failures above and focus debugging on those components.")
Expand All @@ -124,5 +111,6 @@ async def main():
print("2. If some tests fail, focus on those specific components")
print("3. Check if `model_settings` in the agent includes the correct API key")


if __name__ == "__main__":
asyncio.run(main())
asyncio.run(main())
Loading
Loading