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: 3 additions & 1 deletion fenn/agents/llm.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
import time

from fenn.utils.logging import logger

PROVIDERS = {
"openrouter": "https://openrouter.ai/api/v1",
"together": "https://api.together.xyz/v1",
Expand Down Expand Up @@ -205,7 +207,7 @@ def chat_complete(self, messages, schema=None, retries=3):
except RateLimitError:
if attempt < retries - 1:
wait = 5 * (attempt + 1)
print(
logger.info(
f"[fenn] rate limit hit, retrying in {wait}s... ({attempt + 1}/{retries})"
)
time.sleep(wait)
Expand Down
4 changes: 3 additions & 1 deletion fenn/agents/rag/llm.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
import time

from fenn.utils.logging import logger

# ── LLM Providers ─────────────────────────────────────────────────────────────
#
# All providers use an OpenAI-compatible chat completions API.
Expand Down Expand Up @@ -146,7 +148,7 @@ def ask(
except RateLimitError:
if attempt < retries - 1:
wait = 5 * (attempt + 1)
print(
logger.info(
f"[cofone] rate limit hit, retrying in {wait}s... ({attempt + 1}/{retries})"
)
time.sleep(wait)
Expand Down
12 changes: 8 additions & 4 deletions fenn/agents/rag/loader.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from pathlib import Path

from fenn.utils.logging import logger

SUPPORTED_EXTENSIONS = {
".txt",
".md",
Expand Down Expand Up @@ -76,8 +78,10 @@ def load_documents(source):
f for f in found if f.is_file() and f.suffix in SUPPORTED_EXTENSIONS
]
if not supported:
print(f"[cofone] warning: no supported files found in {path}")
print(f"[cofone] supported extensions: {', '.join(SUPPORTED_EXTENSIONS)}")
logger.info(f"[cofone] warning: no supported files found in {path}")
logger.info(
f"[cofone] supported extensions: {', '.join(SUPPORTED_EXTENSIONS)}"
)
for f in supported:
doc = _read_file(f)
if doc:
Expand All @@ -93,7 +97,7 @@ def _read_file(path):
return _read_pdf(path)
return path.read_text(encoding="utf-8")
except Exception as e:
print(f"[cofone] read error {path.name}: {e}")
logger.info(f"[cofone] read error {path.name}: {e}")
return None


Expand All @@ -109,7 +113,7 @@ def _read_pdf(path):
pages = [p.extract_text() or "" for p in reader.pages]
text = "\n".join(pages).strip()
if not text:
print(
logger.info(
f"[cofone] warning: PDF '{path.name}' returned no text (may be scanned/image-based)"
)
return text or None
Expand Down
19 changes: 10 additions & 9 deletions fenn/agents/rag/rag.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from fenn.agents.llm import LLMClient
from fenn.utils.logging import logger

from .loader import load_documents
from .retriever import Retriever
Expand Down Expand Up @@ -162,7 +163,7 @@ def add_source(self, source):
"""
docs = load_documents(source)
if self._debug:
print(f"[cofone] loaded {len(docs)} doc(s) from: {source}")
logger.info(f"[cofone] loaded {len(docs)} doc(s) from: {source}")
self._retriever.index(docs)
return self

Expand Down Expand Up @@ -252,13 +253,13 @@ def run(self, query, schema=None):
chunks = self._retriever.query(query)

if self._debug:
print(
logger.info(
f"\n[cofone] model_provider: {self.model_provider} | model: {self.model}"
)
print(f"[cofone] query: {query}")
print(f"[cofone] chunks found: {len(chunks)}")
logger.info(f"[cofone] query: {query}")
logger.info(f"[cofone] chunks found: {len(chunks)}")
for i, c in enumerate(chunks):
print(f" [{i}] {c[:80]}...")
logger.info(f" [{i}] {c[:80]}...")

context = "\n\n".join(chunks)
prompt = self._build_prompt(query, context)
Expand Down Expand Up @@ -319,13 +320,13 @@ def stream(self, query):
prompt = self._build_prompt(query, context)

if self._debug:
print(
logger.info(
f"\n[cofone] model_provider: {self.model_provider} | model: {self.model}"
)
print(f"[cofone] query: {query}")
print(f"[cofone] chunks found: {len(chunks)}")
logger.info(f"[cofone] query: {query}")
logger.info(f"[cofone] chunks found: {len(chunks)}")
for i, c in enumerate(chunks):
print(f" [{i + 1}] {c[:80]}...")
logger.info(f" [{i + 1}] {c[:80]}...")

yield from self._llm.stream(prompt)

Expand Down
8 changes: 5 additions & 3 deletions fenn/agents/rag/retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from collections import defaultdict
from pathlib import Path

from fenn.utils.logging import logger

from .chunker import chunk_text

try:
Expand Down Expand Up @@ -289,7 +291,7 @@ def _save_to_disk(self):
(self.persist_path / "chunks.json").write_text(
json.dumps(self.chunks, ensure_ascii=False), encoding="utf-8"
)
print(
logger.info(
f"[cofone] index saved to {self.persist_path} ({len(self.chunks)} chunks)"
)

Expand All @@ -303,12 +305,12 @@ def _load_from_disk(self):

self._faiss_index = faiss.read_index(str(index_file))
self.chunks = json.loads(chunks_file.read_text(encoding="utf-8"))
print(
logger.info(
f"[cofone] index loaded from {self.persist_path} ({len(self.chunks)} chunks)"
)
return True
except Exception as e:
print(f"[cofone] cache load failed ({e}), rebuilding index...")
logger.info(f"[cofone] cache load failed ({e}), rebuilding index...")
return False

# ── Query ──────────────────────────────────────────────────────────────────
Expand Down
13 changes: 3 additions & 10 deletions fenn/args/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from colorama import init

from fenn.secrets.keystore import KeyStore
from fenn.utils.logging import logger, write_config


class Parser:
Expand All @@ -29,13 +30,7 @@ def __init__(self, config_file: str | Path = "fenn.yaml") -> None:
self._initialized = True

def _config_missing(self) -> None:
from fenn.logging import Logger

logger = Logger()

logger.display_exception(
f"Configuration file {self._config_file} was not found."
)
logger.exception(f"Configuration file {self._config_file} was not found.")

raise FileNotFoundError(
0,
Expand All @@ -58,9 +53,7 @@ def load_configuration(self) -> Any:

def print(self) -> None:
"""Public method to trigger the flattened print with colored paths."""
from fenn.logging import Logger

Logger().write_config(self._args)
write_config(self._args, self.config_file)

@property
def config_file(self) -> str:
Expand Down
37 changes: 18 additions & 19 deletions fenn/cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
write_credentials,
)
from fenn.remote.exceptions import RemoteError
from fenn.utils.logging import logger


def execute(args: argparse.Namespace) -> None:
sub = getattr(args, "auth_command", None)
if sub is None:
print(
logger.info(
f"{Fore.RED}Missing auth subcommand. Try: "
f"{Fore.LIGHTYELLOW_EX}fenn auth login{Style.RESET_ALL}",
file=sys.stderr,
f"{Fore.LIGHTYELLOW_EX}fenn auth login{Style.RESET_ALL}"
)
sys.exit(1)

Expand All @@ -37,9 +37,8 @@ def execute(args: argparse.Namespace) -> None:
elif sub == "logout":
_logout(args)
else:
print(
logger.info(
f"{Fore.RED}Unknown auth subcommand: {sub}{Style.RESET_ALL}",
file=sys.stderr,
)
sys.exit(1)

Expand All @@ -51,7 +50,7 @@ def _login(args: argparse.Namespace) -> None:
if not api_key:
existing = load_credentials(profile)
if existing is not None:
print(
logger.info(
f"{Fore.GREEN}Already logged in (profile: {profile}, "
f"key: {mask_key(existing.api_key)}). "
f"Run {Fore.LIGHTYELLOW_EX}fenn auth logout{Fore.GREEN} first to switch keys.{Style.RESET_ALL}"
Expand All @@ -66,11 +65,11 @@ def _login(args: argparse.Namespace) -> None:
api_key = sys.stdin.readline().strip()

if not api_key:
print(f"{Fore.RED}No API key provided.{Style.RESET_ALL}", file=sys.stderr)
logger.info(f"{Fore.RED}No API key provided.{Style.RESET_ALL}")
sys.exit(1)

path = write_credentials(api_key, profile=profile)
print(
logger.info(
f"{Fore.GREEN}Saved credentials to "
f"{Fore.LIGHTYELLOW_EX}{path}{Fore.GREEN} (profile: {profile}).{Style.RESET_ALL}"
)
Expand All @@ -80,18 +79,20 @@ def _status(args: argparse.Namespace) -> None:
profile = args.profile or DEFAULT_PROFILE
creds = load_credentials(profile)
if creds is None:
print(
logger.info(
f"{Fore.YELLOW}No saved credentials for profile {profile!r}. "
f"Run {Fore.LIGHTYELLOW_EX}fenn auth login{Fore.YELLOW} to add one.{Style.RESET_ALL}"
)
sys.exit(1)

print(f"{Fore.CYAN}profile : {Fore.LIGHTYELLOW_EX}{creds.profile}{Style.RESET_ALL}")
print(
logger.info(
f"{Fore.CYAN}profile : {Fore.LIGHTYELLOW_EX}{creds.profile}{Style.RESET_ALL}"
)
logger.info(
f"{Fore.CYAN}api_key : "
f"{Fore.LIGHTYELLOW_EX}{mask_key(creds.api_key)}{Style.RESET_ALL}"
)
print(
logger.info(
f"{Fore.CYAN}host : "
f"{Fore.LIGHTYELLOW_EX}{DEFAULT_REMOTE_HOST}{Style.RESET_ALL}"
)
Expand All @@ -103,31 +104,29 @@ def _status(args: argparse.Namespace) -> None:
me = client.me()
credits_remaining = me.get("credits")
plan = me.get("plan")
print(
logger.info(
f"{Fore.GREEN}credits : {Fore.LIGHTYELLOW_EX}{credits_remaining}"
f"{Fore.GREEN} plan: {plan}{Style.RESET_ALL}"
)
except requests.exceptions.SSLError:
print(
logger.info(
f"{Fore.RED}SSL verification failed for {DEFAULT_REMOTE_HOST}. "
f"Try: pip install --upgrade certifi{Style.RESET_ALL}",
file=sys.stderr,
)
except (RemoteError, requests.exceptions.ConnectionError) as exc:
print(
logger.info(
f"{Fore.RED}Could not reach host: {exc}{Style.RESET_ALL}",
file=sys.stderr,
)


def _logout(args: argparse.Namespace) -> None:
profile = args.profile or DEFAULT_PROFILE
if delete_profile(profile):
print(
logger.info(
f"{Fore.GREEN}Removed credentials for profile "
f"{Fore.LIGHTYELLOW_EX}{profile}{Style.RESET_ALL}"
)
else:
print(
logger.info(
f"{Fore.YELLOW}No credentials found for profile {profile!r}.{Style.RESET_ALL}"
)
4 changes: 3 additions & 1 deletion fenn/cli/dashboard.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""fenn dashboard — launch the Fenn log-browser web UI."""

from fenn.utils.logging import logger

# Must remain 127.0.0.1: the dashboard serves the user's own logs without any
# network-layer auth, and the in-process auth gate assumes a local-only socket.
# Do NOT bind to 0.0.0.0 or a non-loopback address.
Expand All @@ -25,4 +27,4 @@ def execute(args) -> None:
log_dirs=args.log_dir,
)
except KeyboardInterrupt:
print("\nDashboard stopped.")
logger.info("\nDashboard stopped.")
5 changes: 4 additions & 1 deletion fenn/cli/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from colorama import Fore, Style

from fenn.args.parser import Parser
from fenn.utils.logging import logger


def execute(args: argparse.Namespace) -> None:
Expand All @@ -27,7 +28,9 @@ def execute(args: argparse.Namespace) -> None:
try:
parsed_grid: list[dict] = _parse_grid(yaml_path=yaml_path)
except TemplateError as e:
print(f"{Fore.RED}Template error: missing grid section{e}{Style.RESET_ALL}")
logger.info(
f"{Fore.RED}Template error: missing grid section{e}{Style.RESET_ALL}"
)
sys.exit(1)
shutil.copy(yaml_path, yaml_copy)
try:
Expand Down
8 changes: 6 additions & 2 deletions fenn/cli/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from rich.console import Console
from rich.table import Table

from fenn.utils.logging import logger

TEMPLATES_REPO = "pyfenn/templates"
REPO_NAME = "templates"
GITHUB_API_BASE = "https://api.github.com"
Expand All @@ -19,7 +21,7 @@ def execute(args: argparse.Namespace) -> None:
try:
_list_templates()
except NetworkError as e:
print(f"{Fore.RED}Network error: {e}{Style.RESET_ALL}")
logger.info(f"{Fore.RED}Network error: {e}{Style.RESET_ALL}")
sys.exit(1)


Expand Down Expand Up @@ -49,7 +51,9 @@ def _list_templates() -> None:
templates = [item["name"] for item in contents if item.get("type") == "dir"]

if not templates:
print(f"{Fore.YELLOW}No templates found in the repository.{Style.RESET_ALL}")
logger.info(
f"{Fore.YELLOW}No templates found in the repository.{Style.RESET_ALL}"
)
return

templates.sort()
Expand Down
Loading
Loading