Spaces:
Sleeping
Sleeping
Ahmed Samir Nagy Mohammed commited on
Commit ·
18d21ca
0
Parent(s):
init
Browse files- README.md +19 -0
- main.py +88 -0
- mock_k2_api.py +49 -0
- quyaml_parser.py +70 -0
- requirements.txt +6 -0
README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: QThink Agentic POC
|
| 3 |
+
emoji: ⚛️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: fastapi
|
| 7 |
+
app_file: main.py
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# QThink Agentic Proof-of-Concept API
|
| 11 |
+
|
| 12 |
+
This repository contains the proof-of-concept (POC) API for the QThink hackathon project. This service is intended to be submitted as our "Inference Endpoint" link.
|
| 13 |
+
|
| 14 |
+
It demonstrates the two core pillars of our project:
|
| 15 |
+
|
| 16 |
+
1. **`/tools/parse-quyaml-to-qasm`**: A tool endpoint that our agent uses. It accepts our novel, token-efficient QuYAML format and returns valid QASM 2.0. This proves our custom parser works.
|
| 17 |
+
2. **`/solve/agentic-trace`**: The **main demo endpoint**. This simulates our full agentic MCP (Multi-Context Protocol) workflow. It takes a user prompt and returns a complete "trace" of the conversation between our (mocked) Planner, Reasoner, and Verifier agents as they collaborate to solve the problem.
|
| 18 |
+
|
| 19 |
+
This POC proves our architecture is sound and ready for the real K2 Think API in Stage 2. See the `/docs` endpoint for a full FastAPI interactive demo.
|
main.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, Body, HTTPException
|
| 2 |
+
from pydantic import BaseModel, Field
|
| 3 |
+
from typing import List, Dict, Any
|
| 4 |
+
import time
|
| 5 |
+
|
| 6 |
+
from mock_k2_api import call_mock_planner, call_mock_reasoner, call_mock_verifier
|
| 7 |
+
from quyaml_parser import parse_quyaml_to_qiskit
|
| 8 |
+
from qiskit.qasm2 import dumps as to_qasm2_str
|
| 9 |
+
|
| 10 |
+
app = FastAPI(
|
| 11 |
+
title="QThink POC Demo - Agentic Workflow",
|
| 12 |
+
description="Proof-of-concept for the QThink Hackathon Project. This API demonstrates the full agentic MCP workflow.",
|
| 13 |
+
version="0.2.0"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# --- Pydantic Models ---
|
| 17 |
+
class SimulationPrompt(BaseModel):
|
| 18 |
+
prompt: str
|
| 19 |
+
|
| 20 |
+
class AgentStep(BaseModel):
|
| 21 |
+
agent: str
|
| 22 |
+
thought: str
|
| 23 |
+
output: Any = Field(..., description="The output of the agent, can be a string, list, or dict.")
|
| 24 |
+
|
| 25 |
+
# --- Endpoints ---
|
| 26 |
+
@app.post("/tools/parse-quyaml-to-qasm")
|
| 27 |
+
async def parse_quyaml_endpoint(quyaml_body: str = Body(..., media_type="application/yaml",
|
| 28 |
+
example="# QYAML v0.1: Bell State...")):
|
| 29 |
+
"""
|
| 30 |
+
**TOOL ENDPOINT:** Proves the QuYAML parser (SDS) is realistic.
|
| 31 |
+
An agent would call this tool to convert QuYAML to QASM.
|
| 32 |
+
"""
|
| 33 |
+
try:
|
| 34 |
+
quantum_circuit = parse_quyaml_to_qiskit(quyaml_body)
|
| 35 |
+
qasm_output = to_qasm2_str(quantum_circuit)
|
| 36 |
+
return {
|
| 37 |
+
"status": "success",
|
| 38 |
+
"qasm_2_0_output": qasm_output,
|
| 39 |
+
"text_diagram": str(quantum_circuit)
|
| 40 |
+
}
|
| 41 |
+
except Exception as e:
|
| 42 |
+
raise HTTPException(status_code=400, detail=f"Error parsing QuYAML: {str(e)}")
|
| 43 |
+
|
| 44 |
+
@app.post("/solve/agentic-trace", response_model=Dict[str, Any])
|
| 45 |
+
async def agentic_trace_endpoint(body: SimulationPrompt):
|
| 46 |
+
"""
|
| 47 |
+
**MAIN ENDPOINT: Proves the Agentic MCP (SRS) is sound.**
|
| 48 |
+
|
| 49 |
+
This endpoint simulates the full Orchestrator workflow, showing the
|
| 50 |
+
step-by-step 'trace' of agents (Planner, Reasoner, Verifier)
|
| 51 |
+
collaborating to solve the user's prompt.
|
| 52 |
+
"""
|
| 53 |
+
start_time = time.time()
|
| 54 |
+
agent_trace: List[AgentStep] = []
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
# --- Orchestrator: Step 1 - Call Planner Agent ---
|
| 58 |
+
agent_trace.append(AgentStep(agent="Orchestrator", thought="Prompt received. Sending to Planner Agent.", output=f"Prompt: {body.prompt}"))
|
| 59 |
+
plan = call_mock_planner(body.prompt)
|
| 60 |
+
agent_trace.append(AgentStep(agent="Planner", thought="I have analyzed the prompt and created a step-by-step plan.", output=plan))
|
| 61 |
+
|
| 62 |
+
# --- Orchestrator: Step 2 - Call Reasoner Agent in a loop ---
|
| 63 |
+
reasoner_results = []
|
| 64 |
+
for task in plan:
|
| 65 |
+
agent_trace.append(AgentStep(agent="Orchestrator", thought=f"Dispatching task to Reasoner Agent: {task}", output=""))
|
| 66 |
+
time.sleep(0.1) # Simulate network latency
|
| 67 |
+
result = call_mock_reasoner(task)
|
| 68 |
+
reasoner_results.append({"task": task, "result": result})
|
| 69 |
+
agent_trace.append(AgentStep(agent="Reasoner", thought=f"I have completed task: {task}", output=result))
|
| 70 |
+
|
| 71 |
+
# --- Orchestrator: Step 3 - Call Verifier Agent ---
|
| 72 |
+
agent_trace.append(AgentStep(agent="Orchestrator", thought="All reasoning steps complete. Sending full results to Verifier Agent.", output=reasoner_results))
|
| 73 |
+
verification = call_mock_verifier(agent_trace)
|
| 74 |
+
agent_trace.append(AgentStep(agent="Verifier", thought="I have analyzed the full trace for correctness.", output=verification))
|
| 75 |
+
|
| 76 |
+
end_time = time.time()
|
| 77 |
+
|
| 78 |
+
return {
|
| 79 |
+
"status": "success",
|
| 80 |
+
"total_time_seconds": round(end_time - start_time, 2),
|
| 81 |
+
"agent_trace": [step.dict() for step in agent_trace]
|
| 82 |
+
}
|
| 83 |
+
except Exception as e:
|
| 84 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 85 |
+
|
| 86 |
+
@app.get("/")
|
| 87 |
+
async def root():
|
| 88 |
+
return {"message": "Welcome to the QThink Agentic POC API. See the /docs for interactive endpoints."}
|
mock_k2_api.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file simulates the K2 Think API for each agent in the MCP.
|
| 2 |
+
|
| 3 |
+
MOCK_QFT_PLAN = [
|
| 4 |
+
"Task 1: Define initial 8D state vector for |101⟩.",
|
| 5 |
+
"Task 2: Apply Hadamard on qubit 0.",
|
| 6 |
+
"Task 3: Apply CPHASE(pi/2) from q[1] to q[0].",
|
| 7 |
+
"Task 4: Apply CPHASE(pi/4) from q[2] to q[0].",
|
| 8 |
+
"Task 5: Apply Hadamard on qubit 1.",
|
| 9 |
+
"Task 6: Apply CPHASE(pi/2) from q[2] to q[1].",
|
| 10 |
+
"Task 7: Apply Hadamard on qubit 2.",
|
| 11 |
+
"Task 8: Apply SWAP(0, 2).",
|
| 12 |
+
"Task 9: Consolidate and present final state vector."
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
MOCK_QFT_STEP_RESULTS = {
|
| 16 |
+
"Task 1": "Initial state |101⟩ is index 5. Vector: [0, 0, 0, 0, 0, 1, 0, 0]",
|
| 17 |
+
"Task 2": "Applying H(0). State becomes (1/sqrt(2)) * [0, 0, 0, 0, 1, -1, 0, 0]",
|
| 18 |
+
"Task 3": "Applying CPHASE(pi/2). Control q[1] is 0 for all non-zero states. Vector is unchanged.",
|
| 19 |
+
"Task 4": "Applying CPHASE(pi/4). Control q[2]=1, Target q[0]=1 for index 5. Phase e^(i*pi/4) is applied.",
|
| 20 |
+
"Task 5": "Applying H(1). Amplitudes for indices 4,5,6,7 are calculated.",
|
| 21 |
+
"Task 6": "Applying CPHASE(pi/2). Phase 'i' applied to indices 6 and 7.",
|
| 22 |
+
"Task 7": "Applying H(2). Final amplitudes calculated across all 8 indices.",
|
| 23 |
+
"Task 8": "Applying SWAP(0, 2). Indices [1,4], [3,6] are permuted.",
|
| 24 |
+
"Task 9": "Final state vector consolidated. Ready for verification."
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
MOCK_QFT_VERIFICATION = {
|
| 28 |
+
"status": "Verified",
|
| 29 |
+
"steps_checked": 9,
|
| 30 |
+
"mathematical_consistency": "High",
|
| 31 |
+
"final_state_hash": "a3f4b01e2c5d6f7a... (mock hash)",
|
| 32 |
+
"final_state_preview": "[0.353, -0.353, 0.353i, -0.353i, -0.25-0.25i, 0.25+0.25i, ...]"
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
def call_mock_planner(prompt: str) -> list:
|
| 36 |
+
"""Simulates the Planner Agent."""
|
| 37 |
+
if "QFT" in prompt and "|101⟩" in prompt:
|
| 38 |
+
return MOCK_QFT_PLAN
|
| 39 |
+
return ["Task 1: Understand user prompt.", "Task 2: Formulate generic plan."]
|
| 40 |
+
|
| 41 |
+
def call_mock_reasoner(task: str) -> str:
|
| 42 |
+
"""Simulates the Reasoner Agent executing one task."""
|
| 43 |
+
return MOCK_QFT_STEP_RESULTS.get(task, f"Mock execution result for: {task}")
|
| 44 |
+
|
| 45 |
+
def call_mock_verifier(full_trace: list) -> dict:
|
| 46 |
+
"""Simulates the Verifier Agent checking the whole process."""
|
| 47 |
+
if len(full_trace) > 5:
|
| 48 |
+
return MOCK_QFT_VERIFICATION
|
| 49 |
+
return {"status": "Verification Failed", "reason": "Trace too short."}
|
quyaml_parser.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import yaml
|
| 2 |
+
from qiskit import QuantumCircuit
|
| 3 |
+
import re
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
def parse_quyaml_to_qiskit(quyaml_string: str) -> QuantumCircuit:
|
| 7 |
+
"""
|
| 8 |
+
Parses a QuYAML string into a Qiskit QuantumCircuit object.
|
| 9 |
+
"""
|
| 10 |
+
try:
|
| 11 |
+
data = yaml.safe_load(quyaml_string)
|
| 12 |
+
except yaml.YAMLError as e:
|
| 13 |
+
raise ValueError(f"Invalid QuYAML format: {e}")
|
| 14 |
+
|
| 15 |
+
circuit_name = data.get('circuit', 'my_circuit')
|
| 16 |
+
|
| 17 |
+
def get_reg_size(reg_str):
|
| 18 |
+
match = re.search(r'\[(\d+)\]', reg_str)
|
| 19 |
+
return int(match.group(1)) if match else 0
|
| 20 |
+
|
| 21 |
+
q_size = get_reg_size(data.get('qreg', 'q[0]'))
|
| 22 |
+
c_size = get_reg_size(data.get('creg', 'c[0]'))
|
| 23 |
+
|
| 24 |
+
if q_size == 0:
|
| 25 |
+
raise ValueError("QuYAML must define at least one quantum register (e.g., qreg: q[1])")
|
| 26 |
+
|
| 27 |
+
qc = QuantumCircuit(q_size, c_size, name=circuit_name)
|
| 28 |
+
|
| 29 |
+
instructions = data.get('instructions', [])
|
| 30 |
+
for inst_str in instructions:
|
| 31 |
+
apply_instruction(qc, inst_str)
|
| 32 |
+
|
| 33 |
+
return qc
|
| 34 |
+
|
| 35 |
+
def apply_instruction(qc: QuantumCircuit, inst_str: str):
|
| 36 |
+
"""
|
| 37 |
+
Parses a single QuYAML instruction string and applies it to the circuit.
|
| 38 |
+
"""
|
| 39 |
+
parts = inst_str.split()
|
| 40 |
+
gate = parts[0].lower()
|
| 41 |
+
|
| 42 |
+
def get_indices(target_strings):
|
| 43 |
+
indices = []
|
| 44 |
+
for s in target_strings:
|
| 45 |
+
match = re.search(r'\[(\d+)\]', s)
|
| 46 |
+
if match:
|
| 47 |
+
indices.append(int(match.group(1)))
|
| 48 |
+
return indices
|
| 49 |
+
|
| 50 |
+
targets = [p.replace(',', '') for p in parts[1:]]
|
| 51 |
+
q_indices = get_indices(targets)
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
if gate == 'h':
|
| 55 |
+
qc.h(q_indices[0])
|
| 56 |
+
elif gate == 'x':
|
| 57 |
+
qc.x(q_indices[0])
|
| 58 |
+
elif gate == 'cx':
|
| 59 |
+
qc.cx(q_indices[0], q_indices[1])
|
| 60 |
+
elif gate == 'swap':
|
| 61 |
+
qc.swap(q_indices[0], q_indices[1])
|
| 62 |
+
elif gate.startswith('cphase'):
|
| 63 |
+
angle_str = re.search(r'\((.*?)\)', gate).group(1)
|
| 64 |
+
angle_map = {'pi/2': np.pi / 2, 'pi/4': np.pi / 4, 'pi': np.pi}
|
| 65 |
+
angle = angle_map.get(angle_str, float(angle_str))
|
| 66 |
+
qc.cp(angle, q_indices[0], q_indices[1])
|
| 67 |
+
elif gate == 'measure':
|
| 68 |
+
qc.measure(range(qc.num_qubits), range(qc.num_clbits))
|
| 69 |
+
except Exception as e:
|
| 70 |
+
raise ValueError(f"Could not parse instruction '{inst_str}'. Error: {e}")
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
pyyaml
|
| 4 |
+
qiskit
|
| 5 |
+
pydantic
|
| 6 |
+
numpy
|