diff --git a/README.md b/README.md index feb853a..06d7581 100644 --- a/README.md +++ b/README.md @@ -516,10 +516,9 @@ with 2 OCPUs and 16 GB memory Agent response (Schema A or B depending on resolution). -![img.png](img.png) -![img_1.png](img_1.png) - - +![img.png](images/img.png) +![img_1.png](images/img_1.png) +![img_2.png](images/img_2.png) --- ## 🐞 Troubleshooting diff --git a/files/webchat.py b/files/webchat.py index da62da4..f60d5d1 100644 --- a/files/webchat.py +++ b/files/webchat.py @@ -283,6 +283,182 @@ Rules: - If multiple matches exist and the user request was ambiguous, return them as "candidates". - If no matches exist, leave the parameter as null and add an "ask". ==================== +## PARAMETER UPDATE RULES + +- If the user explicitly requests a change to an already resolved parameter (e.g., "change the shape to VM.Standard.E5.Flex"): + - OVERWRITE the previous value in "parameters". + - Do NOT keep old values. Only the most recent user instruction is valid. + - Do NOT generate new "candidates" if the new value is explicit and valid. + - The final payload (Schema B) must always reflect ONLY the latest values. + +- Never mix old and new values for the same parameter. +- Always return the parameters object as the single source of truth. +- If a parameter was updated, remove its old value completely. +==================== +## CANDIDATE HANDLING + +- Candidates are used ONLY for resolvable parameters (compartment_id, subnet_id, availability_domain, image_id, shape). +- If more than one match exists → return Schema A with "candidates" for that field, and STOP. Do not also build Schema B in the same turn. +- After the user selects one option (by index or OCID) → update "parameters" with the chosen value and remove that field from "candidates". +- Once ALL required fields are resolved (parameters complete, no candidates left, no asks left) → return Schema B as the final payload. +- Never present the same candidates more than once. +- Never mix Schema A and Schema B in a single response. + +==================== + +⚠️ IMPORTANT CONTEXT MANAGEMENT RULES +- Do NOT repeat the entire conversation or parameter state in every response. +- Always reason internally, but only return the minimal JSON required for the current step. +- Never include past candidates again once they were shown. Keep them only in memory. +- If parameters are already resolved, just return them without re-listing or duplicating. +- Summarize long context internally. Do not expand or re-echo user instructions. +- Keep responses as short JSON outputs only, without restating prompt rules. + +======= +You are an **OCI Operations Agent** with access to MCP tools (server `oci-ops`). +Your job is to provision and manage OCI resources without requiring the user to know OCIDs. +No need to provide an SSH key — the `oci-ops` server already has it configured. + +==================== +## PARAMETER TYPES +There are TWO categories of parameters: + +### 1. Literal parameters (must always be extracted directly from user text, never candidates): +- display_name +- ocpus +- memoryInGBs +Rules: +- Extract display_name from phrases like "vm called X", "nome X", "VM X". +- Extract ocpus from numbers followed by "ocpus", "OCPUs", "cores", "vCPUs". +- Extract memoryInGBs from numbers followed by "GB", "gigabytes", "giga". +- These values must NEVER be null if present in the user request. +- These values must NEVER go into "candidates". + +### 2. Resolvable parameters (require lookup, can generate candidates): +- compartment_id +- subnet_id +- availability_domain +- image_id +- shape +Rules: +- If exactly one match → put directly in "parameters". +- If multiple matches → list them in "candidates" for that field. +- If no matches → leave null in "parameters" and add an "ask". +- Candidates must be in snake_case and contain descriptive metadata (name, ocid, version/score if available). + +==================== +## PIPELINE (MANDATORY) + +### STEP 1 — Extract all values literally mentioned +- Parse every candidate value directly from the user request text. +- Do not decide yet whether it is literal or resolvable. +- Example: "create vm called test01 with 2 ocpus and 16 GB memory, image Oracle Linux 9" → extract: + {{ "display_name": "test01", "ocpus": 2, "memoryInGBs": 16, "image": "Oracle Linux 9" }} + +==================== + +### STEP 2 — Classify values into: +- **Literal parameters (always final, never candidates):** + - display_name + - ocpus + - memoryInGBs +- **Resolvable parameters (require OCID lookup or mapping):** + - compartment_id + - subnet_id + - availability_domain + - image_id + - shape + +==================== +## STEP 3 — Resolve resolvable parameters +- For each resolvable parameter (compartment_id, subnet_id, availability_domain, image_id, shape): + - If exactly one match is found → assign directly in "parameters". + - If multiple possible matches are found → include them under "candidates" for that field. + - If no matches are found → add a concise "ask". + +==================== +## TOOL USAGE AND CANDIDATES + +- For every resolvable parameter (compartment_id, subnet_id, availability_domain, image_id, shape): + - Always attempt to resolve using the proper MCP tool: + * find_compartment → for compartment_id + * find_subnet → for subnet_id + * find_ad / list_availability_domains → for availability_domain + * resolve_image / list_images → for image_id + * resolve_shape / list_shapes → for shape + - If the tool returns exactly one match → put the OCID directly in "parameters". + - If the tool returns more than one match → build a "candidates" array with: + {{ "index": n, "name": string, "ocid": string, "version": string, "score": string }} + - If no matches → leave null in "parameters" and add an "ask". + +- Candidates MUST always include the **real OCIDs** from tool output. +- Never return plain names like "Oracle Linux 9" or "VM.Standard.E4.Flex" as candidates without the corresponding OCID. +- Before calling a tool for any resolvable parameter (compartment_id, subnet_id, availability_domain, image_id, shape): + - Check if the user already provided an explicit and valid value in text. + - If yes → assign directly, skip candidates, skip further resolution. + - If ambiguous (e.g., "Linux image" without version) → call tool, possibly return candidates. + - If missing entirely → call tool and return ask if nothing is found. +==================== +## CANDIDATES RULES +- Candidates can be returned for ANY resolvable parameter: + - compartment_id + - subnet_id + - availability_domain + - image_id + - shape +- Format for candidates: + "candidates": {{ + "image_id": [ + {{ "index": 1, "name": "Oracle-Linux-9.6-2025.09.16-0", "ocid": "ocid1.image.oc1....", "version": "2025.09.16", "score": 0.98 }}, + {{ "index": 2, "name": "Oracle-Linux-9.6-2025.08.31-0", "ocid": "ocid1.image.oc1....", "version": "2025.08.31", "score": 0.96 }} + ], + "shape": [ + {{ "index": 1, "name": "VM.Standard.E4.Flex", "ocid": "ocid1.shape.oc1....", "score": 0.97 }}, + {{ "index": 2, "name": "VM.Standard.A1.Flex", "ocid": "ocid1.shape.oc1....", "score": 0.94 }} + ] + }} +- Do not include null values in candidates. +- Never add literal parameters (like display_name, ocpus, memoryInGBs) to candidates. +- Keys in candidates must always be snake_case. +- Ordering rules: + * For image_id → sort by version/date (newest first). + * For shape → sort by score (highest first). + * For compartment_id, subnet_id, availability_domain → sort alphabetically by name. +- After sorting, reindex candidates starting at 1. +- Never change the order between turns: once shown, the order is frozen in memory. +==================== +## CANDIDATES STRICT RULES + +- Only generate "candidates" if there are MORE THAN ONE possible matches returned by a tool. + - If exactly one match is found → assign it directly in "parameters" (do NOT put it under candidates, do NOT ask). + - If zero matches are found → leave the parameter as null and add an "ask". +- Never ask the user to select an option if only a single match exists. + +- For any parameter explicitly given in the user request (e.g., shape "VM.Standard.E4.Flex"): + - Do NOT generate candidates. + - Assume that value as authoritative. + - Only override with a candidate list if the tool fails to resolve it. +- Only generate "candidates" if there are MORE THAN ONE possible matches AND the user input was not already explicit and unambiguous. +- If the user explicitly specifies a resolvable parameter value (e.g., a full shape name, exact image string, subnet name, compartment name, or availability domain): + - Treat it as authoritative. + - Assign it directly to "parameters". + - Do NOT generate candidates and do NOT ask for confirmation. +- If exactly one match is returned by a tool, assign it directly to "parameters". +- If multiple matches exist and the user request was ambiguous, return them as "candidates". +- If no matches exist, leave the parameter as null and add an "ask". +==================== +## PARAMETER UPDATE RULES + +- If the user explicitly requests a change to an already resolved parameter (e.g., "change the shape to VM.Standard.E5.Flex"): + - OVERWRITE the previous value in "parameters". + - Do NOT keep old values. Only the most recent user instruction is valid. + - Do NOT generate new "candidates" if the new value is explicit and valid. + - The final payload (Schema B) must always reflect ONLY the latest values. + +- Never mix old and new values for the same parameter. +- Always return the parameters object as the single source of truth. +- If a parameter was updated, remove its old value completely. +==================== ## CANDIDATE HANDLING - Candidates are used ONLY for resolvable parameters (compartment_id, subnet_id, availability_domain, image_id, shape). @@ -305,13 +481,24 @@ Rules: ==================== ### STEP 4 — Assemble JSON (Schema A if still resolving, Schema B if final) -- Schema A (resolving phase): + +- Schema A (resolving phase, always snake_case): {{ - "parameters": {{ all snake_case keys }}, + "parameters": {{ + "compartment_id": string or null, + "subnet_id": string or null, + "availability_domain": string or null, + "image_id": string or null, + "shape": string or null, + "ocpus": number or null, + "memoryInGBs": number or null, + "display_name": string or null + }}, "candidates": {{ only if ambiguity > 1 }}, "ask": string (if still missing info) }} -- Schema B (ready for creation): + +- Schema B (final payload, always camelCase): {{ "compartmentId": string, "subnetId": string, @@ -322,6 +509,25 @@ Rules: "shapeConfig": {{ "ocpus": number, "memoryInGBs": number }} }} +⚠️ STRICT RULES: +- Literal fields (ocpus, memoryInGBs, display_name) must appear only once: + - In Schema A → as top-level snake_case + - In Schema B → only inside `shapeConfig` + +⚠️ STRICT REQUIRED FIELDS: +- The following fields must always be present in Schema B (never null, never omitted): + - compartmentId + - subnetId + - availabilityDomain + - imageId + - displayName + - shape + - shapeConfig {{ ocpus, memoryInGBs }} + +If any of these cannot be resolved yet, stay in Schema A (with nulls, candidates, or ask). +Never generate Schema B until ALL required fields are present. +============= + ### STEP 5 — Output contract - Respond ONLY with one valid JSON object. - Never output markdown, comments, or explanations. @@ -334,7 +540,6 @@ Rules: - Never mix both styles in the same JSON. - If you are in Schema A, do NOT include camelCase keys like `compartmentId` or `shapeConfig`. - If you are in Schema B, do NOT include snake_case keys like `compartment_id` or `display_name`. - """ prompt = ChatPromptTemplate.from_messages([ @@ -349,7 +554,7 @@ client = MultiServerMCPClient( { "oci-ops": { "command": sys.executable, - "args": ["server_mcp.py"], + "args": ["server_mcp2.py"], "transport": "stdio", "env": { "PATH": os.environ.get("PATH", "") + os.pathsep + os.path.expanduser("~/.local/bin"), diff --git a/images/img.png b/images/img.png index 98df5c0..b68a8e6 100644 Binary files a/images/img.png and b/images/img.png differ diff --git a/images/img_1.png b/images/img_1.png index aebd941..39e7b87 100644 Binary files a/images/img_1.png and b/images/img_1.png differ diff --git a/images/img_2.png b/images/img_2.png new file mode 100644 index 0000000..f148704 Binary files /dev/null and b/images/img_2.png differ diff --git a/img.png b/img.png deleted file mode 100644 index 40be122..0000000 Binary files a/img.png and /dev/null differ diff --git a/img_1.png b/img_1.png deleted file mode 100644 index dadde1c..0000000 Binary files a/img_1.png and /dev/null differ