generated from coulomb/repo-seed
feat(cya): T01-T07 core console-native MVP slice (CYA-WP-0001)
- T01: Python + Typer/rich + pyproject.toml + full src/ layout + working `cya` CLI entrypoint - T02: Bounded transparent context collector (top-level only, provenance, ignores) + --explain-context - T03: Genuine rule-based risk classifier (primary) + mandatory terminal confirmation, no auto-execute - T04: LLMAdapter Protocol + deterministic FakeLLMAdapter seam (llm-connect boundary, zero bypass) - T05: Strictly minimal phase-memory no-op ports (loud markers, per operator direction 2026-05-26) - T06: Orchestrator coordinating the full flow; CLI is thin delegation - T07: pytest harness + safety-focused tests (risk invariants + collector) All changes verified by running the installed `cya` binary and `pytest tests/`. Workplan updated with status. State Hub progress event logged (workstream 0a1233fd...). Refs: CYA-WP-0001, Decision a644364b-11c4-49a9-bf17-99063382e27b
This commit is contained in:
27
src/cya/llm/__init__.py
Normal file
27
src/cya/llm/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""llm-connect adapter boundary — the integration seam (T04).
|
||||
|
||||
can-you-assist owns orchestration + CLI experience.
|
||||
llm-connect owns provider access, config, token counting, and structured I/O.
|
||||
|
||||
This package defines the small stable Protocol / interface that all model
|
||||
interaction must flow through. A deterministic fake lives here for tests.
|
||||
Real delegation to llm-connect is a small localized change once the contract
|
||||
is stable.
|
||||
|
||||
See workplan CYA-WP-0001-T04 for the full contract and acceptance criteria.
|
||||
"""
|
||||
|
||||
from .adapter import (
|
||||
AssistanceRequest,
|
||||
AssistanceResponse,
|
||||
LLMAdapter,
|
||||
FakeLLMAdapter,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AssistanceRequest",
|
||||
"AssistanceResponse",
|
||||
"LLMAdapter",
|
||||
"FakeLLMAdapter",
|
||||
]
|
||||
|
||||
139
src/cya/llm/adapter.py
Normal file
139
src/cya/llm/adapter.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""llm-connect adapter boundary (T04 — the integration seam).
|
||||
|
||||
Per SCOPE.md and INTENT.md:
|
||||
- `can-you-assist` owns orchestration + CLI experience.
|
||||
- `llm-connect` owns provider access, config, token counting, and structured I/O.
|
||||
|
||||
This module defines the single stable contract that *all* model interaction
|
||||
in this repository must flow through. There must never be a production code
|
||||
path that talks to an LLM (or a mock) while bypassing this boundary.
|
||||
|
||||
Design goals for the MVP slice:
|
||||
- Tiny, stable surface (Protocol + two simple data containers).
|
||||
- A deterministic, fully reproducible FakeLLMAdapter for tests and early demos.
|
||||
- Easy future replacement: swapping the fake for a real (or stubbed)
|
||||
llm-connect client must be a small, localized change.
|
||||
|
||||
See workplan CYA-WP-0001-T04 for the full acceptance criteria and the
|
||||
"Integration Guide for llm-connect" expectations.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Protocol
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Request / Response shapes (kept minimal for T04)
|
||||
# These will evolve slightly when the real orchestrator (T06) and
|
||||
# llm-connect types are known, but the boundary contract stays stable.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclass
|
||||
class AssistanceRequest:
|
||||
"""What we send to the LLM adapter.
|
||||
|
||||
Contains the framed user intent, the packed context envelope (or its
|
||||
serialised form), and any hints the caller wants to pass (model prefs,
|
||||
token budget, etc.). The adapter is allowed to ignore hints it does not
|
||||
understand.
|
||||
"""
|
||||
|
||||
user_request: str
|
||||
context: dict[str, Any] | None = None # usually a ContextEnvelope.to_dict()
|
||||
hints: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AssistanceResponse:
|
||||
"""What comes back from the LLM adapter.
|
||||
|
||||
The orchestrator / CLI is responsible for turning this into the final
|
||||
user-facing output. The raw fields are intentionally rich so that
|
||||
different front-ends (terminal, future voice) can render appropriately.
|
||||
"""
|
||||
|
||||
suggestion: str
|
||||
explanation: str = ""
|
||||
rationale: str = ""
|
||||
risks: list[str] = field(default_factory=list)
|
||||
raw_model_output: str | None = None
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The stable boundary
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class LLMAdapter(Protocol):
|
||||
"""The single seam for all model interaction.
|
||||
|
||||
Any real implementation (llm-connect or otherwise) must satisfy this
|
||||
protocol. All production call sites must go through an instance of
|
||||
something obeying this interface.
|
||||
"""
|
||||
|
||||
def complete(self, request: AssistanceRequest) -> AssistanceResponse:
|
||||
"""Turn a framed request + context into a structured assistant response."""
|
||||
...
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Deterministic fake (used by tests, early demos, and T06 development)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class FakeLLMAdapter:
|
||||
"""A fully deterministic, side-effect-free fake adapter.
|
||||
|
||||
Returns canned but useful responses that are stable across runs.
|
||||
The response content is derived only from the request text so that
|
||||
tests can assert on it without any network or real model.
|
||||
|
||||
This is the implementation that must be used by all unit and safety
|
||||
tests until a real adapter is explicitly swapped in.
|
||||
"""
|
||||
|
||||
def complete(self, request: AssistanceRequest) -> AssistanceResponse:
|
||||
user_text = request.user_request.strip()
|
||||
risks: list[str] = []
|
||||
|
||||
# Very simple deterministic logic for the MVP slice.
|
||||
# In a real adapter this would be the call to llm-connect.
|
||||
if "delete" in user_text.lower() or "remove" in user_text.lower():
|
||||
suggestion = "I cannot recommend executing that directly. Consider a more targeted command or review the exact files first."
|
||||
explanation = "Your request contained destructive language. The safety layer already required confirmation; the model echoes caution."
|
||||
rationale = "Rule-based safety + conservative model policy."
|
||||
risks = ["Destructive intent detected by rules", "Broad scope in request"]
|
||||
elif "git" in user_text.lower() and ("log" in user_text.lower() or "history" in user_text.lower()):
|
||||
suggestion = "Run: git log --oneline -10 --graph --decorate"
|
||||
explanation = "Standard, safe way to view recent history with a compact graph."
|
||||
rationale = "Common informational request; safe read-only operation."
|
||||
else:
|
||||
suggestion = f"Understood: {user_text[:80]}...\n\nSuggested next step: explore the current directory with `ls -la` or `git status` and share more specific intent."
|
||||
explanation = "This is a placeholder response from the FakeLLMAdapter (T04)."
|
||||
rationale = "No high-risk patterns; generic helpful reply."
|
||||
|
||||
return AssistanceResponse(
|
||||
suggestion=suggestion,
|
||||
explanation=explanation,
|
||||
rationale=rationale,
|
||||
risks=risks,
|
||||
raw_model_output=f"[FAKE] echo of request: {user_text[:200]}",
|
||||
metadata={
|
||||
"adapter": "FakeLLMAdapter",
|
||||
"version": "t04-mvp",
|
||||
"deterministic": True,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AssistanceRequest",
|
||||
"AssistanceResponse",
|
||||
"LLMAdapter",
|
||||
"FakeLLMAdapter",
|
||||
]
|
||||
Reference in New Issue
Block a user