From 58d430c90490f37c0402fc3f03864be69e9c5f67 Mon Sep 17 00:00:00 2001
From: nogueiraguh
Date: Wed, 11 Mar 2026 13:55:59 -0300
Subject: [PATCH] feat: terraform prompt generator, provider parameter matrix,
and model penalties
- Add Terraform Prompt Generator: AI-powered structured prompt generation
with dedicated chat UI, curated model selection (7 models), conversation
history with persistent sessions, and OCI TF resource reference context
- Provider parameter matrix: segment API params per model/provider,
explicitly null unsupported params (freq/pres penalty, temperature)
to prevent OCI SDK serialization errors
- Add penalties flag to model catalog: only GPT-4.1/4.1-mini/4o support
frequency_penalty and presence_penalty
- Flat workspace enforcement: prohibit module blocks and duplicate
variable declarations in Terraform system prompt
- Prompt Generator history: sidebar panel with session persistence,
restore, rename/delete (agent_type tf-prompt)
---
README.md | 22 +++-
backend/app.py | 239 +++++++++++++++++++++++++++++++++++++-------
frontend/index.html | 159 ++++++++++++++++++++++++-----
3 files changed, 355 insertions(+), 65 deletions(-)
diff --git a/README.md b/README.md
index 8a5508d..bf6c45d 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-
+
@@ -41,7 +41,8 @@ The platform combines security compliance scanning, AI-powered chat with **RAG (
- 15 chat models + 3 embedding models across 5 providers: **Meta** (Llama 4), **Google** (Gemini 2.5), **OpenAI** (GPT-5.2/5.1/5 Mini/4.1/4o, o3/o4-mini), **xAI** (Grok 4/3)
- OCID-based model resolution: catalog maps model IDs to OCI resource IDs per region for reliable API calls
- 16 OCI regions supported with auto-generated endpoints
-- Full parameter control: temperature, max_tokens, top_p, top_k, frequency/presence penalty
+- **Provider parameter matrix**: automatic parameter segmentation per model — reasoning models (o3/o4-mini) only get `max_completion_tokens` + `reasoning_effort`, OpenAI GPT-4.x gets freq/pres penalties, Google/Meta get `top_k`, xAI gets basic params. Unsupported parameters are explicitly nulled to prevent SDK serialization errors
+- Full parameter control: temperature, max_tokens, top_p, top_k, frequency/presence penalty (when supported by model)
- Toggle MCP tools on/off per chat session
- Conversation history with session management
- On-Demand and Dedicated serving modes
@@ -71,6 +72,18 @@ The platform combines security compliance scanning, AI-powered chat with **RAG (
- **Multi-region provider management**: auto-detects provider aliases and region variables in generated code, generates `provider.tf` with credentials and correct region references (`var.region` instead of hardcoded values)
- **System prompt sync**: Terraform system prompt in code is automatically synced to DB on every restart — single source of truth
- Compact system prompt optimized for OCI Terraform provider best practices (100K max_tokens)
+- **Flat workspace enforcement**: system prompt prohibits `module` blocks (workspace has no subdirectories) and enforces single-file variable declarations (`variables.tf` only)
+
+### 📝 Terraform Prompt Generator
+- **AI-powered prompt generation** for OCI infrastructure — generates structured, optimized prompts to feed into the Terraform Agent
+- Dedicated chat interface (sub-menu under Terraform) matching Terraform Agent layout
+- **Curated model selection**: only 7 recommended models (GPT-4.1, o3, o4-mini, GPT-5.1, GPT-5.2, Gemini 2.5 Pro, Gemini 2.5 Flash), grouped by provider
+- **OCI Terraform Resource Reference**: prompt generation includes the full OCI provider resource catalog as context, ensuring accurate resource names and attributes
+- **Conversation history**: persistent sessions (agent_type `tf-prompt`) with sidebar history panel, rename/delete, session restore
+- **Contextual follow-up**: conversation history sent to GenAI for iterative prompt refinement
+- **Copy & Send**: action buttons on assistant messages to copy the prompt or send directly to the Terraform Agent input
+- Robust system prompt with OCI Provider constraints, inference rules, architecture templates, and workspace restrictions
+- Example prompts as clickable buttons for quick start
### ⚡ OCI Resource Actions
- **Start/Stop Compute Instances** directly from OCI Account Explorer with one click
@@ -396,14 +409,14 @@ Allow group to read buckets in compartment
```
oci-cis-agent/
├── backend/
-│ ├── app.py # FastAPI application (~5300 lines)
+│ ├── app.py # FastAPI application (~5500 lines)
│ ├── cis_reports.py # Oracle CIS Benchmark checker (6660 lines, report engine)
│ ├── mcp_cis_server.py # MCP server with 12 granular CIS tools (~700 lines)
│ ├── gen_tf_reference.py # OCI Terraform provider resource catalog generator
│ ├── Dockerfile # Python 3.12 + OCI CLI + Terraform CLI
│ └── requirements.txt # Dependencies
├── frontend/
-│ └── index.html # SPA with Oracle Dark Premium theme (~2820 lines)
+│ └── index.html # SPA with Oracle Dark Premium theme (~2950 lines)
├── nginx/
│ └── default.conf # Reverse proxy config
├── docker-compose.yml # Orchestration
@@ -560,6 +573,7 @@ oci-cis-agent/
| POST | `/api/terraform/workspaces/{wid}/cancel` | Cancel running Terraform operation |
| DELETE | `/api/terraform/workspaces/{wid}` | Delete workspace |
| POST | `/api/terraform/refresh-reference` | Regenerate OCI Terraform resource reference (UI button) |
+| POST | `/api/terraform/generate-prompt` | Generate structured Terraform prompt via AI (Prompt Generator) |
### Chat & Reports
diff --git a/backend/app.py b/backend/app.py
index bf86885..0e33e56 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -62,38 +62,38 @@ security = HTTPBearer()
# _call_genai resolves the OCID for the configured region at runtime.
GENAI_MODELS = {
# ── Meta ──
- "meta.llama-4-maverick-17b-128e-instruct-fp8": {"provider":"meta","name":"Meta Llama 4 Maverick","api_format":"GENERIC",
+ "meta.llama-4-maverick-17b-128e-instruct-fp8": {"provider":"meta","name":"Meta Llama 4 Maverick","api_format":"GENERIC","max_tokens":8192,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyah6tjdejjashngznsylutuhhvufukzb2g2ls54g2flsfq"}},
- "meta.llama-4-scout-17b-16e-instruct": {"provider":"meta","name":"Meta Llama 4 Scout","api_format":"GENERIC",
+ "meta.llama-4-scout-17b-16e-instruct": {"provider":"meta","name":"Meta Llama 4 Scout","api_format":"GENERIC","max_tokens":8192,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyaw23hfc7mtvv5wef3gwvvaguyzqmhb5lx4r5s3y2xzc4a"}},
# ── Google ──
- "google.gemini-2.5-pro": {"provider":"google","name":"Google Gemini 2.5 Pro","api_format":"GENERIC",
+ "google.gemini-2.5-pro": {"provider":"google","name":"Google Gemini 2.5 Pro","api_format":"GENERIC","max_tokens":65536,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyargceyuaysrjzo2metq2rinavayxqmpu7tkm6mmfojcvq"}},
- "google.gemini-2.5-flash": {"provider":"google","name":"Google Gemini 2.5 Flash","api_format":"GENERIC",
+ "google.gemini-2.5-flash": {"provider":"google","name":"Google Gemini 2.5 Flash","api_format":"GENERIC","max_tokens":8192,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyaeo4ehrn25guuats5s45hnvswlhxo6riop275l2bkr2vq"}},
# ── OpenAI ──
- "openai.gpt-5.2": {"provider":"openai","name":"OpenAI GPT-5.2","api_format":"GENERIC",
+ "openai.gpt-5.2": {"provider":"openai","name":"OpenAI GPT-5.2","api_format":"GENERIC","max_tokens":32768,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceya4fw3p5fddnexbfcurnz7spgkqb4mq4a6y5ubyv7777sa"}},
- "openai.gpt-5.1": {"provider":"openai","name":"OpenAI GPT-5.1","api_format":"GENERIC",
+ "openai.gpt-5.1": {"provider":"openai","name":"OpenAI GPT-5.1","api_format":"GENERIC","max_tokens":32768,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceya3darth2ozqcfssb2bats5jitpgigllccajasdyqljnkq"}},
- "openai.gpt-5-mini": {"provider":"openai","name":"OpenAI GPT-5 Mini","api_format":"GENERIC",
+ "openai.gpt-5-mini": {"provider":"openai","name":"OpenAI GPT-5 Mini","api_format":"GENERIC","max_tokens":16384,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceya3eain4n6v3edm4ryjvze5hnjouujd4vralxntfalwjaq"}},
- "openai.gpt-4.1": {"provider":"openai","name":"OpenAI GPT-4.1 (Padrão)","api_format":"GENERIC",
+ "openai.gpt-4.1": {"provider":"openai","name":"OpenAI GPT-4.1 (Padrão)","api_format":"GENERIC","max_tokens":32768,"penalties":True,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyaa6g75r2qqmtzjaooqtlxv4lxkpcqp2jdd6plpq7yq7ea"}},
- "openai.gpt-4.1-mini": {"provider":"openai","name":"OpenAI GPT-4.1 Mini","api_format":"GENERIC",
+ "openai.gpt-4.1-mini": {"provider":"openai","name":"OpenAI GPT-4.1 Mini","api_format":"GENERIC","max_tokens":16384,"penalties":True,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceya6pk3sxishpiexm2rb5sf4ytb5tsbz4to2g3g23smidaa"}},
- "openai.gpt-4o": {"provider":"openai","name":"OpenAI GPT-4o","api_format":"GENERIC",
+ "openai.gpt-4o": {"provider":"openai","name":"OpenAI GPT-4o","api_format":"GENERIC","max_tokens":16384,"penalties":True,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyah7slrtboxdbfdy5cdspsfts62yumoclpdgwydopse7za"}},
- "openai.o4-mini": {"provider":"openai","name":"OpenAI o4-mini","api_format":"GENERIC",
+ "openai.o4-mini": {"provider":"openai","name":"OpenAI o4-mini","api_format":"GENERIC","reasoning":True,"max_tokens":100000,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceya5ivfqp4fxlajoeg2cahlqtuuswagiv6a7dggpigy23bq"}},
- "openai.o3": {"provider":"openai","name":"OpenAI o3","api_format":"GENERIC",
+ "openai.o3": {"provider":"openai","name":"OpenAI o3","api_format":"GENERIC","reasoning":True,"max_tokens":100000,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyalgnrukpjk6wm5zsf4jzkoneahgswhrk7kukkoagwnzma"}},
# ── xAI ──
- "xai.grok-4": {"provider":"xai","name":"xAI Grok 4","api_format":"GENERIC",
+ "xai.grok-4": {"provider":"xai","name":"xAI Grok 4","api_format":"GENERIC","max_tokens":131072,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyaldmhg25is4nouena4oa2pj4nvwgfeempo4syiaazukia"}},
- "xai.grok-3": {"provider":"xai","name":"xAI Grok 3","api_format":"GENERIC",
+ "xai.grok-3": {"provider":"xai","name":"xAI Grok 3","api_format":"GENERIC","max_tokens":131072,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyag3w2xk76vlahjujj2gdfeuzhflt25gbo3bxidlsqfjla"}},
- "xai.grok-3-mini-fast": {"provider":"xai","name":"xAI Grok 3 Mini Fast","api_format":"GENERIC",
+ "xai.grok-3-mini-fast": {"provider":"xai","name":"xAI Grok 3 Mini Fast","api_format":"GENERIC","max_tokens":131072,
"ocids":{"us-ashburn-1":"ocid1.generativeaimodel.oc1.iad.amaaaaaask7dceyauvjoll2repj5pbtkk7pinwj57ex3lkehzpxd6v6rxscq"}},
}
@@ -197,6 +197,7 @@ def init_db():
top_k INTEGER DEFAULT 1,
frequency_penalty REAL DEFAULT 0,
presence_penalty REAL DEFAULT 0,
+ reasoning_effort TEXT,
is_default INTEGER DEFAULT 0,
system_prompt TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now')),
@@ -353,7 +354,7 @@ def init_db():
c.execute("DELETE FROM config_logs WHERE created_at < datetime('now', '-30 days')")
c.execute("DELETE FROM chat_logs WHERE created_at < datetime('now', '-30 days')")
# ── Migrations ──
- for col in ["system_prompt TEXT DEFAULT ''"]:
+ for col in ["system_prompt TEXT DEFAULT ''", "reasoning_effort TEXT"]:
try:
c.execute(f"ALTER TABLE genai_configs ADD COLUMN {col}")
except sqlite3.OperationalError:
@@ -518,6 +519,7 @@ class ChatMsg(BaseModel):
temperature: Optional[float] = None; max_tokens: Optional[int] = None
top_p: Optional[float] = None; top_k: Optional[int] = None
frequency_penalty: Optional[float] = None; presence_penalty: Optional[float] = None
+ reasoning_effort: Optional[str] = None
use_tools: Optional[bool] = True
class RunReportReq(BaseModel):
config_id: str; regions: Optional[List[str]] = None
@@ -530,6 +532,7 @@ class GenAIConfigReq(BaseModel):
serving_type: str = "ON_DEMAND"; dedicated_endpoint_id: Optional[str] = None
temperature: float = 1; max_tokens: int = 6000; top_p: float = 0.95
top_k: int = 1; frequency_penalty: float = 0; presence_penalty: float = 0
+ reasoning_effort: Optional[str] = None
is_default: bool = False
class IngestDocReq(BaseModel):
adb_config_id: str; documents: List[Dict[str, Any]]; table_name: Optional[str] = None
@@ -1638,11 +1641,11 @@ async def save_genai(req: GenAIConfigReq, u=Depends(require("admin","user"))):
c.execute(
"""INSERT INTO genai_configs (id,user_id,name,oci_config_id,model_id,model_ocid,compartment_id,
genai_region,endpoint,serving_type,dedicated_endpoint_id,temperature,max_tokens,top_p,top_k,
- frequency_penalty,presence_penalty,is_default) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
+ frequency_penalty,presence_penalty,reasoning_effort,is_default) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(gid, u["id"], req.name, req.oci_config_id, req.model_id, req.model_ocid,
req.compartment_id, req.genai_region, ep, req.serving_type, req.dedicated_endpoint_id,
req.temperature, req.max_tokens, req.top_p, req.top_k,
- req.frequency_penalty, req.presence_penalty, int(req.is_default)))
+ req.frequency_penalty, req.presence_penalty, req.reasoning_effort, int(req.is_default)))
_audit(u["id"], u["username"], "save_genai_config", gid, req.model_id)
_config_log("genai", gid, req.name, "success", "save", f"Modelo salvo: {req.model_id} ({req.genai_region})", u["id"], u["username"])
return {"id": gid, "model_id": req.model_id, "endpoint": ep}
@@ -1672,11 +1675,11 @@ async def update_genai(gid: str, req: GenAIConfigReq, u=Depends(require("admin",
c.execute(
"""UPDATE genai_configs SET name=?,oci_config_id=?,model_id=?,model_ocid=?,compartment_id=?,
genai_region=?,endpoint=?,serving_type=?,dedicated_endpoint_id=?,temperature=?,max_tokens=?,
- top_p=?,top_k=?,frequency_penalty=?,presence_penalty=?,is_default=? WHERE id=?""",
+ top_p=?,top_k=?,frequency_penalty=?,presence_penalty=?,reasoning_effort=?,is_default=? WHERE id=?""",
(req.name, req.oci_config_id, req.model_id, req.model_ocid,
req.compartment_id, req.genai_region, ep, req.serving_type, req.dedicated_endpoint_id,
req.temperature, req.max_tokens, req.top_p, req.top_k,
- req.frequency_penalty, req.presence_penalty, int(req.is_default), gid))
+ req.frequency_penalty, req.presence_penalty, req.reasoning_effort, int(req.is_default), gid))
_audit(u["id"], u["username"], "update_genai_config", gid, req.model_id)
_config_log("genai", gid, req.name, "success", "save", f"Modelo atualizado: {req.model_id} ({req.genai_region})", u["id"], u["username"])
return {"id": gid, "model_id": req.model_id, "endpoint": ep}
@@ -1846,7 +1849,8 @@ def _call_genai(gc: dict, message: str, history: list = None, tools: list = None
if system_prompt:
chat_request.preamble_override = system_prompt
chat_request.message = message
- chat_request.max_tokens = int(gc.get("max_tokens", 6000))
+ cohere_max = model_info.get("max_tokens", 4096)
+ chat_request.max_tokens = min(int(gc.get("max_tokens", 4096)), cohere_max)
chat_request.temperature = float(gc.get("temperature", 1))
chat_request.frequency_penalty = float(gc.get("frequency_penalty", 0))
chat_request.presence_penalty = float(gc.get("presence_penalty", 0))
@@ -1887,20 +1891,54 @@ def _call_genai(gc: dict, message: str, history: list = None, tools: list = None
chat_request.api_format = oci.generative_ai_inference.models.BaseChatRequest.API_FORMAT_GENERIC
provider = model_info.get("provider", "")
is_openai = provider == "openai"
- is_xai = provider == "xai"
- # OpenAI models use max_completion_tokens instead of max_tokens
- # and do not support top_k
- if is_openai:
- chat_request.max_completion_tokens = int(gc.get("max_tokens", 6000))
+ is_reasoning = model_info.get("reasoning", False)
+ # Clamp max_tokens to model limit
+ model_max = model_info.get("max_tokens", 16384)
+ requested_tokens = int(gc.get("max_tokens", 6000))
+ clamped_tokens = min(requested_tokens, model_max)
+
+ # ── Parameter matrix per provider ──
+ # OpenAI reasoning (o3, o4-mini): max_completion_tokens + reasoning_effort only
+ if is_reasoning:
+ chat_request.max_completion_tokens = clamped_tokens
+ re = gc.get("reasoning_effort")
+ if re and hasattr(chat_request, "reasoning_effort"):
+ chat_request.reasoning_effort = re
+ # Explicitly unset unsupported params
+ chat_request.temperature = None
+ chat_request.top_p = None
+ chat_request.frequency_penalty = None
+ chat_request.presence_penalty = None
+ # OpenAI standard (GPT-4.1, GPT-5.x, GPT-4o): max_completion_tokens, temperature, top_p
+ # freq/pres penalty only for models with "penalties":True (GPT-4.1, GPT-4.1-mini, GPT-4o)
+ elif is_openai:
+ chat_request.max_completion_tokens = clamped_tokens
+ chat_request.temperature = float(gc.get("temperature", 1))
+ chat_request.top_p = float(gc.get("top_p", 0.95))
+ if model_info.get("penalties"):
+ chat_request.frequency_penalty = float(gc.get("frequency_penalty", 0))
+ chat_request.presence_penalty = float(gc.get("presence_penalty", 0))
+ else:
+ chat_request.frequency_penalty = None
+ chat_request.presence_penalty = None
+ # xAI Grok: max_tokens, temperature, top_p (no top_k, no freq/pres penalty)
+ elif provider == "xai":
+ chat_request.max_tokens = clamped_tokens
+ chat_request.temperature = float(gc.get("temperature", 1))
+ chat_request.top_p = float(gc.get("top_p", 0.95))
+ # Explicitly unset unsupported params to prevent SDK serialization
+ chat_request.frequency_penalty = None
+ chat_request.presence_penalty = None
+ # Meta Llama: max_tokens, temperature, top_p, top_k (no freq/pres penalty)
+ # Google Gemini: max_tokens, temperature, top_p, top_k (no freq/pres penalty)
else:
- chat_request.max_tokens = int(gc.get("max_tokens", 6000))
- if not is_xai:
- chat_request.top_k = int(gc.get("top_k", 1))
- chat_request.temperature = float(gc.get("temperature", 1))
- if not is_xai:
- chat_request.frequency_penalty = float(gc.get("frequency_penalty", 0))
- chat_request.presence_penalty = float(gc.get("presence_penalty", 0))
- chat_request.top_p = float(gc.get("top_p", 0.95))
+ chat_request.max_tokens = clamped_tokens
+ chat_request.temperature = float(gc.get("temperature", 1))
+ chat_request.top_p = float(gc.get("top_p", 0.95))
+ chat_request.top_k = int(gc.get("top_k", 1))
+ # Explicitly unset unsupported params to prevent SDK serialization
+ chat_request.frequency_penalty = None
+ chat_request.presence_penalty = None
messages = []
if system_prompt:
@@ -2164,6 +2202,11 @@ Se pedirem AWS, Azure, GCP ou outro provider, recuse educadamente.
- Remote peering: APENAS UM LADO do par define `peer_id` e `peer_region_name`. O outro lado é criado SEM `peer_id`. Nunca gere dois RPCs apontando um para o outro (causa Cycle error). Exemplo: RPC_mad1 (sem peer_id) e RPC_mad3 (com peer_id = RPC_mad1.id, peer_region_name = var.region).
- Associação de route table com subnet deve ser via `route_table_id` na subnet ou `oci_core_route_table_attachment`.
- `oci_network_firewall_network_firewall` exige `network_firewall_policy_id`.
+
+### Proibições absolutas — módulos e variáveis
+- NUNCA use `module` blocks. Este workspace é flat (um único diretório). Não existem subdiretórios `modules/`. Toda a infraestrutura deve ser definida diretamente com `resource` e `data` blocks.
+- NUNCA declare a mesma `variable` em mais de um arquivo. Todas as variáveis devem estar em `variables.tf` e SOMENTE lá. Os outros arquivos (.tf) apenas referenciam `var.nome`.
+- Antes de gerar, verifique se uma variável já foi declarada. Se já existe em `variables.tf`, NÃO redeclare.
"""
# ── Terraform OCI Resource Reference (generated at build time, updatable at runtime) ──
@@ -3821,6 +3864,7 @@ def _chat_start(msg: ChatMsg, u, attachments: list = None, agent_type: str = "ch
if msg.top_k is not None: genai_cfg["top_k"] = msg.top_k
if msg.frequency_penalty is not None: genai_cfg["frequency_penalty"] = msg.frequency_penalty
if msg.presence_penalty is not None: genai_cfg["presence_penalty"] = msg.presence_penalty
+ if msg.reasoning_effort is not None: genai_cfg["reasoning_effort"] = msg.reasoning_effort
elif msg.model_id and msg.oci_config_id:
with db() as c:
oci_row = c.execute("SELECT * FROM oci_configs WHERE id=?", (msg.oci_config_id,)).fetchone()
@@ -3845,6 +3889,7 @@ def _chat_start(msg: ChatMsg, u, attachments: list = None, agent_type: str = "ch
"top_k": msg.top_k if msg.top_k is not None else 1,
"frequency_penalty": msg.frequency_penalty if msg.frequency_penalty is not None else 0.0,
"presence_penalty": msg.presence_penalty if msg.presence_penalty is not None else 0.0,
+ "reasoning_effort": msg.reasoning_effort,
}
if not genai_cfg:
@@ -3976,9 +4021,11 @@ async def _chat_background(mid: str, sid: str, msg: ChatMsg, user: dict, genai_c
base_prompt = cfg_dict.get("system_prompt", "")
cfg_dict["system_prompt"] = f"{base_prompt}\n\n{config_hint}" if base_prompt else config_hint
- # ── Terraform agent: boost max_tokens for complex infra code ──
+ # ── Terraform agent: boost max_tokens to model limit ──
if agent_type == "terraform":
- cfg_dict["max_tokens"] = max(int(cfg_dict.get("max_tokens", 6000)), 100000)
+ tf_model_info = GENAI_MODELS.get(cfg_dict.get("model_id", ""), {})
+ tf_model_max = tf_model_info.get("max_tokens", 32768)
+ cfg_dict["max_tokens"] = tf_model_max
# ── Inject existing OCI resources for terraform agent ──
if agent_type == "terraform" and active_oci_id:
@@ -4184,6 +4231,7 @@ async def chat_with_files(
top_k: Optional[int] = Form(None),
frequency_penalty: Optional[float] = Form(None),
presence_penalty: Optional[float] = Form(None),
+ reasoning_effort: Optional[str] = Form(None),
files: List[UploadFile] = File(default=[]),
u=Depends(current_user),
):
@@ -4226,6 +4274,7 @@ async def chat_with_files(
top_k=top_k,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
+ reasoning_effort=reasoning_effort,
)
sid, mid_or_result, genai_cfg = _chat_start(msg, u, attachments=attachments if attachments else None)
@@ -4510,6 +4559,120 @@ async def tf_list_resources(oci_config_id: str = Query(...), compartment_id: str
raise HTTPException(500, str(e)[:500])
+TFP_SYSTEM_PROMPT = """Você é um arquiteto sênior de infraestrutura OCI com profundo conhecimento do Terraform OCI Provider.
+O usuário vai descrever a infraestrutura desejada em linguagem natural. Sua tarefa é gerar um **prompt detalhado e estruturado** que será enviado a um agente Terraform para criar o código HCL.
+
+### Formato do Prompt Gerado
+Retorne APENAS o prompt estruturado (não gere código Terraform). Use markdown:
+
+1. **Título**: Uma linha descrevendo o projeto (ex: "## Infraestrutura Multi-Region HA para Aplicação Web")
+2. **Arquitetura**: Diagrama textual ou descrição da topologia
+3. **Recursos por Categoria**: Organizados em seções ## (Networking, Compute, Storage, Database, Security, Observability)
+ - Para cada recurso: nome, especificações técnicas, relacionamentos
+4. **Networking**: Sempre inclua CIDRs (10.0.0.0/16 para VCN, /24 para subnets), gateways necessários, route tables, security lists/NSGs
+5. **Dependências**: Liste dependências entre recursos (ex: "Compute depende de Subnet, que depende de VCN")
+6. **Requisitos Técnicos**: Seção final com regras para o agente Terraform:
+ - Separação em arquivos (provider.tf, variables.tf, vcn.tf, subnets.tf, compute.tf, outputs.tf)
+ - Variáveis obrigatórias (compartment_id, region, ssh_public_key, etc.)
+ - Naming convention (ex: proj-env-resource)
+ - Outputs necessários (IPs, OCIDs, endpoints)
+ - Tags padrão (Environment, Project, ManagedBy=Terraform)
+
+### Regras de Inferência
+- Se pedir VCN, inclua automaticamente: subnets (pública + privada), internet gateway, NAT gateway, service gateway, route tables, security lists
+- Se pedir Compute, inclua: boot volume, NSG, cloud-init básico, shape e imagem
+- Se pedir Database, inclua: subnet privada dedicada, NSG com porta do DB, backup policy
+- Se pedir OKE, inclua: VCN com topologia para K8s (API endpoint subnet, workers subnet, pods subnet, services LB subnet)
+- Se mencionar multi-region, use providers aliased e detalhe RPC/DRG
+- Se mencionar HA/DR, inclua redundância cross-AD ou cross-region
+- Se mencionar segurança/CIS, inclua Vault, Cloud Guard, WAF, logging, encryption at rest
+
+### Restrições do OCI Provider
+- RPC (Remote Peering Connection): NÃO usar oci_core_drg_attachment para RPC. O attachment é criado automaticamente pelo recurso oci_core_remote_peering_connection
+- Cada VCN suporta apenas 1 DRG attachment — não criar duplicados
+- DRG attachment type para VCN é "VCN", não "REMOTE_PEERING_CONNECTION"
+
+### Restrições do Workspace
+- NUNCA sugira o uso de `module` blocks. O workspace é flat (diretório único, sem subdiretórios). Toda infraestrutura deve ser definida com `resource` e `data` blocks diretamente.
+- Variáveis devem ser declaradas SOMENTE em `variables.tf`. Nunca duplicar declarações de variáveis entre arquivos.
+
+### Tom
+- Seja técnico e preciso
+- Use português brasileiro
+- Não gere código, apenas o prompt estruturado"""
+
+class TfPromptReq(BaseModel):
+ message: str
+ genai_config: Optional[dict] = None
+ history: Optional[list] = None
+ session_id: Optional[str] = None
+
+@app.post("/api/terraform/generate-prompt")
+async def tf_generate_prompt(req: TfPromptReq, u=Depends(current_user)):
+ gc = None
+ if req.genai_config:
+ if req.genai_config.get("genai_config_id"):
+ with db() as c:
+ row = c.execute("SELECT * FROM genai_configs WHERE id=?", (req.genai_config["genai_config_id"],)).fetchone()
+ if row: gc = dict(row)
+ elif req.genai_config.get("oci_config_id"):
+ oci_id = req.genai_config["oci_config_id"]
+ with db() as c:
+ oc = c.execute("SELECT * FROM oci_configs WHERE id=?", (oci_id,)).fetchone()
+ if oc:
+ region = req.genai_config.get("genai_region") or oc["region"]
+ compartment = _safe_dec(dict(oc).get("compartment_id") or oc["tenancy_ocid"])
+ gc = {
+ "oci_config_id": oci_id, "model_id": req.genai_config.get("model_id", "openai.gpt-4.1"),
+ "model_ocid": None, "compartment_id": compartment, "genai_region": region,
+ "endpoint": f"https://inference.generativeai.{region}.oci.oraclecloud.com",
+ "serving_type": "ON_DEMAND", "dedicated_endpoint_id": None,
+ "temperature": 0.7, "max_tokens": 4000, "top_p": 0.9,
+ }
+ if not gc:
+ with db() as c:
+ row = c.execute("SELECT * FROM genai_configs WHERE is_default=1").fetchone()
+ if not row:
+ row = c.execute("SELECT * FROM genai_configs LIMIT 1").fetchone()
+ if row: gc = dict(row)
+ if not gc:
+ raise HTTPException(400, "Nenhuma configuração GenAI disponível")
+ # Session persistence
+ is_new = not req.session_id
+ sid = req.session_id or str(uuid.uuid4())
+ with db() as c:
+ c.execute("INSERT INTO chat_messages (id,session_id,user_id,role,content,model_id,status) VALUES (?,?,?,?,?,?,?)",
+ (str(uuid.uuid4()), sid, u["id"], "user", req.message, gc.get("model_id"), "done"))
+ if is_new:
+ title = (req.message or "Prompt Generator")[:80].strip()
+ c.execute("INSERT OR IGNORE INTO chat_sessions (id,user_id,agent_type,title) VALUES (?,?,?,?)",
+ (sid, u["id"], "tf-prompt", title))
+ else:
+ c.execute("UPDATE chat_sessions SET updated_at=datetime('now') WHERE id=?", (sid,))
+ # Load TF resource reference for context
+ tf_ref = _load_tf_resource_reference()
+ system_with_ref = TFP_SYSTEM_PROMPT
+ if tf_ref:
+ system_with_ref += f"\n\n### Referência de Recursos OCI Terraform Disponíveis\nUse esta referência para garantir que os recursos mencionados no prompt existam no provider OCI:\n\n{tf_ref}"
+ gc["system_prompt"] = system_with_ref
+ gc["temperature"] = 0.7
+ gc["max_tokens"] = 4000
+ # Build history from conversation (normalize roles to lowercase for _call_genai)
+ hist = None
+ if req.history:
+ hist = [{"role": "user" if m.get("role","").upper() in ("USER","user") else "assistant",
+ "content": m.get("content", "")} for m in req.history]
+ try:
+ answer, _, _ = _call_genai(gc, req.message, history=hist)
+ # Save assistant response
+ with db() as c:
+ c.execute("INSERT INTO chat_messages (id,session_id,user_id,role,content,model_id,status) VALUES (?,?,?,?,?,?,?)",
+ (str(uuid.uuid4()), sid, u["id"], "assistant", answer, gc.get("model_id"), "done"))
+ return {"prompt": answer, "session_id": sid}
+ except Exception as e:
+ log.error(f"tf_generate_prompt error: {e}")
+ raise HTTPException(500, str(e)[:500])
+
@app.post("/api/terraform/chat")
async def terraform_chat(msg: ChatMsg, bg: BackgroundTasks, u=Depends(current_user)):
sid, mid_or_result, genai_cfg = _chat_start(msg, u, agent_type="terraform")
@@ -5276,12 +5439,12 @@ async def import_config(file: UploadFile = File(...), u=Depends(require("admin")
for cfg in data.get("genai_configs", []):
if c.execute("SELECT 1 FROM genai_configs WHERE id=?", (cfg["id"],)).fetchone():
counts["skipped"] += 1; continue
- c.execute("INSERT INTO genai_configs (id,user_id,name,oci_config_id,model_id,model_ocid,compartment_id,genai_region,endpoint,serving_type,dedicated_endpoint_id,temperature,max_tokens,top_p,top_k,frequency_penalty,presence_penalty,is_default,system_prompt,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
+ c.execute("INSERT INTO genai_configs (id,user_id,name,oci_config_id,model_id,model_ocid,compartment_id,genai_region,endpoint,serving_type,dedicated_endpoint_id,temperature,max_tokens,top_p,top_k,frequency_penalty,presence_penalty,reasoning_effort,is_default,system_prompt,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(cfg["id"], cfg.get("user_id", u["id"]), cfg.get("name",""), cfg.get("oci_config_id",""), cfg.get("model_id",""),
cfg.get("model_ocid"), cfg.get("compartment_id",""), cfg.get("genai_region",""), cfg.get("endpoint",""),
cfg.get("serving_type","ON_DEMAND"), cfg.get("dedicated_endpoint_id"), cfg.get("temperature",1),
cfg.get("max_tokens",6000), cfg.get("top_p",0.95), cfg.get("top_k",1), cfg.get("frequency_penalty",0),
- cfg.get("presence_penalty",0), cfg.get("is_default",0), cfg.get("system_prompt",""), cfg.get("created_at")))
+ cfg.get("presence_penalty",0), cfg.get("reasoning_effort"), cfg.get("is_default",0), cfg.get("system_prompt",""), cfg.get("created_at")))
counts["genai_configs"] += 1
# MCP servers
for srv in data.get("mcp_servers", []):
diff --git a/frontend/index.html b/frontend/index.html
index 1c36bc9..e11f3ac 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -379,6 +379,10 @@ html .spinner,html [class*="kpi-bar-fill"],html canvas{transition:none!important
.tf-chat{flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0}
.tf-toolbar{display:flex;align-items:center;gap:.5rem;padding:.45rem .7rem;border-bottom:1px solid var(--bd);flex-wrap:wrap;background:var(--bg)}
.tf-msgs{flex:1;overflow-y:auto;padding:.7rem}
+.tfp-act{display:inline-flex;align-items:center;gap:.25rem;padding:.2rem .5rem;font-size:.6rem;border-radius:5px;border:1px solid var(--bd);background:var(--bg2);color:var(--t2);cursor:pointer;transition:all .15s;font-family:inherit;line-height:1.3}
+.tfp-act:hover{background:var(--bg3);border-color:var(--t4)}
+.tfp-act-tf{background:#7b42bc18;border-color:#7b42bc55;color:#7b42bc}
+.tfp-act-tf:hover{background:#7b42bc30;border-color:#7b42bc}
.tf-input{display:flex;gap:.5rem;padding:.5rem .7rem;border-top:1px solid var(--bd)}
.tf-input textarea,.tf-input input{flex:1;font-family:inherit;font-size:inherit;line-height:1.4;padding:.45rem .65rem;border:1.5px solid var(--bd);border-radius:10px;background:var(--bg2);min-height:36px;max-height:200px}
.tf-resize{height:5px;cursor:row-resize;background:var(--bd);transition:background .15s;position:relative;flex-shrink:0}
@@ -523,14 +527,16 @@ const LOGO_R=`