poliora 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
poliora/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Poliora - AI cost, carbon, and fine-tuning efficiency toolkit."""
2
+
3
+ from poliora.cost import log_openai_response, log_usage, track_anthropic_client, track_openai_client
4
+
5
+ __version__ = "0.2.0"
6
+
7
+ __all__ = ["__version__", "log_openai_response", "log_usage", "track_anthropic_client", "track_openai_client"]
@@ -0,0 +1,83 @@
1
+ """Standalone Desktop App launcher for Poliora."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import socket
8
+ import sys
9
+ import webbrowser
10
+ from pathlib import Path
11
+ from threading import Timer
12
+
13
+ from poliora.cost import init_workspace, load_workspace
14
+ from poliora.web import run_dashboard
15
+
16
+
17
+ def _available_port(preferred_port: int) -> int:
18
+ """Return the preferred loopback port, or the next available one."""
19
+ for port in range(preferred_port, preferred_port + 20):
20
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
21
+ probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
22
+ if probe.connect_ex(("127.0.0.1", port)) != 0:
23
+ return port
24
+ raise RuntimeError("No local dashboard port was available in the configured range.")
25
+
26
+
27
+ def _desktop_workspace_root() -> Path:
28
+ """Choose the normal per-user application-data location for the desktop app."""
29
+ if sys.platform == "win32":
30
+ return Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local")) / "Poliora" / "workspace"
31
+ if sys.platform == "darwin":
32
+ return Path.home() / "Library" / "Application Support" / "Poliora" / "workspace"
33
+ data_home = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share"))
34
+ return data_home / "poliora" / "workspace"
35
+
36
+
37
+ def main() -> None:
38
+ """Launch Poliora standalone GUI application."""
39
+ parser = argparse.ArgumentParser(description="Launch the Poliora local dashboard.")
40
+ parser.add_argument("--version", action="store_true", help="Show the standalone app version and exit.")
41
+ parser.add_argument("--port", type=int, default=8787, help="Preferred local dashboard port.")
42
+ parser.add_argument("--no-open", action="store_true", help="Do not open a browser window.")
43
+ arguments = parser.parse_args()
44
+ if arguments.version:
45
+ from poliora import __version__
46
+
47
+ print(f"poliora {__version__}")
48
+ return
49
+ if hasattr(sys.stdout, "reconfigure"):
50
+ try:
51
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
52
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
53
+ except Exception:
54
+ pass
55
+
56
+ print("=" * 60)
57
+ print(" [Poliora] Starting Desktop AI Cost Optimizer...")
58
+ print("=" * 60)
59
+
60
+ workspace_root = _desktop_workspace_root()
61
+ workspace = load_workspace(workspace_root)
62
+ if not workspace.config_path.exists():
63
+ init_workspace(workspace_root, project="Desktop", monthly_budget_usd=1000.0)
64
+
65
+ port = _available_port(arguments.port)
66
+ url = f"http://127.0.0.1:{port}"
67
+ print(f"\n[+] Poliora engine running at {url}")
68
+ print("[+] Opening Desktop Application interface...")
69
+
70
+ # Open browser window automatically after 0.5s
71
+ if not arguments.no_open:
72
+ Timer(0.5, webbrowser.open_new_tab, args=(url,)).start()
73
+
74
+ # Serve local dashboard server
75
+ try:
76
+ run_dashboard(workspace_root, host="127.0.0.1", port=port)
77
+ except KeyboardInterrupt:
78
+ print("\nPoliora closed. Goodbye!")
79
+ sys.exit(0)
80
+
81
+
82
+ if __name__ == "__main__":
83
+ main()
poliora/config.py ADDED
@@ -0,0 +1,103 @@
1
+ """Poliora configuration — Pydantic settings for every tunable knob."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Literal, Optional
7
+
8
+ from pydantic import Field, field_validator
9
+ from pydantic_settings import BaseSettings
10
+
11
+
12
+ class PolioraConfig(BaseSettings):
13
+ """Central configuration for a Poliora training run.
14
+
15
+ Values can be set via constructor kwargs, environment variables prefixed
16
+ with ``POLIORA_`` (e.g. ``POLIORA_MODEL_NAME``), or a ``.env`` file.
17
+ """
18
+
19
+ # ── Model & Data ─────────────────────────────────────────────────────
20
+ model_name: str = Field(
21
+ default="microsoft/phi-3-mini-4k-instruct",
22
+ description="Hugging Face model identifier or local path.",
23
+ )
24
+ dataset_path: Path = Field(
25
+ default=Path("data.csv"),
26
+ description="Path to a CSV dataset with at least a 'text' column.",
27
+ )
28
+ output_dir: Path = Field(
29
+ default=Path("tuned_model"),
30
+ description="Directory where the trained model will be saved.",
31
+ )
32
+ text_column: str = Field(
33
+ default="text",
34
+ description="Name of the text column in the CSV dataset.",
35
+ )
36
+ label_column: Optional[str] = Field(
37
+ default=None,
38
+ description="Optional name of the label column (for evaluation).",
39
+ )
40
+
41
+ # ── Training hyper-parameters ────────────────────────────────────────
42
+ epochs: int = Field(default=3, ge=1, description="Number of training epochs.")
43
+ batch_size: int = Field(default=4, ge=1, description="Per-device batch size.")
44
+ learning_rate: float = Field(default=2e-4, gt=0, description="Learning rate.")
45
+ max_seq_length: int = Field(default=512, ge=32, description="Maximum token sequence length.")
46
+ eval_split: float = Field(
47
+ default=0.1, ge=0.0, le=0.5,
48
+ description="Fraction of data to hold out for evaluation (0 = skip eval).",
49
+ )
50
+
51
+ # ── Optimisations ────────────────────────────────────────────────────
52
+ use_lora: bool = Field(default=True, description="Apply LoRA adapters for parameter-efficient training.")
53
+ lora_rank: int = Field(default=8, ge=1, description="LoRA rank (r).")
54
+ lora_alpha: int = Field(default=16, ge=1, description="LoRA alpha scaling factor.")
55
+ lora_dropout: float = Field(default=0.05, ge=0.0, le=1.0, description="LoRA dropout probability.")
56
+
57
+ use_quantization: bool = Field(default=True, description="Load model in quantised mode (bitsandbytes).")
58
+ quant_bits: Literal[4, 8] = Field(default=4, description="Quantisation bitwidth — 4 or 8.")
59
+ low_memory: bool = Field(default=False, description="Enable aggressive memory-saving tweaks.")
60
+
61
+ # ── Carbon tracking ──────────────────────────────────────────────────
62
+ track_carbon: bool = Field(default=True, description="Track energy & CO2 with CodeCarbon.")
63
+ country_iso_code: str = Field(default="USA", description="ISO 3166-1 alpha-3 country code for carbon intensity.")
64
+
65
+ # ── API keys (optional) ──────────────────────────────────────────────
66
+ electricity_maps_key: Optional[str] = Field(
67
+ default=None,
68
+ description="Electricity Maps API key for real-time grid carbon intensity.",
69
+ )
70
+ electricity_zone: str = Field(
71
+ default="US",
72
+ description="Electricity Maps zone code (e.g. 'US-CAL-CISO', 'DE', 'FR').",
73
+ )
74
+ grok_api_key: Optional[str] = Field(
75
+ default=None,
76
+ description="Grok / xAI API key for LLM-powered eco-tips.",
77
+ )
78
+
79
+ # ── Benchmark ────────────────────────────────────────────────────────
80
+ benchmark_output_dir: Path = Field(
81
+ default=Path("benchmark_output"),
82
+ description="Directory for benchmark reports.",
83
+ )
84
+ benchmark_export_json: Optional[Path] = Field(
85
+ default=None,
86
+ description="Path to write the benchmark JSON report.",
87
+ )
88
+ benchmark_export_csv: Optional[Path] = Field(
89
+ default=None,
90
+ description="Path to write the benchmark CSV report.",
91
+ )
92
+
93
+ model_config = {"env_prefix": "POLIORA_", "env_file": ".env", "extra": "ignore"}
94
+
95
+ # ── Validators ───────────────────────────────────────────────────────
96
+
97
+ @field_validator("dataset_path")
98
+ @classmethod
99
+ def _dataset_exists(cls, v: Path) -> Path:
100
+ """Warn early if the dataset path doesn't exist."""
101
+ if not v.exists():
102
+ raise FileNotFoundError(f"Dataset not found: {v}")
103
+ return v
@@ -0,0 +1,134 @@
1
+ """AI spend tracking primitives for Poliora."""
2
+
3
+ from poliora.cost.antigravity import (
4
+ AntigravityPluginInstall,
5
+ install_antigravity_plugin,
6
+ record_antigravity_hook_event,
7
+ )
8
+ from poliora.cost.budget import BudgetCheck, check_budget
9
+ from poliora.cost.capture import (
10
+ CapturedCall,
11
+ track_anthropic_call,
12
+ track_anthropic_client,
13
+ track_gemini_call,
14
+ track_gemini_client,
15
+ track_openai_call,
16
+ track_openai_client,
17
+ track_openai_compatible_call,
18
+ track_openai_compatible_client,
19
+ )
20
+ from poliora.cost.catalog import CatalogModel, ModelCatalog
21
+ from poliora.cost.codex_exec import CodexCli, find_codex_cli, record_codex_exec_event
22
+ from poliora.cost.companion import ConnectorConnection, ConnectorDefinition, ConnectorStore, connector_catalog
23
+ from poliora.cost.dashboard import ReportBranding, render_html_report, write_html_report
24
+ from poliora.cost.decisions import (
25
+ DECISION_STATUSES,
26
+ QUALITY_STATUSES,
27
+ DecisionStore,
28
+ SavingsDecision,
29
+ SavingsLedgerSummary,
30
+ summarize_decisions,
31
+ )
32
+ from poliora.cost.detection import DetectedTool, detect_local_tools
33
+ from poliora.cost.importer import (
34
+ CsvImportPreview,
35
+ CsvImportResult,
36
+ CsvRowIssue,
37
+ import_usage_csv,
38
+ import_usage_csv_text,
39
+ preview_usage_csv,
40
+ preview_usage_csv_text,
41
+ )
42
+ from poliora.cost.local_usage import (
43
+ DetectedPlan,
44
+ LocalUsageScan,
45
+ read_claude_code_usage,
46
+ read_codex_usage,
47
+ scan_local_usage,
48
+ )
49
+ from poliora.cost.pricing import ModelPricing, PricingRegistry, estimate_cost_usd
50
+ from poliora.cost.recommendations import Recommendation, generate_recommendations
51
+ from poliora.cost.reports import DailySpendRow, SpendAnomaly, UsageReport, build_usage_report
52
+ from poliora.cost.scanner import SystemScanReport, ToolScanResult, scan_system_ai_environment
53
+ from poliora.cost.scenarios import SavedScenario, ScenarioStore
54
+ from poliora.cost.sdk import log_anthropic_response, log_gemini_response, log_openai_response, log_usage
55
+ from poliora.cost.simulation import ModelSwitchSimulation, simulate_model_switch
56
+ from poliora.cost.sync import ModelSyncResult, sync_provider_models
57
+ from poliora.cost.usage import JsonlUsageStore, UsageEvent
58
+ from poliora.cost.workspace import PolioraWorkspace, init_workspace, load_workspace
59
+
60
+ __all__ = [
61
+ "CapturedCall",
62
+ "AntigravityPluginInstall",
63
+ "CatalogModel",
64
+ "CodexCli",
65
+ "ConnectorConnection",
66
+ "ConnectorDefinition",
67
+ "ConnectorStore",
68
+ "BudgetCheck",
69
+ "CsvImportResult",
70
+ "CsvImportPreview",
71
+ "CsvRowIssue",
72
+ "DECISION_STATUSES",
73
+ "DailySpendRow",
74
+ "DecisionStore",
75
+ "DetectedTool",
76
+ "PolioraWorkspace",
77
+ "JsonlUsageStore",
78
+ "ModelPricing",
79
+ "ModelCatalog",
80
+ "ModelSwitchSimulation",
81
+ "ModelSyncResult",
82
+ "PricingRegistry",
83
+ "Recommendation",
84
+ "ReportBranding",
85
+ "QUALITY_STATUSES",
86
+ "SavedScenario",
87
+ "SavingsDecision",
88
+ "SavingsLedgerSummary",
89
+ "ScenarioStore",
90
+ "SpendAnomaly",
91
+ "SystemScanReport",
92
+ "ToolScanResult",
93
+ "UsageEvent",
94
+ "UsageReport",
95
+ "build_usage_report",
96
+ "check_budget",
97
+ "connector_catalog",
98
+ "detect_local_tools",
99
+ "DetectedPlan",
100
+ "LocalUsageScan",
101
+ "read_claude_code_usage",
102
+ "read_codex_usage",
103
+ "scan_local_usage",
104
+ "estimate_cost_usd",
105
+ "find_codex_cli",
106
+ "generate_recommendations",
107
+ "init_workspace",
108
+ "install_antigravity_plugin",
109
+ "import_usage_csv",
110
+ "import_usage_csv_text",
111
+ "load_workspace",
112
+ "log_anthropic_response",
113
+ "log_gemini_response",
114
+ "log_openai_response",
115
+ "log_usage",
116
+ "render_html_report",
117
+ "preview_usage_csv",
118
+ "preview_usage_csv_text",
119
+ "record_codex_exec_event",
120
+ "record_antigravity_hook_event",
121
+ "scan_system_ai_environment",
122
+ "simulate_model_switch",
123
+ "sync_provider_models",
124
+ "summarize_decisions",
125
+ "track_anthropic_call",
126
+ "track_anthropic_client",
127
+ "track_gemini_call",
128
+ "track_gemini_client",
129
+ "track_openai_call",
130
+ "track_openai_client",
131
+ "track_openai_compatible_call",
132
+ "track_openai_compatible_client",
133
+ "write_html_report",
134
+ ]
@@ -0,0 +1,182 @@
1
+ """Supported Antigravity activity capture and plugin installation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import shlex
9
+ import subprocess
10
+ import sys
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from poliora.cost.usage import JsonlUsageStore, UsageEvent
16
+ from poliora.cost.workspace import load_workspace
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class AntigravityPluginInstall:
21
+ """Location and scope of an installed Antigravity plugin."""
22
+
23
+ path: Path
24
+ scope: str
25
+
26
+
27
+ def record_antigravity_hook_event(
28
+ payload: dict[str, Any],
29
+ *,
30
+ event_name: str,
31
+ root: str | Path | None = None,
32
+ ) -> UsageEvent | None:
33
+ """Record metadata exposed by an official Antigravity lifecycle hook.
34
+
35
+ Antigravity hooks do not currently expose model IDs or token totals. The
36
+ resulting event is therefore activity-only and is excluded from spend.
37
+ """
38
+ if event_name != "pre-invocation":
39
+ return None
40
+
41
+ workspace_root = Path(root).resolve() if root is not None else _workspace_root(payload)
42
+ workspace = load_workspace(workspace_root)
43
+ invocation_num = _non_negative_int(payload.get("invocationNum"), "invocationNum")
44
+ conversation_id = str(payload.get("conversationId") or "")
45
+ trace_id = _anonymous_trace_id(conversation_id) if conversation_id else None
46
+
47
+ event = UsageEvent(
48
+ provider="google",
49
+ model="antigravity-managed",
50
+ input_tokens=0,
51
+ output_tokens=0,
52
+ cost_usd=0.0,
53
+ operation="antigravity-agent",
54
+ project=workspace.project,
55
+ trace_id=trace_id,
56
+ metadata={
57
+ "source": "antigravity-pre-invocation-hook",
58
+ "billing_basis": "antigravity-subscription-activity",
59
+ "invocation_num": invocation_num,
60
+ "content_collected": False,
61
+ "token_usage_available": False,
62
+ },
63
+ )
64
+ JsonlUsageStore(workspace.usage_path).append(event)
65
+ return event
66
+
67
+
68
+ def install_antigravity_plugin(
69
+ root: str | Path = ".",
70
+ *,
71
+ scope: str = "workspace",
72
+ force: bool = False,
73
+ ) -> AntigravityPluginInstall:
74
+ """Install Poliora into one of Antigravity's documented plugin locations."""
75
+ plugin_path = _plugin_path(Path(root).resolve(), scope)
76
+ manifest_path = plugin_path / "plugin.json"
77
+ if manifest_path.exists() and not force:
78
+ try:
79
+ existing = json.loads(manifest_path.read_text(encoding="utf-8"))
80
+ except (OSError, json.JSONDecodeError) as error:
81
+ raise ValueError(f"Refusing to replace an unreadable plugin at {plugin_path}.") from error
82
+ if existing.get("name") != "poliora":
83
+ raise ValueError(f"A different plugin already exists at {plugin_path}. Use --force to replace it.")
84
+
85
+ files = {
86
+ "plugin.json": json.dumps({"name": "poliora"}, indent=2) + "\n",
87
+ "hooks.json": json.dumps(_hook_config(), indent=2) + "\n",
88
+ "skills/poliora-cost/SKILL.md": _skill_markdown(),
89
+ "rules/poliora-privacy.md": _privacy_rule_markdown(),
90
+ }
91
+ for relative_path, content in files.items():
92
+ destination = plugin_path / relative_path
93
+ destination.parent.mkdir(parents=True, exist_ok=True)
94
+ destination.write_text(content, encoding="utf-8")
95
+ return AntigravityPluginInstall(path=plugin_path, scope=scope)
96
+
97
+
98
+ def _workspace_root(payload: dict[str, Any]) -> Path:
99
+ paths = payload.get("workspacePaths")
100
+ if isinstance(paths, list):
101
+ candidates = [Path(item).resolve() for item in paths if isinstance(item, str) and item.strip()]
102
+ for candidate in candidates:
103
+ if (candidate / ".poliora" / "config.json").exists():
104
+ return candidate
105
+ if candidates:
106
+ return candidates[0]
107
+ return Path.cwd().resolve()
108
+
109
+
110
+ def _plugin_path(root: Path, scope: str) -> Path:
111
+ if scope == "workspace":
112
+ return root / ".agents" / "plugins" / "poliora"
113
+ if scope == "editor-global":
114
+ return Path.home() / ".gemini" / "config" / "plugins" / "poliora"
115
+ if scope == "cli-global":
116
+ return Path.home() / ".gemini" / "antigravity-cli" / "plugins" / "poliora"
117
+ raise ValueError("scope must be workspace, editor-global, or cli-global")
118
+
119
+
120
+ def _hook_config() -> dict[str, object]:
121
+ return {
122
+ "poliora-activity": {
123
+ "PreInvocation": [
124
+ {
125
+ "type": "command",
126
+ "command": _hook_command(),
127
+ "timeout": 10,
128
+ }
129
+ ]
130
+ }
131
+ }
132
+
133
+
134
+ def _hook_command() -> str:
135
+ arguments = [sys.executable, "-m", "poliora.main", "antigravity-hook", "--event", "pre-invocation"]
136
+ if os.name == "nt":
137
+ return subprocess.list2cmdline(arguments)
138
+ return shlex.join(arguments)
139
+
140
+
141
+ def _skill_markdown() -> str:
142
+ return """---
143
+ name: poliora-cost
144
+ description: Inspect and improve local AI usage with Poliora.
145
+ ---
146
+
147
+ # Poliora Cost Operations
148
+
149
+ Use Poliora when the user asks about AI spend, model usage, budgets, or routing savings.
150
+
151
+ - Run `poliora report` for the local usage summary.
152
+ - Run `poliora recommend` for prioritized savings opportunities.
153
+ - Run `poliora dashboard` only when the user asks to open the local dashboard.
154
+ - For Gemini API code, wrap `google.genai.Client` with `poliora.cost.track_gemini_client`.
155
+ - Never infer token counts, model IDs, or dollar cost from Antigravity activity-only events.
156
+ """
157
+
158
+
159
+ def _privacy_rule_markdown() -> str:
160
+ return """# Poliora Privacy
161
+
162
+ Poliora may record lifecycle metadata and provider-reported usage totals. Do not send prompt text,
163
+ responses, source files, commands, transcript contents, credentials, or file-change content to Poliora.
164
+ Antigravity subscription activity must remain separate from measured or estimated API spend.
165
+ """
166
+
167
+
168
+ def _anonymous_trace_id(conversation_id: str) -> str:
169
+ digest = hashlib.sha256(conversation_id.encode("utf-8")).hexdigest()
170
+ return f"antigravity-{digest[:16]}"
171
+
172
+
173
+ def _non_negative_int(value: Any, field: str) -> int:
174
+ if isinstance(value, bool):
175
+ raise ValueError(f"Antigravity {field} must be a non-negative integer.")
176
+ try:
177
+ parsed = int(value)
178
+ except (TypeError, ValueError) as error:
179
+ raise ValueError(f"Antigravity {field} must be a non-negative integer.") from error
180
+ if parsed < 0:
181
+ raise ValueError(f"Antigravity {field} must be a non-negative integer.")
182
+ return parsed
poliora/cost/budget.py ADDED
@@ -0,0 +1,52 @@
1
+ """Budget checks for local reports and CI gates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import asdict, dataclass
6
+
7
+ from poliora.cost.reports import UsageReport
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class BudgetCheck:
12
+ """Result of comparing projected spend against a budget."""
13
+
14
+ passed: bool
15
+ projected_monthly_usd: float
16
+ limit_usd: float
17
+ used_pct: float
18
+ remaining_usd: float
19
+ message: str
20
+
21
+ def to_dict(self) -> dict[str, object]:
22
+ """Serialize budget check."""
23
+ return asdict(self)
24
+
25
+
26
+ def check_budget(report: UsageReport, *, limit_usd: float, warn_at_pct: float = 80.0) -> BudgetCheck:
27
+ """Check whether projected monthly spend is within a budget."""
28
+ if limit_usd <= 0:
29
+ raise ValueError("Budget limit must be greater than zero.")
30
+ if warn_at_pct <= 0:
31
+ raise ValueError("warn_at_pct must be greater than zero.")
32
+
33
+ projected = report.projected_monthly_usd
34
+ used_pct = round(projected / limit_usd * 100, 2)
35
+ remaining = round(limit_usd - projected, 2)
36
+ passed = projected <= limit_usd
37
+
38
+ if not passed:
39
+ message = f"Projected AI spend is ${projected:,.2f}, above the ${limit_usd:,.2f} limit."
40
+ elif used_pct >= warn_at_pct:
41
+ message = f"Projected AI spend is at {used_pct:.1f}% of budget. Watch this closely."
42
+ else:
43
+ message = f"Projected AI spend is within budget with ${remaining:,.2f} remaining."
44
+
45
+ return BudgetCheck(
46
+ passed=passed,
47
+ projected_monthly_usd=projected,
48
+ limit_usd=limit_usd,
49
+ used_pct=used_pct,
50
+ remaining_usd=remaining,
51
+ message=message,
52
+ )