DevelopedBy-Siva commited on
Commit
fb38df2
·
1 Parent(s): f0c7697

deploy to HF

Browse files
.dockerignore CHANGED
@@ -7,4 +7,3 @@ __pycache__
7
  .env.example
8
  extension
9
  tests
10
- flowpilot-vertex-key.json
 
7
  .env.example
8
  extension
9
  tests
 
.github/workflows/deploy.yml CHANGED
@@ -1,4 +1,4 @@
1
- name: Deploy Backend
2
 
3
  on:
4
  push:
@@ -6,56 +6,26 @@ on:
6
  - main
7
  workflow_dispatch:
8
 
9
- env:
10
- PROJECT_ID: flow-pilot-493104
11
- PROJECT_NUMBER: "707525727191"
12
- REGION: us-central1
13
- SERVICE: flowpilot-backend
14
- REPOSITORY: flowpilot
15
- IMAGE: backend
16
- WIF_PROVIDER: projects/707525727191/locations/global/workloadIdentityPools/github-pool/providers/github-provider
17
- SERVICE_ACCOUNT: github-deployer@flow-pilot-493104.iam.gserviceaccount.com
18
-
19
  jobs:
20
  deploy:
21
  runs-on: ubuntu-latest
22
  permissions:
23
  contents: read
24
- id-token: write
25
 
26
  steps:
27
  - name: Checkout
28
  uses: actions/checkout@v4
29
-
30
- - name: Authenticate to Google Cloud
31
- uses: google-github-actions/auth@v3
32
  with:
33
- workload_identity_provider: ${{ env.WIF_PROVIDER }}
34
- service_account: ${{ env.SERVICE_ACCOUNT }}
35
-
36
- - name: Setup gcloud
37
- uses: google-github-actions/setup-gcloud@v2
38
-
39
- - name: Configure Docker auth
40
- run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet
41
 
42
- - name: Build image
 
 
 
43
  run: |
44
- docker build \
45
- -t ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE }}:${{ github.sha }} \
46
- .
47
-
48
- - name: Push image
49
- run: |
50
- docker push \
51
- ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE }}:${{ github.sha }}
52
-
53
- - name: Deploy to Cloud Run
54
- uses: google-github-actions/deploy-cloudrun@v3
55
- with:
56
- service: ${{ env.SERVICE }}
57
- region: ${{ env.REGION }}
58
- image: ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE }}:${{ github.sha }}
59
- flags: >-
60
- --allow-unauthenticated
61
- --set-env-vars=AI_PROVIDER=vertex_ai,VERTEX_PROJECT_ID=${{ env.PROJECT_ID }},VERTEX_LOCATION=${{ env.REGION }},VERTEX_MODEL=gemini-2.5-flash
 
1
+ name: Deploy To Hugging Face Space
2
 
3
  on:
4
  push:
 
6
  - main
7
  workflow_dispatch:
8
 
 
 
 
 
 
 
 
 
 
 
9
  jobs:
10
  deploy:
11
  runs-on: ubuntu-latest
12
  permissions:
13
  contents: read
 
14
 
15
  steps:
16
  - name: Checkout
17
  uses: actions/checkout@v4
 
 
 
18
  with:
19
+ fetch-depth: 0
 
 
 
 
 
 
 
20
 
21
+ - name: Push to Hugging Face Space
22
+ env:
23
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
24
+ HF_SPACE_REPO: ${{ secrets.HF_SPACE_REPO }}
25
  run: |
26
+ test -n "$HF_TOKEN"
27
+ test -n "$HF_SPACE_REPO"
28
+ git config user.name "github-actions[bot]"
29
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
30
+ git remote add hf "https://oauth2:${HF_TOKEN}@huggingface.co/spaces/${HF_SPACE_REPO}"
31
+ git push hf HEAD:main --force
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore CHANGED
@@ -18,6 +18,5 @@ build/
18
  .idea/
19
  .vscode/
20
 
21
- flowpilot-vertex-key.json
22
  *.pem
23
  *.key
 
18
  .idea/
19
  .vscode/
20
 
 
21
  *.pem
22
  *.key
Dockerfile CHANGED
@@ -2,7 +2,7 @@ FROM python:3.12-slim
2
 
