Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/guardian.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ jobs:
- name: Run Guardian Review
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GUARDIAN_MODEL: ${{ vars.GUARDIAN_MODEL }}
run: uv run python scripts/guardian_review.py --output guardian_report.md

- name: Post review as PR comment
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ dev = [
"types-pyyaml>=6.0.12.20260518",
]
guardian = [
"google-generativeai>=0.8.0",
"google-genai>=0.8.0",
"mistralai>=1.0.0",
"cohere>=5.0.0",
"ollama>=0.3.0",
Expand Down
16 changes: 12 additions & 4 deletions scripts/guardian_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

log = structlog.getLogger(__name__)

_DEFAULT_MODEL = "gemini-2.0-flash"


async def main() -> None:
"""Run Guardian review and write the result to stdout or a file."""
Expand All @@ -26,19 +28,25 @@ async def main() -> None:
args = parser.parse_args()

api_key = os.environ["GEMINI_API_KEY"]
model = os.environ.get("GUARDIAN_MODEL") or _DEFAULT_MODEL
project_root = Path(__file__).parent.parent.absolute()

provider = GeminiProvider(api_key=api_key)
provider = GeminiProvider(api_key=api_key, model_name=model)
collector = ContextCollector(project_root=project_root)
reviewer = GuardianReviewer(provider=provider, context_collector=collector)

log.info("Collecting context and building prompts...")
log.info("Collecting context and building prompts...", model=model)
review_result = await reviewer.run_review()
log.info("Review complete.")

if args.output:
args.output.write_text(review_result)
log.info("Review written to file.", path=str(args.output))
safe_root = Path.cwd().resolve()
output_path = (safe_root / args.output).resolve()
if not output_path.is_relative_to(safe_root):
_msg = f"--output must be within the working directory: {output_path}"
raise ValueError(_msg)
output_path.write_text(review_result)
log.info("Review written to file.", path=str(output_path))
else:
print(review_result)

Expand Down
16 changes: 10 additions & 6 deletions src/cgis/guardian/providers/gemini.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ class GeminiProvider(BaseProvider):
"""Google Gemini provider. Requires: uv sync --group guardian"""

def __init__(self, api_key: str, model_name: str = "gemini-2.0-flash") -> None:
"""Store credentials; google-generativeai is imported lazily at call time."""
"""Store credentials; google-genai is imported lazily at call time."""
self._api_key = api_key
self._model_name = model_name

async def generate_content(self, system_prompt: str, user_prompt: str) -> str:
"""Send prompts to Gemini and return the text response."""
_install_hint = "google-generativeai is required. Install with: uv sync --group guardian"
_install_hint = "google-genai is required. Install with: uv sync --group guardian"
try:
import google.generativeai as genai # noqa: PLC0415
from google import genai # noqa: PLC0415
from google.genai import types # noqa: PLC0415
except ImportError as exc:
raise ImportError(_install_hint) from exc
genai.configure(api_key=self._api_key)
model = genai.GenerativeModel(self._model_name, system_instruction=system_prompt)
response = await model.generate_content_async(user_prompt)
client = genai.Client(api_key=self._api_key)
response = await client.aio.models.generate_content(
model=self._model_name,
contents=user_prompt,
config=types.GenerateContentConfig(system_instruction=system_prompt),
)
return str(response.text)
Loading
Loading