feat!: migrate to OpenAI native tool calls and fix circular deps (#fuck-gemini)

- Fix circular dependencies in agent/tools
- Migrate from custom JSON to OpenAI tool calls format
- Add async streaming (step_stream, complete_stream)
- Simplify prompt system and remove token counting
- Add 5 new API endpoints (/health, /v1/models, /api/memory/*)
- Add 3 new tools (get_torrent_by_index, add_torrent_by_index, set_language)
- Fix all 500 tests and add coverage config (80% threshold)
- Add comprehensive docs (README, pytest guide)

BREAKING: LLM interface changed, memory injection via get_memory()
This commit is contained in:
2025-12-06 19:11:05 +01:00
parent 2c8cdd3ab1
commit 9ca31e45e0
92 changed files with 7897 additions and 1786 deletions
+3 -2
View File
@@ -1,10 +1,11 @@
"""Knaben API client."""
from .client import KnabenClient
from .dto import TorrentResult
from .exceptions import (
KnabenError,
KnabenConfigurationError,
KnabenAPIError,
KnabenConfigurationError,
KnabenError,
KnabenNotFoundError,
)
+23 -27
View File
@@ -1,12 +1,15 @@
"""Knaben torrent search API client."""
from typing import Dict, Any, Optional, List
import logging
from typing import Any
import requests
from requests.exceptions import RequestException, Timeout, HTTPError
from requests.exceptions import HTTPError, RequestException, Timeout
from agent.config import Settings, settings
from .dto import TorrentResult
from .exceptions import KnabenError, KnabenAPIError, KnabenNotFoundError
from .exceptions import KnabenAPIError, KnabenNotFoundError
logger = logging.getLogger(__name__)
@@ -26,9 +29,9 @@ class KnabenClient:
def __init__(
self,
base_url: Optional[str] = None,
timeout: Optional[int] = None,
config: Optional[Settings] = None
base_url: str | None = None,
timeout: int | None = None,
config: Settings | None = None,
):
"""
Initialize Knaben client.
@@ -48,10 +51,7 @@ class KnabenClient:
logger.info("Knaben client initialized")
def _make_request(
self,
params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
def _make_request(self, params: dict[str, Any] | None = None) -> dict[str, Any]:
"""
Make a request to Knaben API.
@@ -90,11 +90,7 @@ class KnabenClient:
logger.error(f"Knaben API request failed: {e}")
raise KnabenAPIError(f"Failed to connect to Knaben API: {e}") from e
def search(
self,
query: str,
limit: int = 10
) -> List[TorrentResult]:
def search(self, query: str, limit: int = 10) -> list[TorrentResult]:
"""
Search for torrents.
@@ -138,7 +134,7 @@ class KnabenClient:
# Parse results
results = []
torrents = data.get('hits', [])
torrents = data.get("hits", [])
if not torrents:
logger.info(f"No torrents found for '{query}'")
@@ -155,7 +151,7 @@ class KnabenClient:
logger.info(f"Found {len(results)} torrents for '{query}'")
return results
def _parse_torrent(self, torrent: Dict[str, Any]) -> TorrentResult:
def _parse_torrent(self, torrent: dict[str, Any]) -> TorrentResult:
"""
Parse a torrent result into a TorrentResult object.
@@ -166,17 +162,17 @@ class KnabenClient:
TorrentResult object
"""
# Extract required fields (API uses camelCase)
title = torrent.get('title', 'Unknown')
size = torrent.get('size', 'Unknown')
seeders = int(torrent.get('seeders', 0) or 0)
leechers = int(torrent.get('leechers', 0) or 0)
magnet = torrent.get('magnetUrl', '')
title = torrent.get("title", "Unknown")
size = torrent.get("size", "Unknown")
seeders = int(torrent.get("seeders", 0) or 0)
leechers = int(torrent.get("leechers", 0) or 0)
magnet = torrent.get("magnetUrl", "")
# Extract optional fields
info_hash = torrent.get('hash')
tracker = torrent.get('tracker')
upload_date = torrent.get('date')
category = torrent.get('category')
info_hash = torrent.get("hash")
tracker = torrent.get("tracker")
upload_date = torrent.get("date")
category = torrent.get("category")
return TorrentResult(
title=title,
@@ -187,5 +183,5 @@ class KnabenClient:
info_hash=info_hash,
tracker=tracker,
upload_date=upload_date,
category=category
category=category,
)
+6 -5
View File
@@ -1,17 +1,18 @@
"""Knaben Data Transfer Objects."""
from dataclasses import dataclass
from typing import Optional
@dataclass
class TorrentResult:
"""Represents a torrent search result from Knaben."""
title: str
size: str
seeders: int
leechers: int
magnet: str
info_hash: Optional[str] = None
tracker: Optional[str] = None
upload_date: Optional[str] = None
category: Optional[str] = None
info_hash: str | None = None
tracker: str | None = None
upload_date: str | None = None
category: str | None = None
+4
View File
@@ -3,19 +3,23 @@
class KnabenError(Exception):
"""Base exception for Knaben-related errors."""
pass
class KnabenConfigurationError(KnabenError):
"""Raised when Knaben API is not properly configured."""
pass
class KnabenAPIError(KnabenError):
"""Raised when Knaben API returns an error."""
pass
class KnabenNotFoundError(KnabenError):
"""Raised when no torrents are found."""
pass