3
  ENV PYTHONDONTWRITEBYTECODE=1
4
  ENV PYTHONUNBUFFERED=1
5
- ENV PORT=8080
6
 
7
  WORKDIR /app
8
 
@@ -11,4 +11,6 @@ RUN pip install --no-cache-dir -r /app/backend/requirements.txt
11
 
12
  COPY backend /app/backend
13
 
14
- CMD ["sh", "-c", "uvicorn backend.main:app --host 0.0.0.0 --port ${PORT:-8080}"]
 
 
 
2
 
3
  ENV PYTHONDONTWRITEBYTECODE=1
4
  ENV PYTHONUNBUFFERED=1
5
+ ENV PORT=7860
6
 
7
  WORKDIR /app
8
 
 
11
 
12
  COPY backend /app/backend
13
 
14
+ EXPOSE 7860
15
+
16
+ CMD ["sh", "-c", "uvicorn backend.main:app --host 0.0.0.0 --port ${PORT:-7860}"]
README.md CHANGED
@@ -1,11 +1,21 @@
 
 
 
 
 
 
 
 
 
 
1
  # FlowPilot
2
 
3
- FlowPilot is a Gmail-first automation layer for small business owners. The current scaffold includes:
4
 
5
  - A FastAPI backend with analysis, workflow suggestion, workflow build, deploy, upload, status, and escalation endpoints.
6
  - A primitive-based workflow engine that compiles and executes JSON workflows.
7
- - A Chrome extension scaffold that injects a sidebar into Gmail and walks through the onboarding-to-live journey.
8
- - Lightweight in-memory storage and tests for the core owner flow.
9
 
10
  ## Project Structure
11
 
@@ -13,67 +23,79 @@ FlowPilot is a Gmail-first automation layer for small business owners. The curre
13
  backend/
14
  extension/
15
  tests/
 
 
16
  ```
17
 
18
- ## Backend
19
 
20
- Run locally:
21
 
22
  ```bash
23
  pip install -r backend/requirements.txt
24
  uvicorn backend.main:app --reload
25
  ```
26
 
27
- Create a `.env` in the repo root first. FlowPilot is now wired to read Vertex AI settings from that file.
28
 
29
  ```bash
30
- cp .env.example .env
31
  ```
32
 
33
- ## Extension
 
 
 
 
 
 
 
 
34
 
35
- Load `extension/` as an unpacked Chrome extension. The sidebar injects into Gmail and points to `http://localhost:8000/api`.
36
 
37
- ## Deploying
38
 
39
- The repo includes a Cloud Run GitHub Actions workflow at [`.github/workflows/deploy.yml`](/Users/sivasankernp/Desktop/flow-pilot/.github/workflows/deploy.yml:1).
40
 
41
- Before the action will work, finish these one-time Google Cloud steps:
42
 
43
  ```bash
44
- gcloud iam workload-identity-pools create github-pool \
45
- --project=flow-pilot-493104 \
46
- --location=global \
47
- --display-name="GitHub Pool"
48
  ```
49
 
50
- ```bash
51
- gcloud iam workload-identity-pools providers create-oidc github-provider \
52
- --project=flow-pilot-493104 \
53
- --location=global \
54
- --workload-identity-pool=github-pool \
55
- --display-name="GitHub Provider" \
56
- --issuer-uri="https://token.actions.githubusercontent.com" \
57
- --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.actor=assertion.actor"
58
- ```
59
 
60
  ```bash
61
- gcloud iam service-accounts add-iam-policy-binding \
62
- github-deployer@flow-pilot-493104.iam.gserviceaccount.com \
63
- --project=flow-pilot-493104 \
64
- --role="roles/iam.workloadIdentityUser" \
65
- --member="principalSet://iam.googleapis.com/projects/707525727191/locations/global/workloadIdentityPools/github-pool/attribute.repository/DevelopedBy-Siva/flow-pilot"
66
  ```
67
 
68
- The workflow builds the Docker image, pushes it to Artifact Registry, and deploys the FastAPI backend to Cloud Run.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  ## Notes
71
 
