feat: wire GraphRAG API handlers, activities, and database layer
ci / test (push) Failing after 2m9s

This commit is contained in:
Test
2026-09-05 06:00:58 -07:00
parent 8474d7e494
commit 5c0eb2b66a
10 changed files with 715 additions and 398 deletions
+16
View File
@@ -74,6 +74,22 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.api.ExecuteWorkflow(w, r, parts[2])
}
// GraphRAG query endpoint
case strings.HasSuffix(path, "/query") && method == http.MethodPost:
// POST /workflows/{id}/query
parts := strings.Split(path, "/")
if len(parts) >= 4 && parts[1] == "workflows" && parts[3] == "query" {
s.api.QueryWorkflowGraph(w, r, parts[2])
}
// Relation versions endpoint
case strings.Contains(path, "/relations/") && strings.Contains(path, "/versions") && method == http.MethodGet:
// GET /workflows/{id}/relations/{edge_id}/versions
parts := strings.Split(path, "/")
if len(parts) >= 6 && parts[1] == "workflows" && parts[3] == "relations" && parts[5] == "versions" {
s.api.GetWorkflowRelationVersions(w, r, parts[2], parts[4])
}
// Execution endpoints
case strings.HasPrefix(path, "/executions/") && method == http.MethodGet:
id := extractID(path, "/executions/")
+115
View File
@@ -587,3 +587,118 @@ func (api *WorkflowAPI) nodesToWorkflowSpec(wf *WorkflowResponse, inputs map[str
return spec
}
// QueryWorkflowGraph handles POST /workflows/{id}/query
func (api *WorkflowAPI) QueryWorkflowGraph(w http.ResponseWriter, r *http.Request, workflowID string) {
ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second)
defer cancel()
var req QueryWorkflowGraphRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
// Defaults
if req.SearchType == "" {
req.SearchType = "edges"
}
if req.ConfidenceFloor == 0 {
req.ConfidenceFloor = 0.5
}
if req.TopK == 0 {
req.TopK = 10
}
if req.MaxPathDepth == 0 {
req.MaxPathDepth = 3
}
if req.RankingProfile == "" {
req.RankingProfile = "default"
}
// Get latest version if not specified
if req.Version == 0 {
wf, err := api.db.GetWorkflow(ctx, workflowID)
if err != nil {
http.Error(w, "Workflow not found", http.StatusNotFound)
return
}
req.Version = wf.Version
}
// Call temporal workflow
run, err := api.temporalClient.ExecuteWorkflow(
ctx,
client.StartWorkflowOptions{
ID: fmt.Sprintf("graph-query-%s-v%d", workflowID, req.Version),
TaskQueue: "poimen",
},
"WorkflowGraphQuery",
map[string]interface{}{
"workflow_id": workflowID,
"query": req.Query,
"search_type": req.SearchType,
"relation_type": req.RelationType,
"version": req.Version,
"confidence_floor": req.ConfidenceFloor,
"top_k": req.TopK,
"find_paths": req.FindPaths,
"target_node_id": req.TargetNodeID,
"max_path_depth": req.MaxPathDepth,
"ranking_profile": req.RankingProfile,
"include_reasoning": req.IncludeReasoning,
},
)
if err != nil {
api.logger.Printf("Failed to start workflow: %v", err)
http.Error(w, "Failed to start query workflow", http.StatusInternalServerError)
return
}
var result map[string]interface{}
if err := run.Get(ctx, &result); err != nil {
api.logger.Printf("Workflow execution failed: %v", err)
http.Error(w, "Query execution failed", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
// QueryWorkflowGraphRequest matches frontend payload
type QueryWorkflowGraphRequest struct {
Query string `json:"query"`
SearchType string `json:"search_type"`
RelationType string `json:"relation_type"`
Version int `json:"version"`
ConfidenceFloor float64 `json:"confidence_floor"`
TopK int `json:"top_k"`
FindPaths bool `json:"find_paths"`
TargetNodeID string `json:"target_node_id"`
MaxPathDepth int `json:"max_path_depth"`
RankingProfile string `json:"ranking_profile"`
IncludeReasoning bool `json:"include_reasoning"`
}
// GetWorkflowRelationVersions handles GET /workflows/{id}/relations/{edge_id}/versions
func (api *WorkflowAPI) GetWorkflowRelationVersions(w http.ResponseWriter, r *http.Request, workflowID, edgeID string) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
// Query relation versions from DB
versions, err := api.db.GetRelationVersions(ctx, workflowID, edgeID)
if err != nil {
api.logger.Printf("Failed to get relation versions: %v", err)
http.Error(w, "Failed to fetch relation versions", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"workflow_id": workflowID,
"edge_id": edgeID,
"versions": versions,
"total_count": len(versions),
})
}
-164
View File
@@ -1,164 +0,0 @@
package api
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
"go.temporal.io/sdk/client"
"github.com/rockliang/poimen/workflows/pkg/db"
"github.com/rockliang/poimen/workflows/statemachine"
)
// QueryWorkflowGraphRequest matches frontend payload
type QueryWorkflowGraphRequest struct {
Query string `json:"query"`
SearchType string `json:"search_type"`
RelationType string `json:"relation_type"`
Version int `json:"version"`
ConfidenceFloor float64 `json:"confidence_floor"`
TopK int `json:"top_k"`
FindPaths bool `json:"find_paths"`
TargetNodeID string `json:"target_node_id"`
MaxPathDepth int `json:"max_path_depth"`
RankingProfile string `json:"ranking_profile"`
IncludeReasoning bool `json:"include_reasoning"`
}
// QueryWorkflowGraphHandler handles POST /workflows/{id}/query
func QueryWorkflowGraphHandler(temporalClient client.Client, dbClient *db.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
workflowID := mux.Vars(r)["id"]
if workflowID == "" {
http.Error(w, "Missing workflow ID", http.StatusBadRequest)
return
}
var req QueryWorkflowGraphRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
// Defaults
if req.SearchType == "" {
req.SearchType = "edges"
}
if req.ConfidenceFloor == 0 {
req.ConfidenceFloor = 0.5
}
if req.TopK == 0 {
req.TopK = 10
}
if req.MaxPathDepth == 0 {
req.MaxPathDepth = 3
}
if req.RankingProfile == "" {
req.RankingProfile = "default"
}
// Get latest version if not specified
if req.Version == 0 {
workflow, err := dbClient.GetWorkflow(r.Context(), workflowID)
if err != nil {
http.Error(w, "Failed to get workflow", http.StatusInternalServerError)
return
}
req.Version = workflow.Version
}
// Start Temporal workflow
workflowInput := statemachine.WorkflowGraphQueryInput{
WorkflowID: workflowID,
Query: req.Query,
SearchType: req.SearchType,
RelationType: req.RelationType,
Version: req.Version,
ConfidenceFloor: req.ConfidenceFloor,
TopK: req.TopK,
FindPaths: req.FindPaths,
TargetNodeID: req.TargetNodeID,
MaxPathDepth: req.MaxPathDepth,
RankingProfile: req.RankingProfile,
IncludeReasoning: req.IncludeReasoning,
}
options := client.StartWorkflowOptions{
ID: "graph-query-" + workflowID + "-" + strconv.FormatInt(int64(req.Version), 10),
TaskQueue: "poimen-default",
}
// Execute workflow (blocking)
run, err := temporalClient.ExecuteWorkflow(
r.Context(),
options,
statemachine.WorkflowGraphQuery,
workflowInput,
)
if err != nil {
http.Error(w, "Failed to start workflow", http.StatusInternalServerError)
return
}
var output statemachine.WorkflowGraphQueryOutput
if err := run.Get(r.Context(), &output); err != nil {
http.Error(w, "Workflow execution failed", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(output)
}
}
// GetWorkflowRelationVersionsHandler handles GET /workflows/{id}/relations/{edge_id}/versions
func GetWorkflowRelationVersionsHandler(dbClient *db.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
workflowID := mux.Vars(r)["id"]
edgeID := mux.Vars(r)["edge_id"]
if workflowID == "" || edgeID == "" {
http.Error(w, "Missing parameters", http.StatusBadRequest)
return
}
// Query relation versions from DB
versions, err := dbClient.GetRelationVersions(r.Context(), workflowID, edgeID)
if err != nil {
http.Error(w, "Failed to fetch versions", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"workflow_id": workflowID,
"edge_id": edgeID,
"versions": versions,
"total_count": len(versions),
})
}
}
// RegisterGraphQueryHandlers registers all graph query endpoints
func RegisterGraphQueryHandlers(
router *mux.Router,
temporalClient client.Client,
dbClient *db.Client,
) {
// Query workflow relations via GraphRAG
router.HandleFunc("/workflows/{id}/query", QueryWorkflowGraphHandler(temporalClient, dbClient)).Methods("POST")
// Get relation version history
router.HandleFunc("/workflows/{id}/relations/{edge_id}/versions", GetWorkflowRelationVersionsHandler(dbClient)).Methods("GET")
}