oc_chat
Loading...
Searching...
No Matches
config.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/core/config.py
4
5# OCT-LINT: disable=no_hardcoded_secrets reason="Docstring title triggers Shannon entropy false positive"
6"""
7Shared runtime configuration for OC Chat.
8
9Purpose
10-------
11Provider-specific settings (DeepSeek's API key env var, base URL,
12model registry, default model, summariser-tier model) used to live
13here but moved into ``providers/deepseek/`` during Phase C. This
14module now holds only neutral concerns: context-compression
15heuristics, ANSI styles, ReAct caps, the ``OC_CHAT_*`` env vars, and
16shared default fallbacks.
17
18Responsibilities
19----------------
20- Read environment overrides (``OC_CHAT_CONTEXT_FILE``,
21 ``OC_CHAT_PROVIDER``, ``NO_COLOR``) at import time via
22 ``python-dotenv`` if available.
23- Export the canonical defaults: ``DEFAULT_MAX_REACT_ITERATIONS``,
24 ``CONTEXT_COMPRESS_THRESHOLD``, ``CONTEXT_KEEP_RECENT_TURNS``,
25 ``REASONING_EFFORT_LEVELS``, ``TOKEN_PROFILES``,
26 ``MODEL_MAX_TOKENS``.
27- :func:`get_max_context_tokens` — provider-aware lookup with fallback
28 to ``MODEL_MAX_TOKENS``.
29
30Diagnostics
31-----------
32Domain: OC_CHAT.MAIN
33Levels:
34 L2 — lifecycle events
35 L3 — semantic details
36 L4 — deep tracing
37
38Contracts
39---------
40- All values are module-level constants resolved at import time.
41- Missing env vars fall back to the documented defaults; never raise.
42- ``python-dotenv`` is an optional dependency; absence is silently
43 tolerated.
44
45Dependencies
46------------
47- oc_diagnostics (structured debug logging via _dbg)
48- oc_chat._env_guard (load_dotenv_guarded - loads .env at import, refusing the security-authority vars)
49- oc_chat.core.defaults (default, ceiling, shipped - the shipped defaults and the lower-only output ceiling)
50- oc_chat.core.provider_plugin (lazy; registry - the active provider's model context windows)
51"""
52import os
53
54from oc_diagnostics import _dbg
55
56# M8.0 (F33/F72): a workspace .env may NOT set security-authority vars
57# (profile, provider *_BASE_URL, allow-remote, proxy/cert, package-index).
58from oc_chat._env_guard import load_dotenv_guarded
59load_dotenv_guarded()
60
61# CONFIG-01 (plan 238): every default and ceiling below is bound at import from
62# the shipped, read-only ``oc_chat.defaults.toml``. The names stay; the values
63# live in a file the agent filesystem tools are hard-denied from writing.
64from oc_chat.core.defaults import ceiling, default, shipped
65
66_dbg("OC_CHAT.MAIN", "module", "loaded", 4)
67
68# File auto-ingested into the system prompt on new sessions ("" disables).
69OC_CHAT_CONTEXT_FILE = os.getenv("OC_CHAT_CONTEXT_FILE", "AGENT_CONTEXT.md")
70
71DEFAULT_REASONING_EFFORT = default("reasoning_effort")
72REASONING_EFFORT_LEVELS = ["low", "medium", "high"]
73
74# Context window compression settings
75CONTEXT_COMPRESS_THRESHOLD = default("context_compress_threshold") # fraction of model max
76MODEL_MAX_TOKENS = default("model_max_tokens") # fallback for unknown models / no provider
77# Default context-window token cap used by ``cli.py`` when the operator does
78# not pass ``--max-context-size``. Mirrored UI-side by the constant of the same
79# name in ``oc_ui.terminal.stats`` (the enhanced meter's fallback) so the two
80# agree. Relocated here from the former ``oc_chat.core.stats`` by plan 153,
81# which moved the UI-owned StatisticsTracker to ``oc_ui.terminal.stats``.
82DEFAULT_CONTEXT_WINDOW_SIZE = default("context_window_size")
83CONTEXT_KEEP_RECENT = default("context_keep_recent") # legacy message-count hint
84# Phase 3: keep last N user-rooted conversation turns verbatim; older turns are summarised.
85CONTEXT_KEEP_RECENT_TURNS = default("context_keep_recent_turns")
86
87# ReAct loop — default max iterations (override per session via ``/react``).
88DEFAULT_MAX_REACT_ITERATIONS = default("max_react_iterations")
89
90# Token profile for completion caps (``/budget``) — ``balanced`` is baseline.
91TOKEN_PROFILES = ("cheap", "balanced", "deep")
92
93# ── Completion (output) token budget ──────────────────────────────────────────
94# The per-iteration ``max_tokens`` sent to the provider. It bounds reasoning
95# AND answer together on a thinking model, so a value sized for the answer
96# alone lets the model burn the whole budget on chain-of-thought and stop
97# before writing a word — see the reasoning-only path in ``react_loop``.
98#
99#: Operator ceiling on any single completion. Providers may declare far more
100#: (DeepSeek v4 declares 384_000); this bounds one runaway reply without
101#: bounding the model's ability to finish an ordinary one. Raising it above
102#: this default is a code edit on purpose: the settings file is a
103#: model-reachable write surface and may only LOWER the ceiling, never raise
104#: it — the same non-self-raising invariant ``resource_caps`` applies to the
105#: per-turn tool-call cap. Since plan 238 that invariant has ONE implementation,
106#: :func:`oc_chat.core.defaults.ceiling`, and raising this value is an edit to
107#: the shipped, write-denied defaults file rather than to this module.
108OUTPUT_TOKENS_CEILING = shipped("output_tokens")
109
110#: Used when a provider declares no ``max_output_tokens``. ``ModelCaps``
111#: defaults the field to 0 meaning "undeclared" (Ollama's table declares none),
112#: and 0 must never reach the wire as a cap. Deliberately conservative:
113#: guessing a local model's real ceiling high risks provider-side rejection.
114UNDECLARED_OUTPUT_TOKENS = default("undeclared_output_tokens")
115
116
118 """Return the effective completion ceiling.
119
120 :data:`OUTPUT_TOKENS_CEILING` unless the workspace settings file specifies
121 a smaller ``max_output_tokens``. A settings value at or above the code
122 default is ignored, so a model that talks a human into a settings write
123 cannot widen its own output budget. Degrades to the code default when
124 settings are unavailable or unparseable — the generalised lower-only rule in
125 :func:`oc_chat.core.defaults.ceiling` (plan 238), unchanged in behaviour.
126 """
127 return ceiling("output_tokens")
128
129# ── ANSI styles ───────────────────────────────────────────────────────────────
130# Honour the standard NO_COLOR (https://no-color.org) and FORCE_COLOR
131# environment variables. Precedence: FORCE_COLOR wins over NO_COLOR if both
132# are set, matching the convention used by chalk / clicolors / rich.
133#
134# When colour is disabled, every STYLE_* constant is the empty string — the
135# presenter still emits them, but they're invisible. This keeps the format
136# strings untouched.
137
138def _color_enabled() -> bool:
139 """Return True iff ANSI styles should be emitted."""
140 if os.getenv("FORCE_COLOR"):
141 return True
142 if os.getenv("NO_COLOR") is not None:
143 return False
144 return True
145
146
148
149STYLE_RESET = "\033[0m" if _COLOR else ""
150STYLE_DIM = "\033[2m" if _COLOR else ""
151STYLE_ITALIC = "\033[3m" if _COLOR else ""
152STYLE_BOLD = "\033[1m" if _COLOR else ""
153STYLE_CYAN = "\033[36m" if _COLOR else ""
154STYLE_YELLOW = "\033[33m" if _COLOR else ""
155STYLE_GREEN = "\033[32m" if _COLOR else ""
156STYLE_RED = "\033[31m" if _COLOR else ""
157
158
159def get_max_context_tokens(model_name: str) -> int:
160 """Return the max context window tokens for *model_name*.
161
162 Looks up the active provider's curated model registry. Falls back to
163 :data:`MODEL_MAX_TOKENS` for unknown models or when no provider is
164 registered yet (rare — only happens in early test setup).
165 """
166 func = "get_max_context_tokens"
167 from oc_chat.core.provider_plugin import registry
168
169 provider_name = os.environ.get("OC_CHAT_PROVIDER", "deepseek")
170 try:
171 caps = registry.get(provider_name).models().get(model_name)
172 if caps is not None:
173 return caps.max_context_tokens
174 except (KeyError, AttributeError) as e:
175 # VALIDATION_ERROR — provider not yet registered (early test setup), expected
176 _dbg("OC_CHAT.MAIN", func, f"VALIDATION_ERROR: {type(e).__name__}: {e}", 3)
177 return MODEL_MAX_TOKENS
bool _color_enabled()
Definition config.py:138
int get_output_tokens_ceiling()
Definition config.py:117
int get_max_context_tokens(str model_name)
Definition config.py:159