72
- - Backend storage is currently in-memory for demo speed; the folder layout is ready for SQLite migration work.
73
- - AI services are still mocked for workflow behavior, but config is now set up for Vertex AI credentials and model selection.
74
- - Fill in your real Vertex values in `.env`, especially `VERTEX_PROJECT_ID` and `VERTEX_LOCATION`.
75
- - If you authenticate with `gcloud auth application-default login`, leave `GOOGLE_APPLICATION_CREDENTIALS` blank.
76
- - Only set `GOOGLE_APPLICATION_CREDENTIALS` when you have a real service account JSON path available.
77
- - The default model is `gemini-2.5-flash` for better latency and cost during iteration.
78
- - The backend now attempts live Vertex AI calls through the `google-genai` SDK when credentials are configured.
79
- - If the SDK is missing or `VERTEX_PROJECT_ID` is still a placeholder, FlowPilot falls back to local deterministic mock logic so tests and scaffolding still work.
 
1
+ ---
2
+ title: FlowPilot
3
+ emoji: "📬"
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ app_port: 7860
8
+ short_description: Gmail-first small business workflow automation with FastAPI.
9
+ ---
10
+
11
  # FlowPilot
12
 
13
+ FlowPilot is a Gmail-first automation layer for small business owners. The project includes:
14
 
15
  - A FastAPI backend with analysis, workflow suggestion, workflow build, deploy, upload, status, and escalation endpoints.
16
  - A primitive-based workflow engine that compiles and executes JSON workflows.
17
+ - A Chrome extension scaffold that injects a sidebar into Gmail and walks through the onboarding flow.
18
+ - Lightweight demo storage and tests for the core owner flow.
19
 
20
  ## Project Structure
21
 
 
23
  backend/
24
  extension/
25
  tests/
26
+ Dockerfile
27
+ README.md
28
  ```
29
 
30
+ ## Local Development
31
 
32
+ Install dependencies and run the backend:
33
 
34
  ```bash
35
  pip install -r backend/requirements.txt
36
  uvicorn backend.main:app --reload
37
  ```
38
 
39
+ Create a `.env` in the repo root first:
40
 
41
  ```bash
42
+ nano .env
43
  ```
44
 
45
+ Set these values when you want live Groq responses:
46
+
47
+ ```bash
48
+ AI_PROVIDER=groq
49
+ GROQ_API_KEY=your_groq_key
50
+ GROQ_MODEL=llama-3.3-70b-versatile
51
+ GROQ_BASE_URL=https://api.groq.com/openai/v1
52
+ GROQ_TIMEOUT_SECONDS=8
53
+ ```
54
 
55
+ If `GROQ_API_KEY` is blank, FlowPilot falls back to local deterministic logic so the app and tests still work.
56
 
57
+ ## Hugging Face Space
58
 
59
+ This repo is ready for a Docker Space. The container listens on port `7860`, and the metadata at the top of this README tells Hugging Face to treat it as a Docker app.
60
 
61
+ Typical flow:
62
 
63
  ```bash
64
+ git clone https://huggingface.co/spaces/technophyle/flow-pilot
65
+ cd flow-pilot
 
 
66
  ```
67
 
68
+ Copy this project into the cloned Space repo, then push:
 
 
 
 
 
 
 
 
69
 
70
  ```bash
71
+ git add .
72
+ git commit -m "Switch FlowPilot to Hugging Face Space with Groq"
73
+ git push
 
 
74
  ```
75
 
76
+ In the Hugging Face Space settings, add these secrets:
77
+
78
+ - `GROQ_API_KEY`
79
+
80
+ Optional Space variables:
81
+
82
+ - `AI_PROVIDER=groq`
83
+ - `GROQ_MODEL=llama-3.3-70b-versatile`
84
+ - `GROQ_BASE_URL=https://api.groq.com/openai/v1`
85
+ - `GROQ_TIMEOUT_SECONDS=8`
86
+ - `ANALYZE_WITH_AI=false`
87
+
88
+ For GitHub Actions auto-deploys, add these repository settings:
89
+
90
+ - Secret: `HF_TOKEN`
91
+ - Variable: `HF_SPACE_REPO=technophyle/flow-pilot`
92
+
93
+ ## Extension
94
+
95
+ Load `extension/` as an unpacked Chrome extension. The sidebar currently points to `http://localhost:8000/api` for local work, so update that base URL when you connect it to your hosted Space backend.
96
 
97
  ## Notes
98
 
99
+ - Backend storage is currently lightweight demo storage suitable for iteration.
100
+ - Groq is used for live AI calls through its OpenAI-compatible chat completions API.
101
+ - The `/api/analyze` endpoint can still stay fast and deterministic when `ANALYZE_WITH_AI=false`.
 
 
 
 
 
backend/ai/analyzer.py CHANGED
@@ -1,13 +1,18 @@
1
  from __future__ import annotations
2
 
3
- from backend.ai.client import render_prompt, vertex_client
 
4
  from backend.ai.prompts import ANALYZE_PROMPT, CUSTOM_TASK_PROMPT
5
  from backend.models.schemas import TaskCategory
6
 
7
 
8
  def analyze_business_description(description: str) -> dict:
9
- if vertex_client.is_ready():
10
- return vertex_client.generate_json(render_prompt(ANALYZE_PROMPT, description=description))
 
 
 
 
11
 
12
  lower = description.lower()
13
  fully_automatable = []
@@ -71,8 +76,8 @@ def analyze_business_description(description: str) -> dict:
71
 
72
 
73
  def analyze_custom_task(business_description: str, existing_workflows: list[dict], custom_task: str) -> dict:
74
- if vertex_client.is_ready():
75
- return vertex_client.generate_json(
76
  render_prompt(
77
  CUSTOM_TASK_PROMPT,
78
  business_description=business_description,
 
1
  from __future__ import annotations
2
 
3
+ from backend.ai.client import llm_client, render_prompt
4
+ from backend.config import get_settings
5
  from backend.ai.prompts import ANALYZE_PROMPT, CUSTOM_TASK_PROMPT
6
  from backend.models.schemas import TaskCategory
7
 
8
 
9
  def analyze_business_description(description: str) -> dict:
10
+ settings = get_settings()
11
+ if settings.analyze_with_ai and llm_client.is_ready():
12
+ try:
13
+ return llm_client.generate_json(render_prompt(ANALYZE_PROMPT, description=description))
14
+ except Exception:
15
+ pass
16
 
17
  lower = description.lower()
18
  fully_automatable = []
 
76
 
77
 
78
  def analyze_custom_task(business_description: str, existing_workflows: list[dict], custom_task: str) -> dict:
79
+ if llm_client.is_ready():
80
+ return llm_client.generate_json(
81
  render_prompt(
82
  CUSTOM_TASK_PROMPT,
83
  business_description=business_description,
backend/ai/classifier.py CHANGED
@@ -1,10 +1,10 @@
1
- from backend.ai.client import render_prompt, vertex_client
2
  from backend.ai.prompts import CLASSIFY_PROMPT
3
 
4
 
5
  def classify_email(from_email: str, subject: str, body: str, business_context: str) -> dict:
6
- if vertex_client.is_ready():
7
- return vertex_client.generate_json(
8
  render_prompt(
9
  CLASSIFY_PROMPT,
10
  categories="order, availability_inquiry, complaint, greeting, other",
 
1
+ from backend.ai.client import llm_client, render_prompt
2
  from backend.ai.prompts import CLASSIFY_PROMPT
3
 
4
 
5
  def classify_email(from_email: str, subject: str, body: str, business_context: str) -> dict:
6
+ if llm_client.is_ready():
7
+ return llm_client.generate_json(
8
  render_prompt(
9
  CLASSIFY_PROMPT,
10
  categories="order, availability_inquiry, complaint, greeting, other",
backend/ai/client.py CHANGED
@@ -1,60 +1,55 @@
1
  from __future__ import annotations
2
 
3
  import json
4
- import os
5
  from typing import Any
6
 
7
- from backend.config import get_settings
8
 
9
- try:
10
- from google import genai
11
- except ImportError: # pragma: no cover - depends on optional package
12
- genai = None
13
 
14
 
15
- class VertexAIClient:
16
  def __init__(self) -> None:
17
  self.settings = get_settings()
18
- self._client = None
19
 
20
  def generate_json(self, prompt: str) -> dict[str, Any]:
21
  text = self.generate_text(prompt)
22
  return _extract_json_object(text)
23
 
24
  def generate_text(self, prompt: str) -> str:
25
- client = self._get_client()
26
- response = client.models.generate_content(
27
- model=self.settings.vertex_model,
28
- contents=prompt,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  )
30
- text = getattr(response, "text", None)
31
- if not text:
32
- raise RuntimeError("Vertex AI returned an empty response")
33
- return text.strip()
 
 
34
 
35
  def is_ready(self) -> bool:
36
- placeholders = {"your-gcp-project-id"}
37
- if self.settings.ai_provider != "vertex_ai":
38
- return False
39
- if genai is None:
40
- return False
41
- if self.settings.vertex_project_id in placeholders:
42
- return False
43
- credentials_path = self.settings.google_application_credentials.strip()
44
- if credentials_path and credentials_path != "/absolute/path/to/service-account.json":
45
- return os.path.exists(credentials_path)
46
- return True
47
-
48
- def _get_client(self):
49
- if genai is None:
50
- raise RuntimeError("google-genai is not installed")
51
- if self._client is None:
52
- self._client = genai.Client(
53
- vertexai=True,
54
- project=self.settings.vertex_project_id,
55
- location=self.settings.vertex_location,
56
- )
57
- return self._client
58
 
59
 
60
  def _extract_json_object(text: str) -> dict[str, Any]:
@@ -70,7 +65,7 @@ def _extract_json_object(text: str) -> dict[str, Any]:
70
  return json.loads(cleaned[start : end + 1])
71
 
72
 
73
- vertex_client = VertexAIClient()
74
 
75
 
76
  def render_prompt(template: str, **kwargs: Any) -> str:
 
1
  from __future__ import annotations
2
 
3
  import json
 
4
  from typing import Any
5
 
6
+ import httpx
7
 
8
+ from backend.config import get_settings
 
 
 
9
 
10
 
11
+ class GroqAIClient:
12
  def __init__(self) -> None:
13
  self.settings = get_settings()
 
14
 
15
  def generate_json(self, prompt: str) -> dict[str, Any]:
16
  text = self.generate_text(prompt)
17
  return _extract_json_object(text)
18
 
19
  def generate_text(self, prompt: str) -> str:
20
+ settings = get_settings()
21
+ response = httpx.post(
22
+ f"{settings.groq_base_url.rstrip('/')}/chat/completions",
23
+ headers={
24
+ "Authorization": f"Bearer {settings.groq_api_key}",
25
+ "Content-Type": "application/json",
26
+ },
27
+ json={
28
+ "model": settings.groq_model,
29
+ "messages": [
30
+ {
31
+ "role": "system",
32
+ "content": (
33
+ "You are a precise backend assistant. "
34
+ "Follow the prompt exactly and return valid JSON when requested."
35
+ ),
36
+ },
37
+ {"role": "user", "content": prompt},
38
+ ],
39
+ "temperature": 0.2,
40
+ },
41
+ timeout=settings.groq_timeout_seconds,
42
  )
43
+ response.raise_for_status()
44
+ payload = response.json()
45
+ try:
46
+ return payload["choices"][0]["message"]["content"].strip()
47
+ except (KeyError, IndexError, AttributeError) as exc:
48
+ raise RuntimeError("Groq returned an unexpected response shape") from exc
49
 
50
  def is_ready(self) -> bool:
51
+ settings = get_settings()
52
+ return settings.ai_provider == "groq" and bool(settings.groq_api_key.strip())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
 
55
  def _extract_json_object(text: str) -> dict[str, Any]:
 
65
  return json.loads(cleaned[start : end + 1])
66
 
67
 
68
+ llm_client = GroqAIClient()
69
 
70
 
71
  def render_prompt(template: str, **kwargs: Any) -> str:
backend/ai/composer.py CHANGED
@@ -1,12 +1,12 @@
1
  import json
2
 
3
- from backend.ai.client import render_prompt, vertex_client
4
  from backend.ai.prompts import COMPOSE_PROMPT
5
 
6
 
7
  def compose_reply(context: dict, tone: str = "friendly") -> str:
8
- if vertex_client.is_ready():
9
- return vertex_client.generate_text(
10
  render_prompt(
11
  COMPOSE_PROMPT,
12
  business_name=context.get("business_name", "FlowPilot customer"),
 
1
  import json
2
 
3
+ from backend.ai.client import llm_client, render_prompt
4
  from backend.ai.prompts import COMPOSE_PROMPT
5
 
6
 
7
  def compose_reply(context: dict, tone: str = "friendly") -> str:
8
+ if llm_client.is_ready():
9
+ return llm_client.generate_text(
10
  render_prompt(
11
  COMPOSE_PROMPT,
12
  business_name=context.get("business_name", "FlowPilot customer"),
backend/ai/extractor.py CHANGED
@@ -1,10 +1,10 @@
1
- from backend.ai.client import render_prompt, vertex_client
2
  from backend.ai.prompts import EXTRACT_PROMPT
3
 
4
 
5
  def extract_email_data(from_email: str, subject: str, body: str) -> dict:
6
- if vertex_client.is_ready():
7
- return vertex_client.generate_json(
8
  render_prompt(
9
  EXTRACT_PROMPT,
10
  from_email=from_email,
 
1
+ from backend.ai.client import llm_client, render_prompt
2
  from backend.ai.prompts import EXTRACT_PROMPT
3
 
4
 
5
  def extract_email_data(from_email: str, subject: str, body: str) -> dict:
6
+ if llm_client.is_ready():
7
+ return llm_client.generate_json(
8
  render_prompt(
9
  EXTRACT_PROMPT,
10
  from_email=from_email,
backend/ai/workflow_builder.py CHANGED
@@ -1,12 +1,12 @@
1
  import json
2
 
3
- from backend.ai.client import render_prompt, vertex_client
4
  from backend.ai.prompts import BUILD_WORKFLOW_PROMPT
5
  from backend.models.schemas import BuildWorkflowRequest, WorkflowDefinition, WorkflowStep
6
 
7
 
8
  def build_workflow_definition(request: BuildWorkflowRequest, owner: dict) -> dict:
9
- if vertex_client.is_ready():
10
  prompt = render_prompt(
11
  BUILD_WORKFLOW_PROMPT,
12
  selected_option=json.dumps(request.selected_option, indent=2),
@@ -19,7 +19,7 @@ def build_workflow_definition(request: BuildWorkflowRequest, owner: dict) -> dic
19
  stock_column="stock",
20
  uploaded_data_summary=json.dumps(owner.get("uploaded_data_summary", []), indent=2),
21
  )
22
- return vertex_client.generate_json(prompt)
23
 
24
  trigger_type = "schedule" if "summary" in request.task_name.lower() else "email_received"
25
  steps: list[WorkflowStep] = []
 
1
  import json
2
 
3
+ from backend.ai.client import llm_client, render_prompt
4
  from backend.ai.prompts import BUILD_WORKFLOW_PROMPT
5
  from backend.models.schemas import BuildWorkflowRequest, WorkflowDefinition, WorkflowStep
6
 
7
 
8
  def build_workflow_definition(request: BuildWorkflowRequest, owner: dict) -> dict:
9
+ if llm_client.is_ready():
10
  prompt = render_prompt(
11
  BUILD_WORKFLOW_PROMPT,
12
  selected_option=json.dumps(request.selected_option, indent=2),
 
19
  stock_column="stock",
20
  uploaded_data_summary=json.dumps(owner.get("uploaded_data_summary", []), indent=2),
21
  )
22
+ return llm_client.generate_json(prompt)
23
 
24
  trigger_type = "schedule" if "summary" in request.task_name.lower() else "email_received"
25
  steps: list[WorkflowStep] = []
backend/ai/workflow_suggester.py CHANGED
@@ -1,4 +1,4 @@
1
- from backend.ai.client import render_prompt, vertex_client
2
  from backend.ai.prompts import SUGGEST_WORKFLOWS_PROMPT
3
 
4
 
@@ -9,8 +9,8 @@ def suggest_workflow_options(
9
  spreadsheet_info: dict,
10
  uploaded_files: list[dict],
11
  ) -> dict:
12
- if vertex_client.is_ready():
13
- return vertex_client.generate_json(
14
  render_prompt(
15
  SUGGEST_WORKFLOWS_PROMPT,
16
  task_name=task_name,
 
1
+ from backend.ai.client import llm_client, render_prompt
2
  from backend.ai.prompts import SUGGEST_WORKFLOWS_PROMPT
3
 
4
 
 
9
  spreadsheet_info: dict,
10
  uploaded_files: list[dict],
11
  ) -> dict:
12
+ if llm_client.is_ready():
13
+ return llm_client.generate_json(
14
  render_prompt(
15
  SUGGEST_WORKFLOWS_PROMPT,
16
  task_name=task_name,
backend/api/routes.py CHANGED
@@ -159,13 +159,13 @@ def simulate_run(owner_id: str, workflow_id: str, trigger: dict) -> dict:
159
  return result
160
 
161
 
162
- @router.get("/debug/vertex")
163
- def debug_vertex() -> dict:
164
- from backend.ai.client import vertex_client
165
 
166
  try:
167
- text = vertex_client.generate_text("Reply with exactly: Vertex debug ok")
168
  return {"status": "ok", "response": text}
169
  except Exception as exc:
170
- logger.exception("Vertex debug call failed")
171
- raise HTTPException(status_code=500, detail=f"Vertex debug failed: {exc}") from exc
 
159
  return result
160
 
161
 
162
+ @router.get("/debug/groq")
163
+ def debug_groq() -> dict:
164
+ from backend.ai.client import llm_client
165
 
166
  try:
167
+ text = llm_client.generate_text("Reply with exactly: Groq debug ok")
168
  return {"status": "ok", "response": text}
169
  except Exception as exc:
170
+ logger.exception("Groq debug call failed")
171
+ raise HTTPException(status_code=500, detail=f"Groq debug failed: {exc}") from exc
backend/config.py CHANGED
@@ -12,15 +12,13 @@ class Settings(BaseModel):
12
  api_prefix: str = "/api"
13
  database_url: str = "sqlite:///./flowpilot.db"
14
  allow_origins: list[str] = Field(default_factory=lambda: _split_csv(os.getenv("ALLOW_ORIGINS", "*")))
15
- ai_provider: str = os.getenv("AI_PROVIDER", "vertex_ai")
 
16
  gmail_poll_seconds: int = int(os.getenv("GMAIL_POLL_SECONDS", "30"))
17
- vertex_project_id: str = os.getenv("VERTEX_PROJECT_ID", "your-gcp-project-id")
18
- vertex_location: str = os.getenv("VERTEX_LOCATION", "us-central1")
19
- vertex_model: str = os.getenv("VERTEX_MODEL", "gemini-2.5-flash")
20
- google_application_credentials: str = os.getenv(
21
- "GOOGLE_APPLICATION_CREDENTIALS",
22
- "",
23
- )
24
 
25
 
26
  def _split_csv(value: str) -> list[str]:
 
12
  api_prefix: str = "/api"
13
  database_url: str = "sqlite:///./flowpilot.db"
14
  allow_origins: list[str] = Field(default_factory=lambda: _split_csv(os.getenv("ALLOW_ORIGINS", "*")))
15
+ ai_provider: str = os.getenv("AI_PROVIDER", "groq")
16
+ analyze_with_ai: bool = os.getenv("ANALYZE_WITH_AI", "false").lower() == "true"
17
  gmail_poll_seconds: int = int(os.getenv("GMAIL_POLL_SECONDS", "30"))
18
+ groq_api_key: str = os.getenv("GROQ_API_KEY", "")
19
+ groq_model: str = os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
20
+ groq_base_url: str = os.getenv("GROQ_BASE_URL", "https://api.groq.com/openai/v1")
21
+ groq_timeout_seconds: float = float(os.getenv("GROQ_TIMEOUT_SECONDS", "8"))
 
 
 
22
 
23
 
24
  def _split_csv(value: str) -> list[str]:
backend/requirements.txt CHANGED
@@ -4,4 +4,3 @@ pydantic==2.11.7
4
  pytest==8.4.1
5
  httpx==0.28.1
6
  python-dotenv==1.1.1
7
- google-genai==1.30.0
 
4
  pytest==8.4.1
5
  httpx==0.28.1
6
  python-dotenv==1.1.1