Deployment commited on
Commit
4b1daed
·
1 Parent(s): 6b18e31

Automated deployment update

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +38 -0
  2. .env.example +118 -0
  3. .github/workflows/ci.yml +117 -0
  4. .gitignore +70 -0
  5. .golangci.yml +76 -0
  6. Dockerfile +51 -0
  7. Makefile +118 -0
  8. README.md +464 -7
  9. agent.pb.go +1837 -0
  10. agent_grpc.pb.go +291 -0
  11. cmd/agent-server/main.go +270 -0
  12. cmd/generator-server/main.go +290 -0
  13. cmd/retriever-server/main.go +317 -0
  14. cmd/worker/main.go +327 -0
  15. config.example.yaml +67 -0
  16. deploy.md +149 -0
  17. docs/API_GATEWAY.md +211 -0
  18. gateway.example.yaml +96 -0
  19. generator.pb.go +1414 -0
  20. generator_grpc.pb.go +291 -0
  21. go.mod +118 -0
  22. go.sum +497 -0
  23. go.work +10 -0
  24. internal/agent/interceptors.go +60 -0
  25. internal/agent/orchestrator.go +565 -0
  26. internal/agent/server.go +311 -0
  27. internal/cache/cache.go +310 -0
  28. internal/cache/cache_test.go +310 -0
  29. internal/gateway/cache/cache.go +100 -0
  30. internal/gateway/cmd/main.go +65 -0
  31. internal/gateway/config.go +204 -0
  32. internal/gateway/gateway.go +457 -0
  33. internal/gateway/gwtypes/types.go +196 -0
  34. internal/gateway/handlers/admin.go +93 -0
  35. internal/gateway/handlers/agent.go +270 -0
  36. internal/gateway/handlers/auth.go +115 -0
  37. internal/gateway/handlers/graphql.go +620 -0
  38. internal/gateway/handlers/handlers_test.go +292 -0
  39. internal/gateway/handlers/memory.go +152 -0
  40. internal/gateway/handlers/query.go +224 -0
  41. internal/gateway/handlers/voice_files.go +232 -0
  42. internal/gateway/handlers/websocket.go +289 -0
  43. internal/gateway/middleware/audit.go +133 -0
  44. internal/gateway/middleware/auth.go +311 -0
  45. internal/gateway/middleware/common.go +51 -0
  46. internal/gateway/middleware/cors.go +143 -0
  47. internal/gateway/middleware/middleware_test.go +388 -0
  48. internal/gateway/middleware/ratelimit.go +253 -0
  49. internal/gateway/middleware/tracing.go +96 -0
  50. internal/gateway/observability/metrics.go +104 -0
.dockerignore ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Version Control
2
+ .git
3
+ .gitignore
4
+ .github
5
+
6
+ # Dependencies
7
+ **/node_modules
8
+ **/vendor
9
+
10
+ # Build Artifacts
11
+ **/bin
12
+ **/dist
13
+ **/.next
14
+ **/out
15
+ *.exe
16
+ *.test
17
+ *.prof
18
+
19
+ # IDEs
20
+ .vscode
21
+ .idea
22
+ *.swp
23
+ *.swo
24
+
25
+ # Environment Variables
26
+ .env
27
+ .env.*
28
+ !.env.example
29
+
30
+ # OS Files
31
+ .DS_Store
32
+ Thumbs.db
33
+
34
+ # Large Documentation/Assets
35
+ docs/
36
+ tests/integration
37
+ frontend/packages/*/dist
38
+ frontend/apps/*/dist
.env.example ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AmaniQuery Consolidated Environment Variables Example
2
+ # Copy this file to .env and adjust the values as needed.
3
+
4
+ # =============================================================================
5
+ # General Configuration
6
+ # =============================================================================
7
+ ENV=development
8
+ LOG_LEVEL=info
9
+ VERSION=1.0.0
10
+
11
+ # =============================================================================
12
+ # Server Configuration
13
+ # =============================================================================
14
+ # Main Service
15
+ AMANI_SERVER_GRPC_PORT=9090
16
+ AMANI_SERVER_HTTP_PORT=8080
17
+ AMANI_SERVER_GRACEFUL_TIMEOUT=30s
18
+ AMANI_SERVER_MAX_CONNECTIONS=1000
19
+
20
+ # Notification Service
21
+ PORT=:8080
22
+ NOTIF_SERVER_READ_TIMEOUT=30s
23
+ NOTIF_SERVER_WRITE_TIMEOUT=30s
24
+ NOTIF_SERVER_SHUTDOWN_TIMEOUT=30s
25
+
26
+ # =============================================================================
27
+ # LLM Providers (Main Service)
28
+ # =============================================================================
29
+ # Fallback order: Gemini -> Moonshot -> Ollama -> OpenAI -> Anthropic
30
+ GEMINI_API_KEY=your-gemini-api-key
31
+ GOOGLE_API_KEY=your-gemini-api-key # Alias for GEMINI_API_KEY
32
+
33
+ MOONSHOT_API_KEY=your-moonshot-api-key
34
+
35
+ OLLAMA_BASE_URL=http://localhost:11434
36
+
37
+ OPENAI_API_KEY=your-openai-api-key
38
+
39
+ ANTHROPIC_API_KEY=your-anthropic-api-key
40
+
41
+ # LLM Settings
42
+ AMANI_LLM_DEFAULT_MODEL=gemini-2.5-flash
43
+ AMANI_LLM_MAX_TOKENS=4096
44
+ AMANI_LLM_TEMPERATURE=0.7
45
+ AMANI_LLM_TIMEOUT=60s
46
+ AMANI_LLM_MAX_RETRIES=3
47
+ AMANI_LLM_ENABLE_FALLBACK=true
48
+
49
+ # =============================================================================
50
+ # Vector Store (Qdrant)
51
+ # =============================================================================
52
+ QDRANT_URL=localhost:6334
53
+ QDRANT_API_KEY=your_qdrant_api_key_here
54
+ AMANI_VECTOR_STORE_COLLECTION=amaniquery
55
+ AMANI_VECTOR_STORE_DIMENSION=1536
56
+ AMANI_VECTOR_STORE_DISTANCE=Cosine
57
+
58
+ # =============================================================================
59
+ # Caching & Queue (Redis)
60
+ # =============================================================================
61
+ # Used by both Main and Notification services
62
+ REDIS_URL=redis://localhost:6379
63
+ REDIS_ADDR=localhost:6379
64
+ REDIS_PASSWORD=
65
+ REDIS_DB=0
66
+
67
+ # =============================================================================
68
+ # Database (MongoDB)
69
+ # =============================================================================
70
+ # Primarily used by Notification Service and Memory Service
71
+ MONGO_URI=mongodb://localhost:27017
72
+ MONGO_DATABASE=amaniquery_notifications
73
+
74
+ # =============================================================================
75
+ # Security (JWT & Auth)
76
+ # =============================================================================
77
+ JWT_SECRET=your-secure-jwt-secret-key-at-least-32-characters
78
+ JWT_ISSUER=amaniquery
79
+ JWT_AUDIENCE=amaniquery-notifications
80
+
81
+ # =============================================================================
82
+ # Notification Service Providers
83
+ # =============================================================================
84
+ # Mailtrap (Email)
85
+ MAILTRAP_API_KEY=your-mailtrap-api-key
86
+ MAILTRAP_ACCOUNT_ID=your-mailtrap-account-id
87
+ MAILTRAP_SENDER_NAME=AmaniQuery
88
+ MAILTRAP_SENDER_EMAIL=noreply@yourdomain.com
89
+
90
+ # Africa's Talking (SMS)
91
+ AFRICASTALKING_USERNAME=sandbox
92
+ AFRICASTALKING_API_KEY=your-africastalking-api-key
93
+ AFRICASTALKING_SENDER_ID=AmaniQuery
94
+ AFRICASTALKING_SANDBOX=true
95
+
96
+ # =============================================================================
97
+ # Observability & Monitoring
98
+ # =============================================================================
99
+ JAEGER_ENDPOINT=localhost:4317
100
+ AMANI_OBSERVABILITY_TRACING_ENABLED=true
101
+ AMANI_OBSERVABILITY_METRICS_ENABLED=true
102
+ AMANI_OBSERVABILITY_METRICS_PORT=9091
103
+
104
+ # Prometheus (Notifications)
105
+ METRICS_ENABLED=true
106
+ METRICS_PORT=:9090
107
+ METRICS_PATH=/metrics
108
+
109
+ # Grafana
110
+ GRAFANA_PASSWORD=your-grafana-admin-password
111
+
112
+ # =============================================================================
113
+ # Memory Service Configuration
114
+ # =============================================================================
115
+ AMANI_MEMORY_RUST_SERVICE_HOST=localhost
116
+ AMANI_MEMORY_RUST_SERVICE_PORT=9091
117
+ AMANI_MEMORY_ENABLE_RUST_SERVICE=false
118
+ AMANI_MEMORY_ENABLE_GDPR=true
.github/workflows/ci.yml ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master, develop]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ env:
10
+ GO_VERSION: "1.21"
11
+
12
+ jobs:
13
+ lint:
14
+ name: Lint
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Go
20
+ uses: actions/setup-go@v5
21
+ with:
22
+ go-version: ${{ env.GO_VERSION }}
23
+ cache: true
24
+
25
+ - name: golangci-lint
26
+ uses: golangci/golangci-lint-action@v3
27
+ with:
28
+ version: v1.55.2
29
+ args: --timeout=5m
30
+
31
+ test:
32
+ name: Test
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+
37
+ - name: Set up Go
38
+ uses: actions/setup-go@v5
39
+ with:
40
+ go-version: ${{ env.GO_VERSION }}
41
+ cache: true
42
+
43
+ - name: Run tests
44
+ run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
45
+
46
+ - name: Upload coverage
47
+ uses: codecov/codecov-action@v3
48
+ with:
49
+ file: ./coverage.out
50
+ fail_ci_if_error: false
51
+
52
+ build:
53
+ name: Build
54
+ runs-on: ubuntu-latest
55
+ needs: [lint, test]
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+
59
+ - name: Set up Go
60
+ uses: actions/setup-go@v5
61
+ with:
62
+ go-version: ${{ env.GO_VERSION }}
63
+ cache: true
64
+
65
+ - name: Build agent-server
66
+ run: go build -ldflags="-s -w" -o bin/agent-server ./cmd/agent-server
67
+
68
+ docker:
69
+ name: Docker Build
70
+ runs-on: ubuntu-latest
71
+ needs: [build]
72
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+
76
+ - name: Set up Docker Buildx
77
+ uses: docker/setup-buildx-action@v3
78
+
79
+ - name: Login to GitHub Container Registry
80
+ uses: docker/login-action@v3
81
+ with:
82
+ registry: ghcr.io
83
+ username: ${{ github.actor }}
84
+ password: ${{ secrets.GITHUB_TOKEN }}
85
+
86
+ - name: Build and push agent-server
87
+ uses: docker/build-push-action@v5
88
+ with:
89
+ context: .
90
+ file: deployments/docker/Dockerfile.agent
91
+ push: true
92
+ tags: |
93
+ ghcr.io/${{ github.repository }}/agent-server:latest
94
+ ghcr.io/${{ github.repository }}/agent-server:${{ github.sha }}
95
+ cache-from: type=gha
96
+ cache-to: type=gha,mode=max
97
+
98
+ security-scan:
99
+ name: Security Scan
100
+ runs-on: ubuntu-latest
101
+ steps:
102
+ - uses: actions/checkout@v4
103
+
104
+ - name: Run Trivy vulnerability scanner
105
+ uses: aquasecurity/trivy-action@master
106
+ with:
107
+ scan-type: "fs"
108
+ scan-ref: "."
109
+ ignore-unfixed: true
110
+ format: "sarif"
111
+ output: "trivy-results.sarif"
112
+
113
+ - name: Upload Trivy scan results to GitHub Security tab
114
+ uses: github/codeql-action/upload-sarif@v2
115
+ if: always()
116
+ with:
117
+ sarif_file: "trivy-results.sarif"
.gitignore ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Binaries
2
+ bin/
3
+ *.exe
4
+ *.exe~
5
+ *.dll
6
+ *.so
7
+ *.dylib
8
+
9
+ # Test binary, built with `go test -c`
10
+ *.test
11
+
12
+ # Output of the go coverage tool
13
+ *.out
14
+
15
+ # Dependency directories
16
+ vendor/
17
+
18
+ # Go workspace sum file (keep go.work for local development)
19
+ go.work.sum
20
+
21
+ # IDE
22
+ .idea/
23
+ .vscode/
24
+ *.swp
25
+ *.swo
26
+ *~
27
+
28
+ # OS
29
+ .DS_Store
30
+ Thumbs.db
31
+
32
+ # Logs
33
+ *.log
34
+ logs/
35
+
36
+ # Environment files
37
+ .env
38
+ .env.local
39
+ .env.*.local
40
+ config.yaml
41
+ !config.example.yaml
42
+
43
+ # Temporary files
44
+ tmp/
45
+ temp/
46
+
47
+ # Build artifacts
48
+ dist/
49
+
50
+ # Docker
51
+ .docker/
52
+
53
+ # Kubernetes secrets (never commit)
54
+ *-secret.yaml
55
+ *-secrets.yaml
56
+
57
+ # Coverage reports
58
+ coverage/
59
+ coverage.html
60
+ coverage.txt
61
+
62
+ # Bleve index files
63
+ *.bleve/
64
+ amaniquery_index/
65
+
66
+ # Qdrant data (for local development)
67
+ qdrant_storage/
68
+
69
+ # Proto generated files (regenerate from source)
70
+ pkg/proto/gen/
.golangci.yml ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run:
2
+ timeout: 5m
3
+ tests: true
4
+
5
+ linters:
6
+ enable:
7
+ - errcheck
8
+ - gosimple
9
+ - govet
10
+ - ineffassign
11
+ - staticcheck
12
+ - unused
13
+ - gofmt
14
+ - goimports
15
+ - revive
16
+ - gosec
17
+ - prealloc
18
+ - unconvert
19
+ - misspell
20
+ - goconst
21
+ - bodyclose
22
+ - noctx
23
+ - dupl
24
+ - nestif
25
+
26
+ linters-settings:
27
+ gofmt:
28
+ simplify: true
29
+ goimports:
30
+ local-prefixes: github.com/AmaniQuery/amaniquery
31
+ revive:
32
+ severity: warning
33
+ rules:
34
+ - name: blank-imports
35
+ - name: context-as-argument
36
+ - name: context-keys-type
37
+ - name: error-return
38
+ - name: error-strings
39
+ - name: error-naming
40
+ - name: exported
41
+ - name: increment-decrement
42
+ - name: var-naming
43
+ - name: package-comments
44
+ - name: range
45
+ - name: receiver-naming
46
+ - name: time-naming
47
+ - name: unexported-return
48
+ - name: indent-error-flow
49
+ gosec:
50
+ excludes:
51
+ - G104 # Unhandled error - too noisy for grpc
52
+ - G107 # Potential HTTP request made with variable url
53
+ nestif:
54
+ min-complexity: 5
55
+ goconst:
56
+ min-len: 3
57
+ min-occurrences: 3
58
+ dupl:
59
+ threshold: 150
60
+
61
+ issues:
62
+ exclude-use-default: false
63
+ max-issues-per-linter: 50
64
+ max-same-issues: 10
65
+ exclude-rules:
66
+ - path: _test\.go
67
+ linters:
68
+ - dupl
69
+ - gosec
70
+ - goconst
71
+ - path: cmd/
72
+ linters:
73
+ - gochecknoinits
74
+ - linters:
75
+ - staticcheck
76
+ text: "SA1019:" # Deprecated
Dockerfile ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Go Build Stage
2
+ FROM golang:1.24-alpine AS go-builder
3
+ WORKDIR /app
4
+ RUN apk add --no-cache git ca-certificates
5
+ COPY go.mod go.sum ./
6
+ COPY go.work go.work.sum ./
7
+ RUN go mod download
8
+ COPY . .
9
+ RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /agent-server ./cmd/agent-server
10
+
11
+ # Rust Build Stage
12
+ FROM rust:1.76-alpine AS rust-builder
13
+ WORKDIR /app
14
+ RUN apk add --no-cache musl-dev
15
+ COPY rust-memory-service/Cargo.toml rust-memory-service/Cargo.lock ./
16
+ # Dummy build to cache deps
17
+ RUN mkdir src && echo "fn main() {}" > src/main.rs
18
+ RUN cargo build --release
19
+ RUN rm -rf src
20
+ COPY rust-memory-service/ .
21
+ RUN cargo build --release
22
+ RUN cp target/release/memory-server /memory-server
23
+
24
+ # Runtime Stage
25
+ FROM alpine:3.19
26
+ RUN apk add --no-cache ca-certificates tzdata
27
+ RUN adduser -D -g '' appuser
28
+ WORKDIR /app
29
+
30
+ # Copy binaries
31
+ COPY --from=go-builder /agent-server .
32
+ COPY --from=rust-builder /memory-server .
33
+
34
+ # Copy scripts and configs
35
+ COPY deployments/huggingface/start.sh .
36
+ COPY config.example.yaml ./config.yaml
37
+ COPY rust-memory-service/.env.example .env
38
+
39
+ RUN chmod +x start.sh && chown -R appuser:appuser /app
40
+
41
+ USER appuser
42
+ EXPOSE 7860
43
+
44
+ # Environment variables
45
+ ENV PORT=7860
46
+ ENV MEMORY_BIND_ADDR=127.0.0.1:9091
47
+ # Configure Go agent to use local memory service (needs env var mapping in config/env)
48
+ ENV MEMORY_SERVICE_HOST=127.0.0.1
49
+ ENV MEMORY_SERVICE_PORT=9091
50
+
51
+ CMD ["./start.sh"]
Makefile ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .PHONY: all build clean test proto run-agent run-retriever docker infra-up infra-down lint
2
+
3
+ # Variables
4
+ PROTO_DIR := pkg/proto
5
+ GO_OUT := pkg/proto/gen
6
+ DOCKER_COMPOSE := docker-compose -f deployments/docker/docker-compose.yml
7
+
8
+ # Default target
9
+ all: proto build
10
+
11
+ # Generate Go code from protobuf definitions
12
+ proto:
13
+ @echo "Generating protobuf code..."
14
+ @mkdir -p $(GO_OUT)
15
+ protoc --go_out=. --go_opt=paths=source_relative \
16
+ --go-grpc_out=. --go-grpc_opt=paths=source_relative \
17
+ $(PROTO_DIR)/agent.proto $(PROTO_DIR)/generator.proto $(PROTO_DIR)/retriever.proto
18
+ protoc --go_out=. --go_opt=paths=source_relative \
19
+ --go-grpc_out=. --go-grpc_opt=paths=source_relative \
20
+ $(PROTO_DIR)/clara.proto
21
+
22
+ # Build all services
23
+ build:
24
+ @echo "Building services..."
25
+ go build -o bin/agent-server ./cmd/agent-server
26
+ go build -o bin/retriever-server ./cmd/retriever-server
27
+ go build -o bin/generator-server ./cmd/generator-server
28
+
29
+ # Clean build artifacts
30
+ clean:
31
+ @echo "Cleaning..."
32
+ rm -rf bin/
33
+ rm -rf $(GO_OUT)
34
+
35
+ # Run tests
36
+ test:
37
+ @echo "Running tests..."
38
+ go test -v -race -cover ./...
39
+
40
+ # Run integration tests
41
+ test-integration:
42
+ @echo "Running integration tests..."
43
+ go test -v -tags=integration ./tests/...
44
+
45
+ # Run agent server
46
+ run-agent:
47
+ @echo "Starting agent server..."
48
+ go run ./cmd/agent-server
49
+
50
+ # Run retriever server
51
+ run-retriever:
52
+ @echo "Starting retriever server..."
53
+ go run ./cmd/retriever-server
54
+
55
+ # Run generator server
56
+ run-generator:
57
+ @echo "Starting generator server..."
58
+ go run ./cmd/generator-server
59
+
60
+ # Build Docker images
61
+ docker:
62
+ @echo "Building Docker images..."
63
+ docker build -t amaniquery/agent-server:latest -f deployments/docker/Dockerfile.agent .
64
+ docker build -t amaniquery/retriever-server:latest -f deployments/docker/Dockerfile.retriever .
65
+ docker build -t amaniquery/generator-server:latest -f deployments/docker/Dockerfile.generator .
66
+
67
+ # Start infrastructure (Qdrant, Redis, etc.)
68
+ infra-up:
69
+ @echo "Starting infrastructure..."
70
+ $(DOCKER_COMPOSE) up -d qdrant redis
71
+
72
+ # Stop infrastructure
73
+ infra-down:
74
+ @echo "Stopping infrastructure..."
75
+ $(DOCKER_COMPOSE) down
76
+
77
+ # Start all services with Docker Compose
78
+ up:
79
+ @echo "Starting all services..."
80
+ $(DOCKER_COMPOSE) up -d
81
+
82
+ # Stop all services
83
+ down:
84
+ @echo "Stopping all services..."
85
+ $(DOCKER_COMPOSE) down
86
+
87
+ # View logs
88
+ logs:
89
+ $(DOCKER_COMPOSE) logs -f
90
+
91
+ # Lint code
92
+ lint:
93
+ @echo "Linting..."
94
+ golangci-lint run ./...
95
+
96
+ # Format code
97
+ fmt:
98
+ @echo "Formatting..."
99
+ go fmt ./...
100
+ goimports -w .
101
+
102
+ # Download dependencies
103
+ deps:
104
+ @echo "Downloading dependencies..."
105
+ go mod download
106
+ go mod tidy
107
+
108
+ # Generate mocks for testing
109
+ mocks:
110
+ @echo "Generating mocks..."
111
+ mockgen -source=internal/agent/orchestrator.go -destination=internal/agent/mocks/orchestrator_mock.go
112
+ mockgen -source=internal/retriever/retriever.go -destination=internal/retriever/mocks/retriever_mock.go
113
+
114
+ # Security scan
115
+ security:
116
+ @echo "Running security scan..."
117
+ gosec ./...
118
+ trivy fs .
README.md CHANGED
@@ -1,10 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Amaniquery Agent
3
- emoji: 🐢
4
- colorFrom: indigo
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
+ # AmaniQuery
2
+
3
+ **AI-Powered Legal & News Intelligence Platform for Kenya**
4
+
5
+ [![Go Version](https://img.shields.io/badge/Go-1.21+-00ADD8?style=flat&logo=go)](https://go.dev/)
6
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![Build Status](https://img.shields.io/badge/Build-Passing-success)](https://github.com/amaniquery/amaniquery)
8
+
9
+ AmaniQuery is an AI agent framework designed to democratize access to legal information and civil education in Kenya. Built with a production-ready, Go-based RAG (Retrieval-Augmented Generation) architecture, it provides accurate, contextual answers about Kenya Law and current affairs.
10
+
11
+ ---
12
+
13
+ ## 🎯 Vision
14
+
15
+ **Democratizing Access to Information and Civil Education**
16
+
17
+ AmaniQuery aims to make legal knowledge accessible to every Kenyan citizen by:
18
+ - Providing accurate answers to legal questions in plain language
19
+ - Aggregating and contextualizing news relevant to civic matters
20
+ - Offering a reliable, fast, and secure platform for information retrieval
21
+
22
+ ---
23
+
24
+ ## 📐 Architecture Overview
25
+
26
+ ### High-Level System Architecture
27
+
28
+ ```mermaid
29
+ graph TB
30
+ subgraph "Client Layer"
31
+ UI[Web UI/React]
32
+ API[REST/gRPC API]
33
+ CLI[Command Line]
34
+ end
35
+
36
+ subgraph "Control Plane"
37
+ AGENT[Agent Orchestrator<br/>Go-based Coordinator]
38
+ ROUTER[Query Router<br/>Semantic Classifier]
39
+ EVAL[Evaluation Engine<br/>Quality Scorer]
40
+ GUARDRAILS[NeMo Guardrails<br/>Safety Filter]
41
+ end
42
+
43
+ subgraph "Data Plane"
44
+ EMBED[Embedding Service<br/>GPU-Accelerated]
45
+ RETRIEVER[Retriever Service<br/>Hybrid Search]
46
+ RERANK[Reranker Service<br/>Cross-Encoder]
47
+ GENERATOR[Generator Service<br/>LLM Gateway]
48
+ end
49
+
50
+ subgraph "Storage Layer"
51
+ VDB[(Vector DB<br/>Qdrant)]
52
+ CACHE[(Redis Cache)]
53
+ OBJ[(Object Store<br/>MinIO/S3)]
54
+ GRAPH[(Graph DB<br/>Neo4j)]
55
+ end
56
+
57
+ subgraph "Infrastructure"
58
+ MONITOR[Monitoring<br/>Prometheus/Grafana]
59
+ TRACING[Tracing<br/>Jaeger]
60
+ VAULT[Secrets Vault<br/>HashiCorp Vault]
61
+ end
62
+
63
+ UI --> API
64
+ CLI --> API
65
+ API --> AGENT
66
+ AGENT --> ROUTER
67
+ ROUTER --> GUARDRAILS
68
+ GUARDRAILS --> EMBED
69
+ GUARDRAILS --> RETRIEVER
70
+ RETRIEVER --> VDB
71
+ RETRIEVER --> GRAPH
72
+ EMBED --> VDB
73
+ RETRIEVER --> RERANK
74
+ RERANK --> GENERATOR
75
+ GENERATOR --> EVAL
76
+ EVAL --> CACHE
77
+ CACHE --> API
78
+ AGENT --> MONITOR
79
+ AGENT --> TRACING
80
+ VAULT --> AGENT
81
+ ```
82
+
83
+ ### Query Router & Semantic Classification
84
+
85
+ ```mermaid
86
+ graph LR
87
+ Q[User Query] --> PREPROCESS[Preprocessor<br/>Cleaning/Normalization]
88
+ PREPROCESS --> CLASSIFIER[Classifier<br/>Intent Detection]
89
+ CLASSIFIER --> C1{Query Type}
90
+ C1 -->|Factual| VSS[Vector Search]
91
+ C1 -->|Keyword| BM25[BM25 Search]
92
+ C1 -->|Relational| GRAPH[GraphRAG]
93
+ C1 -->|Multi-hop| AGENT[Agentic Search]
94
+
95
+ VSS --> HYBRID[Hybrid Results]
96
+ BM25 --> HYBRID
97
+ GRAPH --> HYBRID
98
+ AGENT --> HYBRID
99
+ HYBRID --> FUSION[Rank Fusion<br/>Reciprocal Rank]
100
+ FUSION --> OUTPUT[Ranked Chunks]
101
+ ```
102
+
103
+ ### Security Architecture
104
+
105
+ ```mermaid
106
+ graph TB
107
+ subgraph "Perimeter Security"
108
+ WAF[Web App Firewall]
109
+ API_GATEWAY[API Gateway<br/>Kong/Envoy]
110
+ RATE_LIMIT[Rate Limiter<br/>Token Bucket]
111
+ end
112
+
113
+ subgraph "Authentication & Authorization"
114
+ OIDC[OIDC Provider<br/>Keycloak]
115
+ JWT[JWT Validator<br/>RS256]
116
+ OPA[OPA Policy Agent]
117
+ POLICY[Rego Policies]
118
+ end
119
+
120
+ subgraph "Data Security"
121
+ TLS[TLS 1.3<br/>mTLS Internal]
122
+ ENCRYPT[AES-256-GCM]
123
+ VAULT[(HashiCorp Vault)]
124
+ end
125
+
126
+ subgraph "Compliance"
127
+ AUDIT[Audit Logger]
128
+ GUARDRAILS2[Content Filter]
129
+ PII[PII Detector]
130
+ end
131
+
132
+ CLIENT[Client] --> WAF
133
+ WAF --> RATE_LIMIT
134
+ RATE_LIMIT --> API_GATEWAY
135
+ API_GATEWAY --> OIDC
136
+ OIDC --> JWT
137
+ JWT --> OPA
138
+ OPA --> POLICY
139
+ POLICY --> SERVICE[Core Services]
140
+ SERVICE --> TLS
141
+ SERVICE --> VAULT
142
+ SERVICE --> AUDIT
143
+ SERVICE --> GUARDRAILS2
144
+ SERVICE --> PII
145
+ ```
146
+
147
+ ### Synchronous Query Flow
148
+
149
+ ```mermaid
150
+ sequenceDiagram
151
+ participant Client
152
+ participant API_Gateway
153
+ participant Agent
154
+ participant Router
155
+ participant Guardrails
156
+ participant Retriever
157
+ participant Generator
158
+ participant Cache
159
+
160
+ Client->>API_Gateway: POST /query
161
+ API_Gateway->>Agent: Forward query
162
+
163
+ Agent->>Cache: Check cache
164
+ alt Cache Hit
165
+ Cache-->>Agent: Cached result
166
+ Agent-->>Client: Response 200ms
167
+ else Cache Miss
168
+ Agent->>Router: RouteQuery()
169
+ Router-->>Agent: RoutingDecision
170
+
171
+ Agent->>Guardrails: ValidateQuery()
172
+ Guardrails-->>Agent: Safe/Unsafe
173
+
174
+ Agent->>Retriever: HybridSearch()
175
+ Retriever-->>Agent: Top-K chunks
176
+
177
+ Agent->>Generator: GenerateResponse()
178
+ Generator-->>Agent: Answer
179
+
180
+ Agent->>Cache: Store result
181
+ Agent-->>Client: Response 2-3s
182
+ end
183
+ ```
184
+
185
+ ### Agentic Multi-Step Flow
186
+
187
+ ```mermaid
188
+ sequenceDiagram
189
+ participant Client
190
+ participant Agent
191
+ participant Planner
192
+ participant Tools
193
+ participant Evaluator
194
+
195
+ Client->>Agent: Complex query
196
+ Agent->>Planner: Create plan
197
+ Planner-->>Agent: Multi-step plan
198
+
199
+ loop For each step
200
+ Agent->>Tools: Execute tool
201
+ Tools-->>Agent: Result
202
+ Agent->>Planner: Update plan
203
+ end
204
+
205
+ Agent->>Evaluator: Validate answer
206
+ Evaluator-->>Agent: Score
207
+
208
+ alt Score >= threshold
209
+ Agent-->>Client: Final answer
210
+ else Score < threshold
211
+ Agent->>Planner: Revise plan
212
+ end
213
+ ```
214
+
215
+ ### Data Ingestion Pipeline
216
+
217
+ ```mermaid
218
+ graph TB
219
+ SRC[Data Sources<br/>PDF/DB/API] --> INGEST[Ingestion API]
220
+
221
+ INGEST --> QUEUE1[Message Queue<br/>Kafka/RabbitMQ]
222
+
223
+ QUEUE1 --> PREPROC[Preprocessor<br/>Go Workers]
224
+
225
+ PREPROC --> CHUNK[Chunking Engine<br/>Semantic Splitter]
226
+
227
+ CHUNK --> ENRICH[Enrichment<br/>Metadata/NER]
228
+
229
+ ENRICH --> EMBED2[Embedding Worker<br/>Batched GPU]
230
+
231
+ EMBED2 --> INDEX[Indexing Worker]
232
+
233
+ INDEX --> VDB2[(Vector DB)]
234
+ INDEX --> GRAPH2[(Graph DB)]
235
+ INDEX --> CACHE3[(Cache)]
236
+
237
+ style PREPROC fill:#4A90D9
238
+ style CHUNK fill:#50C878
239
+ style EMBED2 fill:#FF6B6B
240
+ style INDEX fill:#DA70D6
241
+ ```
242
+
243
+ ### Multi-Tier Caching Strategy
244
+
245
+ ```mermaid
246
+ graph LR
247
+ CLIENT[Query] --> CACHE1[L1 Cache<br/>CDN/Edge]
248
+
249
+ CACHE1 -->|Miss| CACHE2[L2 Cache<br/>Redis Cluster]
250
+
251
+ CACHE2 -->|Miss| CACHE3[L3 Compute Cache]
252
+
253
+ CACHE3 -->|Miss| EMBED[Embedding Cache<br/>Local LRU]
254
+
255
+ EMBED -->|Miss| MODEL[Embedding Model]
256
+
257
+ style CACHE1 fill:#FF69B4
258
+ style CACHE2 fill:#6495ED
259
+ style CACHE3 fill:#90EE90
260
+ style EMBED fill:#FFB6C1
261
+ ```
262
+
263
+ ---
264
+
265
+ ## 🗂️ Project Structure
266
+
267
+ ```
268
+ AmaniQuery/
269
+ ├── cmd/ # Application entry points
270
+ │ ├── agent-server/ # Agent Orchestrator service
271
+ │ ├── retriever-server/ # Retrieval service
272
+ │ └── generator-server/ # LLM Gateway service
273
+ ├── internal/ # Private application code
274
+ │ ├── agent/ # Agent orchestration logic
275
+ │ ├── retriever/ # Hybrid search implementation
276
+ │ │ ├── vector/ # Vector store clients
277
+ │ │ ├── keyword/ # BM25/Bleve implementation
278
+ │ │ └── graph/ # Neo4j GraphRAG
279
+ │ ├── generator/ # LLM client and prompting
280
+ │ ├── router/ # Query classification
281
+ │ ├── guardrails/ # Content safety filters
282
+ │ ├── cache/ # Multi-tier caching
283
+ │ └── security/ # Auth, encryption, policies
284
+ ├── pkg/ # Public shared libraries
285
+ │ ├── proto/ # gRPC/protobuf definitions
286
+ │ ├── config/ # Configuration management
287
+ │ ├── observability/ # Metrics, tracing, logging
288
+ │ └── errors/ # Custom error types
289
+ ├── api/ # REST API layer
290
+ ├── deployments/ # Kubernetes/Helm configs
291
+ │ ├── docker/ # Dockerfiles
292
+ │ └── k8s/ # Kubernetes manifests
293
+ ├── scripts/ # Build and deployment scripts
294
+ ├── docs/ # Documentation
295
+ └── tests/ # Integration tests
296
+ ```
297
+
298
+ ---
299
+
300
+ ## 🚀 Quick Start
301
+
302
+ ### Prerequisites
303
+
304
+ - Go 1.21+
305
+ - Docker & Docker Compose
306
+ - Make
307
+
308
+ ### Local Development
309
+
310
+ ```bash
311
+ # Clone the repository
312
+ git clone https://github.com/amaniquery/amaniquery.git
313
+ cd amaniquery
314
+
315
+ # Install dependencies
316
+ go mod download
317
+
318
+ # Generate protobuf code
319
+ make proto
320
+
321
+ # Start infrastructure (Qdrant, Redis)
322
+ make infra-up
323
+
324
+ # Run the agent server
325
+ make run-agent
326
+
327
+ # Run tests
328
+ make test
329
+ ```
330
+
331
+ ### Docker Compose
332
+
333
+ ```bash
334
+ # Start all services
335
+ docker-compose up -d
336
+
337
+ # View logs
338
+ docker-compose logs -f agent-server
339
+
340
+ # Stop services
341
+ docker-compose down
342
+ ```
343
+
344
  ---
345
+
346
+ ## 📡 API Usage
347
+
348
+ ### gRPC
349
+
350
+ ```bash
351
+ # List available services
352
+ grpcurl -plaintext localhost:9090 list
353
+
354
+ # Process a query
355
+ grpcurl -plaintext -d '{
356
+ "query": "What does the Kenya Constitution say about land rights?",
357
+ "session_id": "user-123"
358
+ }' localhost:9090 rag.v1.AgentService/ProcessQuery
359
+ ```
360
+
361
+ ### REST API
362
+
363
+ ```bash
364
+ # Health check
365
+ curl http://localhost:8080/health
366
+
367
+ # Process query
368
+ curl -X POST http://localhost:8080/api/v1/query \
369
+ -H "Content-Type: application/json" \
370
+ -d '{"query": "Explain the Bill of Rights in Kenya"}'
371
+ ```
372
+
373
+ ---
374
+
375
+ ## ⚙️ Configuration
376
+
377
+ Configuration is managed through environment variables and YAML files:
378
+
379
+ ```yaml
380
+ # config.yaml
381
+ server:
382
+ grpc_port: 9090
383
+ http_port: 8080
384
+
385
+ vector_store:
386
+ type: qdrant
387
+ host: localhost
388
+ port: 6333
389
+ collection: amaniquery
390
+
391
+ llm:
392
+ provider: openai # openai, anthropic, google
393
+ model: gpt-4o
394
+ max_tokens: 4096
395
+ temperature: 0.7
396
+
397
+ cache:
398
+ redis_url: redis://localhost:6379
399
+ local_size: 10000
400
+ ttl: 3600
401
+
402
+ security:
403
+ jwt_secret: ${JWT_SECRET}
404
+ vault_addr: ${VAULT_ADDR}
405
+ ```
406
+
407
+ ---
408
+
409
+ ## 📊 Performance Targets
410
+
411
+ | Metric | Target | Description |
412
+ |--------|--------|-------------|
413
+ | P95 Latency | < 3s | 95th percentile query latency |
414
+ | P99 Latency | < 5s | 99th percentile query latency |
415
+ | Cache Hit Rate | > 85% | Query cache effectiveness |
416
+ | Throughput | 10k docs/min | Document ingestion rate |
417
+ | Concurrent Users | 10,000+ | Simultaneous connections |
418
+ | Availability | 99.95% | Uptime SLA |
419
+
420
+ ---
421
+
422
+ ## 🔒 Security
423
+
424
+ AmaniQuery implements enterprise-grade security:
425
+
426
+ - **mTLS**: Service-to-service encryption
427
+ - **JWT + OIDC**: Token-based authentication
428
+ - **OPA/Rego**: Fine-grained authorization policies
429
+ - **HashiCorp Vault**: Secrets management
430
+ - **PII Detection**: Automatic sensitive data handling
431
+ - **Audit Logging**: Immutable activity logs
432
+ - **Rate Limiting**: DDoS protection
433
+
434
+ ---
435
+
436
+ ## 📈 Observability
437
+
438
+ - **Metrics**: Prometheus + Grafana dashboards
439
+ - **Tracing**: Jaeger distributed tracing
440
+ - **Logging**: Structured JSON logs with correlation IDs
441
+ - **Alerting**: PagerDuty/Slack integration
442
+
443
+ ---
444
+
445
+ ## 🤝 Contributing
446
+
447
+ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
448
+
449
+ ---
450
+
451
+ ## 📄 License
452
+
453
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
454
+
455
+ ---
456
+
457
+ ## 🙏 Acknowledgments
458
+
459
+ - Built with inspiration from NVIDIA's RAG Blueprint
460
+ - Kenya Law Reports for legal data access
461
+ - The Go community for excellent tooling
462
+
463
  ---
464
 
465
+ <p align="center">
466
+ <b>AmaniQuery</b> - Democratizing Access to Legal Knowledge
467
+ </p>
agent.pb.go ADDED
@@ -0,0 +1,1837 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Code generated by protoc-gen-go. DO NOT EDIT.
2
+ // versions:
3
+ // protoc-gen-go v1.36.11
4
+ // protoc v6.33.2
5
+ // source: agent.proto
6
+
7
+ package ragv1
8
+
9
+ import (
10
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12
+ reflect "reflect"
13
+ sync "sync"
14
+ unsafe "unsafe"
15
+ )
16
+
17
+ const (
18
+ // Verify that this generated code is sufficiently up-to-date.
19
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
20
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
21
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
22
+ )
23
+
24
+ // ChunkType defines the type of streaming chunk
25
+ type ChunkType int32
26
+
27
+ const (
28
+ ChunkType_CHUNK_TYPE_UNSPECIFIED ChunkType = 0
29
+ ChunkType_CHUNK_TYPE_THINKING ChunkType = 1
30
+ ChunkType_CHUNK_TYPE_RETRIEVAL ChunkType = 2
31
+ ChunkType_CHUNK_TYPE_GENERATION ChunkType = 3
32
+ ChunkType_CHUNK_TYPE_COMPLETE ChunkType = 4
33
+ ChunkType_CHUNK_TYPE_ERROR ChunkType = 5
34
+ )
35
+
36
+ // Enum value maps for ChunkType.
37
+ var (
38
+ ChunkType_name = map[int32]string{
39
+ 0: "CHUNK_TYPE_UNSPECIFIED",
40
+ 1: "CHUNK_TYPE_THINKING",
41
+ 2: "CHUNK_TYPE_RETRIEVAL",
42
+ 3: "CHUNK_TYPE_GENERATION",
43
+ 4: "CHUNK_TYPE_COMPLETE",
44
+ 5: "CHUNK_TYPE_ERROR",
45
+ }
46
+ ChunkType_value = map[string]int32{
47
+ "CHUNK_TYPE_UNSPECIFIED": 0,
48
+ "CHUNK_TYPE_THINKING": 1,
49
+ "CHUNK_TYPE_RETRIEVAL": 2,
50
+ "CHUNK_TYPE_GENERATION": 3,
51
+ "CHUNK_TYPE_COMPLETE": 4,
52
+ "CHUNK_TYPE_ERROR": 5,
53
+ }
54
+ )
55
+
56
+ func (x ChunkType) Enum() *ChunkType {
57
+ p := new(ChunkType)
58
+ *p = x
59
+ return p
60
+ }
61
+
62
+ func (x ChunkType) String() string {
63
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
64
+ }
65
+
66
+ func (ChunkType) Descriptor() protoreflect.EnumDescriptor {
67
+ return file_agent_proto_enumTypes[0].Descriptor()
68
+ }
69
+
70
+ func (ChunkType) Type() protoreflect.EnumType {
71
+ return &file_agent_proto_enumTypes[0]
72
+ }
73
+
74
+ func (x ChunkType) Number() protoreflect.EnumNumber {
75
+ return protoreflect.EnumNumber(x)
76
+ }
77
+
78
+ // Deprecated: Use ChunkType.Descriptor instead.
79
+ func (ChunkType) EnumDescriptor() ([]byte, []int) {
80
+ return file_agent_proto_rawDescGZIP(), []int{0}
81
+ }
82
+
83
+ // MessageRole defines who sent the message
84
+ type MessageRole int32
85
+
86
+ const (
87
+ MessageRole_MESSAGE_ROLE_UNSPECIFIED MessageRole = 0
88
+ MessageRole_MESSAGE_ROLE_USER MessageRole = 1
89
+ MessageRole_MESSAGE_ROLE_ASSISTANT MessageRole = 2
90
+ MessageRole_MESSAGE_ROLE_SYSTEM MessageRole = 3
91
+ )
92
+
93
+ // Enum value maps for MessageRole.
94
+ var (
95
+ MessageRole_name = map[int32]string{
96
+ 0: "MESSAGE_ROLE_UNSPECIFIED",
97
+ 1: "MESSAGE_ROLE_USER",
98
+ 2: "MESSAGE_ROLE_ASSISTANT",
99
+ 3: "MESSAGE_ROLE_SYSTEM",
100
+ }
101
+ MessageRole_value = map[string]int32{
102
+ "MESSAGE_ROLE_UNSPECIFIED": 0,
103
+ "MESSAGE_ROLE_USER": 1,
104
+ "MESSAGE_ROLE_ASSISTANT": 2,
105
+ "MESSAGE_ROLE_SYSTEM": 3,
106
+ }
107
+ )
108
+
109
+ func (x MessageRole) Enum() *MessageRole {
110
+ p := new(MessageRole)
111
+ *p = x
112
+ return p
113
+ }
114
+
115
+ func (x MessageRole) String() string {
116
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
117
+ }
118
+
119
+ func (MessageRole) Descriptor() protoreflect.EnumDescriptor {
120
+ return file_agent_proto_enumTypes[1].Descriptor()
121
+ }
122
+
123
+ func (MessageRole) Type() protoreflect.EnumType {
124
+ return &file_agent_proto_enumTypes[1]
125
+ }
126
+
127
+ func (x MessageRole) Number() protoreflect.EnumNumber {
128
+ return protoreflect.EnumNumber(x)
129
+ }
130
+
131
+ // Deprecated: Use MessageRole.Descriptor instead.
132
+ func (MessageRole) EnumDescriptor() ([]byte, []int) {
133
+ return file_agent_proto_rawDescGZIP(), []int{1}
134
+ }
135
+
136
+ // StepType defines execution step types
137
+ type StepType int32
138
+
139
+ const (
140
+ StepType_STEP_TYPE_UNSPECIFIED StepType = 0
141
+ StepType_STEP_TYPE_SEARCH StepType = 1
142
+ StepType_STEP_TYPE_ANALYZE StepType = 2
143
+ StepType_STEP_TYPE_SYNTHESIZE StepType = 3
144
+ StepType_STEP_TYPE_VALIDATE StepType = 4
145
+ )
146
+
147
+ // Enum value maps for StepType.
148
+ var (
149
+ StepType_name = map[int32]string{
150
+ 0: "STEP_TYPE_UNSPECIFIED",
151
+ 1: "STEP_TYPE_SEARCH",
152
+ 2: "STEP_TYPE_ANALYZE",
153
+ 3: "STEP_TYPE_SYNTHESIZE",
154
+ 4: "STEP_TYPE_VALIDATE",
155
+ }
156
+ StepType_value = map[string]int32{
157
+ "STEP_TYPE_UNSPECIFIED": 0,
158
+ "STEP_TYPE_SEARCH": 1,
159
+ "STEP_TYPE_ANALYZE": 2,
160
+ "STEP_TYPE_SYNTHESIZE": 3,
161
+ "STEP_TYPE_VALIDATE": 4,
162
+ }
163
+ )
164
+
165
+ func (x StepType) Enum() *StepType {
166
+ p := new(StepType)
167
+ *p = x
168
+ return p
169
+ }
170
+
171
+ func (x StepType) String() string {
172
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
173
+ }
174
+
175
+ func (StepType) Descriptor() protoreflect.EnumDescriptor {
176
+ return file_agent_proto_enumTypes[2].Descriptor()
177
+ }
178
+
179
+ func (StepType) Type() protoreflect.EnumType {
180
+ return &file_agent_proto_enumTypes[2]
181
+ }
182
+
183
+ func (x StepType) Number() protoreflect.EnumNumber {
184
+ return protoreflect.EnumNumber(x)
185
+ }
186
+
187
+ // Deprecated: Use StepType.Descriptor instead.
188
+ func (StepType) EnumDescriptor() ([]byte, []int) {
189
+ return file_agent_proto_rawDescGZIP(), []int{2}
190
+ }
191
+
192
+ // ExecutionStatus for plan execution
193
+ type ExecutionStatus int32
194
+
195
+ const (
196
+ ExecutionStatus_EXECUTION_STATUS_UNSPECIFIED ExecutionStatus = 0
197
+ ExecutionStatus_EXECUTION_STATUS_PENDING ExecutionStatus = 1
198
+ ExecutionStatus_EXECUTION_STATUS_RUNNING ExecutionStatus = 2
199
+ ExecutionStatus_EXECUTION_STATUS_COMPLETED ExecutionStatus = 3
200
+ ExecutionStatus_EXECUTION_STATUS_FAILED ExecutionStatus = 4
201
+ )
202
+
203
+ // Enum value maps for ExecutionStatus.
204
+ var (
205
+ ExecutionStatus_name = map[int32]string{
206
+ 0: "EXECUTION_STATUS_UNSPECIFIED",
207
+ 1: "EXECUTION_STATUS_PENDING",
208
+ 2: "EXECUTION_STATUS_RUNNING",
209
+ 3: "EXECUTION_STATUS_COMPLETED",
210
+ 4: "EXECUTION_STATUS_FAILED",
211
+ }
212
+ ExecutionStatus_value = map[string]int32{
213
+ "EXECUTION_STATUS_UNSPECIFIED": 0,
214
+ "EXECUTION_STATUS_PENDING": 1,
215
+ "EXECUTION_STATUS_RUNNING": 2,
216
+ "EXECUTION_STATUS_COMPLETED": 3,
217
+ "EXECUTION_STATUS_FAILED": 4,
218
+ }
219
+ )
220
+
221
+ func (x ExecutionStatus) Enum() *ExecutionStatus {
222
+ p := new(ExecutionStatus)
223
+ *p = x
224
+ return p
225
+ }
226
+
227
+ func (x ExecutionStatus) String() string {
228
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
229
+ }
230
+
231
+ func (ExecutionStatus) Descriptor() protoreflect.EnumDescriptor {
232
+ return file_agent_proto_enumTypes[3].Descriptor()
233
+ }
234
+
235
+ func (ExecutionStatus) Type() protoreflect.EnumType {
236
+ return &file_agent_proto_enumTypes[3]
237
+ }
238
+
239
+ func (x ExecutionStatus) Number() protoreflect.EnumNumber {
240
+ return protoreflect.EnumNumber(x)
241
+ }
242
+
243
+ // Deprecated: Use ExecutionStatus.Descriptor instead.
244
+ func (ExecutionStatus) EnumDescriptor() ([]byte, []int) {
245
+ return file_agent_proto_rawDescGZIP(), []int{3}
246
+ }
247
+
248
+ // QueryRequest represents a user query
249
+ type QueryRequest struct {
250
+ state protoimpl.MessageState `protogen:"open.v1"`
251
+ // The user's query text
252
+ Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`
253
+ // Session ID for conversation continuity
254
+ SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
255
+ // User ID for personalization and audit
256
+ UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
257
+ // Conversation history for context
258
+ ConversationHistory []*Message `protobuf:"bytes,4,rep,name=conversation_history,json=conversationHistory,proto3" json:"conversation_history,omitempty"`
259
+ // Additional metadata
260
+ Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
261
+ // Query configuration options
262
+ Options *QueryOptions `protobuf:"bytes,6,opt,name=options,proto3" json:"options,omitempty"`
263
+ unknownFields protoimpl.UnknownFields
264
+ sizeCache protoimpl.SizeCache
265
+ }
266
+
267
+ func (x *QueryRequest) Reset() {
268
+ *x = QueryRequest{}
269
+ mi := &file_agent_proto_msgTypes[0]
270
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
271
+ ms.StoreMessageInfo(mi)
272
+ }
273
+
274
+ func (x *QueryRequest) String() string {
275
+ return protoimpl.X.MessageStringOf(x)
276
+ }
277
+
278
+ func (*QueryRequest) ProtoMessage() {}
279
+
280
+ func (x *QueryRequest) ProtoReflect() protoreflect.Message {
281
+ mi := &file_agent_proto_msgTypes[0]
282
+ if x != nil {
283
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
284
+ if ms.LoadMessageInfo() == nil {
285
+ ms.StoreMessageInfo(mi)
286
+ }
287
+ return ms
288
+ }
289
+ return mi.MessageOf(x)
290
+ }
291
+
292
+ // Deprecated: Use QueryRequest.ProtoReflect.Descriptor instead.
293
+ func (*QueryRequest) Descriptor() ([]byte, []int) {
294
+ return file_agent_proto_rawDescGZIP(), []int{0}
295
+ }
296
+
297
+ func (x *QueryRequest) GetQuery() string {
298
+ if x != nil {
299
+ return x.Query
300
+ }
301
+ return ""
302
+ }
303
+
304
+ func (x *QueryRequest) GetSessionId() string {
305
+ if x != nil {
306
+ return x.SessionId
307
+ }
308
+ return ""
309
+ }
310
+
311
+ func (x *QueryRequest) GetUserId() string {
312
+ if x != nil {
313
+ return x.UserId
314
+ }
315
+ return ""
316
+ }
317
+
318
+ func (x *QueryRequest) GetConversationHistory() []*Message {
319
+ if x != nil {
320
+ return x.ConversationHistory
321
+ }
322
+ return nil
323
+ }
324
+
325
+ func (x *QueryRequest) GetMetadata() map[string]string {
326
+ if x != nil {
327
+ return x.Metadata
328
+ }
329
+ return nil
330
+ }
331
+
332
+ func (x *QueryRequest) GetOptions() *QueryOptions {
333
+ if x != nil {
334
+ return x.Options
335
+ }
336
+ return nil
337
+ }
338
+
339
+ // QueryOptions configures query processing behavior
340
+ type QueryOptions struct {
341
+ state protoimpl.MessageState `protogen:"open.v1"`
342
+ // Maximum number of sources to retrieve
343
+ MaxSources int32 `protobuf:"varint,1,opt,name=max_sources,json=maxSources,proto3" json:"max_sources,omitempty"`
344
+ // Enable/disable caching
345
+ UseCache bool `protobuf:"varint,2,opt,name=use_cache,json=useCache,proto3" json:"use_cache,omitempty"`
346
+ // Enable agentic multi-step reasoning
347
+ EnableAgentic bool `protobuf:"varint,3,opt,name=enable_agentic,json=enableAgentic,proto3" json:"enable_agentic,omitempty"`
348
+ // Specific knowledge bases to search
349
+ KnowledgeBases []string `protobuf:"bytes,4,rep,name=knowledge_bases,json=knowledgeBases,proto3" json:"knowledge_bases,omitempty"`
350
+ // Temperature for LLM generation (0.0 - 1.0)
351
+ Temperature float32 `protobuf:"fixed32,5,opt,name=temperature,proto3" json:"temperature,omitempty"`
352
+ // Maximum tokens for response
353
+ MaxTokens int32 `protobuf:"varint,6,opt,name=max_tokens,json=maxTokens,proto3" json:"max_tokens,omitempty"`
354
+ unknownFields protoimpl.UnknownFields
355
+ sizeCache protoimpl.SizeCache
356
+ }
357
+
358
+ func (x *QueryOptions) Reset() {
359
+ *x = QueryOptions{}
360
+ mi := &file_agent_proto_msgTypes[1]
361
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
362
+ ms.StoreMessageInfo(mi)
363
+ }
364
+
365
+ func (x *QueryOptions) String() string {
366
+ return protoimpl.X.MessageStringOf(x)
367
+ }
368
+
369
+ func (*QueryOptions) ProtoMessage() {}
370
+
371
+ func (x *QueryOptions) ProtoReflect() protoreflect.Message {
372
+ mi := &file_agent_proto_msgTypes[1]
373
+ if x != nil {
374
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
375
+ if ms.LoadMessageInfo() == nil {
376
+ ms.StoreMessageInfo(mi)
377
+ }
378
+ return ms
379
+ }
380
+ return mi.MessageOf(x)
381
+ }
382
+
383
+ // Deprecated: Use QueryOptions.ProtoReflect.Descriptor instead.
384
+ func (*QueryOptions) Descriptor() ([]byte, []int) {
385
+ return file_agent_proto_rawDescGZIP(), []int{1}
386
+ }
387
+
388
+ func (x *QueryOptions) GetMaxSources() int32 {
389
+ if x != nil {
390
+ return x.MaxSources
391
+ }
392
+ return 0
393
+ }
394
+
395
+ func (x *QueryOptions) GetUseCache() bool {
396
+ if x != nil {
397
+ return x.UseCache
398
+ }
399
+ return false
400
+ }
401
+
402
+ func (x *QueryOptions) GetEnableAgentic() bool {
403
+ if x != nil {
404
+ return x.EnableAgentic
405
+ }
406
+ return false
407
+ }
408
+
409
+ func (x *QueryOptions) GetKnowledgeBases() []string {
410
+ if x != nil {
411
+ return x.KnowledgeBases
412
+ }
413
+ return nil
414
+ }
415
+
416
+ func (x *QueryOptions) GetTemperature() float32 {
417
+ if x != nil {
418
+ return x.Temperature
419
+ }
420
+ return 0
421
+ }
422
+
423
+ func (x *QueryOptions) GetMaxTokens() int32 {
424
+ if x != nil {
425
+ return x.MaxTokens
426
+ }
427
+ return 0
428
+ }
429
+
430
+ // QueryResponse contains the complete answer
431
+ type QueryResponse struct {
432
+ state protoimpl.MessageState `protogen:"open.v1"`
433
+ // The generated answer text
434
+ Answer string `protobuf:"bytes,1,opt,name=answer,proto3" json:"answer,omitempty"`
435
+ // Sources used to generate the answer
436
+ Sources []*Source `protobuf:"bytes,2,rep,name=sources,proto3" json:"sources,omitempty"`
437
+ // Confidence score (0.0 - 1.0)
438
+ Confidence float32 `protobuf:"fixed32,3,opt,name=confidence,proto3" json:"confidence,omitempty"`
439
+ // Query processing metadata
440
+ Metadata *QueryMetadata `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"`
441
+ // Suggested follow-up questions
442
+ FollowUpQuestions []string `protobuf:"bytes,5,rep,name=follow_up_questions,json=followUpQuestions,proto3" json:"follow_up_questions,omitempty"`
443
+ unknownFields protoimpl.UnknownFields
444
+ sizeCache protoimpl.SizeCache
445
+ }
446
+
447
+ func (x *QueryResponse) Reset() {
448
+ *x = QueryResponse{}
449
+ mi := &file_agent_proto_msgTypes[2]
450
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
451
+ ms.StoreMessageInfo(mi)
452
+ }
453
+
454
+ func (x *QueryResponse) String() string {
455
+ return protoimpl.X.MessageStringOf(x)
456
+ }
457
+
458
+ func (*QueryResponse) ProtoMessage() {}
459
+
460
+ func (x *QueryResponse) ProtoReflect() protoreflect.Message {
461
+ mi := &file_agent_proto_msgTypes[2]
462
+ if x != nil {
463
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
464
+ if ms.LoadMessageInfo() == nil {
465
+ ms.StoreMessageInfo(mi)
466
+ }
467
+ return ms
468
+ }
469
+ return mi.MessageOf(x)
470
+ }
471
+
472
+ // Deprecated: Use QueryResponse.ProtoReflect.Descriptor instead.
473
+ func (*QueryResponse) Descriptor() ([]byte, []int) {
474
+ return file_agent_proto_rawDescGZIP(), []int{2}
475
+ }
476
+
477
+ func (x *QueryResponse) GetAnswer() string {
478
+ if x != nil {
479
+ return x.Answer
480
+ }
481
+ return ""
482
+ }
483
+
484
+ func (x *QueryResponse) GetSources() []*Source {
485
+ if x != nil {
486
+ return x.Sources
487
+ }
488
+ return nil
489
+ }
490
+
491
+ func (x *QueryResponse) GetConfidence() float32 {
492
+ if x != nil {
493
+ return x.Confidence
494
+ }
495
+ return 0
496
+ }
497
+
498
+ func (x *QueryResponse) GetMetadata() *QueryMetadata {
499
+ if x != nil {
500
+ return x.Metadata
501
+ }
502
+ return nil
503
+ }
504
+
505
+ func (x *QueryResponse) GetFollowUpQuestions() []string {
506
+ if x != nil {
507
+ return x.FollowUpQuestions
508
+ }
509
+ return nil
510
+ }
511
+
512
+ // QueryResponseChunk for streaming responses
513
+ type QueryResponseChunk struct {
514
+ state protoimpl.MessageState `protogen:"open.v1"`
515
+ // Chunk type: THINKING, RETRIEVAL, GENERATION, COMPLETE
516
+ Type ChunkType `protobuf:"varint,1,opt,name=type,proto3,enum=rag.v1.ChunkType" json:"type,omitempty"`
517
+ // Text content of the chunk
518
+ Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
519
+ // Sources (populated in RETRIEVAL chunks)
520
+ Sources []*Source `protobuf:"bytes,3,rep,name=sources,proto3" json:"sources,omitempty"`
521
+ // Is this the final chunk?
522
+ IsFinal bool `protobuf:"varint,4,opt,name=is_final,json=isFinal,proto3" json:"is_final,omitempty"`
523
+ unknownFields protoimpl.UnknownFields
524
+ sizeCache protoimpl.SizeCache
525
+ }
526
+
527
+ func (x *QueryResponseChunk) Reset() {
528
+ *x = QueryResponseChunk{}
529
+ mi := &file_agent_proto_msgTypes[3]
530
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
531
+ ms.StoreMessageInfo(mi)
532
+ }
533
+
534
+ func (x *QueryResponseChunk) String() string {
535
+ return protoimpl.X.MessageStringOf(x)
536
+ }
537
+
538
+ func (*QueryResponseChunk) ProtoMessage() {}
539
+
540
+ func (x *QueryResponseChunk) ProtoReflect() protoreflect.Message {
541
+ mi := &file_agent_proto_msgTypes[3]
542
+ if x != nil {
543
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
544
+ if ms.LoadMessageInfo() == nil {
545
+ ms.StoreMessageInfo(mi)
546
+ }
547
+ return ms
548
+ }
549
+ return mi.MessageOf(x)
550
+ }
551
+
552
+ // Deprecated: Use QueryResponseChunk.ProtoReflect.Descriptor instead.
553
+ func (*QueryResponseChunk) Descriptor() ([]byte, []int) {
554
+ return file_agent_proto_rawDescGZIP(), []int{3}
555
+ }
556
+
557
+ func (x *QueryResponseChunk) GetType() ChunkType {
558
+ if x != nil {
559
+ return x.Type
560
+ }
561
+ return ChunkType_CHUNK_TYPE_UNSPECIFIED
562
+ }
563
+
564
+ func (x *QueryResponseChunk) GetContent() string {
565
+ if x != nil {
566
+ return x.Content
567
+ }
568
+ return ""
569
+ }
570
+
571
+ func (x *QueryResponseChunk) GetSources() []*Source {
572
+ if x != nil {
573
+ return x.Sources
574
+ }
575
+ return nil
576
+ }
577
+
578
+ func (x *QueryResponseChunk) GetIsFinal() bool {
579
+ if x != nil {
580
+ return x.IsFinal
581
+ }
582
+ return false
583
+ }
584
+
585
+ // Source represents a retrieved document chunk
586
+ type Source struct {
587
+ state protoimpl.MessageState `protogen:"open.v1"`
588
+ // Unique identifier for the source
589
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
590
+ // Document title
591
+ Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
592
+ // Relevant text content
593
+ Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"`
594
+ // Source URL or path
595
+ Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"`
596
+ // Relevance score (0.0 - 1.0)
597
+ Score float32 `protobuf:"fixed32,5,opt,name=score,proto3" json:"score,omitempty"`
598
+ // Source metadata
599
+ Metadata map[string]string `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
600
+ // Page number or section reference
601
+ Location string `protobuf:"bytes,7,opt,name=location,proto3" json:"location,omitempty"`
602
+ unknownFields protoimpl.UnknownFields
603
+ sizeCache protoimpl.SizeCache
604
+ }
605
+
606
+ func (x *Source) Reset() {
607
+ *x = Source{}
608
+ mi := &file_agent_proto_msgTypes[4]
609
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
610
+ ms.StoreMessageInfo(mi)
611
+ }
612
+
613
+ func (x *Source) String() string {
614
+ return protoimpl.X.MessageStringOf(x)
615
+ }
616
+
617
+ func (*Source) ProtoMessage() {}
618
+
619
+ func (x *Source) ProtoReflect() protoreflect.Message {
620
+ mi := &file_agent_proto_msgTypes[4]
621
+ if x != nil {
622
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
623
+ if ms.LoadMessageInfo() == nil {
624
+ ms.StoreMessageInfo(mi)
625
+ }
626
+ return ms
627
+ }
628
+ return mi.MessageOf(x)
629
+ }
630
+
631
+ // Deprecated: Use Source.ProtoReflect.Descriptor instead.
632
+ func (*Source) Descriptor() ([]byte, []int) {
633
+ return file_agent_proto_rawDescGZIP(), []int{4}
634
+ }
635
+
636
+ func (x *Source) GetId() string {
637
+ if x != nil {
638
+ return x.Id
639
+ }
640
+ return ""
641
+ }
642
+
643
+ func (x *Source) GetTitle() string {
644
+ if x != nil {
645
+ return x.Title
646
+ }
647
+ return ""
648
+ }
649
+
650
+ func (x *Source) GetContent() string {
651
+ if x != nil {
652
+ return x.Content
653
+ }
654
+ return ""
655
+ }
656
+
657
+ func (x *Source) GetUrl() string {
658
+ if x != nil {
659
+ return x.Url
660
+ }
661
+ return ""
662
+ }
663
+
664
+ func (x *Source) GetScore() float32 {
665
+ if x != nil {
666
+ return x.Score
667
+ }
668
+ return 0
669
+ }
670
+
671
+ func (x *Source) GetMetadata() map[string]string {
672
+ if x != nil {
673
+ return x.Metadata
674
+ }
675
+ return nil
676
+ }
677
+
678
+ func (x *Source) GetLocation() string {
679
+ if x != nil {
680
+ return x.Location
681
+ }
682
+ return ""
683
+ }
684
+
685
+ // Message represents a conversation turn
686
+ type Message struct {
687
+ state protoimpl.MessageState `protogen:"open.v1"`
688
+ // Message role: USER, ASSISTANT, SYSTEM
689
+ Role MessageRole `protobuf:"varint,1,opt,name=role,proto3,enum=rag.v1.MessageRole" json:"role,omitempty"`
690
+ // Message content
691
+ Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
692
+ // Timestamp
693
+ Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
694
+ unknownFields protoimpl.UnknownFields
695
+ sizeCache protoimpl.SizeCache
696
+ }
697
+
698
+ func (x *Message) Reset() {
699
+ *x = Message{}
700
+ mi := &file_agent_proto_msgTypes[5]
701
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
702
+ ms.StoreMessageInfo(mi)
703
+ }
704
+
705
+ func (x *Message) String() string {
706
+ return protoimpl.X.MessageStringOf(x)
707
+ }
708
+
709
+ func (*Message) ProtoMessage() {}
710
+
711
+ func (x *Message) ProtoReflect() protoreflect.Message {
712
+ mi := &file_agent_proto_msgTypes[5]
713
+ if x != nil {
714
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
715
+ if ms.LoadMessageInfo() == nil {
716
+ ms.StoreMessageInfo(mi)
717
+ }
718
+ return ms
719
+ }
720
+ return mi.MessageOf(x)
721
+ }
722
+
723
+ // Deprecated: Use Message.ProtoReflect.Descriptor instead.
724
+ func (*Message) Descriptor() ([]byte, []int) {
725
+ return file_agent_proto_rawDescGZIP(), []int{5}
726
+ }
727
+
728
+ func (x *Message) GetRole() MessageRole {
729
+ if x != nil {
730
+ return x.Role
731
+ }
732
+ return MessageRole_MESSAGE_ROLE_UNSPECIFIED
733
+ }
734
+
735
+ func (x *Message) GetContent() string {
736
+ if x != nil {
737
+ return x.Content
738
+ }
739
+ return ""
740
+ }
741
+
742
+ func (x *Message) GetTimestamp() int64 {
743
+ if x != nil {
744
+ return x.Timestamp
745
+ }
746
+ return 0
747
+ }
748
+
749
+ // QueryMetadata contains processing information
750
+ type QueryMetadata struct {
751
+ state protoimpl.MessageState `protogen:"open.v1"`
752
+ // Total processing time in milliseconds
753
+ ProcessingTimeMs int64 `protobuf:"varint,1,opt,name=processing_time_ms,json=processingTimeMs,proto3" json:"processing_time_ms,omitempty"`
754
+ // Number of chunks retrieved
755
+ ChunksRetrieved int32 `protobuf:"varint,2,opt,name=chunks_retrieved,json=chunksRetrieved,proto3" json:"chunks_retrieved,omitempty"`
756
+ // Tokens used for generation
757
+ TokensUsed int32 `protobuf:"varint,3,opt,name=tokens_used,json=tokensUsed,proto3" json:"tokens_used,omitempty"`
758
+ // Whether result was cached
759
+ CacheHit bool `protobuf:"varint,4,opt,name=cache_hit,json=cacheHit,proto3" json:"cache_hit,omitempty"`
760
+ // Query routing decision
761
+ RoutingStrategy string `protobuf:"bytes,5,opt,name=routing_strategy,json=routingStrategy,proto3" json:"routing_strategy,omitempty"`
762
+ // Trace ID for debugging
763
+ TraceId string `protobuf:"bytes,6,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"`
764
+ unknownFields protoimpl.UnknownFields
765
+ sizeCache protoimpl.SizeCache
766
+ }
767
+
768
+ func (x *QueryMetadata) Reset() {
769
+ *x = QueryMetadata{}
770
+ mi := &file_agent_proto_msgTypes[6]
771
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
772
+ ms.StoreMessageInfo(mi)
773
+ }
774
+
775
+ func (x *QueryMetadata) String() string {
776
+ return protoimpl.X.MessageStringOf(x)
777
+ }
778
+
779
+ func (*QueryMetadata) ProtoMessage() {}
780
+
781
+ func (x *QueryMetadata) ProtoReflect() protoreflect.Message {
782
+ mi := &file_agent_proto_msgTypes[6]
783
+ if x != nil {
784
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
785
+ if ms.LoadMessageInfo() == nil {
786
+ ms.StoreMessageInfo(mi)
787
+ }
788
+ return ms
789
+ }
790
+ return mi.MessageOf(x)
791
+ }
792
+
793
+ // Deprecated: Use QueryMetadata.ProtoReflect.Descriptor instead.
794
+ func (*QueryMetadata) Descriptor() ([]byte, []int) {
795
+ return file_agent_proto_rawDescGZIP(), []int{6}
796
+ }
797
+
798
+ func (x *QueryMetadata) GetProcessingTimeMs() int64 {
799
+ if x != nil {
800
+ return x.ProcessingTimeMs
801
+ }
802
+ return 0
803
+ }
804
+
805
+ func (x *QueryMetadata) GetChunksRetrieved() int32 {
806
+ if x != nil {
807
+ return x.ChunksRetrieved
808
+ }
809
+ return 0
810
+ }
811
+
812
+ func (x *QueryMetadata) GetTokensUsed() int32 {
813
+ if x != nil {
814
+ return x.TokensUsed
815
+ }
816
+ return 0
817
+ }
818
+
819
+ func (x *QueryMetadata) GetCacheHit() bool {
820
+ if x != nil {
821
+ return x.CacheHit
822
+ }
823
+ return false
824
+ }
825
+
826
+ func (x *QueryMetadata) GetRoutingStrategy() string {
827
+ if x != nil {
828
+ return x.RoutingStrategy
829
+ }
830
+ return ""
831
+ }
832
+
833
+ func (x *QueryMetadata) GetTraceId() string {
834
+ if x != nil {
835
+ return x.TraceId
836
+ }
837
+ return ""
838
+ }
839
+
840
+ // CreateAgentRequest for creating specialized agents
841
+ type CreateAgentRequest struct {
842
+ state protoimpl.MessageState `protogen:"open.v1"`
843
+ // Agent name
844
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
845
+ // Agent description
846
+ Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
847
+ // System prompt for the agent
848
+ SystemPrompt string `protobuf:"bytes,3,opt,name=system_prompt,json=systemPrompt,proto3" json:"system_prompt,omitempty"`
849
+ // Tools available to the agent
850
+ Tools []string `protobuf:"bytes,4,rep,name=tools,proto3" json:"tools,omitempty"`
851
+ // Agent configuration
852
+ Config *AgentConfig `protobuf:"bytes,5,opt,name=config,proto3" json:"config,omitempty"`
853
+ unknownFields protoimpl.UnknownFields
854
+ sizeCache protoimpl.SizeCache
855
+ }
856
+
857
+ func (x *CreateAgentRequest) Reset() {
858
+ *x = CreateAgentRequest{}
859
+ mi := &file_agent_proto_msgTypes[7]
860
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
861
+ ms.StoreMessageInfo(mi)
862
+ }
863
+
864
+ func (x *CreateAgentRequest) String() string {
865
+ return protoimpl.X.MessageStringOf(x)
866
+ }
867
+
868
+ func (*CreateAgentRequest) ProtoMessage() {}
869
+
870
+ func (x *CreateAgentRequest) ProtoReflect() protoreflect.Message {
871
+ mi := &file_agent_proto_msgTypes[7]
872
+ if x != nil {
873
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
874
+ if ms.LoadMessageInfo() == nil {
875
+ ms.StoreMessageInfo(mi)
876
+ }
877
+ return ms
878
+ }
879
+ return mi.MessageOf(x)
880
+ }
881
+
882
+ // Deprecated: Use CreateAgentRequest.ProtoReflect.Descriptor instead.
883
+ func (*CreateAgentRequest) Descriptor() ([]byte, []int) {
884
+ return file_agent_proto_rawDescGZIP(), []int{7}
885
+ }
886
+
887
+ func (x *CreateAgentRequest) GetName() string {
888
+ if x != nil {
889
+ return x.Name
890
+ }
891
+ return ""
892
+ }
893
+
894
+ func (x *CreateAgentRequest) GetDescription() string {
895
+ if x != nil {
896
+ return x.Description
897
+ }
898
+ return ""
899
+ }
900
+
901
+ func (x *CreateAgentRequest) GetSystemPrompt() string {
902
+ if x != nil {
903
+ return x.SystemPrompt
904
+ }
905
+ return ""
906
+ }
907
+
908
+ func (x *CreateAgentRequest) GetTools() []string {
909
+ if x != nil {
910
+ return x.Tools
911
+ }
912
+ return nil
913
+ }
914
+
915
+ func (x *CreateAgentRequest) GetConfig() *AgentConfig {
916
+ if x != nil {
917
+ return x.Config
918
+ }
919
+ return nil
920
+ }
921
+
922
+ // Agent represents a configured AI agent
923
+ type Agent struct {
924
+ state protoimpl.MessageState `protogen:"open.v1"`
925
+ // Unique agent ID
926
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
927
+ // Agent name
928
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
929
+ // Agent description
930
+ Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
931
+ // Creation timestamp
932
+ CreatedAt int64 `protobuf:"varint,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
933
+ // Agent configuration
934
+ Config *AgentConfig `protobuf:"bytes,5,opt,name=config,proto3" json:"config,omitempty"`
935
+ unknownFields protoimpl.UnknownFields
936
+ sizeCache protoimpl.SizeCache
937
+ }
938
+
939
+ func (x *Agent) Reset() {
940
+ *x = Agent{}
941
+ mi := &file_agent_proto_msgTypes[8]
942
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
943
+ ms.StoreMessageInfo(mi)
944
+ }
945
+
946
+ func (x *Agent) String() string {
947
+ return protoimpl.X.MessageStringOf(x)
948
+ }
949
+
950
+ func (*Agent) ProtoMessage() {}
951
+
952
+ func (x *Agent) ProtoReflect() protoreflect.Message {
953
+ mi := &file_agent_proto_msgTypes[8]
954
+ if x != nil {
955
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
956
+ if ms.LoadMessageInfo() == nil {
957
+ ms.StoreMessageInfo(mi)
958
+ }
959
+ return ms
960
+ }
961
+ return mi.MessageOf(x)
962
+ }
963
+
964
+ // Deprecated: Use Agent.ProtoReflect.Descriptor instead.
965
+ func (*Agent) Descriptor() ([]byte, []int) {
966
+ return file_agent_proto_rawDescGZIP(), []int{8}
967
+ }
968
+
969
+ func (x *Agent) GetId() string {
970
+ if x != nil {
971
+ return x.Id
972
+ }
973
+ return ""
974
+ }
975
+
976
+ func (x *Agent) GetName() string {
977
+ if x != nil {
978
+ return x.Name
979
+ }
980
+ return ""
981
+ }
982
+
983
+ func (x *Agent) GetDescription() string {
984
+ if x != nil {
985
+ return x.Description
986
+ }
987
+ return ""
988
+ }
989
+
990
+ func (x *Agent) GetCreatedAt() int64 {
991
+ if x != nil {
992
+ return x.CreatedAt
993
+ }
994
+ return 0
995
+ }
996
+
997
+ func (x *Agent) GetConfig() *AgentConfig {
998
+ if x != nil {
999
+ return x.Config
1000
+ }
1001
+ return nil
1002
+ }
1003
+
1004
+ // AgentConfig contains agent settings
1005
+ type AgentConfig struct {
1006
+ state protoimpl.MessageState `protogen:"open.v1"`
1007
+ // LLM model to use
1008
+ Model string `protobuf:"bytes,1,opt,name=model,proto3" json:"model,omitempty"`
1009
+ // Temperature setting
1010
+ Temperature float32 `protobuf:"fixed32,2,opt,name=temperature,proto3" json:"temperature,omitempty"`
1011
+ // Maximum iterations for agentic loops
1012
+ MaxIterations int32 `protobuf:"varint,3,opt,name=max_iterations,json=maxIterations,proto3" json:"max_iterations,omitempty"`
1013
+ // Timeout in seconds
1014
+ TimeoutSeconds int32 `protobuf:"varint,4,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"`
1015
+ unknownFields protoimpl.UnknownFields
1016
+ sizeCache protoimpl.SizeCache
1017
+ }
1018
+
1019
+ func (x *AgentConfig) Reset() {
1020
+ *x = AgentConfig{}
1021
+ mi := &file_agent_proto_msgTypes[9]
1022
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1023
+ ms.StoreMessageInfo(mi)
1024
+ }
1025
+
1026
+ func (x *AgentConfig) String() string {
1027
+ return protoimpl.X.MessageStringOf(x)
1028
+ }
1029
+
1030
+ func (*AgentConfig) ProtoMessage() {}
1031
+
1032
+ func (x *AgentConfig) ProtoReflect() protoreflect.Message {
1033
+ mi := &file_agent_proto_msgTypes[9]
1034
+ if x != nil {
1035
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1036
+ if ms.LoadMessageInfo() == nil {
1037
+ ms.StoreMessageInfo(mi)
1038
+ }
1039
+ return ms
1040
+ }
1041
+ return mi.MessageOf(x)
1042
+ }
1043
+
1044
+ // Deprecated: Use AgentConfig.ProtoReflect.Descriptor instead.
1045
+ func (*AgentConfig) Descriptor() ([]byte, []int) {
1046
+ return file_agent_proto_rawDescGZIP(), []int{9}
1047
+ }
1048
+
1049
+ func (x *AgentConfig) GetModel() string {
1050
+ if x != nil {
1051
+ return x.Model
1052
+ }
1053
+ return ""
1054
+ }
1055
+
1056
+ func (x *AgentConfig) GetTemperature() float32 {
1057
+ if x != nil {
1058
+ return x.Temperature
1059
+ }
1060
+ return 0
1061
+ }
1062
+
1063
+ func (x *AgentConfig) GetMaxIterations() int32 {
1064
+ if x != nil {
1065
+ return x.MaxIterations
1066
+ }
1067
+ return 0
1068
+ }
1069
+
1070
+ func (x *AgentConfig) GetTimeoutSeconds() int32 {
1071
+ if x != nil {
1072
+ return x.TimeoutSeconds
1073
+ }
1074
+ return 0
1075
+ }
1076
+
1077
+ // ExecutionPlan for multi-step queries
1078
+ type ExecutionPlan struct {
1079
+ state protoimpl.MessageState `protogen:"open.v1"`
1080
+ // Plan ID
1081
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
1082
+ // Original query
1083
+ Query string `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"`
1084
+ // Execution steps
1085
+ Steps []*ExecutionStep `protobuf:"bytes,3,rep,name=steps,proto3" json:"steps,omitempty"`
1086
+ unknownFields protoimpl.UnknownFields
1087
+ sizeCache protoimpl.SizeCache
1088
+ }
1089
+
1090
+ func (x *ExecutionPlan) Reset() {
1091
+ *x = ExecutionPlan{}
1092
+ mi := &file_agent_proto_msgTypes[10]
1093
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1094
+ ms.StoreMessageInfo(mi)
1095
+ }
1096
+
1097
+ func (x *ExecutionPlan) String() string {
1098
+ return protoimpl.X.MessageStringOf(x)
1099
+ }
1100
+
1101
+ func (*ExecutionPlan) ProtoMessage() {}
1102
+
1103
+ func (x *ExecutionPlan) ProtoReflect() protoreflect.Message {
1104
+ mi := &file_agent_proto_msgTypes[10]
1105
+ if x != nil {
1106
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1107
+ if ms.LoadMessageInfo() == nil {
1108
+ ms.StoreMessageInfo(mi)
1109
+ }
1110
+ return ms
1111
+ }
1112
+ return mi.MessageOf(x)
1113
+ }
1114
+
1115
+ // Deprecated: Use ExecutionPlan.ProtoReflect.Descriptor instead.
1116
+ func (*ExecutionPlan) Descriptor() ([]byte, []int) {
1117
+ return file_agent_proto_rawDescGZIP(), []int{10}
1118
+ }
1119
+
1120
+ func (x *ExecutionPlan) GetId() string {
1121
+ if x != nil {
1122
+ return x.Id
1123
+ }
1124
+ return ""
1125
+ }
1126
+
1127
+ func (x *ExecutionPlan) GetQuery() string {
1128
+ if x != nil {
1129
+ return x.Query
1130
+ }
1131
+ return ""
1132
+ }
1133
+
1134
+ func (x *ExecutionPlan) GetSteps() []*ExecutionStep {
1135
+ if x != nil {
1136
+ return x.Steps
1137
+ }
1138
+ return nil
1139
+ }
1140
+
1141
+ // ExecutionStep represents a single step in the plan
1142
+ type ExecutionStep struct {
1143
+ state protoimpl.MessageState `protogen:"open.v1"`
1144
+ // Step ID
1145
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
1146
+ // Step type: SEARCH, ANALYZE, SYNTHESIZE
1147
+ Type StepType `protobuf:"varint,2,opt,name=type,proto3,enum=rag.v1.StepType" json:"type,omitempty"`
1148
+ // Step description
1149
+ Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
1150
+ // Tool to use
1151
+ Tool string `protobuf:"bytes,4,opt,name=tool,proto3" json:"tool,omitempty"`
1152
+ // Input for the step
1153
+ Input string `protobuf:"bytes,5,opt,name=input,proto3" json:"input,omitempty"`
1154
+ // Dependencies (IDs of steps that must complete first)
1155
+ Dependencies []string `protobuf:"bytes,6,rep,name=dependencies,proto3" json:"dependencies,omitempty"`
1156
+ unknownFields protoimpl.UnknownFields
1157
+ sizeCache protoimpl.SizeCache
1158
+ }
1159
+
1160
+ func (x *ExecutionStep) Reset() {
1161
+ *x = ExecutionStep{}
1162
+ mi := &file_agent_proto_msgTypes[11]
1163
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1164
+ ms.StoreMessageInfo(mi)
1165
+ }
1166
+
1167
+ func (x *ExecutionStep) String() string {
1168
+ return protoimpl.X.MessageStringOf(x)
1169
+ }
1170
+
1171
+ func (*ExecutionStep) ProtoMessage() {}
1172
+
1173
+ func (x *ExecutionStep) ProtoReflect() protoreflect.Message {
1174
+ mi := &file_agent_proto_msgTypes[11]
1175
+ if x != nil {
1176
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1177
+ if ms.LoadMessageInfo() == nil {
1178
+ ms.StoreMessageInfo(mi)
1179
+ }
1180
+ return ms
1181
+ }
1182
+ return mi.MessageOf(x)
1183
+ }
1184
+
1185
+ // Deprecated: Use ExecutionStep.ProtoReflect.Descriptor instead.
1186
+ func (*ExecutionStep) Descriptor() ([]byte, []int) {
1187
+ return file_agent_proto_rawDescGZIP(), []int{11}
1188
+ }
1189
+
1190
+ func (x *ExecutionStep) GetId() string {
1191
+ if x != nil {
1192
+ return x.Id
1193
+ }
1194
+ return ""
1195
+ }
1196
+
1197
+ func (x *ExecutionStep) GetType() StepType {
1198
+ if x != nil {
1199
+ return x.Type
1200
+ }
1201
+ return StepType_STEP_TYPE_UNSPECIFIED
1202
+ }
1203
+
1204
+ func (x *ExecutionStep) GetDescription() string {
1205
+ if x != nil {
1206
+ return x.Description
1207
+ }
1208
+ return ""
1209
+ }
1210
+
1211
+ func (x *ExecutionStep) GetTool() string {
1212
+ if x != nil {
1213
+ return x.Tool
1214
+ }
1215
+ return ""
1216
+ }
1217
+
1218
+ func (x *ExecutionStep) GetInput() string {
1219
+ if x != nil {
1220
+ return x.Input
1221
+ }
1222
+ return ""
1223
+ }
1224
+
1225
+ func (x *ExecutionStep) GetDependencies() []string {
1226
+ if x != nil {
1227
+ return x.Dependencies
1228
+ }
1229
+ return nil
1230
+ }
1231
+
1232
+ // PlanResult contains execution results
1233
+ type PlanResult struct {
1234
+ state protoimpl.MessageState `protogen:"open.v1"`
1235
+ // Plan ID
1236
+ PlanId string `protobuf:"bytes,1,opt,name=plan_id,json=planId,proto3" json:"plan_id,omitempty"`
1237
+ // Execution status
1238
+ Status ExecutionStatus `protobuf:"varint,2,opt,name=status,proto3,enum=rag.v1.ExecutionStatus" json:"status,omitempty"`
1239
+ // Step results
1240
+ StepResults []*StepResult `protobuf:"bytes,3,rep,name=step_results,json=stepResults,proto3" json:"step_results,omitempty"`
1241
+ // Final answer
1242
+ FinalAnswer string `protobuf:"bytes,4,opt,name=final_answer,json=finalAnswer,proto3" json:"final_answer,omitempty"`
1243
+ // Total execution time
1244
+ ExecutionTimeMs int64 `protobuf:"varint,5,opt,name=execution_time_ms,json=executionTimeMs,proto3" json:"execution_time_ms,omitempty"`
1245
+ unknownFields protoimpl.UnknownFields
1246
+ sizeCache protoimpl.SizeCache
1247
+ }
1248
+
1249
+ func (x *PlanResult) Reset() {
1250
+ *x = PlanResult{}
1251
+ mi := &file_agent_proto_msgTypes[12]
1252
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1253
+ ms.StoreMessageInfo(mi)
1254
+ }
1255
+
1256
+ func (x *PlanResult) String() string {
1257
+ return protoimpl.X.MessageStringOf(x)
1258
+ }
1259
+
1260
+ func (*PlanResult) ProtoMessage() {}
1261
+
1262
+ func (x *PlanResult) ProtoReflect() protoreflect.Message {
1263
+ mi := &file_agent_proto_msgTypes[12]
1264
+ if x != nil {
1265
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1266
+ if ms.LoadMessageInfo() == nil {
1267
+ ms.StoreMessageInfo(mi)
1268
+ }
1269
+ return ms
1270
+ }
1271
+ return mi.MessageOf(x)
1272
+ }
1273
+
1274
+ // Deprecated: Use PlanResult.ProtoReflect.Descriptor instead.
1275
+ func (*PlanResult) Descriptor() ([]byte, []int) {
1276
+ return file_agent_proto_rawDescGZIP(), []int{12}
1277
+ }
1278
+
1279
+ func (x *PlanResult) GetPlanId() string {
1280
+ if x != nil {
1281
+ return x.PlanId
1282
+ }
1283
+ return ""
1284
+ }
1285
+
1286
+ func (x *PlanResult) GetStatus() ExecutionStatus {
1287
+ if x != nil {
1288
+ return x.Status
1289
+ }
1290
+ return ExecutionStatus_EXECUTION_STATUS_UNSPECIFIED
1291
+ }
1292
+
1293
+ func (x *PlanResult) GetStepResults() []*StepResult {
1294
+ if x != nil {
1295
+ return x.StepResults
1296
+ }
1297
+ return nil
1298
+ }
1299
+
1300
+ func (x *PlanResult) GetFinalAnswer() string {
1301
+ if x != nil {
1302
+ return x.FinalAnswer
1303
+ }
1304
+ return ""
1305
+ }
1306
+
1307
+ func (x *PlanResult) GetExecutionTimeMs() int64 {
1308
+ if x != nil {
1309
+ return x.ExecutionTimeMs
1310
+ }
1311
+ return 0
1312
+ }
1313
+
1314
+ // StepResult contains individual step results
1315
+ type StepResult struct {
1316
+ state protoimpl.MessageState `protogen:"open.v1"`
1317
+ // Step ID
1318
+ StepId string `protobuf:"bytes,1,opt,name=step_id,json=stepId,proto3" json:"step_id,omitempty"`
1319
+ // Step status
1320
+ Status ExecutionStatus `protobuf:"varint,2,opt,name=status,proto3,enum=rag.v1.ExecutionStatus" json:"status,omitempty"`
1321
+ // Step output
1322
+ Output string `protobuf:"bytes,3,opt,name=output,proto3" json:"output,omitempty"`
1323
+ // Error message if failed
1324
+ Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
1325
+ // Execution time
1326
+ ExecutionTimeMs int64 `protobuf:"varint,5,opt,name=execution_time_ms,json=executionTimeMs,proto3" json:"execution_time_ms,omitempty"`
1327
+ unknownFields protoimpl.UnknownFields
1328
+ sizeCache protoimpl.SizeCache
1329
+ }
1330
+
1331
+ func (x *StepResult) Reset() {
1332
+ *x = StepResult{}
1333
+ mi := &file_agent_proto_msgTypes[13]
1334
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1335
+ ms.StoreMessageInfo(mi)
1336
+ }
1337
+
1338
+ func (x *StepResult) String() string {
1339
+ return protoimpl.X.MessageStringOf(x)
1340
+ }
1341
+
1342
+ func (*StepResult) ProtoMessage() {}
1343
+
1344
+ func (x *StepResult) ProtoReflect() protoreflect.Message {
1345
+ mi := &file_agent_proto_msgTypes[13]
1346
+ if x != nil {
1347
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1348
+ if ms.LoadMessageInfo() == nil {
1349
+ ms.StoreMessageInfo(mi)
1350
+ }
1351
+ return ms
1352
+ }
1353
+ return mi.MessageOf(x)
1354
+ }
1355
+
1356
+ // Deprecated: Use StepResult.ProtoReflect.Descriptor instead.
1357
+ func (*StepResult) Descriptor() ([]byte, []int) {
1358
+ return file_agent_proto_rawDescGZIP(), []int{13}
1359
+ }
1360
+
1361
+ func (x *StepResult) GetStepId() string {
1362
+ if x != nil {
1363
+ return x.StepId
1364
+ }
1365
+ return ""
1366
+ }
1367
+
1368
+ func (x *StepResult) GetStatus() ExecutionStatus {
1369
+ if x != nil {
1370
+ return x.Status
1371
+ }
1372
+ return ExecutionStatus_EXECUTION_STATUS_UNSPECIFIED
1373
+ }
1374
+
1375
+ func (x *StepResult) GetOutput() string {
1376
+ if x != nil {
1377
+ return x.Output
1378
+ }
1379
+ return ""
1380
+ }
1381
+
1382
+ func (x *StepResult) GetError() string {
1383
+ if x != nil {
1384
+ return x.Error
1385
+ }
1386
+ return ""
1387
+ }
1388
+
1389
+ func (x *StepResult) GetExecutionTimeMs() int64 {
1390
+ if x != nil {
1391
+ return x.ExecutionTimeMs
1392
+ }
1393
+ return 0
1394
+ }
1395
+
1396
+ // QueryStatusRequest to check query progress
1397
+ type QueryStatusRequest struct {
1398
+ state protoimpl.MessageState `protogen:"open.v1"`
1399
+ // Query ID
1400
+ QueryId string `protobuf:"bytes,1,opt,name=query_id,json=queryId,proto3" json:"query_id,omitempty"`
1401
+ unknownFields protoimpl.UnknownFields
1402
+ sizeCache protoimpl.SizeCache
1403
+ }
1404
+
1405
+ func (x *QueryStatusRequest) Reset() {
1406
+ *x = QueryStatusRequest{}
1407
+ mi := &file_agent_proto_msgTypes[14]
1408
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1409
+ ms.StoreMessageInfo(mi)
1410
+ }
1411
+
1412
+ func (x *QueryStatusRequest) String() string {
1413
+ return protoimpl.X.MessageStringOf(x)
1414
+ }
1415
+
1416
+ func (*QueryStatusRequest) ProtoMessage() {}
1417
+
1418
+ func (x *QueryStatusRequest) ProtoReflect() protoreflect.Message {
1419
+ mi := &file_agent_proto_msgTypes[14]
1420
+ if x != nil {
1421
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1422
+ if ms.LoadMessageInfo() == nil {
1423
+ ms.StoreMessageInfo(mi)
1424
+ }
1425
+ return ms
1426
+ }
1427
+ return mi.MessageOf(x)
1428
+ }
1429
+
1430
+ // Deprecated: Use QueryStatusRequest.ProtoReflect.Descriptor instead.
1431
+ func (*QueryStatusRequest) Descriptor() ([]byte, []int) {
1432
+ return file_agent_proto_rawDescGZIP(), []int{14}
1433
+ }
1434
+
1435
+ func (x *QueryStatusRequest) GetQueryId() string {
1436
+ if x != nil {
1437
+ return x.QueryId
1438
+ }
1439
+ return ""
1440
+ }
1441
+
1442
+ // QueryStatus represents current query state
1443
+ type QueryStatus struct {
1444
+ state protoimpl.MessageState `protogen:"open.v1"`
1445
+ // Query ID
1446
+ QueryId string `protobuf:"bytes,1,opt,name=query_id,json=queryId,proto3" json:"query_id,omitempty"`
1447
+ // Current status
1448
+ Status ExecutionStatus `protobuf:"varint,2,opt,name=status,proto3,enum=rag.v1.ExecutionStatus" json:"status,omitempty"`
1449
+ // Progress percentage (0-100)
1450
+ Progress int32 `protobuf:"varint,3,opt,name=progress,proto3" json:"progress,omitempty"`
1451
+ // Current step description
1452
+ CurrentStep string `protobuf:"bytes,4,opt,name=current_step,json=currentStep,proto3" json:"current_step,omitempty"`
1453
+ unknownFields protoimpl.UnknownFields
1454
+ sizeCache protoimpl.SizeCache
1455
+ }
1456
+
1457
+ func (x *QueryStatus) Reset() {
1458
+ *x = QueryStatus{}
1459
+ mi := &file_agent_proto_msgTypes[15]
1460
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1461
+ ms.StoreMessageInfo(mi)
1462
+ }
1463
+
1464
+ func (x *QueryStatus) String() string {
1465
+ return protoimpl.X.MessageStringOf(x)
1466
+ }
1467
+
1468
+ func (*QueryStatus) ProtoMessage() {}
1469
+
1470
+ func (x *QueryStatus) ProtoReflect() protoreflect.Message {
1471
+ mi := &file_agent_proto_msgTypes[15]
1472
+ if x != nil {
1473
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1474
+ if ms.LoadMessageInfo() == nil {
1475
+ ms.StoreMessageInfo(mi)
1476
+ }
1477
+ return ms
1478
+ }
1479
+ return mi.MessageOf(x)
1480
+ }
1481
+
1482
+ // Deprecated: Use QueryStatus.ProtoReflect.Descriptor instead.
1483
+ func (*QueryStatus) Descriptor() ([]byte, []int) {
1484
+ return file_agent_proto_rawDescGZIP(), []int{15}
1485
+ }
1486
+
1487
+ func (x *QueryStatus) GetQueryId() string {
1488
+ if x != nil {
1489
+ return x.QueryId
1490
+ }
1491
+ return ""
1492
+ }
1493
+
1494
+ func (x *QueryStatus) GetStatus() ExecutionStatus {
1495
+ if x != nil {
1496
+ return x.Status
1497
+ }
1498
+ return ExecutionStatus_EXECUTION_STATUS_UNSPECIFIED
1499
+ }
1500
+
1501
+ func (x *QueryStatus) GetProgress() int32 {
1502
+ if x != nil {
1503
+ return x.Progress
1504
+ }
1505
+ return 0
1506
+ }
1507
+
1508
+ func (x *QueryStatus) GetCurrentStep() string {
1509
+ if x != nil {
1510
+ return x.CurrentStep
1511
+ }
1512
+ return ""
1513
+ }
1514
+
1515
+ // SecurityContext for request authentication/authorization
1516
+ type SecurityContext struct {
1517
+ state protoimpl.MessageState `protogen:"open.v1"`
1518
+ // User ID
1519
+ UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
1520
+ // User roles
1521
+ Roles []string `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles,omitempty"`
1522
+ // Tenant ID for multi-tenancy
1523
+ TenantId string `protobuf:"bytes,3,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`
1524
+ // Additional claims
1525
+ Claims map[string]string `protobuf:"bytes,4,rep,name=claims,proto3" json:"claims,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
1526
+ unknownFields protoimpl.UnknownFields
1527
+ sizeCache protoimpl.SizeCache
1528
+ }
1529
+
1530
+ func (x *SecurityContext) Reset() {
1531
+ *x = SecurityContext{}
1532
+ mi := &file_agent_proto_msgTypes[16]
1533
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1534
+ ms.StoreMessageInfo(mi)
1535
+ }
1536
+
1537
+ func (x *SecurityContext) String() string {
1538
+ return protoimpl.X.MessageStringOf(x)
1539
+ }
1540
+
1541
+ func (*SecurityContext) ProtoMessage() {}
1542
+
1543
+ func (x *SecurityContext) ProtoReflect() protoreflect.Message {
1544
+ mi := &file_agent_proto_msgTypes[16]
1545
+ if x != nil {
1546
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1547
+ if ms.LoadMessageInfo() == nil {
1548
+ ms.StoreMessageInfo(mi)
1549
+ }
1550
+ return ms
1551
+ }
1552
+ return mi.MessageOf(x)
1553
+ }
1554
+
1555
+ // Deprecated: Use SecurityContext.ProtoReflect.Descriptor instead.
1556
+ func (*SecurityContext) Descriptor() ([]byte, []int) {
1557
+ return file_agent_proto_rawDescGZIP(), []int{16}
1558
+ }
1559
+
1560
+ func (x *SecurityContext) GetUserId() string {
1561
+ if x != nil {
1562
+ return x.UserId
1563
+ }
1564
+ return ""
1565
+ }
1566
+
1567
+ func (x *SecurityContext) GetRoles() []string {
1568
+ if x != nil {
1569
+ return x.Roles
1570
+ }
1571
+ return nil
1572
+ }
1573
+
1574
+ func (x *SecurityContext) GetTenantId() string {
1575
+ if x != nil {
1576
+ return x.TenantId
1577
+ }
1578
+ return ""
1579
+ }
1580
+
1581
+ func (x *SecurityContext) GetClaims() map[string]string {
1582
+ if x != nil {
1583
+ return x.Claims
1584
+ }
1585
+ return nil
1586
+ }
1587
+
1588
+ var File_agent_proto protoreflect.FileDescriptor
1589
+
1590
+ const file_agent_proto_rawDesc = "" +
1591
+ "\n" +
1592
+ "\vagent.proto\x12\x06rag.v1\"\xcd\x02\n" +
1593
+ "\fQueryRequest\x12\x14\n" +
1594
+ "\x05query\x18\x01 \x01(\tR\x05query\x12\x1d\n" +
1595
+ "\n" +
1596
+ "session_id\x18\x02 \x01(\tR\tsessionId\x12\x17\n" +
1597
+ "\auser_id\x18\x03 \x01(\tR\x06userId\x12B\n" +
1598
+ "\x14conversation_history\x18\x04 \x03(\v2\x0f.rag.v1.MessageR\x13conversationHistory\x12>\n" +
1599
+ "\bmetadata\x18\x05 \x03(\v2\".rag.v1.QueryRequest.MetadataEntryR\bmetadata\x12.\n" +
1600
+ "\aoptions\x18\x06 \x01(\v2\x14.rag.v1.QueryOptionsR\aoptions\x1a;\n" +
1601
+ "\rMetadataEntry\x12\x10\n" +
1602
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
1603
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xdd\x01\n" +
1604
+ "\fQueryOptions\x12\x1f\n" +
1605
+ "\vmax_sources\x18\x01 \x01(\x05R\n" +
1606
+ "maxSources\x12\x1b\n" +
1607
+ "\tuse_cache\x18\x02 \x01(\bR\buseCache\x12%\n" +
1608
+ "\x0eenable_agentic\x18\x03 \x01(\bR\renableAgentic\x12'\n" +
1609
+ "\x0fknowledge_bases\x18\x04 \x03(\tR\x0eknowledgeBases\x12 \n" +
1610
+ "\vtemperature\x18\x05 \x01(\x02R\vtemperature\x12\x1d\n" +
1611
+ "\n" +
1612
+ "max_tokens\x18\x06 \x01(\x05R\tmaxTokens\"\xd4\x01\n" +
1613
+ "\rQueryResponse\x12\x16\n" +
1614
+ "\x06answer\x18\x01 \x01(\tR\x06answer\x12(\n" +
1615
+ "\asources\x18\x02 \x03(\v2\x0e.rag.v1.SourceR\asources\x12\x1e\n" +
1616
+ "\n" +
1617
+ "confidence\x18\x03 \x01(\x02R\n" +
1618
+ "confidence\x121\n" +
1619
+ "\bmetadata\x18\x04 \x01(\v2\x15.rag.v1.QueryMetadataR\bmetadata\x12.\n" +
1620
+ "\x13follow_up_questions\x18\x05 \x03(\tR\x11followUpQuestions\"\x9a\x01\n" +
1621
+ "\x12QueryResponseChunk\x12%\n" +
1622
+ "\x04type\x18\x01 \x01(\x0e2\x11.rag.v1.ChunkTypeR\x04type\x12\x18\n" +
1623
+ "\acontent\x18\x02 \x01(\tR\acontent\x12(\n" +
1624
+ "\asources\x18\x03 \x03(\v2\x0e.rag.v1.SourceR\asources\x12\x19\n" +
1625
+ "\bis_final\x18\x04 \x01(\bR\aisFinal\"\x83\x02\n" +
1626
+ "\x06Source\x12\x0e\n" +
1627
+ "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" +
1628
+ "\x05title\x18\x02 \x01(\tR\x05title\x12\x18\n" +
1629
+ "\acontent\x18\x03 \x01(\tR\acontent\x12\x10\n" +
1630
+ "\x03url\x18\x04 \x01(\tR\x03url\x12\x14\n" +
1631
+ "\x05score\x18\x05 \x01(\x02R\x05score\x128\n" +
1632
+ "\bmetadata\x18\x06 \x03(\v2\x1c.rag.v1.Source.MetadataEntryR\bmetadata\x12\x1a\n" +
1633
+ "\blocation\x18\a \x01(\tR\blocation\x1a;\n" +
1634
+ "\rMetadataEntry\x12\x10\n" +
1635
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
1636
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"j\n" +
1637
+ "\aMessage\x12'\n" +
1638
+ "\x04role\x18\x01 \x01(\x0e2\x13.rag.v1.MessageRoleR\x04role\x12\x18\n" +
1639
+ "\acontent\x18\x02 \x01(\tR\acontent\x12\x1c\n" +
1640
+ "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\"\xec\x01\n" +
1641
+ "\rQueryMetadata\x12,\n" +
1642
+ "\x12processing_time_ms\x18\x01 \x01(\x03R\x10processingTimeMs\x12)\n" +
1643
+ "\x10chunks_retrieved\x18\x02 \x01(\x05R\x0fchunksRetrieved\x12\x1f\n" +
1644
+ "\vtokens_used\x18\x03 \x01(\x05R\n" +
1645
+ "tokensUsed\x12\x1b\n" +
1646
+ "\tcache_hit\x18\x04 \x01(\bR\bcacheHit\x12)\n" +
1647
+ "\x10routing_strategy\x18\x05 \x01(\tR\x0froutingStrategy\x12\x19\n" +
1648
+ "\btrace_id\x18\x06 \x01(\tR\atraceId\"\xb2\x01\n" +
1649
+ "\x12CreateAgentRequest\x12\x12\n" +
1650
+ "\x04name\x18\x01 \x01(\tR\x04name\x12 \n" +
1651
+ "\vdescription\x18\x02 \x01(\tR\vdescription\x12#\n" +
1652
+ "\rsystem_prompt\x18\x03 \x01(\tR\fsystemPrompt\x12\x14\n" +
1653
+ "\x05tools\x18\x04 \x03(\tR\x05tools\x12+\n" +
1654
+ "\x06config\x18\x05 \x01(\v2\x13.rag.v1.AgentConfigR\x06config\"\x99\x01\n" +
1655
+ "\x05Agent\x12\x0e\n" +
1656
+ "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
1657
+ "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" +
1658
+ "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x1d\n" +
1659
+ "\n" +
1660
+ "created_at\x18\x04 \x01(\x03R\tcreatedAt\x12+\n" +
1661
+ "\x06config\x18\x05 \x01(\v2\x13.rag.v1.AgentConfigR\x06config\"\x95\x01\n" +
1662
+ "\vAgentConfig\x12\x14\n" +
1663
+ "\x05model\x18\x01 \x01(\tR\x05model\x12 \n" +
1664
+ "\vtemperature\x18\x02 \x01(\x02R\vtemperature\x12%\n" +
1665
+ "\x0emax_iterations\x18\x03 \x01(\x05R\rmaxIterations\x12'\n" +
1666
+ "\x0ftimeout_seconds\x18\x04 \x01(\x05R\x0etimeoutSeconds\"b\n" +
1667
+ "\rExecutionPlan\x12\x0e\n" +
1668
+ "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" +
1669
+ "\x05query\x18\x02 \x01(\tR\x05query\x12+\n" +
1670
+ "\x05steps\x18\x03 \x03(\v2\x15.rag.v1.ExecutionStepR\x05steps\"\xb5\x01\n" +
1671
+ "\rExecutionStep\x12\x0e\n" +
1672
+ "\x02id\x18\x01 \x01(\tR\x02id\x12$\n" +
1673
+ "\x04type\x18\x02 \x01(\x0e2\x10.rag.v1.StepTypeR\x04type\x12 \n" +
1674
+ "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x12\n" +
1675
+ "\x04tool\x18\x04 \x01(\tR\x04tool\x12\x14\n" +
1676
+ "\x05input\x18\x05 \x01(\tR\x05input\x12\"\n" +
1677
+ "\fdependencies\x18\x06 \x03(\tR\fdependencies\"\xdc\x01\n" +
1678
+ "\n" +
1679
+ "PlanResult\x12\x17\n" +
1680
+ "\aplan_id\x18\x01 \x01(\tR\x06planId\x12/\n" +
1681
+ "\x06status\x18\x02 \x01(\x0e2\x17.rag.v1.ExecutionStatusR\x06status\x125\n" +
1682
+ "\fstep_results\x18\x03 \x03(\v2\x12.rag.v1.StepResultR\vstepResults\x12!\n" +
1683
+ "\ffinal_answer\x18\x04 \x01(\tR\vfinalAnswer\x12*\n" +
1684
+ "\x11execution_time_ms\x18\x05 \x01(\x03R\x0fexecutionTimeMs\"\xb0\x01\n" +
1685
+ "\n" +
1686
+ "StepResult\x12\x17\n" +
1687
+ "\astep_id\x18\x01 \x01(\tR\x06stepId\x12/\n" +
1688
+ "\x06status\x18\x02 \x01(\x0e2\x17.rag.v1.ExecutionStatusR\x06status\x12\x16\n" +
1689
+ "\x06output\x18\x03 \x01(\tR\x06output\x12\x14\n" +
1690
+ "\x05error\x18\x04 \x01(\tR\x05error\x12*\n" +
1691
+ "\x11execution_time_ms\x18\x05 \x01(\x03R\x0fexecutionTimeMs\"/\n" +
1692
+ "\x12QueryStatusRequest\x12\x19\n" +
1693
+ "\bquery_id\x18\x01 \x01(\tR\aqueryId\"\x98\x01\n" +
1694
+ "\vQueryStatus\x12\x19\n" +
1695
+ "\bquery_id\x18\x01 \x01(\tR\aqueryId\x12/\n" +
1696
+ "\x06status\x18\x02 \x01(\x0e2\x17.rag.v1.ExecutionStatusR\x06status\x12\x1a\n" +
1697
+ "\bprogress\x18\x03 \x01(\x05R\bprogress\x12!\n" +
1698
+ "\fcurrent_step\x18\x04 \x01(\tR\vcurrentStep\"\xd5\x01\n" +
1699
+ "\x0fSecurityContext\x12\x17\n" +
1700
+ "\auser_id\x18\x01 \x01(\tR\x06userId\x12\x14\n" +
1701
+ "\x05roles\x18\x02 \x03(\tR\x05roles\x12\x1b\n" +
1702
+ "\ttenant_id\x18\x03 \x01(\tR\btenantId\x12;\n" +
1703
+ "\x06claims\x18\x04 \x03(\v2#.rag.v1.SecurityContext.ClaimsEntryR\x06claims\x1a9\n" +
1704
+ "\vClaimsEntry\x12\x10\n" +
1705
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
1706
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*\xa4\x01\n" +
1707
+ "\tChunkType\x12\x1a\n" +
1708
+ "\x16CHUNK_TYPE_UNSPECIFIED\x10\x00\x12\x17\n" +
1709
+ "\x13CHUNK_TYPE_THINKING\x10\x01\x12\x18\n" +
1710
+ "\x14CHUNK_TYPE_RETRIEVAL\x10\x02\x12\x19\n" +
1711
+ "\x15CHUNK_TYPE_GENERATION\x10\x03\x12\x17\n" +
1712
+ "\x13CHUNK_TYPE_COMPLETE\x10\x04\x12\x14\n" +
1713
+ "\x10CHUNK_TYPE_ERROR\x10\x05*w\n" +
1714
+ "\vMessageRole\x12\x1c\n" +
1715
+ "\x18MESSAGE_ROLE_UNSPECIFIED\x10\x00\x12\x15\n" +
1716
+ "\x11MESSAGE_ROLE_USER\x10\x01\x12\x1a\n" +
1717
+ "\x16MESSAGE_ROLE_ASSISTANT\x10\x02\x12\x17\n" +
1718
+ "\x13MESSAGE_ROLE_SYSTEM\x10\x03*\x84\x01\n" +
1719
+ "\bStepType\x12\x19\n" +
1720
+ "\x15STEP_TYPE_UNSPECIFIED\x10\x00\x12\x14\n" +
1721
+ "\x10STEP_TYPE_SEARCH\x10\x01\x12\x15\n" +
1722
+ "\x11STEP_TYPE_ANALYZE\x10\x02\x12\x18\n" +
1723
+ "\x14STEP_TYPE_SYNTHESIZE\x10\x03\x12\x16\n" +
1724
+ "\x12STEP_TYPE_VALIDATE\x10\x04*\xac\x01\n" +
1725
+ "\x0fExecutionStatus\x12 \n" +
1726
+ "\x1cEXECUTION_STATUS_UNSPECIFIED\x10\x00\x12\x1c\n" +
1727
+ "\x18EXECUTION_STATUS_PENDING\x10\x01\x12\x1c\n" +
1728
+ "\x18EXECUTION_STATUS_RUNNING\x10\x02\x12\x1e\n" +
1729
+ "\x1aEXECUTION_STATUS_COMPLETED\x10\x03\x12\x1b\n" +
1730
+ "\x17EXECUTION_STATUS_FAILED\x10\x042\xcc\x02\n" +
1731
+ "\fAgentService\x12;\n" +
1732
+ "\fProcessQuery\x12\x14.rag.v1.QueryRequest\x1a\x15.rag.v1.QueryResponse\x12H\n" +
1733
+ "\x12ProcessQueryStream\x12\x14.rag.v1.QueryRequest\x1a\x1a.rag.v1.QueryResponseChunk0\x01\x128\n" +
1734
+ "\vCreateAgent\x12\x1a.rag.v1.CreateAgentRequest\x1a\r.rag.v1.Agent\x128\n" +
1735
+ "\vExecutePlan\x12\x15.rag.v1.ExecutionPlan\x1a\x12.rag.v1.PlanResult\x12A\n" +
1736
+ "\x0eGetQueryStatus\x12\x1a.rag.v1.QueryStatusRequest\x1a\x13.rag.v1.QueryStatusB6Z4github.com/AmaniQuery/amaniquery/pkg/proto/gen/ragv1b\x06proto3"
1737
+
1738
+ var (
1739
+ file_agent_proto_rawDescOnce sync.Once
1740
+ file_agent_proto_rawDescData []byte
1741
+ )
1742
+
1743
+ func file_agent_proto_rawDescGZIP() []byte {
1744
+ file_agent_proto_rawDescOnce.Do(func() {
1745
+ file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc)))
1746
+ })
1747
+ return file_agent_proto_rawDescData
1748
+ }
1749
+
1750
+ var file_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
1751
+ var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
1752
+ var file_agent_proto_goTypes = []any{
1753
+ (ChunkType)(0), // 0: rag.v1.ChunkType
1754
+ (MessageRole)(0), // 1: rag.v1.MessageRole
1755
+ (StepType)(0), // 2: rag.v1.StepType
1756
+ (ExecutionStatus)(0), // 3: rag.v1.ExecutionStatus
1757
+ (*QueryRequest)(nil), // 4: rag.v1.QueryRequest
1758
+ (*QueryOptions)(nil), // 5: rag.v1.QueryOptions
1759
+ (*QueryResponse)(nil), // 6: rag.v1.QueryResponse
1760
+ (*QueryResponseChunk)(nil), // 7: rag.v1.QueryResponseChunk
1761
+ (*Source)(nil), // 8: rag.v1.Source
1762
+ (*Message)(nil), // 9: rag.v1.Message
1763
+ (*QueryMetadata)(nil), // 10: rag.v1.QueryMetadata
1764
+ (*CreateAgentRequest)(nil), // 11: rag.v1.CreateAgentRequest
1765
+ (*Agent)(nil), // 12: rag.v1.Agent
1766
+ (*AgentConfig)(nil), // 13: rag.v1.AgentConfig
1767
+ (*ExecutionPlan)(nil), // 14: rag.v1.ExecutionPlan
1768
+ (*ExecutionStep)(nil), // 15: rag.v1.ExecutionStep
1769
+ (*PlanResult)(nil), // 16: rag.v1.PlanResult
1770
+ (*StepResult)(nil), // 17: rag.v1.StepResult
1771
+ (*QueryStatusRequest)(nil), // 18: rag.v1.QueryStatusRequest
1772
+ (*QueryStatus)(nil), // 19: rag.v1.QueryStatus
1773
+ (*SecurityContext)(nil), // 20: rag.v1.SecurityContext
1774
+ nil, // 21: rag.v1.QueryRequest.MetadataEntry
1775
+ nil, // 22: rag.v1.Source.MetadataEntry
1776
+ nil, // 23: rag.v1.SecurityContext.ClaimsEntry
1777
+ }
1778
+ var file_agent_proto_depIdxs = []int32{
1779
+ 9, // 0: rag.v1.QueryRequest.conversation_history:type_name -> rag.v1.Message
1780
+ 21, // 1: rag.v1.QueryRequest.metadata:type_name -> rag.v1.QueryRequest.MetadataEntry
1781
+ 5, // 2: rag.v1.QueryRequest.options:type_name -> rag.v1.QueryOptions
1782
+ 8, // 3: rag.v1.QueryResponse.sources:type_name -> rag.v1.Source
1783
+ 10, // 4: rag.v1.QueryResponse.metadata:type_name -> rag.v1.QueryMetadata
1784
+ 0, // 5: rag.v1.QueryResponseChunk.type:type_name -> rag.v1.ChunkType
1785
+ 8, // 6: rag.v1.QueryResponseChunk.sources:type_name -> rag.v1.Source
1786
+ 22, // 7: rag.v1.Source.metadata:type_name -> rag.v1.Source.MetadataEntry
1787
+ 1, // 8: rag.v1.Message.role:type_name -> rag.v1.MessageRole
1788
+ 13, // 9: rag.v1.CreateAgentRequest.config:type_name -> rag.v1.AgentConfig
1789
+ 13, // 10: rag.v1.Agent.config:type_name -> rag.v1.AgentConfig
1790
+ 15, // 11: rag.v1.ExecutionPlan.steps:type_name -> rag.v1.ExecutionStep
1791
+ 2, // 12: rag.v1.ExecutionStep.type:type_name -> rag.v1.StepType
1792
+ 3, // 13: rag.v1.PlanResult.status:type_name -> rag.v1.ExecutionStatus
1793
+ 17, // 14: rag.v1.PlanResult.step_results:type_name -> rag.v1.StepResult
1794
+ 3, // 15: rag.v1.StepResult.status:type_name -> rag.v1.ExecutionStatus
1795
+ 3, // 16: rag.v1.QueryStatus.status:type_name -> rag.v1.ExecutionStatus
1796
+ 23, // 17: rag.v1.SecurityContext.claims:type_name -> rag.v1.SecurityContext.ClaimsEntry
1797
+ 4, // 18: rag.v1.AgentService.ProcessQuery:input_type -> rag.v1.QueryRequest
1798
+ 4, // 19: rag.v1.AgentService.ProcessQueryStream:input_type -> rag.v1.QueryRequest
1799
+ 11, // 20: rag.v1.AgentService.CreateAgent:input_type -> rag.v1.CreateAgentRequest
1800
+ 14, // 21: rag.v1.AgentService.ExecutePlan:input_type -> rag.v1.ExecutionPlan
1801
+ 18, // 22: rag.v1.AgentService.GetQueryStatus:input_type -> rag.v1.QueryStatusRequest
1802
+ 6, // 23: rag.v1.AgentService.ProcessQuery:output_type -> rag.v1.QueryResponse
1803
+ 7, // 24: rag.v1.AgentService.ProcessQueryStream:output_type -> rag.v1.QueryResponseChunk
1804
+ 12, // 25: rag.v1.AgentService.CreateAgent:output_type -> rag.v1.Agent
1805
+ 16, // 26: rag.v1.AgentService.ExecutePlan:output_type -> rag.v1.PlanResult
1806
+ 19, // 27: rag.v1.AgentService.GetQueryStatus:output_type -> rag.v1.QueryStatus
1807
+ 23, // [23:28] is the sub-list for method output_type
1808
+ 18, // [18:23] is the sub-list for method input_type
1809
+ 18, // [18:18] is the sub-list for extension type_name
1810
+ 18, // [18:18] is the sub-list for extension extendee
1811
+ 0, // [0:18] is the sub-list for field type_name
1812
+ }
1813
+
1814
+ func init() { file_agent_proto_init() }
1815
+ func file_agent_proto_init() {
1816
+ if File_agent_proto != nil {
1817
+ return
1818
+ }
1819
+ type x struct{}
1820
+ out := protoimpl.TypeBuilder{
1821
+ File: protoimpl.DescBuilder{
1822
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
1823
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc)),
1824
+ NumEnums: 4,
1825
+ NumMessages: 20,
1826
+ NumExtensions: 0,
1827
+ NumServices: 1,
1828
+ },
1829
+ GoTypes: file_agent_proto_goTypes,
1830
+ DependencyIndexes: file_agent_proto_depIdxs,
1831
+ EnumInfos: file_agent_proto_enumTypes,
1832
+ MessageInfos: file_agent_proto_msgTypes,
1833
+ }.Build()
1834
+ File_agent_proto = out.File
1835
+ file_agent_proto_goTypes = nil
1836
+ file_agent_proto_depIdxs = nil
1837
+ }
agent_grpc.pb.go ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2
+ // versions:
3
+ // - protoc-gen-go-grpc v1.6.0
4
+ // - protoc v6.33.2
5
+ // source: agent.proto
6
+
7
+ package ragv1
8
+
9
+ import (
10
+ context "context"
11
+ grpc "google.golang.org/grpc"
12
+ codes "google.golang.org/grpc/codes"
13
+ status "google.golang.org/grpc/status"
14
+ )
15
+
16
+ // This is a compile-time assertion to ensure that this generated file
17
+ // is compatible with the grpc package it is being compiled against.
18
+ // Requires gRPC-Go v1.64.0 or later.
19
+ const _ = grpc.SupportPackageIsVersion9
20
+
21
+ const (
22
+ AgentService_ProcessQuery_FullMethodName = "/rag.v1.AgentService/ProcessQuery"
23
+ AgentService_ProcessQueryStream_FullMethodName = "/rag.v1.AgentService/ProcessQueryStream"
24
+ AgentService_CreateAgent_FullMethodName = "/rag.v1.AgentService/CreateAgent"
25
+ AgentService_ExecutePlan_FullMethodName = "/rag.v1.AgentService/ExecutePlan"
26
+ AgentService_GetQueryStatus_FullMethodName = "/rag.v1.AgentService/GetQueryStatus"
27
+ )
28
+
29
+ // AgentServiceClient is the client API for AgentService service.
30
+ //
31
+ // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
32
+ //
33
+ // AgentService handles query processing and orchestration
34
+ type AgentServiceClient interface {
35
+ // Process a single query and return a complete response
36
+ ProcessQuery(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error)
37
+ // Process a query with streaming response for real-time updates
38
+ ProcessQueryStream(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[QueryResponseChunk], error)
39
+ // Create a new agent with specific configuration
40
+ CreateAgent(ctx context.Context, in *CreateAgentRequest, opts ...grpc.CallOption) (*Agent, error)
41
+ // Execute a pre-defined execution plan
42
+ ExecutePlan(ctx context.Context, in *ExecutionPlan, opts ...grpc.CallOption) (*PlanResult, error)
43
+ // Get the status of an ongoing query
44
+ GetQueryStatus(ctx context.Context, in *QueryStatusRequest, opts ...grpc.CallOption) (*QueryStatus, error)
45
+ }
46
+
47
+ type agentServiceClient struct {
48
+ cc grpc.ClientConnInterface
49
+ }
50
+
51
+ func NewAgentServiceClient(cc grpc.ClientConnInterface) AgentServiceClient {
52
+ return &agentServiceClient{cc}
53
+ }
54
+
55
+ func (c *agentServiceClient) ProcessQuery(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error) {
56
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
57
+ out := new(QueryResponse)
58
+ err := c.cc.Invoke(ctx, AgentService_ProcessQuery_FullMethodName, in, out, cOpts...)
59
+ if err != nil {
60
+ return nil, err
61
+ }
62
+ return out, nil
63
+ }
64
+
65
+ func (c *agentServiceClient) ProcessQueryStream(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[QueryResponseChunk], error) {
66
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
67
+ stream, err := c.cc.NewStream(ctx, &AgentService_ServiceDesc.Streams[0], AgentService_ProcessQueryStream_FullMethodName, cOpts...)
68
+ if err != nil {
69
+ return nil, err
70
+ }
71
+ x := &grpc.GenericClientStream[QueryRequest, QueryResponseChunk]{ClientStream: stream}
72
+ if err := x.ClientStream.SendMsg(in); err != nil {
73
+ return nil, err
74
+ }
75
+ if err := x.ClientStream.CloseSend(); err != nil {
76
+ return nil, err
77
+ }
78
+ return x, nil
79
+ }
80
+
81
+ // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
82
+ type AgentService_ProcessQueryStreamClient = grpc.ServerStreamingClient[QueryResponseChunk]
83
+
84
+ func (c *agentServiceClient) CreateAgent(ctx context.Context, in *CreateAgentRequest, opts ...grpc.CallOption) (*Agent, error) {
85
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
86
+ out := new(Agent)
87
+ err := c.cc.Invoke(ctx, AgentService_CreateAgent_FullMethodName, in, out, cOpts...)
88
+ if err != nil {
89
+ return nil, err
90
+ }
91
+ return out, nil
92
+ }
93
+
94
+ func (c *agentServiceClient) ExecutePlan(ctx context.Context, in *ExecutionPlan, opts ...grpc.CallOption) (*PlanResult, error) {
95
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
96
+ out := new(PlanResult)
97
+ err := c.cc.Invoke(ctx, AgentService_ExecutePlan_FullMethodName, in, out, cOpts...)
98
+ if err != nil {
99
+ return nil, err
100
+ }
101
+ return out, nil
102
+ }
103
+
104
+ func (c *agentServiceClient) GetQueryStatus(ctx context.Context, in *QueryStatusRequest, opts ...grpc.CallOption) (*QueryStatus, error) {
105
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
106
+ out := new(QueryStatus)
107
+ err := c.cc.Invoke(ctx, AgentService_GetQueryStatus_FullMethodName, in, out, cOpts...)
108
+ if err != nil {
109
+ return nil, err
110
+ }
111
+ return out, nil
112
+ }
113
+
114
+ // AgentServiceServer is the server API for AgentService service.
115
+ // All implementations must embed UnimplementedAgentServiceServer
116
+ // for forward compatibility.
117
+ //
118
+ // AgentService handles query processing and orchestration
119
+ type AgentServiceServer interface {
120
+ // Process a single query and return a complete response
121
+ ProcessQuery(context.Context, *QueryRequest) (*QueryResponse, error)
122
+ // Process a query with streaming response for real-time updates
123
+ ProcessQueryStream(*QueryRequest, grpc.ServerStreamingServer[QueryResponseChunk]) error
124
+ // Create a new agent with specific configuration
125
+ CreateAgent(context.Context, *CreateAgentRequest) (*Agent, error)
126
+ // Execute a pre-defined execution plan
127
+ ExecutePlan(context.Context, *ExecutionPlan) (*PlanResult, error)
128
+ // Get the status of an ongoing query
129
+ GetQueryStatus(context.Context, *QueryStatusRequest) (*QueryStatus, error)
130
+ mustEmbedUnimplementedAgentServiceServer()
131
+ }
132
+
133
+ // UnimplementedAgentServiceServer must be embedded to have
134
+ // forward compatible implementations.
135
+ //
136
+ // NOTE: this should be embedded by value instead of pointer to avoid a nil
137
+ // pointer dereference when methods are called.
138
+ type UnimplementedAgentServiceServer struct{}
139
+
140
+ func (UnimplementedAgentServiceServer) ProcessQuery(context.Context, *QueryRequest) (*QueryResponse, error) {
141
+ return nil, status.Error(codes.Unimplemented, "method ProcessQuery not implemented")
142
+ }
143
+ func (UnimplementedAgentServiceServer) ProcessQueryStream(*QueryRequest, grpc.ServerStreamingServer[QueryResponseChunk]) error {
144
+ return status.Error(codes.Unimplemented, "method ProcessQueryStream not implemented")
145
+ }
146
+ func (UnimplementedAgentServiceServer) CreateAgent(context.Context, *CreateAgentRequest) (*Agent, error) {
147
+ return nil, status.Error(codes.Unimplemented, "method CreateAgent not implemented")
148
+ }
149
+ func (UnimplementedAgentServiceServer) ExecutePlan(context.Context, *ExecutionPlan) (*PlanResult, error) {
150
+ return nil, status.Error(codes.Unimplemented, "method ExecutePlan not implemented")
151
+ }
152
+ func (UnimplementedAgentServiceServer) GetQueryStatus(context.Context, *QueryStatusRequest) (*QueryStatus, error) {
153
+ return nil, status.Error(codes.Unimplemented, "method GetQueryStatus not implemented")
154
+ }
155
+ func (UnimplementedAgentServiceServer) mustEmbedUnimplementedAgentServiceServer() {}
156
+ func (UnimplementedAgentServiceServer) testEmbeddedByValue() {}
157
+
158
+ // UnsafeAgentServiceServer may be embedded to opt out of forward compatibility for this service.
159
+ // Use of this interface is not recommended, as added methods to AgentServiceServer will
160
+ // result in compilation errors.
161
+ type UnsafeAgentServiceServer interface {
162
+ mustEmbedUnimplementedAgentServiceServer()
163
+ }
164
+
165
+ func RegisterAgentServiceServer(s grpc.ServiceRegistrar, srv AgentServiceServer) {
166
+ // If the following call panics, it indicates UnimplementedAgentServiceServer was
167
+ // embedded by pointer and is nil. This will cause panics if an
168
+ // unimplemented method is ever invoked, so we test this at initialization
169
+ // time to prevent it from happening at runtime later due to I/O.
170
+ if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
171
+ t.testEmbeddedByValue()
172
+ }
173
+ s.RegisterService(&AgentService_ServiceDesc, srv)
174
+ }
175
+
176
+ func _AgentService_ProcessQuery_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
177
+ in := new(QueryRequest)
178
+ if err := dec(in); err != nil {
179
+ return nil, err
180
+ }
181
+ if interceptor == nil {
182
+ return srv.(AgentServiceServer).ProcessQuery(ctx, in)
183
+ }
184
+ info := &grpc.UnaryServerInfo{
185
+ Server: srv,
186
+ FullMethod: AgentService_ProcessQuery_FullMethodName,
187
+ }
188
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
189
+ return srv.(AgentServiceServer).ProcessQuery(ctx, req.(*QueryRequest))
190
+ }
191
+ return interceptor(ctx, in, info, handler)
192
+ }
193
+
194
+ func _AgentService_ProcessQueryStream_Handler(srv interface{}, stream grpc.ServerStream) error {
195
+ m := new(QueryRequest)
196
+ if err := stream.RecvMsg(m); err != nil {
197
+ return err
198
+ }
199
+ return srv.(AgentServiceServer).ProcessQueryStream(m, &grpc.GenericServerStream[QueryRequest, QueryResponseChunk]{ServerStream: stream})
200
+ }
201
+
202
+ // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
203
+ type AgentService_ProcessQueryStreamServer = grpc.ServerStreamingServer[QueryResponseChunk]
204
+
205
+ func _AgentService_CreateAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
206
+ in := new(CreateAgentRequest)
207
+ if err := dec(in); err != nil {
208
+ return nil, err
209
+ }
210
+ if interceptor == nil {
211
+ return srv.(AgentServiceServer).CreateAgent(ctx, in)
212
+ }
213
+ info := &grpc.UnaryServerInfo{
214
+ Server: srv,
215
+ FullMethod: AgentService_CreateAgent_FullMethodName,
216
+ }
217
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
218
+ return srv.(AgentServiceServer).CreateAgent(ctx, req.(*CreateAgentRequest))
219
+ }
220
+ return interceptor(ctx, in, info, handler)
221
+ }
222
+
223
+ func _AgentService_ExecutePlan_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
224
+ in := new(ExecutionPlan)
225
+ if err := dec(in); err != nil {
226
+ return nil, err
227
+ }
228
+ if interceptor == nil {
229
+ return srv.(AgentServiceServer).ExecutePlan(ctx, in)
230
+ }
231
+ info := &grpc.UnaryServerInfo{
232
+ Server: srv,
233
+ FullMethod: AgentService_ExecutePlan_FullMethodName,
234
+ }
235
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
236
+ return srv.(AgentServiceServer).ExecutePlan(ctx, req.(*ExecutionPlan))
237
+ }
238
+ return interceptor(ctx, in, info, handler)
239
+ }
240
+
241
+ func _AgentService_GetQueryStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
242
+ in := new(QueryStatusRequest)
243
+ if err := dec(in); err != nil {
244
+ return nil, err
245
+ }
246
+ if interceptor == nil {
247
+ return srv.(AgentServiceServer).GetQueryStatus(ctx, in)
248
+ }
249
+ info := &grpc.UnaryServerInfo{
250
+ Server: srv,
251
+ FullMethod: AgentService_GetQueryStatus_FullMethodName,
252
+ }
253
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
254
+ return srv.(AgentServiceServer).GetQueryStatus(ctx, req.(*QueryStatusRequest))
255
+ }
256
+ return interceptor(ctx, in, info, handler)
257
+ }
258
+
259
+ // AgentService_ServiceDesc is the grpc.ServiceDesc for AgentService service.
260
+ // It's only intended for direct use with grpc.RegisterService,
261
+ // and not to be introspected or modified (even as a copy)
262
+ var AgentService_ServiceDesc = grpc.ServiceDesc{
263
+ ServiceName: "rag.v1.AgentService",
264
+ HandlerType: (*AgentServiceServer)(nil),
265
+ Methods: []grpc.MethodDesc{
266
+ {
267
+ MethodName: "ProcessQuery",
268
+ Handler: _AgentService_ProcessQuery_Handler,
269
+ },
270
+ {
271
+ MethodName: "CreateAgent",
272
+ Handler: _AgentService_CreateAgent_Handler,
273
+ },
274
+ {
275
+ MethodName: "ExecutePlan",
276
+ Handler: _AgentService_ExecutePlan_Handler,
277
+ },
278
+ {
279
+ MethodName: "GetQueryStatus",
280
+ Handler: _AgentService_GetQueryStatus_Handler,
281
+ },
282
+ },
283
+ Streams: []grpc.StreamDesc{
284
+ {
285
+ StreamName: "ProcessQueryStream",
286
+ Handler: _AgentService_ProcessQueryStream_Handler,
287
+ ServerStreams: true,
288
+ },
289
+ },
290
+ Metadata: "agent.proto",
291
+ }
cmd/agent-server/main.go ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package main provides the entry point for the Agent Orchestrator service.
2
+ // This is the primary service that coordinates query processing and orchestrates
3
+ // interactions between the Retriever and Generator services.
4
+ package main
5
+
6
+ import (
7
+ "context"
8
+ "fmt"
9
+ "net"
10
+ "os"
11
+ "os/signal"
12
+ "syscall"
13
+ "time"
14
+
15
+ "github.com/AmaniQuery/amaniquery/internal/agent"
16
+ "github.com/AmaniQuery/amaniquery/internal/cache"
17
+ "github.com/AmaniQuery/amaniquery/internal/generator"
18
+ "github.com/AmaniQuery/amaniquery/internal/generator/llm"
19
+ "github.com/AmaniQuery/amaniquery/internal/retriever"
20
+ "github.com/AmaniQuery/amaniquery/internal/retriever/keyword"
21
+ "github.com/AmaniQuery/amaniquery/internal/retriever/vector"
22
+ "github.com/AmaniQuery/amaniquery/internal/router"
23
+ "github.com/AmaniQuery/amaniquery/pkg/config"
24
+ "github.com/AmaniQuery/amaniquery/pkg/observability"
25
+
26
+ "go.uber.org/zap"
27
+ "google.golang.org/grpc"
28
+ "google.golang.org/grpc/health"
29
+ "google.golang.org/grpc/health/grpc_health_v1"
30
+ "google.golang.org/grpc/reflection"
31
+ )
32
+
33
+ func main() {
34
+ // Load configuration
35
+ cfg, err := config.Load()
36
+ if err != nil {
37
+ fmt.Fprintf(os.Stderr, "failed to load configuration: %v\n", err)
38
+ os.Exit(1)
39
+ }
40
+
41
+ // Initialize logger
42
+ logger, err := observability.NewLogger(cfg.Observability.LogLevel, cfg.Observability.LogFormat)
43
+ if err != nil {
44
+ fmt.Fprintf(os.Stderr, "failed to initialize logger: %v\n", err)
45
+ os.Exit(1)
46
+ }
47
+ defer logger.Sync()
48
+
49
+ logger.Info("starting AmaniQuery agent server",
50
+ zap.String("version", cfg.Version),
51
+ zap.String("environment", cfg.Environment),
52
+ zap.Strings("llm_providers", cfg.LLM.GetConfiguredProviders()),
53
+ )
54
+
55
+ // Initialize observability (tracing + metrics)
56
+ tracingShutdown, err := observability.InitProvider(observability.Config{
57
+ ServiceName: "amaniquery-agent",
58
+ ServiceVersion: cfg.Version,
59
+ TracingEnabled: cfg.Observability.TracingEnabled,
60
+ TracingEndpoint: cfg.Observability.TracingEndpoint,
61
+ MetricsEnabled: cfg.Observability.MetricsEnabled,
62
+ MetricsPort: cfg.Observability.MetricsPort,
63
+ })
64
+ if err != nil {
65
+ logger.Warn("failed to initialize tracing, continuing without", zap.Error(err))
66
+ } else {
67
+ defer tracingShutdown(context.Background())
68
+ }
69
+
70
+ // Start metrics server
71
+ if cfg.Observability.MetricsEnabled {
72
+ metricsServer := observability.StartMetricsServer(cfg.Observability.MetricsPort)
73
+ defer metricsServer.Shutdown(context.Background())
74
+ logger.Info("metrics server started", zap.Int("port", cfg.Observability.MetricsPort))
75
+ }
76
+
77
+ // Build dependencies
78
+ deps, err := buildDependencies(cfg, logger)
79
+ if err != nil {
80
+ logger.Fatal("failed to build dependencies", zap.Error(err))
81
+ }
82
+ defer deps.Close()
83
+
84
+ // Create gRPC server with interceptors
85
+ grpcServer := grpc.NewServer(
86
+ grpc.ChainUnaryInterceptor(
87
+ observability.UnaryServerInterceptor(),
88
+ agent.LoggingInterceptor(logger),
89
+ agent.RecoveryInterceptor(),
90
+ ),
91
+ grpc.ChainStreamInterceptor(
92
+ observability.StreamServerInterceptor(),
93
+ ),
94
+ )
95
+
96
+ // Register Agent service
97
+ agentServer := agent.NewServer(deps, logger)
98
+ agent.RegisterAgentServiceServer(grpcServer, agentServer)
99
+
100
+ // Register health service
101
+ healthServer := health.NewServer()
102
+ grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
103
+ healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
104
+
105
+ // Enable reflection for grpcurl
106
+ reflection.Register(grpcServer)
107
+
108
+ // Start gRPC server
109
+ addr := fmt.Sprintf(":%d", cfg.Server.GRPCPort)
110
+ listener, err := net.Listen("tcp", addr)
111
+ if err != nil {
112
+ logger.Fatal("failed to listen", zap.String("addr", addr), zap.Error(err))
113
+ }
114
+
115
+ logger.Info("gRPC server starting",
116
+ zap.String("addr", addr),
117
+ zap.Int("http_port", cfg.Server.HTTPPort),
118
+ )
119
+
120
+ // Graceful shutdown handling
121
+ errChan := make(chan error, 1)
122
+ go func() {
123
+ errChan <- grpcServer.Serve(listener)
124
+ }()
125
+
126
+ // Wait for shutdown signal
127
+ quit := make(chan os.Signal, 1)
128
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
129
+
130
+ select {
131
+ case err := <-errChan:
132
+ logger.Fatal("server error", zap.Error(err))
133
+ case sig := <-quit:
134
+ logger.Info("shutting down", zap.String("signal", sig.String()))
135
+ }
136
+
137
+ // Graceful shutdown with timeout
138
+ ctx, cancel := context.WithTimeout(context.Background(), cfg.Server.GracefulTimeout)
139
+ defer cancel()
140
+
141
+ healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_NOT_SERVING)
142
+ grpcServer.GracefulStop()
143
+
144
+ logger.Info("server stopped gracefully")
145
+ _ = ctx // used for cleanup operations
146
+ }
147
+
148
+ // buildDependencies initializes all service dependencies
149
+ func buildDependencies(cfg *config.Config, logger *zap.Logger) (*agent.Dependencies, error) {
150
+ deps := &agent.Dependencies{}
151
+
152
+ // Initialize vector store client (Qdrant)
153
+ logger.Info("connecting to vector store",
154
+ zap.String("type", cfg.VectorStore.Type),
155
+ zap.String("host", cfg.VectorStore.Host),
156
+ zap.Int("port", cfg.VectorStore.Port),
157
+ )
158
+ vectorClient, err := vector.NewQdrantClient(vector.Config{
159
+ Host: cfg.VectorStore.Host,
160
+ Port: cfg.VectorStore.Port,
161
+ APIKey: cfg.VectorStore.APIKey,
162
+ Collection: cfg.VectorStore.Collection,
163
+ Dimension: cfg.VectorStore.Dimension,
164
+ Distance: cfg.VectorStore.Distance,
165
+ })
166
+ if err != nil {
167
+ logger.Warn("failed to connect to vector store, continuing without", zap.Error(err))
168
+ } else {
169
+ deps.VectorStore = vectorClient
170
+ logger.Info("connected to vector store")
171
+ }
172
+
173
+ // Initialize cache
174
+ logger.Info("connecting to cache", zap.String("url", cfg.Cache.RedisURL))
175
+ cacheClient, err := cache.New(cache.Config{
176
+ RedisURL: cfg.Cache.RedisURL,
177
+ LocalSize: cfg.Cache.LocalSize,
178
+ TTL: cfg.Cache.TTL,
179
+ MaxRetries: cfg.Cache.MaxRetries,
180
+ PoolSize: cfg.Cache.PoolSize,
181
+ })
182
+ if err != nil {
183
+ logger.Warn("failed to initialize cache, continuing without", zap.Error(err))
184
+ } else {
185
+ deps.Cache = cacheClient
186
+ logger.Info("cache initialized")
187
+ }
188
+
189
+ // Initialize embedding client
190
+ logger.Info("initializing embedding client",
191
+ zap.String("provider", cfg.Embedding.Provider),
192
+ zap.String("model", cfg.Embedding.Model),
193
+ )
194
+ embeddingClient := generator.NewOpenAIEmbeddingClient(generator.EmbeddingConfig{
195
+ Provider: cfg.Embedding.Provider,
196
+ APIKey: cfg.Embedding.APIKey,
197
+ Model: cfg.Embedding.Model,
198
+ Dimension: cfg.Embedding.Dimension,
199
+ BatchSize: cfg.Embedding.BatchSize,
200
+ Timeout: 30 * time.Second,
201
+ MaxRetries: 3,
202
+ })
203
+ deps.EmbeddingClient = embeddingClient
204
+ logger.Info("embedding client initialized")
205
+
206
+ // Initialize multi-provider LLM client with fallback
207
+ // Fallback order: Gemini → Moonshot → Ollama → OpenAI → Anthropic
208
+ logger.Info("initializing LLM client with fallback",
209
+ zap.Strings("providers", cfg.LLM.GetConfiguredProviders()),
210
+ )
211
+ llmClient := llm.NewFallbackClient(llm.Config{
212
+ GeminiAPIKey: cfg.LLM.GeminiAPIKey,
213
+ MoonshotAPIKey: cfg.LLM.MoonshotAPIKey,
214
+ OllamaBaseURL: cfg.LLM.OllamaBaseURL,
215
+ OpenAIAPIKey: cfg.LLM.OpenAIAPIKey,
216
+ AnthropicAPIKey: cfg.LLM.AnthropicAPIKey,
217
+ DefaultModel: cfg.LLM.DefaultModel,
218
+ MaxTokens: cfg.LLM.MaxTokens,
219
+ Temperature: cfg.LLM.Temperature,
220
+ Timeout: cfg.LLM.Timeout,
221
+ MaxRetries: cfg.LLM.MaxRetries,
222
+ EnableFallback: cfg.LLM.EnableFallback,
223
+ Logger: logger,
224
+ })
225
+ deps.LLMClient = llmClient
226
+ logger.Info("LLM client initialized",
227
+ zap.Int("provider_count", len(llmClient.GetAvailableProviders())),
228
+ zap.Strings("available_providers", toStringSlice(llmClient.GetAvailableProviders())),
229
+ )
230
+
231
+ // Initialize keyword search engine
232
+ logger.Info("initializing keyword search engine")
233
+ keywordEngine, err := keyword.NewBleveEngine(keyword.Config{
234
+ InMemory: true, // Use in-memory for development
235
+ })
236
+ if err != nil {
237
+ logger.Warn("failed to initialize keyword engine, continuing without", zap.Error(err))
238
+ } else {
239
+ deps.KeywordEngine = keywordEngine
240
+ logger.Info("keyword search engine initialized")
241
+ }
242
+
243
+ // Initialize query router
244
+ queryRouter := router.NewRouter(router.DefaultRouterConfig())
245
+ deps.Router = queryRouter
246
+ logger.Info("query router initialized")
247
+
248
+ // Initialize hybrid retriever
249
+ if deps.VectorStore != nil || deps.KeywordEngine != nil {
250
+ hybridRetriever := retriever.NewHybridRetriever(
251
+ deps.VectorStore,
252
+ deps.KeywordEngine,
253
+ deps.EmbeddingClient,
254
+ retriever.DefaultConfig(),
255
+ )
256
+ deps.Retriever = hybridRetriever
257
+ logger.Info("hybrid retriever initialized")
258
+ }
259
+
260
+ return deps, nil
261
+ }
262
+
263
+ // toStringSlice converts Provider slice to string slice for logging
264
+ func toStringSlice(providers []llm.Provider) []string {
265
+ result := make([]string, len(providers))
266
+ for i, p := range providers {
267
+ result[i] = string(p)
268
+ }
269
+ return result
270
+ }
cmd/generator-server/main.go ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package main provides the entry point for the Generator service.
2
+ // This service handles LLM interactions for text generation and embeddings.
3
+ package main
4
+
5
+ import (
6
+ "context"
7
+ "fmt"
8
+ "net"
9
+ "os"
10
+ "os/signal"
11
+ "syscall"
12
+
13
+ "github.com/AmaniQuery/amaniquery/internal/generator"
14
+ "github.com/AmaniQuery/amaniquery/internal/generator/llm"
15
+ "github.com/AmaniQuery/amaniquery/pkg/config"
16
+ "github.com/AmaniQuery/amaniquery/pkg/observability"
17
+
18
+ "go.uber.org/zap"
19
+ "google.golang.org/grpc"
20
+ "google.golang.org/grpc/codes"
21
+ "google.golang.org/grpc/health"
22
+ "google.golang.org/grpc/health/grpc_health_v1"
23
+ "google.golang.org/grpc/reflection"
24
+ "google.golang.org/grpc/status"
25
+ )
26
+
27
+ func main() {
28
+ // Load configuration
29
+ cfg, err := config.Load()
30
+ if err != nil {
31
+ fmt.Fprintf(os.Stderr, "failed to load configuration: %v\n", err)
32
+ os.Exit(1)
33
+ }
34
+
35
+ // Initialize logger
36
+ logger, err := observability.NewLogger(cfg.Observability.LogLevel, cfg.Observability.LogFormat)
37
+ if err != nil {
38
+ fmt.Fprintf(os.Stderr, "failed to initialize logger: %v\n", err)
39
+ os.Exit(1)
40
+ }
41
+ defer logger.Sync()
42
+
43
+ logger.Info("starting AmaniQuery generator server",
44
+ zap.String("version", cfg.Version),
45
+ zap.Strings("llm_providers", cfg.LLM.GetConfiguredProviders()),
46
+ )
47
+
48
+ // Initialize observability
49
+ tracingShutdown, err := observability.InitProvider(observability.Config{
50
+ ServiceName: "amaniquery-generator",
51
+ ServiceVersion: cfg.Version,
52
+ TracingEnabled: cfg.Observability.TracingEnabled,
53
+ TracingEndpoint: cfg.Observability.TracingEndpoint,
54
+ })
55
+ if err != nil {
56
+ logger.Warn("failed to initialize tracing", zap.Error(err))
57
+ } else {
58
+ defer tracingShutdown(context.Background())
59
+ }
60
+
61
+ // Build dependencies
62
+ deps, err := buildGeneratorDependencies(cfg, logger)
63
+ if err != nil {
64
+ logger.Fatal("failed to build dependencies", zap.Error(err))
65
+ }
66
+
67
+ // Create gRPC server
68
+ grpcServer := grpc.NewServer(
69
+ grpc.ChainUnaryInterceptor(
70
+ observability.UnaryServerInterceptor(),
71
+ ),
72
+ )
73
+
74
+ // Register generator service
75
+ generatorServer := NewGeneratorServer(deps, logger)
76
+ RegisterGeneratorServiceServer(grpcServer, generatorServer)
77
+
78
+ // Register health service
79
+ healthServer := health.NewServer()
80
+ grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
81
+ healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
82
+
83
+ // Enable reflection
84
+ reflection.Register(grpcServer)
85
+
86
+ // Start server
87
+ port := 9092 // Different port from other servers
88
+ addr := fmt.Sprintf(":%d", port)
89
+ listener, err := net.Listen("tcp", addr)
90
+ if err != nil {
91
+ logger.Fatal("failed to listen", zap.String("addr", addr), zap.Error(err))
92
+ }
93
+
94
+ logger.Info("gRPC generator server starting", zap.String("addr", addr))
95
+
96
+ // Graceful shutdown
97
+ errChan := make(chan error, 1)
98
+ go func() {
99
+ errChan <- grpcServer.Serve(listener)
100
+ }()
101
+
102
+ quit := make(chan os.Signal, 1)
103
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
104
+
105
+ select {
106
+ case err := <-errChan:
107
+ logger.Fatal("server error", zap.Error(err))
108
+ case sig := <-quit:
109
+ logger.Info("shutting down", zap.String("signal", sig.String()))
110
+ }
111
+
112
+ grpcServer.GracefulStop()
113
+ logger.Info("generator server stopped")
114
+ }
115
+
116
+ // GeneratorDependencies holds generator service dependencies
117
+ type GeneratorDependencies struct {
118
+ LLMClient *llm.FallbackClient
119
+ EmbeddingClient *generator.OpenAIEmbeddingClient
120
+ }
121
+
122
+ func buildGeneratorDependencies(cfg *config.Config, logger *zap.Logger) (*GeneratorDependencies, error) {
123
+ // Initialize LLM client with fallback
124
+ llmClient := llm.NewFallbackClient(llm.Config{
125
+ GeminiAPIKey: cfg.LLM.GeminiAPIKey,
126
+ MoonshotAPIKey: cfg.LLM.MoonshotAPIKey,
127
+ OllamaBaseURL: cfg.LLM.OllamaBaseURL,
128
+ OpenAIAPIKey: cfg.LLM.OpenAIAPIKey,
129
+ AnthropicAPIKey: cfg.LLM.AnthropicAPIKey,
130
+ DefaultModel: cfg.LLM.DefaultModel,
131
+ MaxTokens: cfg.LLM.MaxTokens,
132
+ Temperature: cfg.LLM.Temperature,
133
+ Timeout: cfg.LLM.Timeout,
134
+ MaxRetries: cfg.LLM.MaxRetries,
135
+ EnableFallback: cfg.LLM.EnableFallback,
136
+ Logger: logger,
137
+ })
138
+
139
+ // Initialize embedding client
140
+ embeddingClient := generator.NewOpenAIEmbeddingClient(generator.EmbeddingConfig{
141
+ Provider: cfg.Embedding.Provider,
142
+ APIKey: cfg.Embedding.APIKey,
143
+ Model: cfg.Embedding.Model,
144
+ Dimension: cfg.Embedding.Dimension,
145
+ BatchSize: cfg.Embedding.BatchSize,
146
+ })
147
+
148
+ return &GeneratorDependencies{
149
+ LLMClient: llmClient,
150
+ EmbeddingClient: embeddingClient,
151
+ }, nil
152
+ }
153
+
154
+ // GeneratorServer implements the GeneratorService gRPC server
155
+ type GeneratorServer struct {
156
+ UnimplementedGeneratorServiceServer
157
+ deps *GeneratorDependencies
158
+ logger *zap.Logger
159
+ }
160
+
161
+ // NewGeneratorServer creates a new generator server
162
+ func NewGeneratorServer(deps *GeneratorDependencies, logger *zap.Logger) *GeneratorServer {
163
+ return &GeneratorServer{
164
+ deps: deps,
165
+ logger: logger,
166
+ }
167
+ }
168
+
169
+ // Generate performs text generation
170
+ func (s *GeneratorServer) Generate(ctx context.Context, req *GenerateRequest) (*GenerateResponse, error) {
171
+ if len(req.Messages) == 0 {
172
+ return nil, status.Error(codes.InvalidArgument, "messages are required")
173
+ }
174
+
175
+ // Convert messages
176
+ messages := make([]llm.Message, len(req.Messages))
177
+ for i, m := range req.Messages {
178
+ messages[i] = llm.Message{
179
+ Role: m.Role,
180
+ Content: m.Content,
181
+ }
182
+ }
183
+
184
+ // Generate response
185
+ resp, err := s.deps.LLMClient.Generate(ctx, messages, llm.Options{
186
+ Model: req.Model,
187
+ Temperature: req.Temperature,
188
+ MaxTokens: int(req.MaxTokens),
189
+ })
190
+ if err != nil {
191
+ return nil, status.Error(codes.Internal, err.Error())
192
+ }
193
+
194
+ return &GenerateResponse{
195
+ Content: resp.Content,
196
+ FinishReason: resp.FinishReason,
197
+ Model: resp.Model,
198
+ Provider: string(resp.Provider),
199
+ Usage: &TokenUsage{
200
+ PromptTokens: int32(resp.Usage.PromptTokens),
201
+ CompletionTokens: int32(resp.Usage.CompletionTokens),
202
+ TotalTokens: int32(resp.Usage.TotalTokens),
203
+ },
204
+ }, nil
205
+ }
206
+
207
+ // GenerateEmbedding generates embeddings for text
208
+ func (s *GeneratorServer) GenerateEmbedding(ctx context.Context, req *EmbeddingRequest) (*EmbeddingResponse, error) {
209
+ if req.Text == "" {
210
+ return nil, status.Error(codes.InvalidArgument, "text is required")
211
+ }
212
+
213
+ embedding, err := s.deps.EmbeddingClient.Generate(ctx, req.Text)
214
+ if err != nil {
215
+ return nil, status.Error(codes.Internal, err.Error())
216
+ }
217
+
218
+ return &EmbeddingResponse{
219
+ Embedding: embedding,
220
+ Dimension: int32(len(embedding)),
221
+ }, nil
222
+ }
223
+
224
+ // BatchGenerateEmbeddings generates embeddings for multiple texts
225
+ func (s *GeneratorServer) BatchGenerateEmbeddings(ctx context.Context, req *BatchEmbeddingRequest) (*BatchEmbeddingResponse, error) {
226
+ if len(req.Texts) == 0 {
227
+ return nil, status.Error(codes.InvalidArgument, "texts are required")
228
+ }
229
+
230
+ embeddings, err := s.deps.EmbeddingClient.GenerateBatch(ctx, req.Texts)
231
+ if err != nil {
232
+ return nil, status.Error(codes.Internal, err.Error())
233
+ }
234
+
235
+ return &BatchEmbeddingResponse{
236
+ Embeddings: embeddings,
237
+ }, nil
238
+ }
239
+
240
+ // Stub types - replace with generated protobuf code
241
+ type Message struct {
242
+ Role string
243
+ Content string
244
+ }
245
+
246
+ type GenerateRequest struct {
247
+ Messages []*Message
248
+ Model string
249
+ Temperature float32
250
+ MaxTokens int32
251
+ }
252
+
253
+ type GenerateResponse struct {
254
+ Content string
255
+ FinishReason string
256
+ Model string
257
+ Provider string
258
+ Usage *TokenUsage
259
+ }
260
+
261
+ type TokenUsage struct {
262
+ PromptTokens int32
263
+ CompletionTokens int32
264
+ TotalTokens int32
265
+ }
266
+
267
+ type EmbeddingRequest struct {
268
+ Text string
269
+ Model string
270
+ }
271
+
272
+ type EmbeddingResponse struct {
273
+ Embedding []float32
274
+ Dimension int32
275
+ }
276
+
277
+ type BatchEmbeddingRequest struct {
278
+ Texts []string
279
+ Model string
280
+ }
281
+
282
+ type BatchEmbeddingResponse struct {
283
+ Embeddings [][]float32
284
+ }
285
+
286
+ type UnimplementedGeneratorServiceServer struct{}
287
+
288
+ func RegisterGeneratorServiceServer(s *grpc.Server, srv *GeneratorServer) {
289
+ // Registration happens when protobuf is generated
290
+ }
cmd/retriever-server/main.go ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package main provides the entry point for the Retriever service.
2
+ // This service handles document retrieval using vector, keyword, and graph search.
3
+ package main
4
+
5
+ import (
6
+ "context"
7
+ "fmt"
8
+ "net"
9
+ "os"
10
+ "os/signal"
11
+ "syscall"
12
+
13
+ "github.com/AmaniQuery/amaniquery/internal/generator"
14
+ "github.com/AmaniQuery/amaniquery/internal/retriever"
15
+ "github.com/AmaniQuery/amaniquery/internal/retriever/keyword"
16
+ "github.com/AmaniQuery/amaniquery/internal/retriever/vector"
17
+ "github.com/AmaniQuery/amaniquery/pkg/config"
18
+ "github.com/AmaniQuery/amaniquery/pkg/observability"
19
+
20
+ "go.uber.org/zap"
21
+ "google.golang.org/grpc"
22
+ "google.golang.org/grpc/codes"
23
+ "google.golang.org/grpc/health"
24
+ "google.golang.org/grpc/health/grpc_health_v1"
25
+ "google.golang.org/grpc/reflection"
26
+ "google.golang.org/grpc/status"
27
+ )
28
+
29
+ func main() {
30
+ // Load configuration
31
+ cfg, err := config.Load()
32
+ if err != nil {
33
+ fmt.Fprintf(os.Stderr, "failed to load configuration: %v\n", err)
34
+ os.Exit(1)
35
+ }
36
+
37
+ // Initialize logger
38
+ logger, err := observability.NewLogger(cfg.Observability.LogLevel, cfg.Observability.LogFormat)
39
+ if err != nil {
40
+ fmt.Fprintf(os.Stderr, "failed to initialize logger: %v\n", err)
41
+ os.Exit(1)
42
+ }
43
+ defer logger.Sync()
44
+
45
+ logger.Info("starting AmaniQuery retriever server",
46
+ zap.String("version", cfg.Version),
47
+ zap.String("environment", cfg.Environment),
48
+ )
49
+
50
+ // Initialize observability
51
+ tracingShutdown, err := observability.InitProvider(observability.Config{
52
+ ServiceName: "amaniquery-retriever",
53
+ ServiceVersion: cfg.Version,
54
+ TracingEnabled: cfg.Observability.TracingEnabled,
55
+ TracingEndpoint: cfg.Observability.TracingEndpoint,
56
+ })
57
+ if err != nil {
58
+ logger.Warn("failed to initialize tracing", zap.Error(err))
59
+ } else {
60
+ defer tracingShutdown(context.Background())
61
+ }
62
+
63
+ // Build dependencies
64
+ deps, err := buildRetrieverDependencies(cfg, logger)
65
+ if err != nil {
66
+ logger.Fatal("failed to build dependencies", zap.Error(err))
67
+ }
68
+
69
+ // Create gRPC server
70
+ grpcServer := grpc.NewServer(
71
+ grpc.ChainUnaryInterceptor(
72
+ observability.UnaryServerInterceptor(),
73
+ ),
74
+ )
75
+
76
+ // Register retriever service
77
+ retrieverServer := NewRetrieverServer(deps, logger)
78
+ RegisterRetrieverServiceServer(grpcServer, retrieverServer)
79
+
80
+ // Register health service
81
+ healthServer := health.NewServer()
82
+ grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
83
+ healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
84
+
85
+ // Enable reflection
86
+ reflection.Register(grpcServer)
87
+
88
+ // Start server
89
+ port := 9091 // Different port from agent server
90
+ addr := fmt.Sprintf(":%d", port)
91
+ listener, err := net.Listen("tcp", addr)
92
+ if err != nil {
93
+ logger.Fatal("failed to listen", zap.String("addr", addr), zap.Error(err))
94
+ }
95
+
96
+ logger.Info("gRPC retriever server starting", zap.String("addr", addr))
97
+
98
+ // Graceful shutdown
99
+ errChan := make(chan error, 1)
100
+ go func() {
101
+ errChan <- grpcServer.Serve(listener)
102
+ }()
103
+
104
+ quit := make(chan os.Signal, 1)
105
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
106
+
107
+ select {
108
+ case err := <-errChan:
109
+ logger.Fatal("server error", zap.Error(err))
110
+ case sig := <-quit:
111
+ logger.Info("shutting down", zap.String("signal", sig.String()))
112
+ }
113
+
114
+ grpcServer.GracefulStop()
115
+ logger.Info("retriever server stopped")
116
+ }
117
+
118
+ // RetrieverDependencies holds retriever service dependencies
119
+ type RetrieverDependencies struct {
120
+ VectorStore *vector.QdrantClient
121
+ KeywordEngine *keyword.BleveEngine
122
+ EmbeddingClient *generator.OpenAIEmbeddingClient
123
+ HybridRetriever *retriever.HybridRetriever
124
+ }
125
+
126
+ func buildRetrieverDependencies(cfg *config.Config, logger *zap.Logger) (*RetrieverDependencies, error) {
127
+ deps := &RetrieverDependencies{}
128
+
129
+ // Initialize vector store
130
+ vectorClient, err := vector.NewQdrantClient(vector.Config{
131
+ Host: cfg.VectorStore.Host,
132
+ Port: cfg.VectorStore.Port,
133
+ APIKey: cfg.VectorStore.APIKey,
134
+ Collection: cfg.VectorStore.Collection,
135
+ Dimension: cfg.VectorStore.Dimension,
136
+ Distance: cfg.VectorStore.Distance,
137
+ })
138
+ if err != nil {
139
+ logger.Warn("failed to connect to vector store", zap.Error(err))
140
+ } else {
141
+ deps.VectorStore = vectorClient
142
+ }
143
+
144
+ // Initialize keyword engine
145
+ keywordEngine, err := keyword.NewBleveEngine(keyword.Config{
146
+ InMemory: true,
147
+ })
148
+ if err != nil {
149
+ logger.Warn("failed to initialize keyword engine", zap.Error(err))
150
+ } else {
151
+ deps.KeywordEngine = keywordEngine
152
+ }
153
+
154
+ // Initialize embedding client
155
+ embeddingClient := generator.NewOpenAIEmbeddingClient(generator.EmbeddingConfig{
156
+ Provider: cfg.Embedding.Provider,
157
+ APIKey: cfg.Embedding.APIKey,
158
+ Model: cfg.Embedding.Model,
159
+ Dimension: cfg.Embedding.Dimension,
160
+ BatchSize: cfg.Embedding.BatchSize,
161
+ })
162
+ deps.EmbeddingClient = embeddingClient
163
+
164
+ // Initialize hybrid retriever
165
+ deps.HybridRetriever = retriever.NewHybridRetriever(
166
+ deps.VectorStore,
167
+ deps.KeywordEngine,
168
+ deps.EmbeddingClient,
169
+ retriever.DefaultConfig(),
170
+ )
171
+
172
+ return deps, nil
173
+ }
174
+
175
+ // RetrieverServer implements the RetrieverService gRPC server
176
+ type RetrieverServer struct {
177
+ UnimplementedRetrieverServiceServer
178
+ deps *RetrieverDependencies
179
+ logger *zap.Logger
180
+ }
181
+
182
+ // NewRetrieverServer creates a new retriever server
183
+ func NewRetrieverServer(deps *RetrieverDependencies, logger *zap.Logger) *RetrieverServer {
184
+ return &RetrieverServer{
185
+ deps: deps,
186
+ logger: logger,
187
+ }
188
+ }
189
+
190
+ // HybridSearch performs hybrid search
191
+ func (s *RetrieverServer) HybridSearch(ctx context.Context, req *SearchRequest) (*SearchResponse, error) {
192
+ if req.Query == "" {
193
+ return nil, status.Error(codes.InvalidArgument, "query is required")
194
+ }
195
+
196
+ topK := int(req.TopK)
197
+ if topK == 0 {
198
+ topK = 10
199
+ }
200
+
201
+ searchResp, err := s.deps.HybridRetriever.Search(ctx, retriever.SearchRequest{
202
+ Query: req.Query,
203
+ TopK: topK,
204
+ UseVector: true,
205
+ UseKeyword: true,
206
+ })
207
+ if err != nil {
208
+ return nil, status.Error(codes.Internal, err.Error())
209
+ }
210
+
211
+ // Convert results
212
+ documents := make([]*Document, len(searchResp.Results))
213
+ for i, r := range searchResp.Results {
214
+ documents[i] = &Document{
215
+ Id: r.ID,
216
+ Content: r.Content,
217
+ Title: r.Title,
218
+ Source: r.Source,
219
+ Score: r.Score,
220
+ }
221
+ }
222
+
223
+ return &SearchResponse{
224
+ Documents: documents,
225
+ TotalCount: int32(searchResp.TotalCount),
226
+ SearchTimeMs: searchResp.SearchTimeMs,
227
+ }, nil
228
+ }
229
+
230
+ // IndexDocument indexes a document
231
+ func (s *RetrieverServer) IndexDocument(ctx context.Context, req *IndexRequest) (*IndexResponse, error) {
232
+ if req.Document == nil {
233
+ return nil, status.Error(codes.InvalidArgument, "document is required")
234
+ }
235
+
236
+ // Generate embedding
237
+ embedding, err := s.deps.EmbeddingClient.Generate(ctx, req.Document.Content)
238
+ if err != nil {
239
+ return nil, status.Error(codes.Internal, "failed to generate embedding")
240
+ }
241
+
242
+ // Index in vector store
243
+ if s.deps.VectorStore != nil {
244
+ err = s.deps.VectorStore.Index(ctx, vector.Document{
245
+ ID: req.Document.Id,
246
+ Content: req.Document.Content,
247
+ Embedding: embedding,
248
+ Metadata: convertMetadata(req.Document.Metadata),
249
+ })
250
+ if err != nil {
251
+ return nil, status.Error(codes.Internal, "failed to index in vector store")
252
+ }
253
+ }
254
+
255
+ // Index in keyword engine
256
+ if s.deps.KeywordEngine != nil {
257
+ err = s.deps.KeywordEngine.Index(ctx, keyword.Document{
258
+ ID: req.Document.Id,
259
+ Title: req.Document.Title,
260
+ Content: req.Document.Content,
261
+ Source: req.Document.Source,
262
+ })
263
+ if err != nil {
264
+ s.logger.Warn("failed to index in keyword engine", zap.Error(err))
265
+ }
266
+ }
267
+
268
+ return &IndexResponse{
269
+ DocumentId: req.Document.Id,
270
+ Success: true,
271
+ }, nil
272
+ }
273
+
274
+ func convertMetadata(m map[string]string) map[string]interface{} {
275
+ result := make(map[string]interface{}, len(m))
276
+ for k, v := range m {
277
+ result[k] = v
278
+ }
279
+ return result
280
+ }
281
+
282
+ // Stub types for protobuf - replace with generated code
283
+ type SearchRequest struct {
284
+ Query string
285
+ TopK int32
286
+ Embedding []float32
287
+ }
288
+
289
+ type SearchResponse struct {
290
+ Documents []*Document
291
+ TotalCount int32
292
+ SearchTimeMs int64
293
+ }
294
+
295
+ type Document struct {
296
+ Id string
297
+ Content string
298
+ Title string
299
+ Source string
300
+ Score float32
301
+ Metadata map[string]string
302
+ }
303
+
304
+ type IndexRequest struct {
305
+ Document *Document
306
+ }
307
+
308
+ type IndexResponse struct {
309
+ DocumentId string
310
+ Success bool
311
+ }
312
+
313
+ type UnimplementedRetrieverServiceServer struct{}
314
+
315
+ func RegisterRetrieverServiceServer(s *grpc.Server, srv *RetrieverServer) {
316
+ // Registration happens when protobuf is generated
317
+ }
cmd/worker/main.go ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package main provides the Temporal worker for RAG pipeline execution
2
+ package main
3
+
4
+ import (
5
+ "context"
6
+ "fmt"
7
+ "os"
8
+ "os/signal"
9
+ "syscall"
10
+
11
+ "github.com/AmaniQuery/amaniquery/internal/generator"
12
+ "github.com/AmaniQuery/amaniquery/internal/generator/llm"
13
+ "github.com/AmaniQuery/amaniquery/internal/guardrails"
14
+ "github.com/AmaniQuery/amaniquery/internal/retriever/graph"
15
+ "github.com/AmaniQuery/amaniquery/internal/retriever/keyword"
16
+ "github.com/AmaniQuery/amaniquery/internal/retriever/vector"
17
+ "github.com/AmaniQuery/amaniquery/internal/workflow"
18
+ "github.com/AmaniQuery/amaniquery/pkg/config"
19
+ "github.com/AmaniQuery/amaniquery/pkg/observability"
20
+
21
+ "go.temporal.io/sdk/client"
22
+ "go.temporal.io/sdk/worker"
23
+ "go.uber.org/zap"
24
+ )
25
+
26
+ const taskQueue = "amaniquery-rag"
27
+
28
+ func main() {
29
+ // Load configuration
30
+ cfg, err := config.Load()
31
+ if err != nil {
32
+ fmt.Fprintf(os.Stderr, "failed to load config: %v\n", err)
33
+ os.Exit(1)
34
+ }
35
+
36
+ // Initialize logger
37
+ logger, err := observability.NewLogger(cfg.Observability.LogLevel, cfg.Observability.LogFormat)
38
+ if err != nil {
39
+ fmt.Fprintf(os.Stderr, "failed to init logger: %v\n", err)
40
+ os.Exit(1)
41
+ }
42
+ defer logger.Sync()
43
+
44
+ logger.Info("starting Temporal worker",
45
+ zap.String("task_queue", taskQueue),
46
+ )
47
+
48
+ // Create Temporal client
49
+ temporalClient, err := client.Dial(client.Options{
50
+ HostPort: getEnv("TEMPORAL_HOST", "localhost:7233"),
51
+ Logger: NewTemporalZapLogger(logger),
52
+ })
53
+ if err != nil {
54
+ logger.Fatal("failed to create Temporal client", zap.Error(err))
55
+ }
56
+ defer temporalClient.Close()
57
+
58
+ // Build activity dependencies
59
+ deps, err := buildDependencies(cfg, logger)
60
+ if err != nil {
61
+ logger.Fatal("failed to build dependencies", zap.Error(err))
62
+ }
63
+
64
+ // Create activities instance
65
+ activities := workflow.NewActivities(deps)
66
+
67
+ // Create worker
68
+ w := worker.New(temporalClient, taskQueue, worker.Options{
69
+ MaxConcurrentActivityExecutionSize: 10,
70
+ MaxConcurrentWorkflowTaskExecutionSize: 10,
71
+ })
72
+
73
+ // Register workflow and activities
74
+ w.RegisterWorkflow(workflow.RAGWorkflow)
75
+ w.RegisterActivity(activities.ValidateInput)
76
+ w.RegisterActivity(activities.GenerateEmbedding)
77
+ w.RegisterActivity(activities.VectorSearch)
78
+ w.RegisterActivity(activities.KeywordSearch)
79
+ w.RegisterActivity(activities.GraphSearch)
80
+ w.RegisterActivity(activities.RankSources)
81
+ w.RegisterActivity(activities.GenerateResponse)
82
+ w.RegisterActivity(activities.ValidateOutput)
83
+
84
+ // Start worker
85
+ errChan := make(chan error, 1)
86
+ go func() {
87
+ errChan <- w.Run(worker.InterruptCh())
88
+ }()
89
+
90
+ logger.Info("Temporal worker started", zap.String("task_queue", taskQueue))
91
+
92
+ // Wait for shutdown
93
+ quit := make(chan os.Signal, 1)
94
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
95
+
96
+ select {
97
+ case err := <-errChan:
98
+ logger.Fatal("worker error", zap.Error(err))
99
+ case sig := <-quit:
100
+ logger.Info("shutting down", zap.String("signal", sig.String()))
101
+ }
102
+
103
+ w.Stop()
104
+ logger.Info("worker stopped")
105
+ }
106
+
107
+ func buildDependencies(cfg *config.Config, logger *zap.Logger) (*workflow.ActivityDependencies, error) {
108
+ deps := &workflow.ActivityDependencies{}
109
+
110
+ // Embedding client
111
+ embeddingClient := generator.NewOpenAIEmbeddingClient(generator.EmbeddingConfig{
112
+ Provider: cfg.Embedding.Provider,
113
+ APIKey: cfg.Embedding.APIKey,
114
+ Model: cfg.Embedding.Model,
115
+ Dimension: cfg.Embedding.Dimension,
116
+ })
117
+ deps.EmbeddingClient = embeddingClient
118
+
119
+ // Vector store
120
+ vectorClient, err := vector.NewQdrantClient(vector.Config{
121
+ Host: cfg.VectorStore.Host,
122
+ Port: cfg.VectorStore.Port,
123
+ APIKey: cfg.VectorStore.APIKey,
124
+ Collection: cfg.VectorStore.Collection,
125
+ })
126
+ if err != nil {
127
+ logger.Warn("vector store unavailable", zap.Error(err))
128
+ } else {
129
+ deps.VectorStore = &vectorStoreAdapter{client: vectorClient}
130
+ }
131
+
132
+ // Keyword engine
133
+ keywordEngine, err := keyword.NewBleveEngine(keyword.Config{InMemory: true})
134
+ if err != nil {
135
+ logger.Warn("keyword engine unavailable", zap.Error(err))
136
+ } else {
137
+ deps.KeywordEngine = &keywordAdapter{engine: keywordEngine}
138
+ }
139
+
140
+ // Graph store (Neo4j)
141
+ neo4jCfg := graph.DefaultConfig()
142
+ if uri := os.Getenv("NEO4J_URI"); uri != "" {
143
+ neo4jCfg.URI = uri
144
+ }
145
+ if user := os.Getenv("NEO4J_USERNAME"); user != "" {
146
+ neo4jCfg.Username = user
147
+ neo4jCfg.Password = os.Getenv("NEO4J_PASSWORD")
148
+ }
149
+ graphClient, err := graph.NewClient(neo4jCfg)
150
+ if err != nil {
151
+ logger.Warn("graph store unavailable", zap.Error(err))
152
+ } else {
153
+ deps.GraphStore = &graphAdapter{client: graphClient}
154
+ }
155
+
156
+ // Guardrails
157
+ guardrailsCfg := guardrails.DefaultConfig()
158
+ if url := os.Getenv("GUARDRAILS_URL"); url != "" {
159
+ guardrailsCfg.BaseURL = url
160
+ }
161
+ guardrailsClient := guardrails.NewClient(guardrailsCfg)
162
+ deps.Guardrails = &guardrailsAdapter{client: guardrailsClient}
163
+
164
+ // LLM client
165
+ llmClient := llm.NewFallbackClient(llm.Config{
166
+ GeminiAPIKey: cfg.LLM.GeminiAPIKey,
167
+ MoonshotAPIKey: cfg.LLM.MoonshotAPIKey,
168
+ OllamaBaseURL: cfg.LLM.OllamaBaseURL,
169
+ OpenAIAPIKey: cfg.LLM.OpenAIAPIKey,
170
+ AnthropicAPIKey: cfg.LLM.AnthropicAPIKey,
171
+ Logger: logger,
172
+ })
173
+ deps.LLMClient = &llmAdapter{client: llmClient}
174
+
175
+ return deps, nil
176
+ }
177
+
178
+ // Adapters to match workflow interfaces
179
+
180
+ type vectorStoreAdapter struct {
181
+ client *vector.QdrantClient
182
+ }
183
+
184
+ func (a *vectorStoreAdapter) Search(ctx context.Context, embedding []float32, topK int, filters map[string]interface{}) ([]workflow.Source, error) {
185
+ results, err := a.client.Search(ctx, embedding, topK, filters)
186
+ if err != nil {
187
+ return nil, err
188
+ }
189
+ sources := make([]workflow.Source, len(results))
190
+ for i, r := range results {
191
+ sources[i] = workflow.Source{
192
+ ID: r.ID,
193
+ Title: r.Title,
194
+ Content: r.Content,
195
+ Score: r.Score,
196
+ }
197
+ }
198
+ return sources, nil
199
+ }
200
+
201
+ type keywordAdapter struct {
202
+ engine *keyword.BleveEngine
203
+ }
204
+
205
+ func (a *keywordAdapter) Search(ctx context.Context, query string, topK int) ([]workflow.Source, error) {
206
+ results, err := a.engine.Search(ctx, query, topK)
207
+ if err != nil {
208
+ return nil, err
209
+ }
210
+ sources := make([]workflow.Source, len(results))
211
+ for i, r := range results {
212
+ sources[i] = workflow.Source{
213
+ ID: r.ID,
214
+ Title: r.Title,
215
+ Content: r.Content,
216
+ Score: r.Score,
217
+ }
218
+ }
219
+ return sources, nil
220
+ }
221
+
222
+ type graphAdapter struct {
223
+ client *graph.Client
224
+ }
225
+
226
+ func (a *graphAdapter) Search(ctx context.Context, query string, embedding []float32, topK int) ([]workflow.Source, error) {
227
+ results, err := a.client.Search(ctx, graph.SearchRequest{
228
+ Query: query,
229
+ Embedding: embedding,
230
+ TopK: topK,
231
+ MaxHops: 2,
232
+ })
233
+ if err != nil {
234
+ return nil, err
235
+ }
236
+ sources := make([]workflow.Source, len(results))
237
+ for i, r := range results {
238
+ sources[i] = workflow.Source{
239
+ ID: r.ID,
240
+ Title: r.Title,
241
+ Content: r.Content,
242
+ Score: r.Score,
243
+ }
244
+ }
245
+ return sources, nil
246
+ }
247
+
248
+ type guardrailsAdapter struct {
249
+ client *guardrails.Client
250
+ }
251
+
252
+ func (a *guardrailsAdapter) ValidateInput(ctx context.Context, input string) (bool, string, error) {
253
+ resp, err := a.client.ValidateInput(ctx, input)
254
+ if err != nil {
255
+ return true, "", err // Fail open
256
+ }
257
+ return !resp.Blocked, resp.Reason, nil
258
+ }
259
+
260
+ func (a *guardrailsAdapter) ValidateOutput(ctx context.Context, input, output string) (bool, string, error) {
261
+ resp, err := a.client.ValidateOutput(ctx, input, output)
262
+ if err != nil {
263
+ return true, "", err
264
+ }
265
+ return !resp.Blocked, resp.Reason, nil
266
+ }
267
+
268
+ type llmAdapter struct {
269
+ client *llm.FallbackClient
270
+ }
271
+
272
+ func (a *llmAdapter) Generate(ctx context.Context, prompt string, options workflow.GenerateOptions) (string, int, error) {
273
+ resp, err := a.client.Generate(ctx, []llm.Message{
274
+ {Role: "user", Content: prompt},
275
+ }, llm.Options{
276
+ Temperature: options.Temperature,
277
+ MaxTokens: options.MaxTokens,
278
+ })
279
+ if err != nil {
280
+ return "", 0, err
281
+ }
282
+ return resp.Content, resp.Usage.TotalTokens, nil
283
+ }
284
+
285
+ func getEnv(key, fallback string) string {
286
+ if v := os.Getenv(key); v != "" {
287
+ return v
288
+ }
289
+ return fallback
290
+ }
291
+
292
+ // TemporalZapLogger adapts zap.Logger for Temporal
293
+ type TemporalZapLogger struct {
294
+ logger *zap.Logger
295
+ }
296
+
297
+ func NewTemporalZapLogger(logger *zap.Logger) *TemporalZapLogger {
298
+ return &TemporalZapLogger{logger: logger.Named("temporal")}
299
+ }
300
+
301
+ func (l *TemporalZapLogger) Debug(msg string, keyvals ...interface{}) {
302
+ l.logger.Debug(msg, toZapFields(keyvals)...)
303
+ }
304
+
305
+ func (l *TemporalZapLogger) Info(msg string, keyvals ...interface{}) {
306
+ l.logger.Info(msg, toZapFields(keyvals)...)
307
+ }
308
+
309
+ func (l *TemporalZapLogger) Warn(msg string, keyvals ...interface{}) {
310
+ l.logger.Warn(msg, toZapFields(keyvals)...)
311
+ }
312
+
313
+ func (l *TemporalZapLogger) Error(msg string, keyvals ...interface{}) {
314
+ l.logger.Error(msg, toZapFields(keyvals)...)
315
+ }
316
+
317
+ func toZapFields(keyvals []interface{}) []zap.Field {
318
+ fields := make([]zap.Field, 0, len(keyvals)/2)
319
+ for i := 0; i < len(keyvals)-1; i += 2 {
320
+ key, ok := keyvals[i].(string)
321
+ if !ok {
322
+ continue
323
+ }
324
+ fields = append(fields, zap.Any(key, keyvals[i+1]))
325
+ }
326
+ return fields
327
+ }
config.example.yaml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AmaniQuery Configuration
2
+ # Copy this file to config.yaml and customize as needed
3
+
4
+ version: "1.0.0"
5
+ environment: "development"
6
+
7
+ server:
8
+ grpc_port: 9090
9
+ http_port: 8080
10
+ graceful_timeout: 30s
11
+ max_connections: 1000
12
+
13
+ vector_store:
14
+ type: qdrant
15
+ host: localhost
16
+ port: 6334
17
+ # api_key: set via QDRANT_API_KEY env var
18
+ collection: amaniquery
19
+ dimension: 1536
20
+ distance: Cosine
21
+
22
+ cache:
23
+ redis_url: redis://localhost:6379
24
+ local_size: 10000
25
+ ttl: 1h
26
+ max_retries: 3
27
+ pool_size: 10
28
+
29
+ # LLM Configuration with Multi-Provider Fallback
30
+ # Fallback order: Gemini → Moonshot → Ollama → OpenAI → Anthropic
31
+ # Set API keys via environment variables:
32
+ # GEMINI_API_KEY or GOOGLE_API_KEY
33
+ # MOONSHOT_API_KEY
34
+ # OLLAMA_BASE_URL (for local Ollama, default: http://localhost:11434)
35
+ # OPENAI_API_KEY
36
+ # ANTHROPIC_API_KEY
37
+ llm:
38
+ default_model: gemini-1.5-flash # Used when provider doesn't specify model
39
+ max_tokens: 4096
40
+ temperature: 0.7
41
+ timeout: 60s
42
+ max_retries: 3
43
+ enable_fallback: true # Automatically try next provider on failure
44
+ ollama_base_url: http://localhost:11434 # For local Ollama
45
+
46
+ embedding:
47
+ provider: openai # openai for text-embedding-3-small
48
+ # api_key: falls back to OPENAI_API_KEY
49
+ model: text-embedding-3-small
50
+ dimension: 1536
51
+ batch_size: 100
52
+
53
+ observability:
54
+ tracing_enabled: true
55
+ tracing_endpoint: localhost:4317
56
+ metrics_enabled: true
57
+ metrics_port: 9091
58
+ log_level: info # debug, info, warn, error
59
+ log_format: json # json, console
60
+
61
+ security:
62
+ # jwt_secret: set via JWT_SECRET env var
63
+ jwt_issuer: amaniquery
64
+ enable_mtls: false
65
+ # cert_file: /path/to/cert.pem
66
+ # key_file: /path/to/key.pem
67
+ # ca_file: /path/to/ca.pem
deploy.md ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AmaniQuery Render Deployment Guide
2
+
3
+ This guide details the steps to deploy the AmaniQuery monorepo components to [Render.com](https://render.com).
4
+
5
+ ## Prerequisites
6
+
7
+ 1. **GitHub Repository**: Ensure your code is pushed to a GitHub repository connected to Render.
8
+ 2. **Docker**, **Node.js**, and **Go** knowledge.
9
+ 3. **Render Account**: Created and ready.
10
+
11
+ ## 1. Database & Infrastructure (Render Postgres & Redis)
12
+
13
+ Before deploying services, set up your managed data stores.
14
+
15
+ ### PostgreSQL
16
+
17
+ - **Type**: PostgreSQL
18
+ - **Name**: `amaniquery-db`
19
+ - **Region**: Frankfurt (EU-Central) or nearest.
20
+ - **Environment**:
21
+ - `POSTGRES_USER`: `amaniquery`
22
+ - `POSTGRES_DB`: `amaniquery`
23
+ - **Internal Connection URL**: Copy this for use in service env vars.
24
+
25
+ ### Redis
26
+
27
+ - **Type**: Redis
28
+ - **Name**: `amaniquery-redis`
29
+ - **Max Memory Policy**: `allkeys-lru`
30
+ - **Internal Connection URL**: Copy this (`redis://...`).
31
+
32
+ ## 2. Backend Services (Docker Runtime)
33
+
34
+ Since AmaniQuery is a monorepo, using **Docker** as the Runtime is recommended for Go services to handle build context correctly (copying `go.mod` from root).
35
+
36
+ ### Common Configuration for All Backend Services
37
+
38
+ - **Runtime**: Docker
39
+ - **Repository**: `your-repo/amaniquery`
40
+ - **Region**: Same as Database.
41
+
42
+ | Service Name | Dockerfile Path | Build Context Directory | Env Vars |
43
+ | :--- | :--- | :--- | :--- |
44
+ | `amaniquery-portal` | `services/portal/Dockerfile` | `.` (Root) | `SERVER_HTTP_PORT=8080`, `DB_HOST=...` |
45
+ | `amaniquery-ingestion`| `services/ingestion/Dockerfile`| `.` (Root) | `QDRANT_URL=...`, `RABBITMQ_URL=...` |
46
+ | `amaniquery-voice` | `services/voice/Dockerfile` | `.` (Root) | `OPENAI_API_KEY=...` |
47
+ | `amaniquery-files` | `services/files/Dockerfile` | `.` (Root) | `MINIO_ENDPOINT=...` |
48
+ | `amaniquery-notifications`| `services/notifications/Dockerfile.gateway` | `.` (Root) | `MAILTRAP_API_KEY=...` |
49
+
50
+ > [!TIP]
51
+ > **Root Directory Setting**: In Render, set "Root Directory" to `.` (default) so Docker builds have access to the full monorepo context.
52
+
53
+ ## 3. Frontend Applications (Static Sites)
54
+
55
+ We will use the **Static Site** type for frontends, relying on the `Dockerfile` or Render's Native Node build.
56
+ *Recommendation*: Use **Static Site** with Node build command for faster deploys, or **Docker** if you need Nginx customization.
57
+
58
+ ### Option A: Static Site (Native Node - Recommended)
59
+
60
+ - **Build Command**: `yarn && yarn turbo run build --filter=admin-portal`
61
+ - **Publish Directory**: `frontend/apps/admin/dist`
62
+ - **Root Directory**: `frontend`
63
+
64
+ ### Option B: Docker (Using our new Turbo Dockerfiles)
65
+
66
+ - **Runtime**: Docker
67
+ - **Dockerfile Path**: `frontend/apps/admin/Dockerfile`
68
+ - **Context**: `frontend` (Important: context is `frontend` subfolder, not root, for these specific Dockerfiles)
69
+
70
+ | App Name | Build Command (Static) | Publish Dir | Context |
71
+ | :--- | :--- | :--- | :--- |
72
+ | `admin-portal` | `yarn build:admin` | `apps/admin/dist` | `frontend` |
73
+ | `developer-portal` | `yarn build:dev` | `apps/developer-portal/dist` | `frontend` |
74
+ | `web-app` | `yarn build:web` | `apps/web/dist` | `frontend` |
75
+
76
+ > *Note: You may need to add helper scripts in `frontend/package.json` like `"build:admin": "turbo run build --filter=admin-portal"` to keep commands simple.*
77
+
78
+ ## 4. HuggingFace Spaces (Docker Deployment)
79
+
80
+ For users preferring HuggingFace Spaces (free tier: 2 vCPU, 16GB RAM, 50GB disk), follow these steps.
81
+
82
+ ### Prerequisites
83
+
84
+ - HuggingFace Account
85
+ - External Databases (Managed Services):
86
+ - **PostgreSQL**: Neon.tech (Free Tier available)
87
+ - **Redis**: Upstash (Free Tier available)
88
+ - **Qdrant**: Qdrant Cloud (Free Tier available)
89
+
90
+ ### Deployment Steps
91
+
92
+ 1. **Create a New Space**:
93
+ - Go to [HuggingFace Spaces](https://huggingface.co/new-space)
94
+ - Enter a name (e.g., `amaniquery`)
95
+ - Select **Docker** as the Space SDK
96
+ - Choose "Blank" for the template
97
+
98
+ 2. **Deploy via Script (Recommended)**:
99
+ - Set `HF_TOKEN` in your local `.env`.
100
+ - Run: `python scripts/deploy_hf.py agent`
101
+ - This deploys both the Go Agent and Rust Memory Service in a single container (Sidecar pattern) for maximum efficiency and localhost communication.
102
+
103
+ 3. **Deploy Manually (Alternative)**:
104
+ - Clone your Space's repository locally.
105
+ - Copy `deployments/huggingface/Dockerfile.hf` to `Dockerfile` in the root.
106
+ - Copy `deployments/huggingface/README.md` to the root.
107
+ - Push to HuggingFace.
108
+
109
+ 4. **Configure Secrets**:
110
+ - Go to **Settings** -> **Variables and Secrets** in your Space.
111
+ - Add the secrets listed in `deployments/huggingface/.env.hf.example`.
112
+
113
+ 5. **Status**:
114
+ - The Space will build and start both `agent-server` and `memory-server`.
115
+ - The Go agent will automatically connect to the local memory service.
116
+
117
+ 6. **Automated Deployment (Optional)**:
118
+ We provided a script `scripts/deploy_hf.py` to automate the deployment process.
119
+
120
+ **Prerequisites**:
121
+ - `HF_TOKEN` must be set in your `.env` file (Get it from [HF Settings](https://huggingface.co/settings/tokens)).
122
+ - The Spaces must be created first (e.g., `AmaniQuery/amaniquery-agent` and `AmaniQuery/amaniquery-memory`).
123
+
124
+ **Usage**:
125
+
126
+ ```bash
127
+ # Deploy Agent
128
+ python scripts/deploy_hf.py agent
129
+
130
+ # Deploy Memory Service
131
+ python scripts/deploy_hf.py memory
132
+
133
+ # Deploy Both
134
+ python scripts/deploy_hf.py all
135
+ ```
136
+
137
+ ## 5. Environment Variables Checklist
138
+
139
+ Transfer these from your `env.example` files to Render's "Environment" tab for each service.
140
+
141
+ - [ ] **Portal**: `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`, `REDIS_URL`, `JWT_SECRET`
142
+ - [ ] **Voice**: `OPENAI_API_KEY`, `ELEVENLABS_API_KEY`, `REDIS_URL`
143
+ - [ ] **Frontend**: `VITE_API_BASE_URL` (Set this to the `https://...onrender.com` URL of your Portal service).
144
+
145
+ ## 6. Deployment Order
146
+
147
+ 1. **Infrastructure** (Postgres/Redis) - Wait for healthy.
148
+ 2. **Backend Services** (Portal, etc.) - Deploy & check logs.
149
+ 3. **Frontend Apps** - Deploy & update `VITE_API_BASE_URL` with backend URL.
docs/API_GATEWAY.md ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # API Gateway for RAG Agent Framework
2
+
3
+ A production-ready, multi-protocol API Gateway for the AmaniQuery RAG Agent Framework. Provides unified access for frontend clients with comprehensive security, observability, and performance features.
4
+
5
+ ## Architecture
6
+
7
+ ![API Gateway Architecture](docs/images/api-gateway-architecture.png)
8
+
9
+ ```mermaid
10
+ graph TB
11
+ subgraph "Client Layer"
12
+ WEB[Web Browser]
13
+ MOBILE[Mobile App]
14
+ DEVELOPER[Developer Portal]
15
+ end
16
+
17
+ subgraph "API Gateway Layer"
18
+ GW[API Gateway<br/>Go Service]
19
+
20
+ subgraph "Middleware Stack"
21
+ CORS[CORS Handler]
22
+ RATE[Rate Limiter]
23
+ AUTH[JWT Validator]
24
+ AUDIT[Audit Logger]
25
+ end
26
+
27
+ subgraph "Protocol Handlers"
28
+ REST[REST Handler]
29
+ WS[WebSocket Handler]
30
+ GQL[GraphQL Handler]
31
+ end
32
+ end
33
+
34
+ subgraph "Backend Services"
35
+ AGENT[Agent Service]
36
+ RETRIEVER[Retriever Service]
37
+ GENERATOR[Generator Service]
38
+ MEMORY[Memory Service]
39
+ end
40
+
41
+ WEB --> GW
42
+ MOBILE --> GW
43
+ DEVELOPER --> GW
44
+
45
+ GW --> CORS --> RATE --> AUTH --> AUDIT
46
+ AUDIT --> REST
47
+ AUDIT --> WS
48
+ AUDIT --> GQL
49
+
50
+ REST --> AGENT
51
+ WS --> AGENT
52
+ GQL --> AGENT
53
+ ```
54
+
55
+ ## Features
56
+
57
+ ### Multi-Protocol Support
58
+ - **REST API** - Standard HTTP endpoints for queries, agents, memory
59
+ - **WebSocket** - Real-time streaming for query responses
60
+ - **Server-Sent Events** - Lightweight streaming alternative
61
+ - **GraphQL** - Flexible query interface (placeholder)
62
+
63
+ ### Security
64
+ - **JWT Authentication** - Token-based auth with HMAC/RSA signing
65
+ - **OPA Authorization** - Fine-grained policy-based access control
66
+ - **Rate Limiting** - Token bucket with per-tenant/user isolation
67
+ - **CORS** - Configurable cross-origin policies
68
+ - **Security Headers** - HSTS, CSP, X-Frame-Options, etc.
69
+
70
+ ### Observability
71
+ - **Prometheus Metrics** - Request counts, latencies, cache hits
72
+ - **OpenTelemetry Tracing** - Distributed request tracing
73
+ - **Audit Logging** - Structured logs for compliance
74
+
75
+ ### Performance
76
+ - **Redis Caching** - Query response caching with smart TTL
77
+ - **Circuit Breakers** - Failure isolation per service
78
+ - **Connection Pooling** - Efficient gRPC connections
79
+
80
+ ## Quick Start
81
+
82
+ ### Prerequisites
83
+ - Go 1.21+
84
+ - Docker & Docker Compose
85
+ - Redis (for caching/rate limiting)
86
+
87
+ ### Running Locally
88
+
89
+ ```bash
90
+ # Clone the repository
91
+ cd AmaniQuery
92
+
93
+ # Copy example config
94
+ cp gateway.example.yaml gateway.yaml
95
+
96
+ # Run with Docker Compose
97
+ cd deployments/docker
98
+ docker-compose up -d api-gateway
99
+ ```
100
+
101
+ ### Configuration
102
+
103
+ See `gateway.example.yaml` for all options. Key settings:
104
+
105
+ ```yaml
106
+ server:
107
+ bindAddr: ":8443"
108
+
109
+ auth:
110
+ jwtSecret: "${JWT_SECRET}"
111
+
112
+ cache:
113
+ redisAddr: "redis:6379"
114
+ ```
115
+
116
+ Environment variables override config with `GATEWAY_` prefix.
117
+
118
+ ## API Endpoints
119
+
120
+ ### Queries
121
+ | Method | Path | Description |
122
+ |--------|------|-------------|
123
+ | POST | `/v2/queries` | Execute RAG query |
124
+ | GET | `/v2/queries/{id}` | Get async query result |
125
+ | WS | `/v2/queries/stream` | Streaming query |
126
+
127
+ ### Agents
128
+ | Method | Path | Description |
129
+ |--------|------|-------------|
130
+ | POST | `/v2/agents` | Create agent |
131
+ | GET | `/v2/agents/{id}` | Get agent |
132
+ | DELETE | `/v2/agents/{id}` | Delete agent |
133
+ | POST | `/v2/agents/{id}/execute` | Execute plan |
134
+
135
+ ### Memory
136
+ | Method | Path | Description |
137
+ |--------|------|-------------|
138
+ | GET | `/v2/memory/context` | Get context window |
139
+ | POST | `/v2/memory/sessions/{id}/consolidate` | Consolidate memory |
140
+
141
+ ### Admin
142
+ | Method | Path | Description |
143
+ |--------|------|-------------|
144
+ | GET | `/admin/health` | Health check |
145
+ | GET | `/admin/metrics` | Prometheus metrics |
146
+
147
+ ## WebSocket Protocol
148
+
149
+ ```javascript
150
+ // Connect
151
+ const ws = new WebSocket('wss://api.example.com/v2/queries/stream?token=JWT');
152
+
153
+ // Send query
154
+ ws.send(JSON.stringify({
155
+ type: 'query',
156
+ payload: { query: 'What is RAG?', userId: 'user-123' }
157
+ }));
158
+
159
+ // Receive chunks
160
+ ws.onmessage = (e) => {
161
+ const msg = JSON.parse(e.data);
162
+ if (msg.type === 'chunk') console.log(msg.data);
163
+ if (msg.type === 'done') console.log('Complete');
164
+ };
165
+ ```
166
+
167
+ ## Deployment
168
+
169
+ ### Docker
170
+ ```bash
171
+ docker build -f deployments/docker/Dockerfile.gateway -t api-gateway .
172
+ docker run -p 8443:8443 api-gateway
173
+ ```
174
+
175
+ ### Kubernetes
176
+ ```bash
177
+ kubectl apply -f deployments/k8s/gateway.yaml
178
+ ```
179
+
180
+ ## Project Structure
181
+
182
+ ```
183
+ internal/gateway/
184
+ ├── config.go # Configuration
185
+ ├── gateway.go # Main server
186
+ ├── types.go # Request/response types
187
+ ├── cache/
188
+ │ └── cache.go # Redis cache
189
+ ├── handlers/
190
+ │ ├── query.go # Query endpoints
191
+ │ ├── websocket.go # WebSocket streaming
192
+ │ ├── agent.go # Agent CRUD
193
+ │ ├── memory.go # Memory endpoints
194
+ │ ├── admin.go # Health/metrics
195
+ │ └���─ auth.go # Token endpoint
196
+ ├── middleware/
197
+ │ ├── cors.go # CORS handling
198
+ │ ├── ratelimit.go # Rate limiting
199
+ │ ├── auth.go # JWT + OPA auth
200
+ │ ├── audit.go # Audit logging
201
+ │ └── tracing.go # OpenTelemetry
202
+ ├── observability/
203
+ │ └── metrics.go # Prometheus metrics
204
+ └── services/
205
+ ├── registry.go # Service discovery
206
+ └── clients.go # gRPC clients
207
+ ```
208
+
209
+ ## License
210
+
211
+ Apache 2.0
gateway.example.yaml ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gateway Configuration Example
2
+ server:
3
+ bindAddr: ":8443"
4
+ readTimeout: 30s
5
+ writeTimeout: 60s
6
+ idleTimeout: 120s
7
+ shutdownTimeout: 30s
8
+ maxHeaderBytes: 1048576
9
+ enableHttp2: true
10
+
11
+ tls:
12
+ enabled: false
13
+ certFile: "/certs/tls.crt"
14
+ keyFile: "/certs/tls.key"
15
+ minVersion: "1.2"
16
+
17
+ rateLimit:
18
+ enabled: true
19
+ requestsPerSec: 100
20
+ burstSize: 200
21
+ perTenant: true
22
+ perUser: true
23
+ redisEnabled: true
24
+ cleanupInterval: 10m
25
+
26
+ cors:
27
+ allowedOrigins:
28
+ - "https://app.rag-agent.io"
29
+ - "https://admin.rag-agent.io"
30
+ - "http://localhost:3000"
31
+ allowedMethods:
32
+ - "GET"
33
+ - "POST"
34
+ - "PUT"
35
+ - "DELETE"
36
+ - "OPTIONS"
37
+ allowedHeaders:
38
+ - "Authorization"
39
+ - "Content-Type"
40
+ - "X-Request-ID"
41
+ - "X-Client-Version"
42
+ allowCredentials: true
43
+ maxAge: 3600
44
+
45
+ auth:
46
+ jwtSecret: "${JWT_SECRET}"
47
+ jwtIssuer: "rag-agent"
48
+ jwtAudience: "api"
49
+ tokenDuration: 24h
50
+ opaEnabled: false
51
+ opaAddr: "http://opa:8181"
52
+ opaPolicy: "authz/allow"
53
+ skipPaths:
54
+ - "/admin/health"
55
+ - "/admin/metrics"
56
+
57
+ cache:
58
+ enabled: true
59
+ redisAddr: "redis:6379"
60
+ redisPassword: ""
61
+ redisDb: 0
62
+ defaultTtl: 5m
63
+ maxEntrySize: 1048576
64
+ keyPrefix: "rag:gateway:"
65
+
66
+ serviceDiscovery:
67
+ enabled: false
68
+ consulAddr: "consul:8500"
69
+ consulToken: ""
70
+ serviceRefreshInterval: 30s
71
+ agentServiceAddr: "agent-server:9090"
72
+ retrieverServiceAddr: "retriever-server:9091"
73
+ generatorServiceAddr: "generator-server:9092"
74
+ memoryServiceAddr: "memory-server:9093"
75
+
76
+ circuitBreaker:
77
+ maxRequests: 5
78
+ interval: 60s
79
+ timeout: 30s
80
+ failureThreshold: 3
81
+
82
+ observability:
83
+ metricsEnabled: true
84
+ metricsPath: "/admin/metrics"
85
+ tracingEnabled: true
86
+ tracingEndpoint: "jaeger:4317"
87
+ serviceName: "api-gateway"
88
+ auditLogEnabled: true
89
+
90
+ websocket:
91
+ readBufferSize: 1024
92
+ writeBufferSize: 1024
93
+ pingInterval: 30s
94
+ pongWait: 60s
95
+ writeWait: 10s
96
+ maxMessageSize: 524288
generator.pb.go ADDED
@@ -0,0 +1,1414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Code generated by protoc-gen-go. DO NOT EDIT.
2
+ // versions:
3
+ // protoc-gen-go v1.36.11
4
+ // protoc v6.33.2
5
+ // source: generator.proto
6
+
7
+ package ragv1
8
+
9
+ import (
10
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12
+ reflect "reflect"
13
+ sync "sync"
14
+ unsafe "unsafe"
15
+ )
16
+
17
+ const (
18
+ // Verify that this generated code is sufficiently up-to-date.
19
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
20
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
21
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
22
+ )
23
+
24
+ // GenerateRequest for LLM generation
25
+ type GenerateRequest struct {
26
+ state protoimpl.MessageState `protogen:"open.v1"`
27
+ // System prompt
28
+ SystemPrompt string `protobuf:"bytes,1,opt,name=system_prompt,json=systemPrompt,proto3" json:"system_prompt,omitempty"`
29
+ // User query/prompt
30
+ Prompt string `protobuf:"bytes,2,opt,name=prompt,proto3" json:"prompt,omitempty"`
31
+ // Context from retrieved documents
32
+ Context []*ContextDocument `protobuf:"bytes,3,rep,name=context,proto3" json:"context,omitempty"`
33
+ // Conversation history
34
+ History []*ChatMessage `protobuf:"bytes,4,rep,name=history,proto3" json:"history,omitempty"`
35
+ // Generation configuration
36
+ Config *GenerateConfig `protobuf:"bytes,5,opt,name=config,proto3" json:"config,omitempty"`
37
+ unknownFields protoimpl.UnknownFields
38
+ sizeCache protoimpl.SizeCache
39
+ }
40
+
41
+ func (x *GenerateRequest) Reset() {
42
+ *x = GenerateRequest{}
43
+ mi := &file_generator_proto_msgTypes[0]
44
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
45
+ ms.StoreMessageInfo(mi)
46
+ }
47
+
48
+ func (x *GenerateRequest) String() string {
49
+ return protoimpl.X.MessageStringOf(x)
50
+ }
51
+
52
+ func (*GenerateRequest) ProtoMessage() {}
53
+
54
+ func (x *GenerateRequest) ProtoReflect() protoreflect.Message {
55
+ mi := &file_generator_proto_msgTypes[0]
56
+ if x != nil {
57
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
58
+ if ms.LoadMessageInfo() == nil {
59
+ ms.StoreMessageInfo(mi)
60
+ }
61
+ return ms
62
+ }
63
+ return mi.MessageOf(x)
64
+ }
65
+
66
+ // Deprecated: Use GenerateRequest.ProtoReflect.Descriptor instead.
67
+ func (*GenerateRequest) Descriptor() ([]byte, []int) {
68
+ return file_generator_proto_rawDescGZIP(), []int{0}
69
+ }
70
+
71
+ func (x *GenerateRequest) GetSystemPrompt() string {
72
+ if x != nil {
73
+ return x.SystemPrompt
74
+ }
75
+ return ""
76
+ }
77
+
78
+ func (x *GenerateRequest) GetPrompt() string {
79
+ if x != nil {
80
+ return x.Prompt
81
+ }
82
+ return ""
83
+ }
84
+
85
+ func (x *GenerateRequest) GetContext() []*ContextDocument {
86
+ if x != nil {
87
+ return x.Context
88
+ }
89
+ return nil
90
+ }
91
+
92
+ func (x *GenerateRequest) GetHistory() []*ChatMessage {
93
+ if x != nil {
94
+ return x.History
95
+ }
96
+ return nil
97
+ }
98
+
99
+ func (x *GenerateRequest) GetConfig() *GenerateConfig {
100
+ if x != nil {
101
+ return x.Config
102
+ }
103
+ return nil
104
+ }
105
+
106
+ // ContextDocument represents retrieved context
107
+ type ContextDocument struct {
108
+ state protoimpl.MessageState `protogen:"open.v1"`
109
+ // Document content
110
+ Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"`
111
+ // Document title
112
+ Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
113
+ // Source reference
114
+ Source string `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty"`
115
+ // Relevance score
116
+ Score float32 `protobuf:"fixed32,4,opt,name=score,proto3" json:"score,omitempty"`
117
+ unknownFields protoimpl.UnknownFields
118
+ sizeCache protoimpl.SizeCache
119
+ }
120
+
121
+ func (x *ContextDocument) Reset() {
122
+ *x = ContextDocument{}
123
+ mi := &file_generator_proto_msgTypes[1]
124
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
125
+ ms.StoreMessageInfo(mi)
126
+ }
127
+
128
+ func (x *ContextDocument) String() string {
129
+ return protoimpl.X.MessageStringOf(x)
130
+ }
131
+
132
+ func (*ContextDocument) ProtoMessage() {}
133
+
134
+ func (x *ContextDocument) ProtoReflect() protoreflect.Message {
135
+ mi := &file_generator_proto_msgTypes[1]
136
+ if x != nil {
137
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
138
+ if ms.LoadMessageInfo() == nil {
139
+ ms.StoreMessageInfo(mi)
140
+ }
141
+ return ms
142
+ }
143
+ return mi.MessageOf(x)
144
+ }
145
+
146
+ // Deprecated: Use ContextDocument.ProtoReflect.Descriptor instead.
147
+ func (*ContextDocument) Descriptor() ([]byte, []int) {
148
+ return file_generator_proto_rawDescGZIP(), []int{1}
149
+ }
150
+
151
+ func (x *ContextDocument) GetContent() string {
152
+ if x != nil {
153
+ return x.Content
154
+ }
155
+ return ""
156
+ }
157
+
158
+ func (x *ContextDocument) GetTitle() string {
159
+ if x != nil {
160
+ return x.Title
161
+ }
162
+ return ""
163
+ }
164
+
165
+ func (x *ContextDocument) GetSource() string {
166
+ if x != nil {
167
+ return x.Source
168
+ }
169
+ return ""
170
+ }
171
+
172
+ func (x *ContextDocument) GetScore() float32 {
173
+ if x != nil {
174
+ return x.Score
175
+ }
176
+ return 0
177
+ }
178
+
179
+ // ChatMessage for conversation history
180
+ type ChatMessage struct {
181
+ state protoimpl.MessageState `protogen:"open.v1"`
182
+ // Role: user, assistant, system
183
+ Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"`
184
+ // Message content
185
+ Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
186
+ unknownFields protoimpl.UnknownFields
187
+ sizeCache protoimpl.SizeCache
188
+ }
189
+
190
+ func (x *ChatMessage) Reset() {
191
+ *x = ChatMessage{}
192
+ mi := &file_generator_proto_msgTypes[2]
193
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
194
+ ms.StoreMessageInfo(mi)
195
+ }
196
+
197
+ func (x *ChatMessage) String() string {
198
+ return protoimpl.X.MessageStringOf(x)
199
+ }
200
+
201
+ func (*ChatMessage) ProtoMessage() {}
202
+
203
+ func (x *ChatMessage) ProtoReflect() protoreflect.Message {
204
+ mi := &file_generator_proto_msgTypes[2]
205
+ if x != nil {
206
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
207
+ if ms.LoadMessageInfo() == nil {
208
+ ms.StoreMessageInfo(mi)
209
+ }
210
+ return ms
211
+ }
212
+ return mi.MessageOf(x)
213
+ }
214
+
215
+ // Deprecated: Use ChatMessage.ProtoReflect.Descriptor instead.
216
+ func (*ChatMessage) Descriptor() ([]byte, []int) {
217
+ return file_generator_proto_rawDescGZIP(), []int{2}
218
+ }
219
+
220
+ func (x *ChatMessage) GetRole() string {
221
+ if x != nil {
222
+ return x.Role
223
+ }
224
+ return ""
225
+ }
226
+
227
+ func (x *ChatMessage) GetContent() string {
228
+ if x != nil {
229
+ return x.Content
230
+ }
231
+ return ""
232
+ }
233
+
234
+ // GenerateConfig for generation parameters
235
+ type GenerateConfig struct {
236
+ state protoimpl.MessageState `protogen:"open.v1"`
237
+ // Model to use
238
+ Model string `protobuf:"bytes,1,opt,name=model,proto3" json:"model,omitempty"`
239
+ // Temperature (0.0 - 2.0)
240
+ Temperature float32 `protobuf:"fixed32,2,opt,name=temperature,proto3" json:"temperature,omitempty"`
241
+ // Maximum tokens to generate
242
+ MaxTokens int32 `protobuf:"varint,3,opt,name=max_tokens,json=maxTokens,proto3" json:"max_tokens,omitempty"`
243
+ // Top-p sampling
244
+ TopP float32 `protobuf:"fixed32,4,opt,name=top_p,json=topP,proto3" json:"top_p,omitempty"`
245
+ // Frequency penalty
246
+ FrequencyPenalty float32 `protobuf:"fixed32,5,opt,name=frequency_penalty,json=frequencyPenalty,proto3" json:"frequency_penalty,omitempty"`
247
+ // Presence penalty
248
+ PresencePenalty float32 `protobuf:"fixed32,6,opt,name=presence_penalty,json=presencePenalty,proto3" json:"presence_penalty,omitempty"`
249
+ // Stop sequences
250
+ StopSequences []string `protobuf:"bytes,7,rep,name=stop_sequences,json=stopSequences,proto3" json:"stop_sequences,omitempty"`
251
+ // Response format: text, json
252
+ ResponseFormat string `protobuf:"bytes,8,opt,name=response_format,json=responseFormat,proto3" json:"response_format,omitempty"`
253
+ unknownFields protoimpl.UnknownFields
254
+ sizeCache protoimpl.SizeCache
255
+ }
256
+
257
+ func (x *GenerateConfig) Reset() {
258
+ *x = GenerateConfig{}
259
+ mi := &file_generator_proto_msgTypes[3]
260
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
261
+ ms.StoreMessageInfo(mi)
262
+ }
263
+
264
+ func (x *GenerateConfig) String() string {
265
+ return protoimpl.X.MessageStringOf(x)
266
+ }
267
+
268
+ func (*GenerateConfig) ProtoMessage() {}
269
+
270
+ func (x *GenerateConfig) ProtoReflect() protoreflect.Message {
271
+ mi := &file_generator_proto_msgTypes[3]
272
+ if x != nil {
273
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
274
+ if ms.LoadMessageInfo() == nil {
275
+ ms.StoreMessageInfo(mi)
276
+ }
277
+ return ms
278
+ }
279
+ return mi.MessageOf(x)
280
+ }
281
+
282
+ // Deprecated: Use GenerateConfig.ProtoReflect.Descriptor instead.
283
+ func (*GenerateConfig) Descriptor() ([]byte, []int) {
284
+ return file_generator_proto_rawDescGZIP(), []int{3}
285
+ }
286
+
287
+ func (x *GenerateConfig) GetModel() string {
288
+ if x != nil {
289
+ return x.Model
290
+ }
291
+ return ""
292
+ }
293
+
294
+ func (x *GenerateConfig) GetTemperature() float32 {
295
+ if x != nil {
296
+ return x.Temperature
297
+ }
298
+ return 0
299
+ }
300
+
301
+ func (x *GenerateConfig) GetMaxTokens() int32 {
302
+ if x != nil {
303
+ return x.MaxTokens
304
+ }
305
+ return 0
306
+ }
307
+
308
+ func (x *GenerateConfig) GetTopP() float32 {
309
+ if x != nil {
310
+ return x.TopP
311
+ }
312
+ return 0
313
+ }
314
+
315
+ func (x *GenerateConfig) GetFrequencyPenalty() float32 {
316
+ if x != nil {
317
+ return x.FrequencyPenalty
318
+ }
319
+ return 0
320
+ }
321
+
322
+ func (x *GenerateConfig) GetPresencePenalty() float32 {
323
+ if x != nil {
324
+ return x.PresencePenalty
325
+ }
326
+ return 0
327
+ }
328
+
329
+ func (x *GenerateConfig) GetStopSequences() []string {
330
+ if x != nil {
331
+ return x.StopSequences
332
+ }
333
+ return nil
334
+ }
335
+
336
+ func (x *GenerateConfig) GetResponseFormat() string {
337
+ if x != nil {
338
+ return x.ResponseFormat
339
+ }
340
+ return ""
341
+ }
342
+
343
+ // GenerateResponse contains the generated text
344
+ type GenerateResponse struct {
345
+ state protoimpl.MessageState `protogen:"open.v1"`
346
+ // Generated text
347
+ Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"`
348
+ // Token usage
349
+ Usage *TokenUsage `protobuf:"bytes,2,opt,name=usage,proto3" json:"usage,omitempty"`
350
+ // Finish reason: stop, length, content_filter
351
+ FinishReason string `protobuf:"bytes,3,opt,name=finish_reason,json=finishReason,proto3" json:"finish_reason,omitempty"`
352
+ // Model used
353
+ Model string `protobuf:"bytes,4,opt,name=model,proto3" json:"model,omitempty"`
354
+ // Generation metadata
355
+ Metadata *GenerateMetadata `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"`
356
+ unknownFields protoimpl.UnknownFields
357
+ sizeCache protoimpl.SizeCache
358
+ }
359
+
360
+ func (x *GenerateResponse) Reset() {
361
+ *x = GenerateResponse{}
362
+ mi := &file_generator_proto_msgTypes[4]
363
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
364
+ ms.StoreMessageInfo(mi)
365
+ }
366
+
367
+ func (x *GenerateResponse) String() string {
368
+ return protoimpl.X.MessageStringOf(x)
369
+ }
370
+
371
+ func (*GenerateResponse) ProtoMessage() {}
372
+
373
+ func (x *GenerateResponse) ProtoReflect() protoreflect.Message {
374
+ mi := &file_generator_proto_msgTypes[4]
375
+ if x != nil {
376
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
377
+ if ms.LoadMessageInfo() == nil {
378
+ ms.StoreMessageInfo(mi)
379
+ }
380
+ return ms
381
+ }
382
+ return mi.MessageOf(x)
383
+ }
384
+
385
+ // Deprecated: Use GenerateResponse.ProtoReflect.Descriptor instead.
386
+ func (*GenerateResponse) Descriptor() ([]byte, []int) {
387
+ return file_generator_proto_rawDescGZIP(), []int{4}
388
+ }
389
+
390
+ func (x *GenerateResponse) GetText() string {
391
+ if x != nil {
392
+ return x.Text
393
+ }
394
+ return ""
395
+ }
396
+
397
+ func (x *GenerateResponse) GetUsage() *TokenUsage {
398
+ if x != nil {
399
+ return x.Usage
400
+ }
401
+ return nil
402
+ }
403
+
404
+ func (x *GenerateResponse) GetFinishReason() string {
405
+ if x != nil {
406
+ return x.FinishReason
407
+ }
408
+ return ""
409
+ }
410
+
411
+ func (x *GenerateResponse) GetModel() string {
412
+ if x != nil {
413
+ return x.Model
414
+ }
415
+ return ""
416
+ }
417
+
418
+ func (x *GenerateResponse) GetMetadata() *GenerateMetadata {
419
+ if x != nil {
420
+ return x.Metadata
421
+ }
422
+ return nil
423
+ }
424
+
425
+ // TokenUsage tracks token consumption
426
+ type TokenUsage struct {
427
+ state protoimpl.MessageState `protogen:"open.v1"`
428
+ // Prompt tokens
429
+ PromptTokens int32 `protobuf:"varint,1,opt,name=prompt_tokens,json=promptTokens,proto3" json:"prompt_tokens,omitempty"`
430
+ // Completion tokens
431
+ CompletionTokens int32 `protobuf:"varint,2,opt,name=completion_tokens,json=completionTokens,proto3" json:"completion_tokens,omitempty"`
432
+ // Total tokens
433
+ TotalTokens int32 `protobuf:"varint,3,opt,name=total_tokens,json=totalTokens,proto3" json:"total_tokens,omitempty"`
434
+ // Estimated cost in USD
435
+ EstimatedCost float32 `protobuf:"fixed32,4,opt,name=estimated_cost,json=estimatedCost,proto3" json:"estimated_cost,omitempty"`
436
+ unknownFields protoimpl.UnknownFields
437
+ sizeCache protoimpl.SizeCache
438
+ }
439
+
440
+ func (x *TokenUsage) Reset() {
441
+ *x = TokenUsage{}
442
+ mi := &file_generator_proto_msgTypes[5]
443
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
444
+ ms.StoreMessageInfo(mi)
445
+ }
446
+
447
+ func (x *TokenUsage) String() string {
448
+ return protoimpl.X.MessageStringOf(x)
449
+ }
450
+
451
+ func (*TokenUsage) ProtoMessage() {}
452
+
453
+ func (x *TokenUsage) ProtoReflect() protoreflect.Message {
454
+ mi := &file_generator_proto_msgTypes[5]
455
+ if x != nil {
456
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
457
+ if ms.LoadMessageInfo() == nil {
458
+ ms.StoreMessageInfo(mi)
459
+ }
460
+ return ms
461
+ }
462
+ return mi.MessageOf(x)
463
+ }
464
+
465
+ // Deprecated: Use TokenUsage.ProtoReflect.Descriptor instead.
466
+ func (*TokenUsage) Descriptor() ([]byte, []int) {
467
+ return file_generator_proto_rawDescGZIP(), []int{5}
468
+ }
469
+
470
+ func (x *TokenUsage) GetPromptTokens() int32 {
471
+ if x != nil {
472
+ return x.PromptTokens
473
+ }
474
+ return 0
475
+ }
476
+
477
+ func (x *TokenUsage) GetCompletionTokens() int32 {
478
+ if x != nil {
479
+ return x.CompletionTokens
480
+ }
481
+ return 0
482
+ }
483
+
484
+ func (x *TokenUsage) GetTotalTokens() int32 {
485
+ if x != nil {
486
+ return x.TotalTokens
487
+ }
488
+ return 0
489
+ }
490
+
491
+ func (x *TokenUsage) GetEstimatedCost() float32 {
492
+ if x != nil {
493
+ return x.EstimatedCost
494
+ }
495
+ return 0
496
+ }
497
+
498
+ // GenerateMetadata for generation info
499
+ type GenerateMetadata struct {
500
+ state protoimpl.MessageState `protogen:"open.v1"`
501
+ // Latency in milliseconds
502
+ LatencyMs int64 `protobuf:"varint,1,opt,name=latency_ms,json=latencyMs,proto3" json:"latency_ms,omitempty"`
503
+ // Provider used
504
+ Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
505
+ // Trace ID
506
+ TraceId string `protobuf:"bytes,3,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"`
507
+ unknownFields protoimpl.UnknownFields
508
+ sizeCache protoimpl.SizeCache
509
+ }
510
+
511
+ func (x *GenerateMetadata) Reset() {
512
+ *x = GenerateMetadata{}
513
+ mi := &file_generator_proto_msgTypes[6]
514
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
515
+ ms.StoreMessageInfo(mi)
516
+ }
517
+
518
+ func (x *GenerateMetadata) String() string {
519
+ return protoimpl.X.MessageStringOf(x)
520
+ }
521
+
522
+ func (*GenerateMetadata) ProtoMessage() {}
523
+
524
+ func (x *GenerateMetadata) ProtoReflect() protoreflect.Message {
525
+ mi := &file_generator_proto_msgTypes[6]
526
+ if x != nil {
527
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
528
+ if ms.LoadMessageInfo() == nil {
529
+ ms.StoreMessageInfo(mi)
530
+ }
531
+ return ms
532
+ }
533
+ return mi.MessageOf(x)
534
+ }
535
+
536
+ // Deprecated: Use GenerateMetadata.ProtoReflect.Descriptor instead.
537
+ func (*GenerateMetadata) Descriptor() ([]byte, []int) {
538
+ return file_generator_proto_rawDescGZIP(), []int{6}
539
+ }
540
+
541
+ func (x *GenerateMetadata) GetLatencyMs() int64 {
542
+ if x != nil {
543
+ return x.LatencyMs
544
+ }
545
+ return 0
546
+ }
547
+
548
+ func (x *GenerateMetadata) GetProvider() string {
549
+ if x != nil {
550
+ return x.Provider
551
+ }
552
+ return ""
553
+ }
554
+
555
+ func (x *GenerateMetadata) GetTraceId() string {
556
+ if x != nil {
557
+ return x.TraceId
558
+ }
559
+ return ""
560
+ }
561
+
562
+ // GenerateChunk for streaming responses
563
+ type GenerateChunk struct {
564
+ state protoimpl.MessageState `protogen:"open.v1"`
565
+ // Text delta
566
+ Delta string `protobuf:"bytes,1,opt,name=delta,proto3" json:"delta,omitempty"`
567
+ // Is this the final chunk?
568
+ IsFinal bool `protobuf:"varint,2,opt,name=is_final,json=isFinal,proto3" json:"is_final,omitempty"`
569
+ // Token usage (in final chunk)
570
+ Usage *TokenUsage `protobuf:"bytes,3,opt,name=usage,proto3" json:"usage,omitempty"`
571
+ // Finish reason (in final chunk)
572
+ FinishReason string `protobuf:"bytes,4,opt,name=finish_reason,json=finishReason,proto3" json:"finish_reason,omitempty"`
573
+ unknownFields protoimpl.UnknownFields
574
+ sizeCache protoimpl.SizeCache
575
+ }
576
+
577
+ func (x *GenerateChunk) Reset() {
578
+ *x = GenerateChunk{}
579
+ mi := &file_generator_proto_msgTypes[7]
580
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
581
+ ms.StoreMessageInfo(mi)
582
+ }
583
+
584
+ func (x *GenerateChunk) String() string {
585
+ return protoimpl.X.MessageStringOf(x)
586
+ }
587
+
588
+ func (*GenerateChunk) ProtoMessage() {}
589
+
590
+ func (x *GenerateChunk) ProtoReflect() protoreflect.Message {
591
+ mi := &file_generator_proto_msgTypes[7]
592
+ if x != nil {
593
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
594
+ if ms.LoadMessageInfo() == nil {
595
+ ms.StoreMessageInfo(mi)
596
+ }
597
+ return ms
598
+ }
599
+ return mi.MessageOf(x)
600
+ }
601
+
602
+ // Deprecated: Use GenerateChunk.ProtoReflect.Descriptor instead.
603
+ func (*GenerateChunk) Descriptor() ([]byte, []int) {
604
+ return file_generator_proto_rawDescGZIP(), []int{7}
605
+ }
606
+
607
+ func (x *GenerateChunk) GetDelta() string {
608
+ if x != nil {
609
+ return x.Delta
610
+ }
611
+ return ""
612
+ }
613
+
614
+ func (x *GenerateChunk) GetIsFinal() bool {
615
+ if x != nil {
616
+ return x.IsFinal
617
+ }
618
+ return false
619
+ }
620
+
621
+ func (x *GenerateChunk) GetUsage() *TokenUsage {
622
+ if x != nil {
623
+ return x.Usage
624
+ }
625
+ return nil
626
+ }
627
+
628
+ func (x *GenerateChunk) GetFinishReason() string {
629
+ if x != nil {
630
+ return x.FinishReason
631
+ }
632
+ return ""
633
+ }
634
+
635
+ // EmbeddingRequest for generating embeddings
636
+ type EmbeddingRequest struct {
637
+ state protoimpl.MessageState `protogen:"open.v1"`
638
+ // Text to embed
639
+ Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"`
640
+ // Model to use
641
+ Model string `protobuf:"bytes,2,opt,name=model,proto3" json:"model,omitempty"`
642
+ // Embedding dimensions (if configurable)
643
+ Dimensions int32 `protobuf:"varint,3,opt,name=dimensions,proto3" json:"dimensions,omitempty"`
644
+ unknownFields protoimpl.UnknownFields
645
+ sizeCache protoimpl.SizeCache
646
+ }
647
+
648
+ func (x *EmbeddingRequest) Reset() {
649
+ *x = EmbeddingRequest{}
650
+ mi := &file_generator_proto_msgTypes[8]
651
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
652
+ ms.StoreMessageInfo(mi)
653
+ }
654
+
655
+ func (x *EmbeddingRequest) String() string {
656
+ return protoimpl.X.MessageStringOf(x)
657
+ }
658
+
659
+ func (*EmbeddingRequest) ProtoMessage() {}
660
+
661
+ func (x *EmbeddingRequest) ProtoReflect() protoreflect.Message {
662
+ mi := &file_generator_proto_msgTypes[8]
663
+ if x != nil {
664
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
665
+ if ms.LoadMessageInfo() == nil {
666
+ ms.StoreMessageInfo(mi)
667
+ }
668
+ return ms
669
+ }
670
+ return mi.MessageOf(x)
671
+ }
672
+
673
+ // Deprecated: Use EmbeddingRequest.ProtoReflect.Descriptor instead.
674
+ func (*EmbeddingRequest) Descriptor() ([]byte, []int) {
675
+ return file_generator_proto_rawDescGZIP(), []int{8}
676
+ }
677
+
678
+ func (x *EmbeddingRequest) GetText() string {
679
+ if x != nil {
680
+ return x.Text
681
+ }
682
+ return ""
683
+ }
684
+
685
+ func (x *EmbeddingRequest) GetModel() string {
686
+ if x != nil {
687
+ return x.Model
688
+ }
689
+ return ""
690
+ }
691
+
692
+ func (x *EmbeddingRequest) GetDimensions() int32 {
693
+ if x != nil {
694
+ return x.Dimensions
695
+ }
696
+ return 0
697
+ }
698
+
699
+ // EmbeddingResponse contains the embedding
700
+ type EmbeddingResponse struct {
701
+ state protoimpl.MessageState `protogen:"open.v1"`
702
+ // Embedding vector
703
+ Embedding []float32 `protobuf:"fixed32,1,rep,packed,name=embedding,proto3" json:"embedding,omitempty"`
704
+ // Model used
705
+ Model string `protobuf:"bytes,2,opt,name=model,proto3" json:"model,omitempty"`
706
+ // Token usage
707
+ Tokens int32 `protobuf:"varint,3,opt,name=tokens,proto3" json:"tokens,omitempty"`
708
+ // Dimensions
709
+ Dimensions int32 `protobuf:"varint,4,opt,name=dimensions,proto3" json:"dimensions,omitempty"`
710
+ unknownFields protoimpl.UnknownFields
711
+ sizeCache protoimpl.SizeCache
712
+ }
713
+
714
+ func (x *EmbeddingResponse) Reset() {
715
+ *x = EmbeddingResponse{}
716
+ mi := &file_generator_proto_msgTypes[9]
717
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
718
+ ms.StoreMessageInfo(mi)
719
+ }
720
+
721
+ func (x *EmbeddingResponse) String() string {
722
+ return protoimpl.X.MessageStringOf(x)
723
+ }
724
+
725
+ func (*EmbeddingResponse) ProtoMessage() {}
726
+
727
+ func (x *EmbeddingResponse) ProtoReflect() protoreflect.Message {
728
+ mi := &file_generator_proto_msgTypes[9]
729
+ if x != nil {
730
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
731
+ if ms.LoadMessageInfo() == nil {
732
+ ms.StoreMessageInfo(mi)
733
+ }
734
+ return ms
735
+ }
736
+ return mi.MessageOf(x)
737
+ }
738
+
739
+ // Deprecated: Use EmbeddingResponse.ProtoReflect.Descriptor instead.
740
+ func (*EmbeddingResponse) Descriptor() ([]byte, []int) {
741
+ return file_generator_proto_rawDescGZIP(), []int{9}
742
+ }
743
+
744
+ func (x *EmbeddingResponse) GetEmbedding() []float32 {
745
+ if x != nil {
746
+ return x.Embedding
747
+ }
748
+ return nil
749
+ }
750
+
751
+ func (x *EmbeddingResponse) GetModel() string {
752
+ if x != nil {
753
+ return x.Model
754
+ }
755
+ return ""
756
+ }
757
+
758
+ func (x *EmbeddingResponse) GetTokens() int32 {
759
+ if x != nil {
760
+ return x.Tokens
761
+ }
762
+ return 0
763
+ }
764
+
765
+ func (x *EmbeddingResponse) GetDimensions() int32 {
766
+ if x != nil {
767
+ return x.Dimensions
768
+ }
769
+ return 0
770
+ }
771
+
772
+ // BatchEmbeddingRequest for bulk embeddings
773
+ type BatchEmbeddingRequest struct {
774
+ state protoimpl.MessageState `protogen:"open.v1"`
775
+ // Texts to embed
776
+ Texts []string `protobuf:"bytes,1,rep,name=texts,proto3" json:"texts,omitempty"`
777
+ // Model to use
778
+ Model string `protobuf:"bytes,2,opt,name=model,proto3" json:"model,omitempty"`
779
+ // Embedding dimensions
780
+ Dimensions int32 `protobuf:"varint,3,opt,name=dimensions,proto3" json:"dimensions,omitempty"`
781
+ unknownFields protoimpl.UnknownFields
782
+ sizeCache protoimpl.SizeCache
783
+ }
784
+
785
+ func (x *BatchEmbeddingRequest) Reset() {
786
+ *x = BatchEmbeddingRequest{}
787
+ mi := &file_generator_proto_msgTypes[10]
788
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
789
+ ms.StoreMessageInfo(mi)
790
+ }
791
+
792
+ func (x *BatchEmbeddingRequest) String() string {
793
+ return protoimpl.X.MessageStringOf(x)
794
+ }
795
+
796
+ func (*BatchEmbeddingRequest) ProtoMessage() {}
797
+
798
+ func (x *BatchEmbeddingRequest) ProtoReflect() protoreflect.Message {
799
+ mi := &file_generator_proto_msgTypes[10]
800
+ if x != nil {
801
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
802
+ if ms.LoadMessageInfo() == nil {
803
+ ms.StoreMessageInfo(mi)
804
+ }
805
+ return ms
806
+ }
807
+ return mi.MessageOf(x)
808
+ }
809
+
810
+ // Deprecated: Use BatchEmbeddingRequest.ProtoReflect.Descriptor instead.
811
+ func (*BatchEmbeddingRequest) Descriptor() ([]byte, []int) {
812
+ return file_generator_proto_rawDescGZIP(), []int{10}
813
+ }
814
+
815
+ func (x *BatchEmbeddingRequest) GetTexts() []string {
816
+ if x != nil {
817
+ return x.Texts
818
+ }
819
+ return nil
820
+ }
821
+
822
+ func (x *BatchEmbeddingRequest) GetModel() string {
823
+ if x != nil {
824
+ return x.Model
825
+ }
826
+ return ""
827
+ }
828
+
829
+ func (x *BatchEmbeddingRequest) GetDimensions() int32 {
830
+ if x != nil {
831
+ return x.Dimensions
832
+ }
833
+ return 0
834
+ }
835
+
836
+ // BatchEmbeddingResponse for bulk embeddings
837
+ type BatchEmbeddingResponse struct {
838
+ state protoimpl.MessageState `protogen:"open.v1"`
839
+ // Embeddings
840
+ Embeddings []*EmbeddingResult `protobuf:"bytes,1,rep,name=embeddings,proto3" json:"embeddings,omitempty"`
841
+ // Total tokens used
842
+ TotalTokens int32 `protobuf:"varint,2,opt,name=total_tokens,json=totalTokens,proto3" json:"total_tokens,omitempty"`
843
+ // Model used
844
+ Model string `protobuf:"bytes,3,opt,name=model,proto3" json:"model,omitempty"`
845
+ unknownFields protoimpl.UnknownFields
846
+ sizeCache protoimpl.SizeCache
847
+ }
848
+
849
+ func (x *BatchEmbeddingResponse) Reset() {
850
+ *x = BatchEmbeddingResponse{}
851
+ mi := &file_generator_proto_msgTypes[11]
852
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
853
+ ms.StoreMessageInfo(mi)
854
+ }
855
+
856
+ func (x *BatchEmbeddingResponse) String() string {
857
+ return protoimpl.X.MessageStringOf(x)
858
+ }
859
+
860
+ func (*BatchEmbeddingResponse) ProtoMessage() {}
861
+
862
+ func (x *BatchEmbeddingResponse) ProtoReflect() protoreflect.Message {
863
+ mi := &file_generator_proto_msgTypes[11]
864
+ if x != nil {
865
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
866
+ if ms.LoadMessageInfo() == nil {
867
+ ms.StoreMessageInfo(mi)
868
+ }
869
+ return ms
870
+ }
871
+ return mi.MessageOf(x)
872
+ }
873
+
874
+ // Deprecated: Use BatchEmbeddingResponse.ProtoReflect.Descriptor instead.
875
+ func (*BatchEmbeddingResponse) Descriptor() ([]byte, []int) {
876
+ return file_generator_proto_rawDescGZIP(), []int{11}
877
+ }
878
+
879
+ func (x *BatchEmbeddingResponse) GetEmbeddings() []*EmbeddingResult {
880
+ if x != nil {
881
+ return x.Embeddings
882
+ }
883
+ return nil
884
+ }
885
+
886
+ func (x *BatchEmbeddingResponse) GetTotalTokens() int32 {
887
+ if x != nil {
888
+ return x.TotalTokens
889
+ }
890
+ return 0
891
+ }
892
+
893
+ func (x *BatchEmbeddingResponse) GetModel() string {
894
+ if x != nil {
895
+ return x.Model
896
+ }
897
+ return ""
898
+ }
899
+
900
+ // EmbeddingResult for individual embedding
901
+ type EmbeddingResult struct {
902
+ state protoimpl.MessageState `protogen:"open.v1"`
903
+ // Index in the batch
904
+ Index int32 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"`
905
+ // Embedding vector
906
+ Embedding []float32 `protobuf:"fixed32,2,rep,packed,name=embedding,proto3" json:"embedding,omitempty"`
907
+ // Tokens used
908
+ Tokens int32 `protobuf:"varint,3,opt,name=tokens,proto3" json:"tokens,omitempty"`
909
+ unknownFields protoimpl.UnknownFields
910
+ sizeCache protoimpl.SizeCache
911
+ }
912
+
913
+ func (x *EmbeddingResult) Reset() {
914
+ *x = EmbeddingResult{}
915
+ mi := &file_generator_proto_msgTypes[12]
916
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
917
+ ms.StoreMessageInfo(mi)
918
+ }
919
+
920
+ func (x *EmbeddingResult) String() string {
921
+ return protoimpl.X.MessageStringOf(x)
922
+ }
923
+
924
+ func (*EmbeddingResult) ProtoMessage() {}
925
+
926
+ func (x *EmbeddingResult) ProtoReflect() protoreflect.Message {
927
+ mi := &file_generator_proto_msgTypes[12]
928
+ if x != nil {
929
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
930
+ if ms.LoadMessageInfo() == nil {
931
+ ms.StoreMessageInfo(mi)
932
+ }
933
+ return ms
934
+ }
935
+ return mi.MessageOf(x)
936
+ }
937
+
938
+ // Deprecated: Use EmbeddingResult.ProtoReflect.Descriptor instead.
939
+ func (*EmbeddingResult) Descriptor() ([]byte, []int) {
940
+ return file_generator_proto_rawDescGZIP(), []int{12}
941
+ }
942
+
943
+ func (x *EmbeddingResult) GetIndex() int32 {
944
+ if x != nil {
945
+ return x.Index
946
+ }
947
+ return 0
948
+ }
949
+
950
+ func (x *EmbeddingResult) GetEmbedding() []float32 {
951
+ if x != nil {
952
+ return x.Embedding
953
+ }
954
+ return nil
955
+ }
956
+
957
+ func (x *EmbeddingResult) GetTokens() int32 {
958
+ if x != nil {
959
+ return x.Tokens
960
+ }
961
+ return 0
962
+ }
963
+
964
+ // RerankRequest for document reranking
965
+ type RerankRequest struct {
966
+ state protoimpl.MessageState `protogen:"open.v1"`
967
+ // Query for relevance scoring
968
+ Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`
969
+ // Documents to rerank
970
+ Documents []*RerankDocument `protobuf:"bytes,2,rep,name=documents,proto3" json:"documents,omitempty"`
971
+ // Number of top results to return
972
+ TopN int32 `protobuf:"varint,3,opt,name=top_n,json=topN,proto3" json:"top_n,omitempty"`
973
+ // Model to use for reranking
974
+ Model string `protobuf:"bytes,4,opt,name=model,proto3" json:"model,omitempty"`
975
+ unknownFields protoimpl.UnknownFields
976
+ sizeCache protoimpl.SizeCache
977
+ }
978
+
979
+ func (x *RerankRequest) Reset() {
980
+ *x = RerankRequest{}
981
+ mi := &file_generator_proto_msgTypes[13]
982
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
983
+ ms.StoreMessageInfo(mi)
984
+ }
985
+
986
+ func (x *RerankRequest) String() string {
987
+ return protoimpl.X.MessageStringOf(x)
988
+ }
989
+
990
+ func (*RerankRequest) ProtoMessage() {}
991
+
992
+ func (x *RerankRequest) ProtoReflect() protoreflect.Message {
993
+ mi := &file_generator_proto_msgTypes[13]
994
+ if x != nil {
995
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
996
+ if ms.LoadMessageInfo() == nil {
997
+ ms.StoreMessageInfo(mi)
998
+ }
999
+ return ms
1000
+ }
1001
+ return mi.MessageOf(x)
1002
+ }
1003
+
1004
+ // Deprecated: Use RerankRequest.ProtoReflect.Descriptor instead.
1005
+ func (*RerankRequest) Descriptor() ([]byte, []int) {
1006
+ return file_generator_proto_rawDescGZIP(), []int{13}
1007
+ }
1008
+
1009
+ func (x *RerankRequest) GetQuery() string {
1010
+ if x != nil {
1011
+ return x.Query
1012
+ }
1013
+ return ""
1014
+ }
1015
+
1016
+ func (x *RerankRequest) GetDocuments() []*RerankDocument {
1017
+ if x != nil {
1018
+ return x.Documents
1019
+ }
1020
+ return nil
1021
+ }
1022
+
1023
+ func (x *RerankRequest) GetTopN() int32 {
1024
+ if x != nil {
1025
+ return x.TopN
1026
+ }
1027
+ return 0
1028
+ }
1029
+
1030
+ func (x *RerankRequest) GetModel() string {
1031
+ if x != nil {
1032
+ return x.Model
1033
+ }
1034
+ return ""
1035
+ }
1036
+
1037
+ // RerankDocument for reranking input
1038
+ type RerankDocument struct {
1039
+ state protoimpl.MessageState `protogen:"open.v1"`
1040
+ // Document ID
1041
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
1042
+ // Document content
1043
+ Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
1044
+ // Original score (optional)
1045
+ OriginalScore float32 `protobuf:"fixed32,3,opt,name=original_score,json=originalScore,proto3" json:"original_score,omitempty"`
1046
+ unknownFields protoimpl.UnknownFields
1047
+ sizeCache protoimpl.SizeCache
1048
+ }
1049
+
1050
+ func (x *RerankDocument) Reset() {
1051
+ *x = RerankDocument{}
1052
+ mi := &file_generator_proto_msgTypes[14]
1053
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1054
+ ms.StoreMessageInfo(mi)
1055
+ }
1056
+
1057
+ func (x *RerankDocument) String() string {
1058
+ return protoimpl.X.MessageStringOf(x)
1059
+ }
1060
+
1061
+ func (*RerankDocument) ProtoMessage() {}
1062
+
1063
+ func (x *RerankDocument) ProtoReflect() protoreflect.Message {
1064
+ mi := &file_generator_proto_msgTypes[14]
1065
+ if x != nil {
1066
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1067
+ if ms.LoadMessageInfo() == nil {
1068
+ ms.StoreMessageInfo(mi)
1069
+ }
1070
+ return ms
1071
+ }
1072
+ return mi.MessageOf(x)
1073
+ }
1074
+
1075
+ // Deprecated: Use RerankDocument.ProtoReflect.Descriptor instead.
1076
+ func (*RerankDocument) Descriptor() ([]byte, []int) {
1077
+ return file_generator_proto_rawDescGZIP(), []int{14}
1078
+ }
1079
+
1080
+ func (x *RerankDocument) GetId() string {
1081
+ if x != nil {
1082
+ return x.Id
1083
+ }
1084
+ return ""
1085
+ }
1086
+
1087
+ func (x *RerankDocument) GetContent() string {
1088
+ if x != nil {
1089
+ return x.Content
1090
+ }
1091
+ return ""
1092
+ }
1093
+
1094
+ func (x *RerankDocument) GetOriginalScore() float32 {
1095
+ if x != nil {
1096
+ return x.OriginalScore
1097
+ }
1098
+ return 0
1099
+ }
1100
+
1101
+ // RerankResponse contains reranked documents
1102
+ type RerankResponse struct {
1103
+ state protoimpl.MessageState `protogen:"open.v1"`
1104
+ // Reranked results
1105
+ Results []*RerankResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
1106
+ // Model used
1107
+ Model string `protobuf:"bytes,2,opt,name=model,proto3" json:"model,omitempty"`
1108
+ unknownFields protoimpl.UnknownFields
1109
+ sizeCache protoimpl.SizeCache
1110
+ }
1111
+
1112
+ func (x *RerankResponse) Reset() {
1113
+ *x = RerankResponse{}
1114
+ mi := &file_generator_proto_msgTypes[15]
1115
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1116
+ ms.StoreMessageInfo(mi)
1117
+ }
1118
+
1119
+ func (x *RerankResponse) String() string {
1120
+ return protoimpl.X.MessageStringOf(x)
1121
+ }
1122
+
1123
+ func (*RerankResponse) ProtoMessage() {}
1124
+
1125
+ func (x *RerankResponse) ProtoReflect() protoreflect.Message {
1126
+ mi := &file_generator_proto_msgTypes[15]
1127
+ if x != nil {
1128
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1129
+ if ms.LoadMessageInfo() == nil {
1130
+ ms.StoreMessageInfo(mi)
1131
+ }
1132
+ return ms
1133
+ }
1134
+ return mi.MessageOf(x)
1135
+ }
1136
+
1137
+ // Deprecated: Use RerankResponse.ProtoReflect.Descriptor instead.
1138
+ func (*RerankResponse) Descriptor() ([]byte, []int) {
1139
+ return file_generator_proto_rawDescGZIP(), []int{15}
1140
+ }
1141
+
1142
+ func (x *RerankResponse) GetResults() []*RerankResult {
1143
+ if x != nil {
1144
+ return x.Results
1145
+ }
1146
+ return nil
1147
+ }
1148
+
1149
+ func (x *RerankResponse) GetModel() string {
1150
+ if x != nil {
1151
+ return x.Model
1152
+ }
1153
+ return ""
1154
+ }
1155
+
1156
+ // RerankResult for reranked document
1157
+ type RerankResult struct {
1158
+ state protoimpl.MessageState `protogen:"open.v1"`
1159
+ // Document ID
1160
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
1161
+ // New relevance score
1162
+ Score float32 `protobuf:"fixed32,2,opt,name=score,proto3" json:"score,omitempty"`
1163
+ // New rank position
1164
+ Rank int32 `protobuf:"varint,3,opt,name=rank,proto3" json:"rank,omitempty"`
1165
+ // Original rank position
1166
+ OriginalRank int32 `protobuf:"varint,4,opt,name=original_rank,json=originalRank,proto3" json:"original_rank,omitempty"`
1167
+ unknownFields protoimpl.UnknownFields
1168
+ sizeCache protoimpl.SizeCache
1169
+ }
1170
+
1171
+ func (x *RerankResult) Reset() {
1172
+ *x = RerankResult{}
1173
+ mi := &file_generator_proto_msgTypes[16]
1174
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1175
+ ms.StoreMessageInfo(mi)
1176
+ }
1177
+
1178
+ func (x *RerankResult) String() string {
1179
+ return protoimpl.X.MessageStringOf(x)
1180
+ }
1181
+
1182
+ func (*RerankResult) ProtoMessage() {}
1183
+
1184
+ func (x *RerankResult) ProtoReflect() protoreflect.Message {
1185
+ mi := &file_generator_proto_msgTypes[16]
1186
+ if x != nil {
1187
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1188
+ if ms.LoadMessageInfo() == nil {
1189
+ ms.StoreMessageInfo(mi)
1190
+ }
1191
+ return ms
1192
+ }
1193
+ return mi.MessageOf(x)
1194
+ }
1195
+
1196
+ // Deprecated: Use RerankResult.ProtoReflect.Descriptor instead.
1197
+ func (*RerankResult) Descriptor() ([]byte, []int) {
1198
+ return file_generator_proto_rawDescGZIP(), []int{16}
1199
+ }
1200
+
1201
+ func (x *RerankResult) GetId() string {
1202
+ if x != nil {
1203
+ return x.Id
1204
+ }
1205
+ return ""
1206
+ }
1207
+
1208
+ func (x *RerankResult) GetScore() float32 {
1209
+ if x != nil {
1210
+ return x.Score
1211
+ }
1212
+ return 0
1213
+ }
1214
+
1215
+ func (x *RerankResult) GetRank() int32 {
1216
+ if x != nil {
1217
+ return x.Rank
1218
+ }
1219
+ return 0
1220
+ }
1221
+
1222
+ func (x *RerankResult) GetOriginalRank() int32 {
1223
+ if x != nil {
1224
+ return x.OriginalRank
1225
+ }
1226
+ return 0
1227
+ }
1228
+
1229
+ var File_generator_proto protoreflect.FileDescriptor
1230
+
1231
+ const file_generator_proto_rawDesc = "" +
1232
+ "\n" +
1233
+ "\x0fgenerator.proto\x12\x06rag.v1\"\xe0\x01\n" +
1234
+ "\x0fGenerateRequest\x12#\n" +
1235
+ "\rsystem_prompt\x18\x01 \x01(\tR\fsystemPrompt\x12\x16\n" +
1236
+ "\x06prompt\x18\x02 \x01(\tR\x06prompt\x121\n" +
1237
+ "\acontext\x18\x03 \x03(\v2\x17.rag.v1.ContextDocumentR\acontext\x12-\n" +
1238
+ "\ahistory\x18\x04 \x03(\v2\x13.rag.v1.ChatMessageR\ahistory\x12.\n" +
1239
+ "\x06config\x18\x05 \x01(\v2\x16.rag.v1.GenerateConfigR\x06config\"o\n" +
1240
+ "\x0fContextDocument\x12\x18\n" +
1241
+ "\acontent\x18\x01 \x01(\tR\acontent\x12\x14\n" +
1242
+ "\x05title\x18\x02 \x01(\tR\x05title\x12\x16\n" +
1243
+ "\x06source\x18\x03 \x01(\tR\x06source\x12\x14\n" +
1244
+ "\x05score\x18\x04 \x01(\x02R\x05score\";\n" +
1245
+ "\vChatMessage\x12\x12\n" +
1246
+ "\x04role\x18\x01 \x01(\tR\x04role\x12\x18\n" +
1247
+ "\acontent\x18\x02 \x01(\tR\acontent\"\xa4\x02\n" +
1248
+ "\x0eGenerateConfig\x12\x14\n" +
1249
+ "\x05model\x18\x01 \x01(\tR\x05model\x12 \n" +
1250
+ "\vtemperature\x18\x02 \x01(\x02R\vtemperature\x12\x1d\n" +
1251
+ "\n" +
1252
+ "max_tokens\x18\x03 \x01(\x05R\tmaxTokens\x12\x13\n" +
1253
+ "\x05top_p\x18\x04 \x01(\x02R\x04topP\x12+\n" +
1254
+ "\x11frequency_penalty\x18\x05 \x01(\x02R\x10frequencyPenalty\x12)\n" +
1255
+ "\x10presence_penalty\x18\x06 \x01(\x02R\x0fpresencePenalty\x12%\n" +
1256
+ "\x0estop_sequences\x18\a \x03(\tR\rstopSequences\x12'\n" +
1257
+ "\x0fresponse_format\x18\b \x01(\tR\x0eresponseFormat\"\xc1\x01\n" +
1258
+ "\x10GenerateResponse\x12\x12\n" +
1259
+ "\x04text\x18\x01 \x01(\tR\x04text\x12(\n" +
1260
+ "\x05usage\x18\x02 \x01(\v2\x12.rag.v1.TokenUsageR\x05usage\x12#\n" +
1261
+ "\rfinish_reason\x18\x03 \x01(\tR\ffinishReason\x12\x14\n" +
1262
+ "\x05model\x18\x04 \x01(\tR\x05model\x124\n" +
1263
+ "\bmetadata\x18\x05 \x01(\v2\x18.rag.v1.GenerateMetadataR\bmetadata\"\xa8\x01\n" +
1264
+ "\n" +
1265
+ "TokenUsage\x12#\n" +
1266
+ "\rprompt_tokens\x18\x01 \x01(\x05R\fpromptTokens\x12+\n" +
1267
+ "\x11completion_tokens\x18\x02 \x01(\x05R\x10completionTokens\x12!\n" +
1268
+ "\ftotal_tokens\x18\x03 \x01(\x05R\vtotalTokens\x12%\n" +
1269
+ "\x0eestimated_cost\x18\x04 \x01(\x02R\restimatedCost\"h\n" +
1270
+ "\x10GenerateMetadata\x12\x1d\n" +
1271
+ "\n" +
1272
+ "latency_ms\x18\x01 \x01(\x03R\tlatencyMs\x12\x1a\n" +
1273
+ "\bprovider\x18\x02 \x01(\tR\bprovider\x12\x19\n" +
1274
+ "\btrace_id\x18\x03 \x01(\tR\atraceId\"\x8f\x01\n" +
1275
+ "\rGenerateChunk\x12\x14\n" +
1276
+ "\x05delta\x18\x01 \x01(\tR\x05delta\x12\x19\n" +
1277
+ "\bis_final\x18\x02 \x01(\bR\aisFinal\x12(\n" +
1278
+ "\x05usage\x18\x03 \x01(\v2\x12.rag.v1.TokenUsageR\x05usage\x12#\n" +
1279
+ "\rfinish_reason\x18\x04 \x01(\tR\ffinishReason\"\\\n" +
1280
+ "\x10EmbeddingRequest\x12\x12\n" +
1281
+ "\x04text\x18\x01 \x01(\tR\x04text\x12\x14\n" +
1282
+ "\x05model\x18\x02 \x01(\tR\x05model\x12\x1e\n" +
1283
+ "\n" +
1284
+ "dimensions\x18\x03 \x01(\x05R\n" +
1285
+ "dimensions\"\x7f\n" +
1286
+ "\x11EmbeddingResponse\x12\x1c\n" +
1287
+ "\tembedding\x18\x01 \x03(\x02R\tembedding\x12\x14\n" +
1288
+ "\x05model\x18\x02 \x01(\tR\x05model\x12\x16\n" +
1289
+ "\x06tokens\x18\x03 \x01(\x05R\x06tokens\x12\x1e\n" +
1290
+ "\n" +
1291
+ "dimensions\x18\x04 \x01(\x05R\n" +
1292
+ "dimensions\"c\n" +
1293
+ "\x15BatchEmbeddingRequest\x12\x14\n" +
1294
+ "\x05texts\x18\x01 \x03(\tR\x05texts\x12\x14\n" +
1295
+ "\x05model\x18\x02 \x01(\tR\x05model\x12\x1e\n" +
1296
+ "\n" +
1297
+ "dimensions\x18\x03 \x01(\x05R\n" +
1298
+ "dimensions\"\x8a\x01\n" +
1299
+ "\x16BatchEmbeddingResponse\x127\n" +
1300
+ "\n" +
1301
+ "embeddings\x18\x01 \x03(\v2\x17.rag.v1.EmbeddingResultR\n" +
1302
+ "embeddings\x12!\n" +
1303
+ "\ftotal_tokens\x18\x02 \x01(\x05R\vtotalTokens\x12\x14\n" +
1304
+ "\x05model\x18\x03 \x01(\tR\x05model\"]\n" +
1305
+ "\x0fEmbeddingResult\x12\x14\n" +
1306
+ "\x05index\x18\x01 \x01(\x05R\x05index\x12\x1c\n" +
1307
+ "\tembedding\x18\x02 \x03(\x02R\tembedding\x12\x16\n" +
1308
+ "\x06tokens\x18\x03 \x01(\x05R\x06tokens\"\x86\x01\n" +
1309
+ "\rRerankRequest\x12\x14\n" +
1310
+ "\x05query\x18\x01 \x01(\tR\x05query\x124\n" +
1311
+ "\tdocuments\x18\x02 \x03(\v2\x16.rag.v1.RerankDocumentR\tdocuments\x12\x13\n" +
1312
+ "\x05top_n\x18\x03 \x01(\x05R\x04topN\x12\x14\n" +
1313
+ "\x05model\x18\x04 \x01(\tR\x05model\"a\n" +
1314
+ "\x0eRerankDocument\x12\x0e\n" +
1315
+ "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" +
1316
+ "\acontent\x18\x02 \x01(\tR\acontent\x12%\n" +
1317
+ "\x0eoriginal_score\x18\x03 \x01(\x02R\roriginalScore\"V\n" +
1318
+ "\x0eRerankResponse\x12.\n" +
1319
+ "\aresults\x18\x01 \x03(\v2\x14.rag.v1.RerankResultR\aresults\x12\x14\n" +
1320
+ "\x05model\x18\x02 \x01(\tR\x05model\"m\n" +
1321
+ "\fRerankResult\x12\x0e\n" +
1322
+ "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" +
1323
+ "\x05score\x18\x02 \x01(\x02R\x05score\x12\x12\n" +
1324
+ "\x04rank\x18\x03 \x01(\x05R\x04rank\x12#\n" +
1325
+ "\roriginal_rank\x18\x04 \x01(\x05R\foriginalRank2\xf2\x02\n" +
1326
+ "\x10GeneratorService\x12=\n" +
1327
+ "\bGenerate\x12\x17.rag.v1.GenerateRequest\x1a\x18.rag.v1.GenerateResponse\x12B\n" +
1328
+ "\x0eGenerateStream\x12\x17.rag.v1.GenerateRequest\x1a\x15.rag.v1.GenerateChunk0\x01\x12H\n" +
1329
+ "\x11GenerateEmbedding\x12\x18.rag.v1.EmbeddingRequest\x1a\x19.rag.v1.EmbeddingResponse\x12X\n" +
1330
+ "\x17BatchGenerateEmbeddings\x12\x1d.rag.v1.BatchEmbeddingRequest\x1a\x1e.rag.v1.BatchEmbeddingResponse\x127\n" +
1331
+ "\x06Rerank\x12\x15.rag.v1.RerankRequest\x1a\x16.rag.v1.RerankResponseB6Z4github.com/AmaniQuery/amaniquery/pkg/proto/gen/ragv1b\x06proto3"
1332
+
1333
+ var (
1334
+ file_generator_proto_rawDescOnce sync.Once
1335
+ file_generator_proto_rawDescData []byte
1336
+ )
1337
+
1338
+ func file_generator_proto_rawDescGZIP() []byte {
1339
+ file_generator_proto_rawDescOnce.Do(func() {
1340
+ file_generator_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_generator_proto_rawDesc), len(file_generator_proto_rawDesc)))
1341
+ })
1342
+ return file_generator_proto_rawDescData
1343
+ }
1344
+
1345
+ var file_generator_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
1346
+ var file_generator_proto_goTypes = []any{
1347
+ (*GenerateRequest)(nil), // 0: rag.v1.GenerateRequest
1348
+ (*ContextDocument)(nil), // 1: rag.v1.ContextDocument
1349
+ (*ChatMessage)(nil), // 2: rag.v1.ChatMessage
1350
+ (*GenerateConfig)(nil), // 3: rag.v1.GenerateConfig
1351
+ (*GenerateResponse)(nil), // 4: rag.v1.GenerateResponse
1352
+ (*TokenUsage)(nil), // 5: rag.v1.TokenUsage
1353
+ (*GenerateMetadata)(nil), // 6: rag.v1.GenerateMetadata
1354
+ (*GenerateChunk)(nil), // 7: rag.v1.GenerateChunk
1355
+ (*EmbeddingRequest)(nil), // 8: rag.v1.EmbeddingRequest
1356
+ (*EmbeddingResponse)(nil), // 9: rag.v1.EmbeddingResponse
1357
+ (*BatchEmbeddingRequest)(nil), // 10: rag.v1.BatchEmbeddingRequest
1358
+ (*BatchEmbeddingResponse)(nil), // 11: rag.v1.BatchEmbeddingResponse
1359
+ (*EmbeddingResult)(nil), // 12: rag.v1.EmbeddingResult
1360
+ (*RerankRequest)(nil), // 13: rag.v1.RerankRequest
1361
+ (*RerankDocument)(nil), // 14: rag.v1.RerankDocument
1362
+ (*RerankResponse)(nil), // 15: rag.v1.RerankResponse
1363
+ (*RerankResult)(nil), // 16: rag.v1.RerankResult
1364
+ }
1365
+ var file_generator_proto_depIdxs = []int32{
1366
+ 1, // 0: rag.v1.GenerateRequest.context:type_name -> rag.v1.ContextDocument
1367
+ 2, // 1: rag.v1.GenerateRequest.history:type_name -> rag.v1.ChatMessage
1368
+ 3, // 2: rag.v1.GenerateRequest.config:type_name -> rag.v1.GenerateConfig
1369
+ 5, // 3: rag.v1.GenerateResponse.usage:type_name -> rag.v1.TokenUsage
1370
+ 6, // 4: rag.v1.GenerateResponse.metadata:type_name -> rag.v1.GenerateMetadata
1371
+ 5, // 5: rag.v1.GenerateChunk.usage:type_name -> rag.v1.TokenUsage
1372
+ 12, // 6: rag.v1.BatchEmbeddingResponse.embeddings:type_name -> rag.v1.EmbeddingResult
1373
+ 14, // 7: rag.v1.RerankRequest.documents:type_name -> rag.v1.RerankDocument
1374
+ 16, // 8: rag.v1.RerankResponse.results:type_name -> rag.v1.RerankResult
1375
+ 0, // 9: rag.v1.GeneratorService.Generate:input_type -> rag.v1.GenerateRequest
1376
+ 0, // 10: rag.v1.GeneratorService.GenerateStream:input_type -> rag.v1.GenerateRequest
1377
+ 8, // 11: rag.v1.GeneratorService.GenerateEmbedding:input_type -> rag.v1.EmbeddingRequest
1378
+ 10, // 12: rag.v1.GeneratorService.BatchGenerateEmbeddings:input_type -> rag.v1.BatchEmbeddingRequest
1379
+ 13, // 13: rag.v1.GeneratorService.Rerank:input_type -> rag.v1.RerankRequest
1380
+ 4, // 14: rag.v1.GeneratorService.Generate:output_type -> rag.v1.GenerateResponse
1381
+ 7, // 15: rag.v1.GeneratorService.GenerateStream:output_type -> rag.v1.GenerateChunk
1382
+ 9, // 16: rag.v1.GeneratorService.GenerateEmbedding:output_type -> rag.v1.EmbeddingResponse
1383
+ 11, // 17: rag.v1.GeneratorService.BatchGenerateEmbeddings:output_type -> rag.v1.BatchEmbeddingResponse
1384
+ 15, // 18: rag.v1.GeneratorService.Rerank:output_type -> rag.v1.RerankResponse
1385
+ 14, // [14:19] is the sub-list for method output_type
1386
+ 9, // [9:14] is the sub-list for method input_type
1387
+ 9, // [9:9] is the sub-list for extension type_name
1388
+ 9, // [9:9] is the sub-list for extension extendee
1389
+ 0, // [0:9] is the sub-list for field type_name
1390
+ }
1391
+
1392
+ func init() { file_generator_proto_init() }
1393
+ func file_generator_proto_init() {
1394
+ if File_generator_proto != nil {
1395
+ return
1396
+ }
1397
+ type x struct{}
1398
+ out := protoimpl.TypeBuilder{
1399
+ File: protoimpl.DescBuilder{
1400
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
1401
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_generator_proto_rawDesc), len(file_generator_proto_rawDesc)),
1402
+ NumEnums: 0,
1403
+ NumMessages: 17,
1404
+ NumExtensions: 0,
1405
+ NumServices: 1,
1406
+ },
1407
+ GoTypes: file_generator_proto_goTypes,
1408
+ DependencyIndexes: file_generator_proto_depIdxs,
1409
+ MessageInfos: file_generator_proto_msgTypes,
1410
+ }.Build()
1411
+ File_generator_proto = out.File
1412
+ file_generator_proto_goTypes = nil
1413
+ file_generator_proto_depIdxs = nil
1414
+ }
generator_grpc.pb.go ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2
+ // versions:
3
+ // - protoc-gen-go-grpc v1.6.0
4
+ // - protoc v6.33.2
5
+ // source: generator.proto
6
+
7
+ package ragv1
8
+
9
+ import (
10
+ context "context"
11
+ grpc "google.golang.org/grpc"
12
+ codes "google.golang.org/grpc/codes"
13
+ status "google.golang.org/grpc/status"
14
+ )
15
+
16
+ // This is a compile-time assertion to ensure that this generated file
17
+ // is compatible with the grpc package it is being compiled against.
18
+ // Requires gRPC-Go v1.64.0 or later.
19
+ const _ = grpc.SupportPackageIsVersion9
20
+
21
+ const (
22
+ GeneratorService_Generate_FullMethodName = "/rag.v1.GeneratorService/Generate"
23
+ GeneratorService_GenerateStream_FullMethodName = "/rag.v1.GeneratorService/GenerateStream"
24
+ GeneratorService_GenerateEmbedding_FullMethodName = "/rag.v1.GeneratorService/GenerateEmbedding"
25
+ GeneratorService_BatchGenerateEmbeddings_FullMethodName = "/rag.v1.GeneratorService/BatchGenerateEmbeddings"
26
+ GeneratorService_Rerank_FullMethodName = "/rag.v1.GeneratorService/Rerank"
27
+ )
28
+
29
+ // GeneratorServiceClient is the client API for GeneratorService service.
30
+ //
31
+ // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
32
+ //
33
+ // GeneratorService handles LLM-based response generation
34
+ type GeneratorServiceClient interface {
35
+ // Generate a complete response
36
+ Generate(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (*GenerateResponse, error)
37
+ // Generate with streaming output
38
+ GenerateStream(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GenerateChunk], error)
39
+ // Generate embeddings for text
40
+ GenerateEmbedding(ctx context.Context, in *EmbeddingRequest, opts ...grpc.CallOption) (*EmbeddingResponse, error)
41
+ // Batch generate embeddings
42
+ BatchGenerateEmbeddings(ctx context.Context, in *BatchEmbeddingRequest, opts ...grpc.CallOption) (*BatchEmbeddingResponse, error)
43
+ // Rerank documents based on query relevance
44
+ Rerank(ctx context.Context, in *RerankRequest, opts ...grpc.CallOption) (*RerankResponse, error)
45
+ }
46
+
47
+ type generatorServiceClient struct {
48
+ cc grpc.ClientConnInterface
49
+ }
50
+
51
+ func NewGeneratorServiceClient(cc grpc.ClientConnInterface) GeneratorServiceClient {
52
+ return &generatorServiceClient{cc}
53
+ }
54
+
55
+ func (c *generatorServiceClient) Generate(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (*GenerateResponse, error) {
56
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
57
+ out := new(GenerateResponse)
58
+ err := c.cc.Invoke(ctx, GeneratorService_Generate_FullMethodName, in, out, cOpts...)
59
+ if err != nil {
60
+ return nil, err
61
+ }
62
+ return out, nil
63
+ }
64
+
65
+ func (c *generatorServiceClient) GenerateStream(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GenerateChunk], error) {
66
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
67
+ stream, err := c.cc.NewStream(ctx, &GeneratorService_ServiceDesc.Streams[0], GeneratorService_GenerateStream_FullMethodName, cOpts...)
68
+ if err != nil {
69
+ return nil, err
70
+ }
71
+ x := &grpc.GenericClientStream[GenerateRequest, GenerateChunk]{ClientStream: stream}
72
+ if err := x.ClientStream.SendMsg(in); err != nil {
73
+ return nil, err
74
+ }
75
+ if err := x.ClientStream.CloseSend(); err != nil {
76
+ return nil, err
77
+ }
78
+ return x, nil
79
+ }
80
+
81
+ // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
82
+ type GeneratorService_GenerateStreamClient = grpc.ServerStreamingClient[GenerateChunk]
83
+
84
+ func (c *generatorServiceClient) GenerateEmbedding(ctx context.Context, in *EmbeddingRequest, opts ...grpc.CallOption) (*EmbeddingResponse, error) {
85
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
86
+ out := new(EmbeddingResponse)
87
+ err := c.cc.Invoke(ctx, GeneratorService_GenerateEmbedding_FullMethodName, in, out, cOpts...)
88
+ if err != nil {
89
+ return nil, err
90
+ }
91
+ return out, nil
92
+ }
93
+
94
+ func (c *generatorServiceClient) BatchGenerateEmbeddings(ctx context.Context, in *BatchEmbeddingRequest, opts ...grpc.CallOption) (*BatchEmbeddingResponse, error) {
95
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
96
+ out := new(BatchEmbeddingResponse)
97
+ err := c.cc.Invoke(ctx, GeneratorService_BatchGenerateEmbeddings_FullMethodName, in, out, cOpts...)
98
+ if err != nil {
99
+ return nil, err
100
+ }
101
+ return out, nil
102
+ }
103
+
104
+ func (c *generatorServiceClient) Rerank(ctx context.Context, in *RerankRequest, opts ...grpc.CallOption) (*RerankResponse, error) {
105
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
106
+ out := new(RerankResponse)
107
+ err := c.cc.Invoke(ctx, GeneratorService_Rerank_FullMethodName, in, out, cOpts...)
108
+ if err != nil {
109
+ return nil, err
110
+ }
111
+ return out, nil
112
+ }
113
+
114
+ // GeneratorServiceServer is the server API for GeneratorService service.
115
+ // All implementations must embed UnimplementedGeneratorServiceServer
116
+ // for forward compatibility.
117
+ //
118
+ // GeneratorService handles LLM-based response generation
119
+ type GeneratorServiceServer interface {
120
+ // Generate a complete response
121
+ Generate(context.Context, *GenerateRequest) (*GenerateResponse, error)
122
+ // Generate with streaming output
123
+ GenerateStream(*GenerateRequest, grpc.ServerStreamingServer[GenerateChunk]) error
124
+ // Generate embeddings for text
125
+ GenerateEmbedding(context.Context, *EmbeddingRequest) (*EmbeddingResponse, error)
126
+ // Batch generate embeddings
127
+ BatchGenerateEmbeddings(context.Context, *BatchEmbeddingRequest) (*BatchEmbeddingResponse, error)
128
+ // Rerank documents based on query relevance
129
+ Rerank(context.Context, *RerankRequest) (*RerankResponse, error)
130
+ mustEmbedUnimplementedGeneratorServiceServer()
131
+ }
132
+
133
+ // UnimplementedGeneratorServiceServer must be embedded to have
134
+ // forward compatible implementations.
135
+ //
136
+ // NOTE: this should be embedded by value instead of pointer to avoid a nil
137
+ // pointer dereference when methods are called.
138
+ type UnimplementedGeneratorServiceServer struct{}
139
+
140
+ func (UnimplementedGeneratorServiceServer) Generate(context.Context, *GenerateRequest) (*GenerateResponse, error) {
141
+ return nil, status.Error(codes.Unimplemented, "method Generate not implemented")
142
+ }
143
+ func (UnimplementedGeneratorServiceServer) GenerateStream(*GenerateRequest, grpc.ServerStreamingServer[GenerateChunk]) error {
144
+ return status.Error(codes.Unimplemented, "method GenerateStream not implemented")
145
+ }
146
+ func (UnimplementedGeneratorServiceServer) GenerateEmbedding(context.Context, *EmbeddingRequest) (*EmbeddingResponse, error) {
147
+ return nil, status.Error(codes.Unimplemented, "method GenerateEmbedding not implemented")
148
+ }
149
+ func (UnimplementedGeneratorServiceServer) BatchGenerateEmbeddings(context.Context, *BatchEmbeddingRequest) (*BatchEmbeddingResponse, error) {
150
+ return nil, status.Error(codes.Unimplemented, "method BatchGenerateEmbeddings not implemented")
151
+ }
152
+ func (UnimplementedGeneratorServiceServer) Rerank(context.Context, *RerankRequest) (*RerankResponse, error) {
153
+ return nil, status.Error(codes.Unimplemented, "method Rerank not implemented")
154
+ }
155
+ func (UnimplementedGeneratorServiceServer) mustEmbedUnimplementedGeneratorServiceServer() {}
156
+ func (UnimplementedGeneratorServiceServer) testEmbeddedByValue() {}
157
+
158
+ // UnsafeGeneratorServiceServer may be embedded to opt out of forward compatibility for this service.
159
+ // Use of this interface is not recommended, as added methods to GeneratorServiceServer will
160
+ // result in compilation errors.
161
+ type UnsafeGeneratorServiceServer interface {
162
+ mustEmbedUnimplementedGeneratorServiceServer()
163
+ }
164
+
165
+ func RegisterGeneratorServiceServer(s grpc.ServiceRegistrar, srv GeneratorServiceServer) {
166
+ // If the following call panics, it indicates UnimplementedGeneratorServiceServer was
167
+ // embedded by pointer and is nil. This will cause panics if an
168
+ // unimplemented method is ever invoked, so we test this at initialization
169
+ // time to prevent it from happening at runtime later due to I/O.
170
+ if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
171
+ t.testEmbeddedByValue()
172
+ }
173
+ s.RegisterService(&GeneratorService_ServiceDesc, srv)
174
+ }
175
+
176
+ func _GeneratorService_Generate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
177
+ in := new(GenerateRequest)
178
+ if err := dec(in); err != nil {
179
+ return nil, err
180
+ }
181
+ if interceptor == nil {
182
+ return srv.(GeneratorServiceServer).Generate(ctx, in)
183
+ }
184
+ info := &grpc.UnaryServerInfo{
185
+ Server: srv,
186
+ FullMethod: GeneratorService_Generate_FullMethodName,
187
+ }
188
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
189
+ return srv.(GeneratorServiceServer).Generate(ctx, req.(*GenerateRequest))
190
+ }
191
+ return interceptor(ctx, in, info, handler)
192
+ }
193
+
194
+ func _GeneratorService_GenerateStream_Handler(srv interface{}, stream grpc.ServerStream) error {
195
+ m := new(GenerateRequest)
196
+ if err := stream.RecvMsg(m); err != nil {
197
+ return err
198
+ }
199
+ return srv.(GeneratorServiceServer).GenerateStream(m, &grpc.GenericServerStream[GenerateRequest, GenerateChunk]{ServerStream: stream})
200
+ }
201
+
202
+ // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
203
+ type GeneratorService_GenerateStreamServer = grpc.ServerStreamingServer[GenerateChunk]
204
+
205
+ func _GeneratorService_GenerateEmbedding_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
206
+ in := new(EmbeddingRequest)
207
+ if err := dec(in); err != nil {
208
+ return nil, err
209
+ }
210
+ if interceptor == nil {
211
+ return srv.(GeneratorServiceServer).GenerateEmbedding(ctx, in)
212
+ }
213
+ info := &grpc.UnaryServerInfo{
214
+ Server: srv,
215
+ FullMethod: GeneratorService_GenerateEmbedding_FullMethodName,
216
+ }
217
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
218
+ return srv.(GeneratorServiceServer).GenerateEmbedding(ctx, req.(*EmbeddingRequest))
219
+ }
220
+ return interceptor(ctx, in, info, handler)
221
+ }
222
+
223
+ func _GeneratorService_BatchGenerateEmbeddings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
224
+ in := new(BatchEmbeddingRequest)
225
+ if err := dec(in); err != nil {
226
+ return nil, err
227
+ }
228
+ if interceptor == nil {
229
+ return srv.(GeneratorServiceServer).BatchGenerateEmbeddings(ctx, in)
230
+ }
231
+ info := &grpc.UnaryServerInfo{
232
+ Server: srv,
233
+ FullMethod: GeneratorService_BatchGenerateEmbeddings_FullMethodName,
234
+ }
235
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
236
+ return srv.(GeneratorServiceServer).BatchGenerateEmbeddings(ctx, req.(*BatchEmbeddingRequest))
237
+ }
238
+ return interceptor(ctx, in, info, handler)
239
+ }
240
+
241
+ func _GeneratorService_Rerank_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
242
+ in := new(RerankRequest)
243
+ if err := dec(in); err != nil {
244
+ return nil, err
245
+ }
246
+ if interceptor == nil {
247
+ return srv.(GeneratorServiceServer).Rerank(ctx, in)
248
+ }
249
+ info := &grpc.UnaryServerInfo{
250
+ Server: srv,
251
+ FullMethod: GeneratorService_Rerank_FullMethodName,
252
+ }
253
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
254
+ return srv.(GeneratorServiceServer).Rerank(ctx, req.(*RerankRequest))
255
+ }
256
+ return interceptor(ctx, in, info, handler)
257
+ }
258
+
259
+ // GeneratorService_ServiceDesc is the grpc.ServiceDesc for GeneratorService service.
260
+ // It's only intended for direct use with grpc.RegisterService,
261
+ // and not to be introspected or modified (even as a copy)
262
+ var GeneratorService_ServiceDesc = grpc.ServiceDesc{
263
+ ServiceName: "rag.v1.GeneratorService",
264
+ HandlerType: (*GeneratorServiceServer)(nil),
265
+ Methods: []grpc.MethodDesc{
266
+ {
267
+ MethodName: "Generate",
268
+ Handler: _GeneratorService_Generate_Handler,
269
+ },
270
+ {
271
+ MethodName: "GenerateEmbedding",
272
+ Handler: _GeneratorService_GenerateEmbedding_Handler,
273
+ },
274
+ {
275
+ MethodName: "BatchGenerateEmbeddings",
276
+ Handler: _GeneratorService_BatchGenerateEmbeddings_Handler,
277
+ },
278
+ {
279
+ MethodName: "Rerank",
280
+ Handler: _GeneratorService_Rerank_Handler,
281
+ },
282
+ },
283
+ Streams: []grpc.StreamDesc{
284
+ {
285
+ StreamName: "GenerateStream",
286
+ Handler: _GeneratorService_GenerateStream_Handler,
287
+ ServerStreams: true,
288
+ },
289
+ },
290
+ Metadata: "generator.proto",
291
+ }
go.mod ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module github.com/AmaniQuery/amaniquery
2
+
3
+ go 1.24.0
4
+
5
+ require (
6
+ github.com/blevesearch/bleve/v2 v2.3.10
7
+ github.com/golang-jwt/jwt/v5 v5.2.0
8
+ github.com/google/uuid v1.6.0
9
+ github.com/gorilla/mux v1.8.1
10
+ github.com/gorilla/websocket v1.5.1
11
+ github.com/graph-gophers/graphql-go v1.5.0
12
+ github.com/hashicorp/consul/api v1.27.0
13
+ github.com/neo4j/neo4j-go-driver/v5 v5.17.0
14
+ github.com/pierrec/lz4/v4 v4.1.15
15
+ github.com/prometheus/client_golang v1.18.0
16
+ github.com/qdrant/go-client v1.7.0
17
+ github.com/redis/go-redis/v9 v9.4.0
18
+ github.com/sony/gobreaker v0.5.0
19
+ github.com/spf13/viper v1.18.2
20
+ go.opentelemetry.io/otel v1.38.0
21
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0
22
+ go.opentelemetry.io/otel/sdk v1.38.0
23
+ go.opentelemetry.io/otel/trace v1.38.0
24
+ go.temporal.io/sdk v1.26.0
25
+ go.uber.org/zap v1.26.0
26
+ golang.org/x/sync v0.19.0
27
+ golang.org/x/time v0.5.0
28
+ google.golang.org/grpc v1.77.0
29
+ google.golang.org/protobuf v1.36.11
30
+ )
31
+
32
+ require (
33
+ github.com/RoaringBitmap/roaring v1.2.3 // indirect
34
+ github.com/armon/go-metrics v0.4.1 // indirect
35
+ github.com/beorn7/perks v1.0.1 // indirect
36
+ github.com/bits-and-blooms/bitset v1.2.0 // indirect
37
+ github.com/blevesearch/bleve_index_api v1.0.6 // indirect
38
+ github.com/blevesearch/geo v0.1.18 // indirect
39
+ github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
40
+ github.com/blevesearch/gtreap v0.1.1 // indirect
41
+ github.com/blevesearch/mmap-go v1.0.4 // indirect
42
+ github.com/blevesearch/scorch_segment_api/v2 v2.1.6 // indirect
43
+ github.com/blevesearch/segment v0.9.1 // indirect
44
+ github.com/blevesearch/snowballstem v0.9.0 // indirect
45
+ github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
46
+ github.com/blevesearch/vellum v1.0.10 // indirect
47
+ github.com/blevesearch/zapx/v11 v11.3.10 // indirect
48
+ github.com/blevesearch/zapx/v12 v12.3.10 // indirect
49
+ github.com/blevesearch/zapx/v13 v13.3.10 // indirect
50
+ github.com/blevesearch/zapx/v14 v14.3.10 // indirect
51
+ github.com/blevesearch/zapx/v15 v15.3.13 // indirect
52
+ github.com/cenkalti/backoff/v4 v4.2.1 // indirect
53
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
54
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
55
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
56
+ github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
57
+ github.com/fatih/color v1.14.1 // indirect
58
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
59
+ github.com/go-logr/logr v1.4.3 // indirect
60
+ github.com/go-logr/stdr v1.2.2 // indirect
61
+ github.com/gogo/protobuf v1.3.2 // indirect
62
+ github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
63
+ github.com/golang/mock v1.6.0 // indirect
64
+ github.com/golang/protobuf v1.5.4 // indirect
65
+ github.com/golang/snappy v0.0.4 // indirect
66
+ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
67
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
68
+ github.com/hashicorp/errwrap v1.1.0 // indirect
69
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
70
+ github.com/hashicorp/go-hclog v1.5.0 // indirect
71
+ github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
72
+ github.com/hashicorp/go-multierror v1.1.1 // indirect
73
+ github.com/hashicorp/go-rootcerts v1.0.2 // indirect
74
+ github.com/hashicorp/golang-lru v0.5.4 // indirect
75
+ github.com/hashicorp/hcl v1.0.0 // indirect
76
+ github.com/hashicorp/serf v0.10.1 // indirect
77
+ github.com/json-iterator/go v1.1.12 // indirect
78
+ github.com/magiconair/properties v1.8.7 // indirect
79
+ github.com/mattn/go-colorable v0.1.13 // indirect
80
+ github.com/mattn/go-isatty v0.0.17 // indirect
81
+ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
82
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
83
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
84
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
85
+ github.com/modern-go/reflect2 v1.0.2 // indirect
86
+ github.com/mschoch/smat v0.2.0 // indirect
87
+ github.com/pborman/uuid v1.2.1 // indirect
88
+ github.com/pelletier/go-toml/v2 v2.1.0 // indirect
89
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
90
+ github.com/prometheus/client_model v0.5.0 // indirect
91
+ github.com/prometheus/common v0.45.0 // indirect
92
+ github.com/prometheus/procfs v0.12.0 // indirect
93
+ github.com/robfig/cron v1.2.0 // indirect
94
+ github.com/sagikazarmark/locafero v0.4.0 // indirect
95
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
96
+ github.com/sourcegraph/conc v0.3.0 // indirect
97
+ github.com/spf13/afero v1.11.0 // indirect
98
+ github.com/spf13/cast v1.6.0 // indirect
99
+ github.com/spf13/pflag v1.0.5 // indirect
100
+ github.com/stretchr/objx v0.5.2 // indirect
101
+ github.com/stretchr/testify v1.11.1 // indirect
102
+ github.com/subosito/gotenv v1.6.0 // indirect
103
+ go.etcd.io/bbolt v1.3.7 // indirect
104
+ go.opentelemetry.io/auto/sdk v1.2.1 // indirect
105
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect
106
+ go.opentelemetry.io/otel/metric v1.38.0 // indirect
107
+ go.opentelemetry.io/proto/otlp v1.1.0 // indirect
108
+ go.temporal.io/api v1.29.1 // indirect
109
+ go.uber.org/multierr v1.10.0 // indirect
110
+ golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
111
+ golang.org/x/net v0.48.0 // indirect
112
+ golang.org/x/sys v0.39.0 // indirect
113
+ golang.org/x/text v0.32.0 // indirect
114
+ google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
115
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
116
+ gopkg.in/ini.v1 v1.67.0 // indirect
117
+ gopkg.in/yaml.v3 v3.0.1 // indirect
118
+ )
go.sum ADDED
@@ -0,0 +1,497 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2
+ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3
+ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
4
+ github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY=
5
+ github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
6
+ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
7
+ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
8
+ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
9
+ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
10
+ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
11
+ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
12
+ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
13
+ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
14
+ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
15
+ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
16
+ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
17
+ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
18
+ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
19
+ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
20
+ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
21
+ github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
22
+ github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
23
+ github.com/blevesearch/bleve/v2 v2.3.10 h1:z8V0wwGoL4rp7nG/O3qVVLYxUqCbEwskMt4iRJsPLgg=
24
+ github.com/blevesearch/bleve/v2 v2.3.10/go.mod h1:RJzeoeHC+vNHsoLR54+crS1HmOWpnH87fL70HAUCzIA=
25
+ github.com/blevesearch/bleve_index_api v1.0.6 h1:gyUUxdsrvmW3jVhhYdCVL6h9dCjNT/geNU7PxGn37p8=
26
+ github.com/blevesearch/bleve_index_api v1.0.6/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
27
+ github.com/blevesearch/geo v0.1.18 h1:Np8jycHTZ5scFe7VEPLrDoHnnb9C4j636ue/CGrhtDw=
28
+ github.com/blevesearch/geo v0.1.18/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM=
29
+ github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
30
+ github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
31
+ github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
32
+ github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
33
+ github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
34
+ github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
35
+ github.com/blevesearch/scorch_segment_api/v2 v2.1.6 h1:CdekX/Ob6YCYmeHzD72cKpwzBjvkOGegHOqhAkXp6yA=
36
+ github.com/blevesearch/scorch_segment_api/v2 v2.1.6/go.mod h1:nQQYlp51XvoSVxcciBjtvuHPIVjlWrN1hX4qwK2cqdc=
37
+ github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
38
+ github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
39
+ github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
40
+ github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
41
+ github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
42
+ github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
43
+ github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
44
+ github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
45
+ github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk=
46
+ github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
47
+ github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s=
48
+ github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
49
+ github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8=
50
+ github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
51
+ github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU=
52
+ github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
53
+ github.com/blevesearch/zapx/v15 v15.3.13 h1:6EkfaZiPlAxqXz0neniq35my6S48QI94W/wyhnpDHHQ=
54
+ github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
55
+ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
56
+ github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
57
+ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
58
+ github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
59
+ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
60
+ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
61
+ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
62
+ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
63
+ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
64
+ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
65
+ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
66
+ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
67
+ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
68
+ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
69
+ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
70
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
71
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
72
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
73
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
74
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
75
+ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
76
+ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
77
+ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
78
+ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
79
+ github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
80
+ github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
81
+ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
82
+ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
83
+ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
84
+ github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
85
+ github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
86
+ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
87
+ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
88
+ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
89
+ github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
90
+ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
91
+ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
92
+ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
93
+ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
94
+ github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
95
+ github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
96
+ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
97
+ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
98
+ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
99
+ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
100
+ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
101
+ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
102
+ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
103
+ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
104
+ github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
105
+ github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
106
+ github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
107
+ github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
108
+ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
109
+ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
110
+ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
111
+ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
112
+ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
113
+ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
114
+ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
115
+ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
116
+ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
117
+ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
118
+ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
119
+ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
120
+ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
121
+ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
122
+ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
123
+ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
124
+ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
125
+ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
126
+ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
127
+ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
128
+ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
129
+ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
130
+ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
131
+ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
132
+ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
133
+ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
134
+ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
135
+ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
136
+ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
137
+ github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc=
138
+ github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
139
+ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
140
+ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
141
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
142
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
143
+ github.com/hashicorp/consul/api v1.27.0 h1:gmJ6DPKQog1426xsdmgk5iqDyoRiNc+ipBdJOqKQFjc=
144
+ github.com/hashicorp/consul/api v1.27.0/go.mod h1:JkekNRSou9lANFdt+4IKx3Za7XY0JzzpQjEb4Ivo1c8=
145
+ github.com/hashicorp/consul/sdk v0.15.1 h1:kKIGxc7CZtflcF5DLfHeq7rOQmRq3vk7kwISN9bif8Q=
146
+ github.com/hashicorp/consul/sdk v0.15.1/go.mod h1:7pxqqhqoaPqnBnzXD1StKed62LqJeClzVsUEy85Zr0A=
147
+ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
148
+ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
149
+ github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
150
+ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
151
+ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
152
+ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
153
+ github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
154
+ github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
155
+ github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
156
+ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
157
+ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
158
+ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
159
+ github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
160
+ github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
161
+ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
162
+ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
163
+ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
164
+ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
165
+ github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
166
+ github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
167
+ github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
168
+ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
169
+ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
170
+ github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
171
+ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
172
+ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
173
+ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
174
+ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
175
+ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
176
+ github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
177
+ github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
178
+ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
179
+ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
180
+ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
181
+ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
182
+ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
183
+ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
184
+ github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
185
+ github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
186
+ github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
187
+ github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
188
+ github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
189
+ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
190
+ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
191
+ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
192
+ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
193
+ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
194
+ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
195
+ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
196
+ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
197
+ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
198
+ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
199
+ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
200
+ github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
201
+ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
202
+ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
203
+ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
204
+ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
205
+ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
206
+ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
207
+ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
208
+ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
209
+ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
210
+ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
211
+ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
212
+ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
213
+ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
214
+ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
215
+ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
216
+ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
217
+ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
218
+ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
219
+ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
220
+ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
221
+ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
222
+ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
223
+ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
224
+ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
225
+ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
226
+ github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
227
+ github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
228
+ github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
229
+ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
230
+ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
231
+ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
232
+ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
233
+ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
234
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
235
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
236
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
237
+ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
238
+ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
239
+ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
240
+ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
241
+ github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
242
+ github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
243
+ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
244
+ github.com/neo4j/neo4j-go-driver/v5 v5.17.0 h1:Bdqg1Y8Hd3uLYToXtBjysDYXTdMiP7zeUNUEwfbJkSo=
245
+ github.com/neo4j/neo4j-go-driver/v5 v5.17.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
246
+ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
247
+ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
248
+ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
249
+ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
250
+ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
251
+ github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
252
+ github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
253
+ github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
254
+ github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
255
+ github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
256
+ github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
257
+ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
258
+ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
259
+ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
260
+ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
261
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
262
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
263
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
264
+ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
265
+ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
266
+ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
267
+ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
268
+ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
269
+ github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
270
+ github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
271
+ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
272
+ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
273
+ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
274
+ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
275
+ github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
276
+ github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
277
+ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
278
+ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
279
+ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
280
+ github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
281
+ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
282
+ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
283
+ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
284
+ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
285
+ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
286
+ github.com/qdrant/go-client v1.7.0 h1:2TeeWyZAWIup7vvD7Ne6aAvo0H+F5OUb1pB9Z8Y4pFk=
287
+ github.com/qdrant/go-client v1.7.0/go.mod h1:680gkxNAsVtre0Z8hAQmtPzJtz1xFAyCu2TUxULtnoE=
288
+ github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
289
+ github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
290
+ github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
291
+ github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
292
+ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
293
+ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
294
+ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
295
+ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
296
+ github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
297
+ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
298
+ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
299
+ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
300
+ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
301
+ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
302
+ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
303
+ github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
304
+ github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
305
+ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
306
+ github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
307
+ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
308
+ github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
309
+ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
310
+ github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
311
+ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
312
+ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
313
+ github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
314
+ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
315
+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
316
+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
317
+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
318
+ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
319
+ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
320
+ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
321
+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
322
+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
323
+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
324
+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
325
+ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
326
+ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
327
+ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
328
+ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
329
+ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
330
+ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
331
+ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
332
+ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
333
+ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
334
+ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
335
+ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
336
+ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
337
+ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
338
+ go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
339
+ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
340
+ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
341
+ go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
342
+ go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
343
+ go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
344
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4=
345
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc=
346
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8=
347
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ=
348
+ go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
349
+ go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
350
+ go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
351
+ go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
352
+ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
353
+ go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
354
+ go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
355
+ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
356
+ go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
357
+ go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
358
+ go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
359
+ go.temporal.io/api v1.29.1 h1:L722DCy3xCzpTe3Rvh1sFC9kcSaMJXqvodCF+swHGtQ=
360
+ go.temporal.io/api v1.29.1/go.mod h1:wZtsUJ3PySASGWbpXBWYVKJ4aHB2ZODEn/xNcTr9HRs=
361
+ go.temporal.io/sdk v1.26.0 h1:QAi7irgKvJI+5cKmvy+1lkdCDJJDDNpIQAoXdr3dcyM=
362
+ go.temporal.io/sdk v1.26.0/go.mod h1:rcAf1YWlbWgMsjJEuz7XiQd6UYxTQDOk2AqRRIDwq/U=
363
+ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
364
+ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
365
+ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
366
+ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
367
+ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
368
+ go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
369
+ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
370
+ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
371
+ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
372
+ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
373
+ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
374
+ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
375
+ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
376
+ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
377
+ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
378
+ golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
379
+ golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
380
+ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
381
+ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
382
+ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
383
+ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
384
+ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
385
+ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
386
+ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
387
+ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
388
+ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
389
+ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
390
+ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
391
+ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
392
+ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
393
+ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
394
+ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
395
+ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
396
+ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
397
+ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
398
+ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
399
+ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
400
+ golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
401
+ golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
402
+ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
403
+ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
404
+ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
405
+ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
406
+ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
407
+ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
408
+ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
409
+ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
410
+ golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
411
+ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
412
+ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
413
+ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
414
+ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
415
+ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
416
+ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
417
+ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
418
+ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
419
+ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
420
+ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
421
+ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
422
+ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
423
+ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
424
+ golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
425
+ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
426
+ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
427
+ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
428
+ golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
429
+ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
430
+ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
431
+ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
432
+ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
433
+ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
434
+ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
435
+ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
436
+ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
437
+ golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
438
+ golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
439
+ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
440
+ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
441
+ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
442
+ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
443
+ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
444
+ golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
445
+ golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
446
+ golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
447
+ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
448
+ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
449
+ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
450
+ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
451
+ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
452
+ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
453
+ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
454
+ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
455
+ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
456
+ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
457
+ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
458
+ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
459
+ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
460
+ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
461
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
462
+ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
463
+ gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
464
+ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
465
+ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
466
+ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
467
+ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
468
+ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
469
+ google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
470
+ google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
471
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
472
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
473
+ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
474
+ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
475
+ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
476
+ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
477
+ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
478
+ google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
479
+ google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
480
+ google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
481
+ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
482
+ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
483
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
484
+ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
485
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
486
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
487
+ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
488
+ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
489
+ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
490
+ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
491
+ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
492
+ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
493
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
494
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
495
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
496
+ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
497
+ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
go.work ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ go 1.24.0
2
+
3
+ use (
4
+ .
5
+ ./services/files
6
+ ./services/ingestion
7
+ ./services/notifications
8
+ ./services/portal
9
+ ./services/voice
10
+ )
internal/agent/interceptors.go ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package agent provides gRPC interceptors for logging, recovery, and authentication
2
+ package agent
3
+
4
+ import (
5
+ "context"
6
+ "runtime/debug"
7
+
8
+ "go.uber.org/zap"
9
+ "google.golang.org/grpc"
10
+ "google.golang.org/grpc/codes"
11
+ "google.golang.org/grpc/status"
12
+ )
13
+
14
+ // LoggingInterceptor returns a gRPC unary server interceptor for logging requests
15
+ func LoggingInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor {
16
+ return func(
17
+ ctx context.Context,
18
+ req interface{},
19
+ info *grpc.UnaryServerInfo,
20
+ handler grpc.UnaryHandler,
21
+ ) (interface{}, error) {
22
+ logger.Debug("handling request",
23
+ zap.String("method", info.FullMethod),
24
+ )
25
+
26
+ resp, err := handler(ctx, req)
27
+
28
+ if err != nil {
29
+ logger.Error("request failed",
30
+ zap.String("method", info.FullMethod),
31
+ zap.Error(err),
32
+ )
33
+ } else {
34
+ logger.Debug("request completed",
35
+ zap.String("method", info.FullMethod),
36
+ )
37
+ }
38
+
39
+ return resp, err
40
+ }
41
+ }
42
+
43
+ // RecoveryInterceptor returns a gRPC unary server interceptor that recovers from panics
44
+ func RecoveryInterceptor() grpc.UnaryServerInterceptor {
45
+ return func(
46
+ ctx context.Context,
47
+ req interface{},
48
+ info *grpc.UnaryServerInfo,
49
+ handler grpc.UnaryHandler,
50
+ ) (resp interface{}, err error) {
51
+ defer func() {
52
+ if r := recover(); r != nil {
53
+ stack := debug.Stack()
54
+ err = status.Errorf(codes.Internal, "panic recovered: %v\n%s", r, string(stack))
55
+ }
56
+ }()
57
+
58
+ return handler(ctx, req)
59
+ }
60
+ }
internal/agent/orchestrator.go ADDED
@@ -0,0 +1,565 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package agent implements the Agent Orchestrator service that handles
2
+ // query processing, tool orchestration, and conversation state management.
3
+ package agent
4
+
5
+ import (
6
+ "context"
7
+ "fmt"
8
+ "sync"
9
+ "time"
10
+
11
+ llmclient "github.com/AmaniQuery/amaniquery/internal/generator/llm"
12
+ "github.com/AmaniQuery/amaniquery/internal/retriever"
13
+ "github.com/AmaniQuery/amaniquery/internal/router"
14
+ "github.com/AmaniQuery/amaniquery/pkg/observability"
15
+
16
+ "github.com/sony/gobreaker"
17
+ "go.opentelemetry.io/otel"
18
+ "go.opentelemetry.io/otel/attribute"
19
+ "go.opentelemetry.io/otel/trace"
20
+ "go.uber.org/zap"
21
+ )
22
+
23
+ var tracer = otel.Tracer("agent-orchestrator")
24
+
25
+ // Default system prompt for Kenya Law and News Intelligence
26
+ const defaultSystemPrompt = `You are AmaniQuery, an AI assistant specialized in Kenya Law and News Intelligence.
27
+ Your mission is to democratize access to legal information and civil education in Kenya.
28
+
29
+ When answering questions:
30
+ 1. Provide accurate information based on the context provided
31
+ 2. Cite specific laws, sections, or articles when relevant
32
+ 3. Explain legal concepts in plain, understandable language
33
+ 4. If the context doesn't contain enough information, say so clearly
34
+ 5. For news-related queries, provide balanced and factual information
35
+
36
+ Always maintain a helpful, educational tone while being precise about legal matters.`
37
+
38
+ // Dependencies contains all service dependencies
39
+ type Dependencies struct {
40
+ VectorStore VectorStore
41
+ Cache CacheClient
42
+ LLMClient LLMClient
43
+ EmbeddingClient EmbeddingClient
44
+ KeywordEngine KeywordEngine
45
+ Router *router.Router
46
+ Retriever *retriever.HybridRetriever
47
+ }
48
+
49
+ // Close closes all dependencies
50
+ func (d *Dependencies) Close() error {
51
+ var errs []error
52
+ if d.VectorStore != nil {
53
+ if closer, ok := d.VectorStore.(interface{ Close() error }); ok {
54
+ if err := closer.Close(); err != nil {
55
+ errs = append(errs, err)
56
+ }
57
+ }
58
+ }
59
+ if d.Cache != nil {
60
+ if closer, ok := d.Cache.(interface{ Close() error }); ok {
61
+ if err := closer.Close(); err != nil {
62
+ errs = append(errs, err)
63
+ }
64
+ }
65
+ }
66
+ if d.KeywordEngine != nil {
67
+ if closer, ok := d.KeywordEngine.(interface{ Close() error }); ok {
68
+ if err := closer.Close(); err != nil {
69
+ errs = append(errs, err)
70
+ }
71
+ }
72
+ }
73
+ if len(errs) > 0 {
74
+ return errs[0]
75
+ }
76
+ return nil
77
+ }
78
+
79
+ // Interface definitions
80
+ type VectorStore interface {
81
+ Search(ctx context.Context, embedding []float32, topK int, filters map[string]interface{}) ([]retriever.SearchResult, error)
82
+ }
83
+
84
+ type CacheClient interface {
85
+ Get(ctx context.Context, key string) ([]byte, error)
86
+ Set(ctx context.Context, key string, value []byte, ttlSeconds int) error
87
+ GetJSON(ctx context.Context, key string, v interface{}) error
88
+ SetJSON(ctx context.Context, key string, v interface{}, ttlSeconds int) error
89
+ }
90
+
91
+ type LLMClient interface {
92
+ Generate(ctx context.Context, messages []llmclient.Message, opts llmclient.Options) (*llmclient.Response, error)
93
+ GenerateStream(ctx context.Context, messages []llmclient.Message, opts llmclient.Options, callback llmclient.StreamCallback) error
94
+ }
95
+
96
+ type EmbeddingClient interface {
97
+ Generate(ctx context.Context, text string) ([]float32, error)
98
+ GenerateBatch(ctx context.Context, texts []string) ([][]float32, error)
99
+ }
100
+
101
+ type KeywordEngine interface {
102
+ Search(ctx context.Context, query string, topK int) ([]retriever.SearchResult, error)
103
+ }
104
+
105
+ // QueryRequest represents an incoming query
106
+ type QueryRequest struct {
107
+ Query string
108
+ SessionID string
109
+ UserID string
110
+ ConversationHistory []Message
111
+ Metadata map[string]string
112
+ Options QueryOptions
113
+ }
114
+
115
+ // QueryOptions configures query processing
116
+ type QueryOptions struct {
117
+ MaxSources int
118
+ UseCache bool
119
+ EnableAgentic bool
120
+ KnowledgeBases []string
121
+ Temperature float32
122
+ MaxTokens int
123
+ }
124
+
125
+ // QueryResponse contains the query result
126
+ type QueryResponse struct {
127
+ Answer string
128
+ Sources []Source
129
+ Confidence float32
130
+ Metadata QueryMetadata
131
+ FollowUpQuestions []string
132
+ }
133
+
134
+ // QueryMetadata contains processing information
135
+ type QueryMetadata struct {
136
+ ProcessingTimeMs int64
137
+ ChunksRetrieved int
138
+ TokensUsed int
139
+ CacheHit bool
140
+ RoutingStrategy string
141
+ TraceID string
142
+ }
143
+
144
+ // Source represents a retrieved source document
145
+ type Source struct {
146
+ ID string
147
+ Title string
148
+ Content string
149
+ URL string
150
+ Score float32
151
+ Metadata map[string]string
152
+ Location string
153
+ }
154
+
155
+ // Message represents a conversation message
156
+ type Message struct {
157
+ Role string
158
+ Content string
159
+ Timestamp time.Time
160
+ }
161
+
162
+ // Tool represents an executable tool for agentic workflows
163
+ type Tool interface {
164
+ Name() string
165
+ Description() string
166
+ Execute(ctx context.Context, input string) (string, error)
167
+ }
168
+
169
+ // ResponseStream for streaming responses
170
+ type ResponseStream interface {
171
+ Send(chunk *ResponseChunk) error
172
+ }
173
+
174
+ // ResponseChunk for streaming
175
+ type ResponseChunk struct {
176
+ Type string
177
+ Content string
178
+ Sources []Source
179
+ IsFinal bool
180
+ }
181
+
182
+ // Orchestrator implements the main query processing logic
183
+ type Orchestrator struct {
184
+ deps *Dependencies
185
+ logger *zap.Logger
186
+ tools map[string]Tool
187
+ toolsMu sync.RWMutex
188
+ circuitBreaker *gobreaker.CircuitBreaker
189
+ }
190
+
191
+ // NewOrchestrator creates a new agent orchestrator
192
+ func NewOrchestrator(deps *Dependencies, logger *zap.Logger) *Orchestrator {
193
+ cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{
194
+ Name: "agent-orchestrator",
195
+ MaxRequests: 5,
196
+ Interval: 10 * time.Second,
197
+ Timeout: 60 * time.Second,
198
+ ReadyToTrip: func(counts gobreaker.Counts) bool {
199
+ failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
200
+ return counts.Requests >= 3 && failureRatio >= 0.6
201
+ },
202
+ OnStateChange: func(name string, from, to gobreaker.State) {
203
+ logger.Warn("circuit breaker state changed",
204
+ zap.String("name", name),
205
+ zap.String("from", from.String()),
206
+ zap.String("to", to.String()),
207
+ )
208
+ },
209
+ })
210
+
211
+ return &Orchestrator{
212
+ deps: deps,
213
+ logger: logger,
214
+ tools: make(map[string]Tool),
215
+ circuitBreaker: cb,
216
+ }
217
+ }
218
+
219
+ // ProcessQuery handles a single query through the full RAG pipeline
220
+ func (o *Orchestrator) ProcessQuery(ctx context.Context, req *QueryRequest) (*QueryResponse, error) {
221
+ ctx, span := tracer.Start(ctx, "ProcessQuery",
222
+ trace.WithAttributes(
223
+ attribute.String("query", req.Query),
224
+ attribute.String("session_id", req.SessionID),
225
+ attribute.String("user_id", req.UserID),
226
+ ),
227
+ )
228
+ defer span.End()
229
+
230
+ startTime := time.Now()
231
+ traceID := span.SpanContext().TraceID().String()
232
+
233
+ // Set default options
234
+ if req.Options.MaxSources == 0 {
235
+ req.Options.MaxSources = 10
236
+ }
237
+ if req.Options.Temperature == 0 {
238
+ req.Options.Temperature = 0.7
239
+ }
240
+ if req.Options.MaxTokens == 0 {
241
+ req.Options.MaxTokens = 4096
242
+ }
243
+
244
+ // 1. Check cache first
245
+ if req.Options.UseCache && o.deps.Cache != nil {
246
+ cacheKey := o.buildCacheKey(req.Query)
247
+ var cached QueryResponse
248
+ if err := o.deps.Cache.GetJSON(ctx, cacheKey, &cached); err == nil {
249
+ span.SetAttributes(attribute.Bool("cache_hit", true))
250
+ cached.Metadata.CacheHit = true
251
+ cached.Metadata.TraceID = traceID
252
+ observability.RecordCacheHit("query")
253
+ observability.RecordQuery("success", true, cached.Metadata.RoutingStrategy, time.Since(startTime))
254
+ return &cached, nil
255
+ }
256
+ observability.RecordCacheMiss("query")
257
+ }
258
+
259
+ // 2. Route the query to determine retrieval strategy
260
+ var routingDecision *router.RoutingDecision
261
+ if o.deps.Router != nil {
262
+ routingDecision = o.deps.Router.Route(ctx, req.Query)
263
+ span.SetAttributes(
264
+ attribute.String("routing_strategy", string(routingDecision.Strategy)),
265
+ attribute.String("query_type", string(routingDecision.QueryType)),
266
+ attribute.Float64("routing_confidence", float64(routingDecision.Confidence)),
267
+ )
268
+ } else {
269
+ routingDecision = &router.RoutingDecision{
270
+ Strategy: router.StrategyHybrid,
271
+ SuggestedTopK: req.Options.MaxSources,
272
+ }
273
+ }
274
+
275
+ // 3. Retrieve relevant documents
276
+ retrieveStart := time.Now()
277
+ var documents []retriever.SearchResult
278
+ var retrieveErr error
279
+
280
+ if o.deps.Retriever != nil {
281
+ searchResp, err := o.deps.Retriever.Search(ctx, retriever.SearchRequest{
282
+ Query: req.Query,
283
+ TopK: routingDecision.SuggestedTopK,
284
+ UseVector: routingDecision.Strategy != router.StrategyKeyword,
285
+ UseKeyword: routingDecision.Strategy != router.StrategyVector,
286
+ })
287
+ if err != nil {
288
+ o.logger.Error("retrieval failed", zap.Error(err))
289
+ retrieveErr = err
290
+ } else {
291
+ documents = searchResp.Results
292
+ }
293
+ }
294
+
295
+ observability.RecordRetrieval("hybrid", time.Since(retrieveStart))
296
+ span.SetAttributes(attribute.Int("documents_retrieved", len(documents)))
297
+
298
+ if retrieveErr != nil && len(documents) == 0 {
299
+ return nil, fmt.Errorf("retrieval failed: %w", retrieveErr)
300
+ }
301
+
302
+ // 4. Generate response using LLM
303
+ generateStart := time.Now()
304
+ answer, tokensUsed, err := o.generate(ctx, req, documents)
305
+ if err != nil {
306
+ observability.RecordError("generation", "orchestrator")
307
+ return nil, fmt.Errorf("generation failed: %w", err)
308
+ }
309
+ observability.RecordGeneration(time.Since(generateStart))
310
+ observability.RecordTokens("total", tokensUsed)
311
+
312
+ // 5. Build response
313
+ response := &QueryResponse{
314
+ Answer: answer,
315
+ Sources: o.buildSources(documents),
316
+ Confidence: o.calculateConfidence(documents),
317
+ Metadata: QueryMetadata{
318
+ ProcessingTimeMs: time.Since(startTime).Milliseconds(),
319
+ ChunksRetrieved: len(documents),
320
+ TokensUsed: tokensUsed,
321
+ CacheHit: false,
322
+ RoutingStrategy: string(routingDecision.Strategy),
323
+ TraceID: traceID,
324
+ },
325
+ FollowUpQuestions: o.generateFollowUps(ctx, req.Query, documents),
326
+ }
327
+
328
+ // 6. Cache the response
329
+ if req.Options.UseCache && o.deps.Cache != nil {
330
+ cacheKey := o.buildCacheKey(req.Query)
331
+ if err := o.deps.Cache.SetJSON(ctx, cacheKey, response, 3600); err != nil {
332
+ o.logger.Warn("failed to cache response", zap.Error(err))
333
+ }
334
+ }
335
+
336
+ observability.RecordQuery("success", false, string(routingDecision.Strategy), time.Since(startTime))
337
+ return response, nil
338
+ }
339
+
340
+ // ProcessQueryStream handles a query with streaming response
341
+ func (o *Orchestrator) ProcessQueryStream(ctx context.Context, req *QueryRequest, stream ResponseStream) error {
342
+ ctx, span := tracer.Start(ctx, "ProcessQueryStream")
343
+ defer span.End()
344
+
345
+ // Send thinking status
346
+ if err := stream.Send(&ResponseChunk{Type: "thinking", Content: "Analyzing your query..."}); err != nil {
347
+ return err
348
+ }
349
+
350
+ // Route query
351
+ var routingDecision *router.RoutingDecision
352
+ if o.deps.Router != nil {
353
+ routingDecision = o.deps.Router.Route(ctx, req.Query)
354
+ } else {
355
+ routingDecision = &router.RoutingDecision{
356
+ Strategy: router.StrategyHybrid,
357
+ SuggestedTopK: 10,
358
+ }
359
+ }
360
+
361
+ // Send retrieval status
362
+ if err := stream.Send(&ResponseChunk{Type: "retrieval", Content: "Searching knowledge base..."}); err != nil {
363
+ return err
364
+ }
365
+
366
+ // Retrieve documents
367
+ var documents []retriever.SearchResult
368
+ if o.deps.Retriever != nil {
369
+ searchResp, err := o.deps.Retriever.Search(ctx, retriever.SearchRequest{
370
+ Query: req.Query,
371
+ TopK: routingDecision.SuggestedTopK,
372
+ UseVector: true,
373
+ UseKeyword: true,
374
+ })
375
+ if err != nil {
376
+ o.logger.Warn("retrieval error in stream", zap.Error(err))
377
+ } else {
378
+ documents = searchResp.Results
379
+ }
380
+ }
381
+
382
+ // Send sources
383
+ sources := o.buildSources(documents)
384
+ if err := stream.Send(&ResponseChunk{
385
+ Type: "retrieval",
386
+ Content: fmt.Sprintf("Found %d relevant sources", len(documents)),
387
+ Sources: sources,
388
+ }); err != nil {
389
+ return err
390
+ }
391
+
392
+ // Stream generation
393
+ if o.deps.LLMClient == nil {
394
+ return stream.Send(&ResponseChunk{
395
+ Type: "complete",
396
+ Content: "LLM client not configured",
397
+ IsFinal: true,
398
+ })
399
+ }
400
+
401
+ messages := o.buildMessages(req, documents)
402
+ opts := llmclient.Options{
403
+ Temperature: req.Options.Temperature,
404
+ MaxTokens: req.Options.MaxTokens,
405
+ }
406
+
407
+ var fullContent string
408
+ err := o.deps.LLMClient.GenerateStream(ctx, messages, opts, func(chunk string) error {
409
+ fullContent += chunk
410
+ return stream.Send(&ResponseChunk{
411
+ Type: "generation",
412
+ Content: chunk,
413
+ })
414
+ })
415
+
416
+ if err != nil {
417
+ return err
418
+ }
419
+
420
+ // Send final response
421
+ return stream.Send(&ResponseChunk{
422
+ Type: "complete",
423
+ Content: fullContent,
424
+ Sources: sources,
425
+ IsFinal: true,
426
+ })
427
+ }
428
+
429
+ // RegisterTool registers a tool for agentic execution
430
+ func (o *Orchestrator) RegisterTool(name string, tool Tool) error {
431
+ o.toolsMu.Lock()
432
+ defer o.toolsMu.Unlock()
433
+
434
+ if _, exists := o.tools[name]; exists {
435
+ return fmt.Errorf("tool %s already registered", name)
436
+ }
437
+
438
+ o.tools[name] = tool
439
+ o.logger.Info("registered tool", zap.String("name", name))
440
+ return nil
441
+ }
442
+
443
+ func (o *Orchestrator) buildCacheKey(query string) string {
444
+ return "query:" + query
445
+ }
446
+
447
+ func (o *Orchestrator) generate(ctx context.Context, req *QueryRequest, docs []retriever.SearchResult) (string, int, error) {
448
+ if o.deps.LLMClient == nil {
449
+ return "LLM client not configured. Please set OPENAI_API_KEY environment variable.", 0, nil
450
+ }
451
+
452
+ messages := o.buildMessages(req, docs)
453
+ opts := llmclient.Options{
454
+ Temperature: req.Options.Temperature,
455
+ MaxTokens: req.Options.MaxTokens,
456
+ }
457
+
458
+ resp, err := o.deps.LLMClient.Generate(ctx, messages, opts)
459
+ if err != nil {
460
+ return "", 0, err
461
+ }
462
+
463
+ return resp.Content, resp.Usage.TotalTokens, nil
464
+ }
465
+
466
+ func (o *Orchestrator) buildMessages(req *QueryRequest, docs []retriever.SearchResult) []llmclient.Message {
467
+ // Build context from documents
468
+ contextDocs := make([]llmclient.ContextDoc, len(docs))
469
+ for i, doc := range docs {
470
+ title := doc.Title
471
+ if title == "" {
472
+ if t, ok := doc.Metadata["title"].(string); ok {
473
+ title = t
474
+ } else {
475
+ title = fmt.Sprintf("Source %d", i+1)
476
+ }
477
+ }
478
+ contextDocs[i] = llmclient.ContextDoc{
479
+ Title: title,
480
+ Content: doc.Content,
481
+ Source: doc.Source,
482
+ Score: doc.Score,
483
+ }
484
+ }
485
+
486
+ return llmclient.BuildPrompt(defaultSystemPrompt, req.Query, contextDocs)
487
+ }
488
+
489
+ func (o *Orchestrator) buildSources(docs []retriever.SearchResult) []Source {
490
+ sources := make([]Source, len(docs))
491
+ for i, doc := range docs {
492
+ metadata := make(map[string]string)
493
+ for k, v := range doc.Metadata {
494
+ if s, ok := v.(string); ok {
495
+ metadata[k] = s
496
+ }
497
+ }
498
+ sources[i] = Source{
499
+ ID: doc.ID,
500
+ Title: doc.Title,
501
+ Content: doc.Content,
502
+ URL: doc.Source,
503
+ Score: doc.Score,
504
+ Metadata: metadata,
505
+ }
506
+ }
507
+ return sources
508
+ }
509
+
510
+ func (o *Orchestrator) calculateConfidence(docs []retriever.SearchResult) float32 {
511
+ if len(docs) == 0 {
512
+ return 0.0
513
+ }
514
+ var totalScore float32
515
+ for _, doc := range docs {
516
+ totalScore += doc.Score
517
+ }
518
+ return totalScore / float32(len(docs))
519
+ }
520
+
521
+ func (o *Orchestrator) generateFollowUps(ctx context.Context, query string, docs []retriever.SearchResult) []string {
522
+ // Generate simple follow-up suggestions based on query patterns
523
+ followUps := []string{}
524
+
525
+ // Check for legal terms to suggest related questions
526
+ if containsLegalTerms(query) {
527
+ followUps = append(followUps, "What are the penalties for violating this law?")
528
+ followUps = append(followUps, "Are there any recent amendments to this law?")
529
+ }
530
+
531
+ // Suggest comparative questions
532
+ if len(docs) > 0 {
533
+ followUps = append(followUps, "How does this compare to similar laws in other East African countries?")
534
+ }
535
+
536
+ if len(followUps) > 3 {
537
+ followUps = followUps[:3]
538
+ }
539
+
540
+ return followUps
541
+ }
542
+
543
+ func containsLegalTerms(query string) bool {
544
+ legalTerms := []string{"constitution", "law", "act", "section", "article", "court", "case", "rights"}
545
+ queryLower := query
546
+ for _, term := range legalTerms {
547
+ if contains(queryLower, term) {
548
+ return true
549
+ }
550
+ }
551
+ return false
552
+ }
553
+
554
+ func contains(s, substr string) bool {
555
+ return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))
556
+ }
557
+
558
+ func containsHelper(s, substr string) bool {
559
+ for i := 0; i <= len(s)-len(substr); i++ {
560
+ if s[i:i+len(substr)] == substr {
561
+ return true
562
+ }
563
+ }
564
+ return false
565
+ }
internal/agent/server.go ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package agent provides gRPC server implementation for the Agent service
2
+ package agent
3
+
4
+ import (
5
+ "context"
6
+ "crypto/rand"
7
+ "encoding/hex"
8
+ "sync"
9
+ "time"
10
+
11
+ ragv1 "github.com/AmaniQuery/amaniquery/pkg/proto/gen/ragv1"
12
+
13
+ "go.uber.org/zap"
14
+ "google.golang.org/grpc"
15
+ "google.golang.org/grpc/codes"
16
+ "google.golang.org/grpc/status"
17
+ )
18
+
19
+ // Server implements the AgentService gRPC server
20
+ type Server struct {
21
+ ragv1.UnimplementedAgentServiceServer
22
+ orchestrator *Orchestrator
23
+ logger *zap.Logger
24
+ mu sync.RWMutex
25
+ }
26
+
27
+ // NewServer creates a new Agent gRPC server
28
+ func NewServer(deps *Dependencies, logger *zap.Logger) *Server {
29
+ return &Server{
30
+ orchestrator: NewOrchestrator(deps, logger),
31
+ logger: logger,
32
+ }
33
+ }
34
+
35
+ // ProcessQuery handles a single query via gRPC (unary RPC)
36
+ func (s *Server) ProcessQuery(ctx context.Context, req *ragv1.QueryRequest) (*ragv1.QueryResponse, error) {
37
+ if req == nil || req.Query == "" {
38
+ return nil, status.Error(codes.InvalidArgument, "query is required")
39
+ }
40
+
41
+ s.logger.Info("processing query",
42
+ zap.String("query", req.Query),
43
+ zap.String("session_id", req.SessionId),
44
+ zap.String("user_id", req.UserId),
45
+ )
46
+
47
+ // Convert proto request to internal type
48
+ internalReq := s.protoToQueryRequest(req)
49
+
50
+ // Process query through orchestrator
51
+ response, err := s.orchestrator.ProcessQuery(ctx, internalReq)
52
+ if err != nil {
53
+ s.logger.Error("query processing failed",
54
+ zap.Error(err),
55
+ zap.String("query", req.Query),
56
+ )
57
+ return nil, status.Error(codes.Internal, "query processing failed: "+err.Error())
58
+ }
59
+
60
+ // Convert internal response to proto
61
+ return s.queryResponseToProto(response), nil
62
+ }
63
+
64
+ // ProcessQueryStream handles a query with streaming response via gRPC (server streaming RPC)
65
+ func (s *Server) ProcessQueryStream(req *ragv1.QueryRequest, stream ragv1.AgentService_ProcessQueryStreamServer) error {
66
+ if req == nil || req.Query == "" {
67
+ return status.Error(codes.InvalidArgument, "query is required")
68
+ }
69
+
70
+ s.logger.Info("processing streaming query",
71
+ zap.String("query", req.Query),
72
+ zap.String("session_id", req.SessionId),
73
+ )
74
+
75
+ // Convert proto request to internal type
76
+ internalReq := s.protoToQueryRequest(req)
77
+
78
+ // Create a wrapper that implements ResponseStream
79
+ streamWrapper := &grpcStreamWrapper{
80
+ stream: stream,
81
+ logger: s.logger,
82
+ }
83
+
84
+ return s.orchestrator.ProcessQueryStream(stream.Context(), internalReq, streamWrapper)
85
+ }
86
+
87
+ // CreateAgent creates a new agent with specific configuration
88
+ func (s *Server) CreateAgent(ctx context.Context, req *ragv1.CreateAgentRequest) (*ragv1.Agent, error) {
89
+ if req == nil || req.Name == "" {
90
+ return nil, status.Error(codes.InvalidArgument, "agent name is required")
91
+ }
92
+
93
+ s.logger.Info("creating agent", zap.String("name", req.Name))
94
+
95
+ // TODO: Implement agent creation logic with persistence
96
+ // For now, return a mock agent
97
+ return &ragv1.Agent{
98
+ Id: generateAgentID(),
99
+ Name: req.Name,
100
+ Description: req.Description,
101
+ CreatedAt: currentTimestamp(),
102
+ Config: req.Config,
103
+ }, nil
104
+ }
105
+
106
+ // ExecutePlan executes a pre-defined execution plan
107
+ func (s *Server) ExecutePlan(ctx context.Context, req *ragv1.ExecutionPlan) (*ragv1.PlanResult, error) {
108
+ if req == nil || req.Id == "" {
109
+ return nil, status.Error(codes.InvalidArgument, "plan ID is required")
110
+ }
111
+
112
+ s.logger.Info("executing plan",
113
+ zap.String("plan_id", req.Id),
114
+ zap.Int("steps", len(req.Steps)),
115
+ )
116
+
117
+ // TODO: Implement multi-step plan execution
118
+ return &ragv1.PlanResult{
119
+ PlanId: req.Id,
120
+ Status: ragv1.ExecutionStatus_EXECUTION_STATUS_COMPLETED,
121
+ StepResults: make([]*ragv1.StepResult, 0),
122
+ FinalAnswer: "Plan execution not yet implemented",
123
+ ExecutionTimeMs: 0,
124
+ }, nil
125
+ }
126
+
127
+ // GetQueryStatus returns the status of an ongoing query
128
+ func (s *Server) GetQueryStatus(ctx context.Context, req *ragv1.QueryStatusRequest) (*ragv1.QueryStatus, error) {
129
+ if req == nil || req.QueryId == "" {
130
+ return nil, status.Error(codes.InvalidArgument, "query_id is required")
131
+ }
132
+
133
+ // TODO: Implement query status tracking
134
+ return &ragv1.QueryStatus{
135
+ QueryId: req.QueryId,
136
+ Status: ragv1.ExecutionStatus_EXECUTION_STATUS_COMPLETED,
137
+ Progress: 100,
138
+ CurrentStep: "completed",
139
+ }, nil
140
+ }
141
+
142
+ // RegisterTool registers a tool with the orchestrator
143
+ func (s *Server) RegisterTool(name string, tool Tool) error {
144
+ return s.orchestrator.RegisterTool(name, tool)
145
+ }
146
+
147
+ // grpcStreamWrapper wraps gRPC stream to implement ResponseStream interface
148
+ type grpcStreamWrapper struct {
149
+ stream ragv1.AgentService_ProcessQueryStreamServer
150
+ logger *zap.Logger
151
+ }
152
+
153
+ // Send implements ResponseStream.Send by converting internal chunk to protobuf
154
+ func (w *grpcStreamWrapper) Send(chunk *ResponseChunk) error {
155
+ w.logger.Debug("sending stream chunk",
156
+ zap.String("type", chunk.Type),
157
+ zap.Bool("is_final", chunk.IsFinal),
158
+ zap.Int("content_length", len(chunk.Content)),
159
+ )
160
+
161
+ // Convert chunk type string to proto enum
162
+ chunkType := w.stringToChunkType(chunk.Type)
163
+
164
+ // Convert sources to proto format
165
+ protoSources := make([]*ragv1.Source, len(chunk.Sources))
166
+ for i, src := range chunk.Sources {
167
+ protoSources[i] = &ragv1.Source{
168
+ Id: src.ID,
169
+ Title: src.Title,
170
+ Content: src.Content,
171
+ Url: src.URL,
172
+ Score: src.Score,
173
+ Metadata: src.Metadata,
174
+ Location: src.Location,
175
+ }
176
+ }
177
+
178
+ // Create proto chunk and send
179
+ protoChunk := &ragv1.QueryResponseChunk{
180
+ Type: chunkType,
181
+ Content: chunk.Content,
182
+ Sources: protoSources,
183
+ IsFinal: chunk.IsFinal,
184
+ }
185
+
186
+ return w.stream.Send(protoChunk)
187
+ }
188
+
189
+ // stringToChunkType converts string chunk type to proto enum
190
+ func (w *grpcStreamWrapper) stringToChunkType(t string) ragv1.ChunkType {
191
+ switch t {
192
+ case "thinking":
193
+ return ragv1.ChunkType_CHUNK_TYPE_THINKING
194
+ case "retrieval":
195
+ return ragv1.ChunkType_CHUNK_TYPE_RETRIEVAL
196
+ case "generation":
197
+ return ragv1.ChunkType_CHUNK_TYPE_GENERATION
198
+ case "complete":
199
+ return ragv1.ChunkType_CHUNK_TYPE_COMPLETE
200
+ case "error":
201
+ return ragv1.ChunkType_CHUNK_TYPE_ERROR
202
+ default:
203
+ return ragv1.ChunkType_CHUNK_TYPE_UNSPECIFIED
204
+ }
205
+ }
206
+
207
+ // protoToQueryRequest converts proto QueryRequest to internal type
208
+ func (s *Server) protoToQueryRequest(req *ragv1.QueryRequest) *QueryRequest {
209
+ // Convert conversation history
210
+ history := make([]Message, len(req.ConversationHistory))
211
+ for i, msg := range req.ConversationHistory {
212
+ history[i] = Message{
213
+ Role: s.protoRoleToString(msg.Role),
214
+ Content: msg.Content,
215
+ }
216
+ }
217
+
218
+ // Set default options if not provided
219
+ maxSources := int(req.Options.GetMaxSources())
220
+ if maxSources == 0 {
221
+ maxSources = 10
222
+ }
223
+ maxTokens := int(req.Options.GetMaxTokens())
224
+ if maxTokens == 0 {
225
+ maxTokens = 4096
226
+ }
227
+ temperature := req.Options.GetTemperature()
228
+ if temperature == 0 {
229
+ temperature = 0.7
230
+ }
231
+
232
+ return &QueryRequest{
233
+ Query: req.Query,
234
+ SessionID: req.SessionId,
235
+ UserID: req.UserId,
236
+ ConversationHistory: history,
237
+ Metadata: req.Metadata,
238
+ Options: QueryOptions{
239
+ MaxSources: maxSources,
240
+ UseCache: req.Options.GetUseCache(),
241
+ EnableAgentic: req.Options.GetEnableAgentic(),
242
+ KnowledgeBases: req.Options.GetKnowledgeBases(),
243
+ Temperature: temperature,
244
+ MaxTokens: maxTokens,
245
+ },
246
+ }
247
+ }
248
+
249
+ // queryResponseToProto converts internal QueryResponse to proto type
250
+ func (s *Server) queryResponseToProto(resp *QueryResponse) *ragv1.QueryResponse {
251
+ // Convert sources
252
+ protoSources := make([]*ragv1.Source, len(resp.Sources))
253
+ for i, src := range resp.Sources {
254
+ protoSources[i] = &ragv1.Source{
255
+ Id: src.ID,
256
+ Title: src.Title,
257
+ Content: src.Content,
258
+ Url: src.URL,
259
+ Score: src.Score,
260
+ Metadata: src.Metadata,
261
+ Location: src.Location,
262
+ }
263
+ }
264
+
265
+ return &ragv1.QueryResponse{
266
+ Answer: resp.Answer,
267
+ Sources: protoSources,
268
+ Confidence: resp.Confidence,
269
+ Metadata: &ragv1.QueryMetadata{
270
+ ProcessingTimeMs: resp.Metadata.ProcessingTimeMs,
271
+ ChunksRetrieved: int32(resp.Metadata.ChunksRetrieved),
272
+ TokensUsed: int32(resp.Metadata.TokensUsed),
273
+ CacheHit: resp.Metadata.CacheHit,
274
+ RoutingStrategy: resp.Metadata.RoutingStrategy,
275
+ TraceId: resp.Metadata.TraceID,
276
+ },
277
+ FollowUpQuestions: resp.FollowUpQuestions,
278
+ }
279
+ }
280
+
281
+ // protoRoleToString converts proto MessageRole to string
282
+ func (s *Server) protoRoleToString(role ragv1.MessageRole) string {
283
+ switch role {
284
+ case ragv1.MessageRole_MESSAGE_ROLE_USER:
285
+ return "user"
286
+ case ragv1.MessageRole_MESSAGE_ROLE_ASSISTANT:
287
+ return "assistant"
288
+ case ragv1.MessageRole_MESSAGE_ROLE_SYSTEM:
289
+ return "system"
290
+ default:
291
+ return "user"
292
+ }
293
+ }
294
+
295
+ // RegisterAgentServiceServer registers the server with gRPC using generated code
296
+ func RegisterAgentServiceServer(s *grpc.Server, srv *Server) {
297
+ ragv1.RegisterAgentServiceServer(s, srv)
298
+ }
299
+
300
+ // Helper functions
301
+
302
+ func generateAgentID() string {
303
+ b := make([]byte, 16)
304
+ rand.Read(b)
305
+ return "agent_" + hex.EncodeToString(b)
306
+ }
307
+
308
+ func currentTimestamp() int64 {
309
+ return time.Now().Unix()
310
+ }
311
+
internal/cache/cache.go ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package cache provides multi-tier caching implementation with local LRU and Redis
2
+ package cache
3
+
4
+ import (
5
+ "context"
6
+ "crypto/sha256"
7
+ "encoding/hex"
8
+ "encoding/json"
9
+ "sync"
10
+ "time"
11
+
12
+ "github.com/redis/go-redis/v9"
13
+ )
14
+
15
+ // MultiTierCache implements a two-tier caching system with local LRU and Redis
16
+ type MultiTierCache struct {
17
+ local *LRUCache
18
+ redis *redis.Client
19
+ ttl time.Duration
20
+ metrics *CacheMetrics
21
+ }
22
+
23
+ // CacheMetrics tracks cache performance
24
+ type CacheMetrics struct {
25
+ mu sync.RWMutex
26
+ LocalHits int64
27
+ LocalMisses int64
28
+ RedisHits int64
29
+ RedisMisses int64
30
+ }
31
+
32
+ // Config for cache initialization
33
+ type Config struct {
34
+ RedisURL string
35
+ LocalSize int
36
+ TTL time.Duration
37
+ MaxRetries int
38
+ PoolSize int
39
+ }
40
+
41
+ // New creates a new multi-tier cache
42
+ func New(cfg Config) (*MultiTierCache, error) {
43
+ // Parse Redis URL
44
+ opt, err := redis.ParseURL(cfg.RedisURL)
45
+ if err != nil {
46
+ return nil, err
47
+ }
48
+ opt.MaxRetries = cfg.MaxRetries
49
+ opt.PoolSize = cfg.PoolSize
50
+
51
+ redisClient := redis.NewClient(opt)
52
+
53
+ // Test Redis connection
54
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
55
+ defer cancel()
56
+ if err := redisClient.Ping(ctx).Err(); err != nil {
57
+ // Redis not available, continue with local cache only
58
+ redisClient = nil
59
+ }
60
+
61
+ return &MultiTierCache{
62
+ local: NewLRUCache(cfg.LocalSize),
63
+ redis: redisClient,
64
+ ttl: cfg.TTL,
65
+ metrics: &CacheMetrics{},
66
+ }, nil
67
+ }
68
+
69
+ // Get retrieves a value from cache, checking local first then Redis
70
+ func (c *MultiTierCache) Get(ctx context.Context, key string) ([]byte, error) {
71
+ hashedKey := c.hashKey(key)
72
+
73
+ // Check local cache first (sub-millisecond)
74
+ if value, found := c.local.Get(hashedKey); found {
75
+ c.metrics.mu.Lock()
76
+ c.metrics.LocalHits++
77
+ c.metrics.mu.Unlock()
78
+ return value, nil
79
+ }
80
+ c.metrics.mu.Lock()
81
+ c.metrics.LocalMisses++
82
+ c.metrics.mu.Unlock()
83
+
84
+ // Check Redis if available (1-5ms)
85
+ if c.redis != nil {
86
+ value, err := c.redis.Get(ctx, hashedKey).Bytes()
87
+ if err == nil {
88
+ c.metrics.mu.Lock()
89
+ c.metrics.RedisHits++
90
+ c.metrics.mu.Unlock()
91
+ // Backfill local cache
92
+ c.local.Set(hashedKey, value)
93
+ return value, nil
94
+ }
95
+ if err != redis.Nil {
96
+ // Log error but continue
97
+ }
98
+ c.metrics.mu.Lock()
99
+ c.metrics.RedisMisses++
100
+ c.metrics.mu.Unlock()
101
+ }
102
+
103
+ return nil, ErrCacheMiss
104
+ }
105
+
106
+ // Set stores a value in both cache tiers
107
+ func (c *MultiTierCache) Set(ctx context.Context, key string, value []byte, ttlSeconds int) error {
108
+ hashedKey := c.hashKey(key)
109
+ ttl := time.Duration(ttlSeconds) * time.Second
110
+ if ttlSeconds == 0 {
111
+ ttl = c.ttl
112
+ }
113
+
114
+ // Set in local cache
115
+ c.local.Set(hashedKey, value)
116
+
117
+ // Set in Redis if available
118
+ if c.redis != nil {
119
+ if err := c.redis.Set(ctx, hashedKey, value, ttl).Err(); err != nil {
120
+ // Log error but don't fail - local cache is still valid
121
+ return nil
122
+ }
123
+ }
124
+
125
+ return nil
126
+ }
127
+
128
+ // Delete removes a value from both cache tiers
129
+ func (c *MultiTierCache) Delete(ctx context.Context, key string) error {
130
+ hashedKey := c.hashKey(key)
131
+
132
+ // Delete from local cache
133
+ c.local.Delete(hashedKey)
134
+
135
+ // Delete from Redis if available
136
+ if c.redis != nil {
137
+ if err := c.redis.Del(ctx, hashedKey).Err(); err != nil {
138
+ return err
139
+ }
140
+ }
141
+
142
+ return nil
143
+ }
144
+
145
+ // GetJSON retrieves and unmarshals a JSON value from cache
146
+ func (c *MultiTierCache) GetJSON(ctx context.Context, key string, v interface{}) error {
147
+ data, err := c.Get(ctx, key)
148
+ if err != nil {
149
+ return err
150
+ }
151
+ return json.Unmarshal(data, v)
152
+ }
153
+
154
+ // SetJSON marshals and stores a JSON value in cache
155
+ func (c *MultiTierCache) SetJSON(ctx context.Context, key string, v interface{}, ttlSeconds int) error {
156
+ data, err := json.Marshal(v)
157
+ if err != nil {
158
+ return err
159
+ }
160
+ return c.Set(ctx, key, data, ttlSeconds)
161
+ }
162
+
163
+ // GetMetrics returns cache performance metrics
164
+ func (c *MultiTierCache) GetMetrics() CacheMetrics {
165
+ c.metrics.mu.RLock()
166
+ defer c.metrics.mu.RUnlock()
167
+ return CacheMetrics{
168
+ LocalHits: c.metrics.LocalHits,
169
+ LocalMisses: c.metrics.LocalMisses,
170
+ RedisHits: c.metrics.RedisHits,
171
+ RedisMisses: c.metrics.RedisMisses,
172
+ }
173
+ }
174
+
175
+ // Close closes the cache connections
176
+ func (c *MultiTierCache) Close() error {
177
+ if c.redis != nil {
178
+ return c.redis.Close()
179
+ }
180
+ return nil
181
+ }
182
+
183
+ func (c *MultiTierCache) hashKey(key string) string {
184
+ hash := sha256.Sum256([]byte(key))
185
+ return "amani:" + hex.EncodeToString(hash[:16])
186
+ }
187
+
188
+ // ErrCacheMiss indicates the key was not found in cache
189
+ var ErrCacheMiss = &CacheMissError{}
190
+
191
+ // CacheMissError represents a cache miss
192
+ type CacheMissError struct{}
193
+
194
+ func (e *CacheMissError) Error() string {
195
+ return "cache miss"
196
+ }
197
+
198
+ // LRUCache implements a simple LRU cache
199
+ type LRUCache struct {
200
+ mu sync.RWMutex
201
+ capacity int
202
+ items map[string]*lruItem
203
+ head *lruItem
204
+ tail *lruItem
205
+ }
206
+
207
+ type lruItem struct {
208
+ key string
209
+ value []byte
210
+ prev *lruItem
211
+ next *lruItem
212
+ }
213
+
214
+ // NewLRUCache creates a new LRU cache with the given capacity
215
+ func NewLRUCache(capacity int) *LRUCache {
216
+ return &LRUCache{
217
+ capacity: capacity,
218
+ items: make(map[string]*lruItem),
219
+ }
220
+ }
221
+
222
+ // Get retrieves a value from the LRU cache
223
+ func (c *LRUCache) Get(key string) ([]byte, bool) {
224
+ c.mu.Lock()
225
+ defer c.mu.Unlock()
226
+
227
+ item, found := c.items[key]
228
+ if !found {
229
+ return nil, false
230
+ }
231
+
232
+ // Move to front (most recently used)
233
+ c.moveToFront(item)
234
+ return item.value, true
235
+ }
236
+
237
+ // Set stores a value in the LRU cache
238
+ func (c *LRUCache) Set(key string, value []byte) {
239
+ c.mu.Lock()
240
+ defer c.mu.Unlock()
241
+
242
+ // Check if item exists
243
+ if item, found := c.items[key]; found {
244
+ item.value = value
245
+ c.moveToFront(item)
246
+ return
247
+ }
248
+
249
+ // Create new item
250
+ item := &lruItem{key: key, value: value}
251
+ c.items[key] = item
252
+ c.addToFront(item)
253
+
254
+ // Evict if over capacity
255
+ if len(c.items) > c.capacity {
256
+ c.evictLRU()
257
+ }
258
+ }
259
+
260
+ // Delete removes a value from the LRU cache
261
+ func (c *LRUCache) Delete(key string) {
262
+ c.mu.Lock()
263
+ defer c.mu.Unlock()
264
+
265
+ if item, found := c.items[key]; found {
266
+ c.removeItem(item)
267
+ delete(c.items, key)
268
+ }
269
+ }
270
+
271
+ func (c *LRUCache) moveToFront(item *lruItem) {
272
+ if item == c.head {
273
+ return
274
+ }
275
+ c.removeItem(item)
276
+ c.addToFront(item)
277
+ }
278
+
279
+ func (c *LRUCache) addToFront(item *lruItem) {
280
+ item.prev = nil
281
+ item.next = c.head
282
+ if c.head != nil {
283
+ c.head.prev = item
284
+ }
285
+ c.head = item
286
+ if c.tail == nil {
287
+ c.tail = item
288
+ }
289
+ }
290
+
291
+ func (c *LRUCache) removeItem(item *lruItem) {
292
+ if item.prev != nil {
293
+ item.prev.next = item.next
294
+ } else {
295
+ c.head = item.next
296
+ }
297
+ if item.next != nil {
298
+ item.next.prev = item.prev
299
+ } else {
300
+ c.tail = item.prev
301
+ }
302
+ }
303
+
304
+ func (c *LRUCache) evictLRU() {
305
+ if c.tail == nil {
306
+ return
307
+ }
308
+ delete(c.items, c.tail.key)
309
+ c.removeItem(c.tail)
310
+ }
internal/cache/cache_test.go ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package cache_test
2
+
3
+ import (
4
+ "context"
5
+ "testing"
6
+ "time"
7
+
8
+ "github.com/AmaniQuery/amaniquery/internal/cache"
9
+ )
10
+
11
+ // TestLRUCache_SetGet verifies basic set/get operations
12
+ func TestLRUCache_SetGet(t *testing.T) {
13
+ lru := cache.NewLRUCache(100)
14
+
15
+ // Test set and get
16
+ lru.Set("key1", []byte("value1"))
17
+
18
+ value, found := lru.Get("key1")
19
+ if !found {
20
+ t.Fatal("Expected to find key1")
21
+ }
22
+ if string(value) != "value1" {
23
+ t.Errorf("Expected 'value1', got '%s'", string(value))
24
+ }
25
+
26
+ // Test missing key
27
+ _, found = lru.Get("nonexistent")
28
+ if found {
29
+ t.Error("Expected not to find nonexistent key")
30
+ }
31
+ }
32
+
33
+ // TestLRUCache_Update verifies updating existing keys
34
+ func TestLRUCache_Update(t *testing.T) {
35
+ lru := cache.NewLRUCache(100)
36
+
37
+ lru.Set("key1", []byte("original"))
38
+ lru.Set("key1", []byte("updated"))
39
+
40
+ value, found := lru.Get("key1")
41
+ if !found {
42
+ t.Fatal("Expected to find key1")
43
+ }
44
+ if string(value) != "updated" {
45
+ t.Errorf("Expected 'updated', got '%s'", string(value))
46
+ }
47
+ }
48
+
49
+ // TestLRUCache_Eviction verifies LRU eviction when over capacity
50
+ func TestLRUCache_Eviction(t *testing.T) {
51
+ lru := cache.NewLRUCache(3)
52
+
53
+ // Fill cache to capacity
54
+ lru.Set("key1", []byte("value1"))
55
+ lru.Set("key2", []byte("value2"))
56
+ lru.Set("key3", []byte("value3"))
57
+
58
+ // Access key1 to make it recently used
59
+ lru.Get("key1")
60
+
61
+ // Add new key, should evict key2 (least recently used)
62
+ lru.Set("key4", []byte("value4"))
63
+
64
+ // key2 should be evicted
65
+ _, found := lru.Get("key2")
66
+ if found {
67
+ t.Error("Expected key2 to be evicted")
68
+ }
69
+
70
+ // key1 should still exist (was accessed recently)
71
+ _, found = lru.Get("key1")
72
+ if !found {
73
+ t.Error("Expected key1 to still exist")
74
+ }
75
+
76
+ // key3 and key4 should exist
77
+ _, found = lru.Get("key3")
78
+ if !found {
79
+ t.Error("Expected key3 to exist")
80
+ }
81
+ _, found = lru.Get("key4")
82
+ if !found {
83
+ t.Error("Expected key4 to exist")
84
+ }
85
+ }
86
+
87
+ // TestLRUCache_Delete verifies deletion operations
88
+ func TestLRUCache_Delete(t *testing.T) {
89
+ lru := cache.NewLRUCache(100)
90
+
91
+ lru.Set("key1", []byte("value1"))
92
+ lru.Set("key2", []byte("value2"))
93
+
94
+ lru.Delete("key1")
95
+
96
+ _, found := lru.Get("key1")
97
+ if found {
98
+ t.Error("Expected key1 to be deleted")
99
+ }
100
+
101
+ // key2 should still exist
102
+ _, found = lru.Get("key2")
103
+ if !found {
104
+ t.Error("Expected key2 to still exist")
105
+ }
106
+ }
107
+
108
+ // TestLRUCache_DeleteNonexistent verifies deleting nonexistent keys doesn't panic
109
+ func TestLRUCache_DeleteNonexistent(t *testing.T) {
110
+ lru := cache.NewLRUCache(100)
111
+
112
+ // Should not panic
113
+ lru.Delete("nonexistent")
114
+ }
115
+
116
+ // TestCacheMissError tests the error type
117
+ func TestCacheMissError(t *testing.T) {
118
+ err := cache.ErrCacheMiss
119
+ if err.Error() != "cache miss" {
120
+ t.Errorf("Expected 'cache miss', got '%s'", err.Error())
121
+ }
122
+ }
123
+
124
+ // TestMultiTierCache_LocalOnly tests cache without Redis connection
125
+ func TestMultiTierCache_LocalOnly(t *testing.T) {
126
+ // Create cache with invalid Redis URL to ensure Redis is not used
127
+ cfg := cache.Config{
128
+ RedisURL: "redis://invalid:6379", // Will fail connection
129
+ LocalSize: 100,
130
+ TTL: time.Hour,
131
+ MaxRetries: 1,
132
+ PoolSize: 1,
133
+ }
134
+
135
+ c, err := cache.New(cfg)
136
+ if err != nil {
137
+ t.Fatalf("Failed to create cache: %v", err)
138
+ }
139
+ defer c.Close()
140
+
141
+ ctx := context.Background()
142
+
143
+ // Set and get should work with local cache
144
+ err = c.Set(ctx, "key1", []byte("value1"), 0)
145
+ if err != nil {
146
+ t.Fatalf("Set failed: %v", err)
147
+ }
148
+
149
+ value, err := c.Get(ctx, "key1")
150
+ if err != nil {
151
+ t.Fatalf("Get failed: %v", err)
152
+ }
153
+ if string(value) != "value1" {
154
+ t.Errorf("Expected 'value1', got '%s'", string(value))
155
+ }
156
+ }
157
+
158
+ // TestMultiTierCache_CacheMiss tests cache miss behavior
159
+ func TestMultiTierCache_CacheMiss(t *testing.T) {
160
+ cfg := cache.Config{
161
+ RedisURL: "redis://invalid:6379",
162
+ LocalSize: 100,
163
+ TTL: time.Hour,
164
+ MaxRetries: 1,
165
+ PoolSize: 1,
166
+ }
167
+
168
+ c, err := cache.New(cfg)
169
+ if err != nil {
170
+ t.Fatalf("Failed to create cache: %v", err)
171
+ }
172
+ defer c.Close()
173
+
174
+ ctx := context.Background()
175
+
176
+ _, err = c.Get(ctx, "nonexistent")
177
+ if err == nil {
178
+ t.Error("Expected cache miss error")
179
+ }
180
+ }
181
+
182
+ // TestMultiTierCache_Delete tests deletion
183
+ func TestMultiTierCache_Delete(t *testing.T) {
184
+ cfg := cache.Config{
185
+ RedisURL: "redis://invalid:6379",
186
+ LocalSize: 100,
187
+ TTL: time.Hour,
188
+ MaxRetries: 1,
189
+ PoolSize: 1,
190
+ }
191
+
192
+ c, err := cache.New(cfg)
193
+ if err != nil {
194
+ t.Fatalf("Failed to create cache: %v", err)
195
+ }
196
+ defer c.Close()
197
+
198
+ ctx := context.Background()
199
+
200
+ // Set then delete
201
+ c.Set(ctx, "key1", []byte("value1"), 0)
202
+ err = c.Delete(ctx, "key1")
203
+ if err != nil {
204
+ t.Fatalf("Delete failed: %v", err)
205
+ }
206
+
207
+ // Should be gone
208
+ _, err = c.Get(ctx, "key1")
209
+ if err == nil {
210
+ t.Error("Expected cache miss after delete")
211
+ }
212
+ }
213
+
214
+ // TestMultiTierCache_JSON tests JSON operations
215
+ func TestMultiTierCache_JSON(t *testing.T) {
216
+ cfg := cache.Config{
217
+ RedisURL: "redis://invalid:6379",
218
+ LocalSize: 100,
219
+ TTL: time.Hour,
220
+ MaxRetries: 1,
221
+ PoolSize: 1,
222
+ }
223
+
224
+ c, err := cache.New(cfg)
225
+ if err != nil {
226
+ t.Fatalf("Failed to create cache: %v", err)
227
+ }
228
+ defer c.Close()
229
+
230
+ ctx := context.Background()
231
+
232
+ type testData struct {
233
+ Name string `json:"name"`
234
+ Value int `json:"value"`
235
+ }
236
+
237
+ original := testData{Name: "test", Value: 42}
238
+
239
+ err = c.SetJSON(ctx, "json-key", original, 0)
240
+ if err != nil {
241
+ t.Fatalf("SetJSON failed: %v", err)
242
+ }
243
+
244
+ var result testData
245
+ err = c.GetJSON(ctx, "json-key", &result)
246
+ if err != nil {
247
+ t.Fatalf("GetJSON failed: %v", err)
248
+ }
249
+
250
+ if result.Name != original.Name || result.Value != original.Value {
251
+ t.Errorf("JSON mismatch: expected %+v, got %+v", original, result)
252
+ }
253
+ }
254
+
255
+ // TestMultiTierCache_Metrics tests metrics tracking
256
+ func TestMultiTierCache_Metrics(t *testing.T) {
257
+ cfg := cache.Config{
258
+ RedisURL: "redis://invalid:6379",
259
+ LocalSize: 100,
260
+ TTL: time.Hour,
261
+ MaxRetries: 1,
262
+ PoolSize: 1,
263
+ }
264
+
265
+ c, err := cache.New(cfg)
266
+ if err != nil {
267
+ t.Fatalf("Failed to create cache: %v", err)
268
+ }
269
+ defer c.Close()
270
+
271
+ ctx := context.Background()
272
+
273
+ // Set and get to generate metrics
274
+ c.Set(ctx, "key1", []byte("value1"), 0)
275
+ c.Get(ctx, "key1") // Hit
276
+ c.Get(ctx, "key2") // Miss
277
+
278
+ metrics := c.GetMetrics()
279
+ if metrics.LocalHits != 1 {
280
+ t.Errorf("Expected 1 local hit, got %d", metrics.LocalHits)
281
+ }
282
+ if metrics.LocalMisses != 1 {
283
+ t.Errorf("Expected 1 local miss, got %d", metrics.LocalMisses)
284
+ }
285
+ }
286
+
287
+ // BenchmarkLRUCache_Set benchmarks LRU set operations
288
+ func BenchmarkLRUCache_Set(b *testing.B) {
289
+ lru := cache.NewLRUCache(10000)
290
+
291
+ b.ResetTimer()
292
+ for i := 0; i < b.N; i++ {
293
+ lru.Set("key"+string(rune(i%1000)), []byte("value"))
294
+ }
295
+ }
296
+
297
+ // BenchmarkLRUCache_Get benchmarks LRU get operations
298
+ func BenchmarkLRUCache_Get(b *testing.B) {
299
+ lru := cache.NewLRUCache(10000)
300
+
301
+ // Pre-populate
302
+ for i := 0; i < 1000; i++ {
303
+ lru.Set("key"+string(rune(i)), []byte("value"))
304
+ }
305
+
306
+ b.ResetTimer()
307
+ for i := 0; i < b.N; i++ {
308
+ lru.Get("key" + string(rune(i%1000)))
309
+ }
310
+ }
internal/gateway/cache/cache.go ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package cache provides caching for the API Gateway.
2
+ package cache
3
+
4
+ import (
5
+ "context"
6
+ "crypto/sha256"
7
+ "encoding/hex"
8
+ "encoding/json"
9
+ "time"
10
+
11
+ "github.com/redis/go-redis/v9"
12
+ "go.uber.org/zap"
13
+ )
14
+
15
+ // Config for cache manager
16
+ type Config struct {
17
+ RedisAddr string
18
+ RedisPassword string
19
+ RedisDB int
20
+ DefaultTTL time.Duration
21
+ MaxEntrySize int
22
+ KeyPrefix string
23
+ }
24
+
25
+ // Manager handles caching operations
26
+ type Manager struct {
27
+ client *redis.Client
28
+ config Config
29
+ logger *zap.Logger
30
+ }
31
+
32
+ // NewManager creates a cache manager
33
+ func NewManager(cfg Config, logger *zap.Logger) (*Manager, error) {
34
+ client := redis.NewClient(&redis.Options{
35
+ Addr: cfg.RedisAddr,
36
+ Password: cfg.RedisPassword,
37
+ DB: cfg.RedisDB,
38
+ })
39
+
40
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
41
+ defer cancel()
42
+
43
+ if err := client.Ping(ctx).Err(); err != nil {
44
+ return nil, err
45
+ }
46
+
47
+ return &Manager{
48
+ client: client,
49
+ config: cfg,
50
+ logger: logger,
51
+ }, nil
52
+ }
53
+
54
+ // GenerateKey creates cache key from request
55
+ func (m *Manager) GenerateKey(parts ...string) string {
56
+ h := sha256.New()
57
+ for _, p := range parts {
58
+ h.Write([]byte(p))
59
+ }
60
+ return m.config.KeyPrefix + hex.EncodeToString(h.Sum(nil))
61
+ }
62
+
63
+ // Get retrieves cached value
64
+ func (m *Manager) Get(ctx context.Context, key string) (interface{}, bool) {
65
+ data, err := m.client.Get(ctx, key).Bytes()
66
+ if err != nil {
67
+ return nil, false
68
+ }
69
+ var result interface{}
70
+ if err := json.Unmarshal(data, &result); err != nil {
71
+ return nil, false
72
+ }
73
+ return result, true
74
+ }
75
+
76
+ // Set stores value in cache
77
+ func (m *Manager) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
78
+ data, err := json.Marshal(value)
79
+ if err != nil {
80
+ return err
81
+ }
82
+ if len(data) > m.config.MaxEntrySize {
83
+ m.logger.Warn("Cache entry exceeds max size", zap.String("key", key))
84
+ return nil
85
+ }
86
+ if ttl == 0 {
87
+ ttl = m.config.DefaultTTL
88
+ }
89
+ return m.client.Set(ctx, key, data, ttl).Err()
90
+ }
91
+
92
+ // Delete removes value from cache
93
+ func (m *Manager) Delete(ctx context.Context, key string) error {
94
+ return m.client.Del(ctx, key).Err()
95
+ }
96
+
97
+ // Close closes Redis connection
98
+ func (m *Manager) Close() error {
99
+ return m.client.Close()
100
+ }
internal/gateway/cmd/main.go ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Gateway entry point
2
+ package main
3
+
4
+ import (
5
+ "flag"
6
+ "os"
7
+
8
+ "github.com/spf13/viper"
9
+ "go.uber.org/zap"
10
+
11
+ "github.com/AmaniQuery/amaniquery/internal/gateway"
12
+ )
13
+
14
+ func main() {
15
+ configPath := flag.String("config", "gateway.yaml", "Path to config file")
16
+ flag.Parse()
17
+
18
+ // Initialize logger
19
+ logger, _ := zap.NewProduction()
20
+ defer logger.Sync()
21
+
22
+ // Load configuration
23
+ cfg, err := loadConfig(*configPath)
24
+ if err != nil {
25
+ logger.Fatal("Failed to load config", zap.Error(err))
26
+ }
27
+
28
+ // Create and start gateway
29
+ gw, err := gateway.New(cfg, logger)
30
+ if err != nil {
31
+ logger.Fatal("Failed to create gateway", zap.Error(err))
32
+ }
33
+
34
+ logger.Info("Starting API Gateway", zap.String("addr", cfg.Server.BindAddr))
35
+ if err := gw.Start(); err != nil {
36
+ logger.Fatal("Gateway error", zap.Error(err))
37
+ }
38
+ }
39
+
40
+ func loadConfig(path string) (*gateway.Config, error) {
41
+ v := viper.New()
42
+ v.SetConfigFile(path)
43
+ v.SetConfigType("yaml")
44
+
45
+ // Environment variable overrides
46
+ v.AutomaticEnv()
47
+ v.SetEnvPrefix("GATEWAY")
48
+
49
+ // Set defaults
50
+ cfg := gateway.DefaultConfig()
51
+
52
+ if err := v.ReadInConfig(); err != nil {
53
+ if !os.IsNotExist(err) {
54
+ return nil, err
55
+ }
56
+ // Use defaults if config file doesn't exist
57
+ return cfg, nil
58
+ }
59
+
60
+ if err := v.Unmarshal(cfg); err != nil {
61
+ return nil, err
62
+ }
63
+
64
+ return cfg, nil
65
+ }
internal/gateway/config.go ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package gateway provides the API Gateway for the RAG Agent Framework.
2
+ // It implements a multi-protocol gateway supporting REST, GraphQL, WebSocket, and SSE,
3
+ // with comprehensive middleware for security, observability, and performance.
4
+ package gateway
5
+
6
+ import (
7
+ "time"
8
+ )
9
+
10
+ // Config holds all gateway configuration
11
+ type Config struct {
12
+ Server ServerConfig `yaml:"server"`
13
+ TLS TLSConfig `yaml:"tls"`
14
+ RateLimit RateLimitConfig `yaml:"rateLimit"`
15
+ CORS CORSConfig `yaml:"cors"`
16
+ Auth AuthConfig `yaml:"auth"`
17
+ Cache CacheConfig `yaml:"cache"`
18
+ ServiceDiscovery ServiceDiscoveryConfig `yaml:"serviceDiscovery"`
19
+ CircuitBreaker CircuitBreakerConfig `yaml:"circuitBreaker"`
20
+ Observability ObservabilityConfig `yaml:"observability"`
21
+ WebSocket WebSocketConfig `yaml:"websocket"`
22
+ }
23
+
24
+ // ServerConfig contains HTTP server settings
25
+ type ServerConfig struct {
26
+ BindAddr string `yaml:"bindAddr"`
27
+ ReadTimeout time.Duration `yaml:"readTimeout"`
28
+ WriteTimeout time.Duration `yaml:"writeTimeout"`
29
+ IdleTimeout time.Duration `yaml:"idleTimeout"`
30
+ ShutdownTimeout time.Duration `yaml:"shutdownTimeout"`
31
+ MaxHeaderBytes int `yaml:"maxHeaderBytes"`
32
+ EnableHTTP2 bool `yaml:"enableHttp2"`
33
+ }
34
+
35
+ // TLSConfig holds TLS certificate settings
36
+ type TLSConfig struct {
37
+ Enabled bool `yaml:"enabled"`
38
+ CertFile string `yaml:"certFile"`
39
+ KeyFile string `yaml:"keyFile"`
40
+ MinVersion string `yaml:"minVersion"`
41
+ ClientCAFile string `yaml:"clientCaFile"`
42
+ RequireClientCert bool `yaml:"requireClientCert"`
43
+ }
44
+
45
+ // RateLimitConfig specifies rate limiting parameters
46
+ type RateLimitConfig struct {
47
+ Enabled bool `yaml:"enabled"`
48
+ RequestsPerSec float64 `yaml:"requestsPerSec"`
49
+ BurstSize int `yaml:"burstSize"`
50
+ PerTenant bool `yaml:"perTenant"`
51
+ PerUser bool `yaml:"perUser"`
52
+ RedisEnabled bool `yaml:"redisEnabled"`
53
+ CleanupInterval time.Duration `yaml:"cleanupInterval"`
54
+ }
55
+
56
+ // CORSConfig specifies CORS settings
57
+ type CORSConfig struct {
58
+ AllowedOrigins []string `yaml:"allowedOrigins"`
59
+ AllowedMethods []string `yaml:"allowedMethods"`
60
+ AllowedHeaders []string `yaml:"allowedHeaders"`
61
+ ExposedHeaders []string `yaml:"exposedHeaders"`
62
+ AllowCredentials bool `yaml:"allowCredentials"`
63
+ MaxAge time.Duration `yaml:"maxAge"`
64
+ }
65
+
66
+ // AuthConfig contains authentication settings
67
+ type AuthConfig struct {
68
+ JWTSecret string `yaml:"jwtSecret"`
69
+ JWTPublicKeyFile string `yaml:"jwtPublicKeyFile"`
70
+ JWTIssuer string `yaml:"jwtIssuer"`
71
+ JWTAudience string `yaml:"jwtAudience"`
72
+ TokenDuration time.Duration `yaml:"tokenDuration"`
73
+ OPAEnabled bool `yaml:"opaEnabled"`
74
+ OPAAddr string `yaml:"opaAddr"`
75
+ OPAPolicy string `yaml:"opaPolicy"`
76
+ SkipPaths []string `yaml:"skipPaths"`
77
+ }
78
+
79
+ // CacheConfig specifies caching settings
80
+ type CacheConfig struct {
81
+ Enabled bool `yaml:"enabled"`
82
+ RedisAddr string `yaml:"redisAddr"`
83
+ RedisPassword string `yaml:"redisPassword"`
84
+ RedisDB int `yaml:"redisDb"`
85
+ DefaultTTL time.Duration `yaml:"defaultTtl"`
86
+ MaxEntrySize int `yaml:"maxEntrySize"`
87
+ KeyPrefix string `yaml:"keyPrefix"`
88
+ }
89
+
90
+ // ServiceDiscoveryConfig for Consul-based service discovery
91
+ type ServiceDiscoveryConfig struct {
92
+ Enabled bool `yaml:"enabled"`
93
+ ConsulAddr string `yaml:"consulAddr"`
94
+ ConsulToken string `yaml:"consulToken"`
95
+ ConsulDatacenter string `yaml:"consulDatacenter"`
96
+ ServiceRefreshInterval time.Duration `yaml:"serviceRefreshInterval"`
97
+ // Direct service addresses (when Consul is disabled)
98
+ AgentServiceAddr string `yaml:"agentServiceAddr"`
99
+ RetrieverServiceAddr string `yaml:"retrieverServiceAddr"`
100
+ GeneratorServiceAddr string `yaml:"generatorServiceAddr"`
101
+ MemoryServiceAddr string `yaml:"memoryServiceAddr"`
102
+ }
103
+
104
+ // CircuitBreakerConfig for per-service circuit breakers
105
+ type CircuitBreakerConfig struct {
106
+ MaxRequests uint32 `yaml:"maxRequests"`
107
+ Interval time.Duration `yaml:"interval"`
108
+ Timeout time.Duration `yaml:"timeout"`
109
+ FailureThreshold uint32 `yaml:"failureThreshold"`
110
+ }
111
+
112
+ // ObservabilityConfig for tracing and metrics
113
+ type ObservabilityConfig struct {
114
+ MetricsEnabled bool `yaml:"metricsEnabled"`
115
+ MetricsPath string `yaml:"metricsPath"`
116
+ TracingEnabled bool `yaml:"tracingEnabled"`
117
+ TracingEndpoint string `yaml:"tracingEndpoint"`
118
+ ServiceName string `yaml:"serviceName"`
119
+ AuditLogEnabled bool `yaml:"auditLogEnabled"`
120
+ }
121
+
122
+ // WebSocketConfig for WebSocket connections
123
+ type WebSocketConfig struct {
124
+ ReadBufferSize int `yaml:"readBufferSize"`
125
+ WriteBufferSize int `yaml:"writeBufferSize"`
126
+ PingInterval time.Duration `yaml:"pingInterval"`
127
+ PongWait time.Duration `yaml:"pongWait"`
128
+ WriteWait time.Duration `yaml:"writeWait"`
129
+ MaxMessageSize int64 `yaml:"maxMessageSize"`
130
+ }
131
+
132
+ // DefaultConfig returns a configuration with sensible defaults
133
+ func DefaultConfig() *Config {
134
+ return &Config{
135
+ Server: ServerConfig{
136
+ BindAddr: ":8443",
137
+ ReadTimeout: 30 * time.Second,
138
+ WriteTimeout: 60 * time.Second,
139
+ IdleTimeout: 120 * time.Second,
140
+ ShutdownTimeout: 30 * time.Second,
141
+ MaxHeaderBytes: 1 << 20, // 1MB
142
+ EnableHTTP2: true,
143
+ },
144
+ TLS: TLSConfig{
145
+ Enabled: false,
146
+ MinVersion: "1.2",
147
+ },
148
+ RateLimit: RateLimitConfig{
149
+ Enabled: true,
150
+ RequestsPerSec: 100,
151
+ BurstSize: 200,
152
+ PerTenant: true,
153
+ PerUser: true,
154
+ CleanupInterval: 10 * time.Minute,
155
+ },
156
+ CORS: CORSConfig{
157
+ AllowedOrigins: []string{"*"},
158
+ AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
159
+ AllowedHeaders: []string{"Authorization", "Content-Type", "X-Request-ID", "X-Client-Version"},
160
+ ExposedHeaders: []string{"X-Request-ID", "X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Reset"},
161
+ AllowCredentials: true,
162
+ MaxAge: 3600 * time.Second,
163
+ },
164
+ Auth: AuthConfig{
165
+ TokenDuration: 24 * time.Hour,
166
+ OPAPolicy: "authz/allow",
167
+ SkipPaths: []string{"/admin/health", "/admin/metrics"},
168
+ },
169
+ Cache: CacheConfig{
170
+ Enabled: true,
171
+ DefaultTTL: 5 * time.Minute,
172
+ MaxEntrySize: 1 << 20, // 1MB
173
+ KeyPrefix: "rag:gateway:",
174
+ },
175
+ ServiceDiscovery: ServiceDiscoveryConfig{
176
+ ServiceRefreshInterval: 30 * time.Second,
177
+ AgentServiceAddr: "localhost:9090",
178
+ RetrieverServiceAddr: "localhost:9091",
179
+ GeneratorServiceAddr: "localhost:9092",
180
+ MemoryServiceAddr: "localhost:9093",
181
+ },
182
+ CircuitBreaker: CircuitBreakerConfig{
183
+ MaxRequests: 5,
184
+ Interval: 60 * time.Second,
185
+ Timeout: 30 * time.Second,
186
+ FailureThreshold: 3,
187
+ },
188
+ Observability: ObservabilityConfig{
189
+ MetricsEnabled: true,
190
+ MetricsPath: "/admin/metrics",
191
+ TracingEnabled: true,
192
+ ServiceName: "api-gateway",
193
+ AuditLogEnabled: true,
194
+ },
195
+ WebSocket: WebSocketConfig{
196
+ ReadBufferSize: 1024,
197
+ WriteBufferSize: 1024,
198
+ PingInterval: 30 * time.Second,
199
+ PongWait: 60 * time.Second,
200
+ WriteWait: 10 * time.Second,
201
+ MaxMessageSize: 512 * 1024, // 512KB
202
+ },
203
+ }
204
+ }
internal/gateway/gateway.go ADDED
@@ -0,0 +1,457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package gateway
2
+
3
+ import (
4
+ "context"
5
+ "crypto/tls"
6
+ "crypto/x509"
7
+ "errors"
8
+ "fmt"
9
+ "net/http"
10
+ "os"
11
+ "os/signal"
12
+ "sync"
13
+ "syscall"
14
+
15
+ "github.com/gorilla/mux"
16
+ "github.com/prometheus/client_golang/prometheus/promhttp"
17
+ "go.opentelemetry.io/otel"
18
+ "go.opentelemetry.io/otel/trace"
19
+ "go.uber.org/zap"
20
+
21
+ gatewaycache "github.com/AmaniQuery/amaniquery/internal/gateway/cache"
22
+ "github.com/AmaniQuery/amaniquery/internal/gateway/gwtypes"
23
+ "github.com/AmaniQuery/amaniquery/internal/gateway/handlers"
24
+ "github.com/AmaniQuery/amaniquery/internal/gateway/middleware"
25
+ "github.com/AmaniQuery/amaniquery/internal/gateway/services"
26
+ )
27
+
28
+ // Gateway is the main API Gateway server
29
+ type Gateway struct {
30
+ config *Config
31
+ router *mux.Router
32
+ server *http.Server
33
+ logger *zap.Logger
34
+ tracer trace.Tracer
35
+
36
+ // Middleware
37
+ corsMiddleware *middleware.CORSMiddleware
38
+ rateLimitMiddleware *middleware.RateLimitMiddleware
39
+ authMiddleware *middleware.AuthMiddleware
40
+ auditMiddleware *middleware.AuditMiddleware
41
+ tracingMiddleware *middleware.TracingMiddleware
42
+
43
+ // Services
44
+ serviceRegistry *services.Registry
45
+ cacheManager *gatewaycache.Manager
46
+
47
+ // Handlers
48
+ queryHandler *handlers.QueryHandler
49
+ memoryHandler *handlers.MemoryHandler
50
+ agentHandler *handlers.AgentHandler
51
+ wsHandler *handlers.WebSocketHandler
52
+ adminHandler *handlers.AdminHandler
53
+ authHandler *handlers.AuthHandler
54
+ graphqlHandler *handlers.GraphQLHandler
55
+
56
+ // Lifecycle
57
+ wg sync.WaitGroup
58
+ shutdown chan struct{}
59
+ }
60
+
61
+ // New creates a new Gateway instance
62
+ func New(cfg *Config, logger *zap.Logger) (*Gateway, error) {
63
+ if cfg == nil {
64
+ cfg = DefaultConfig()
65
+ }
66
+ if logger == nil {
67
+ var err error
68
+ logger, err = zap.NewProduction()
69
+ if err != nil {
70
+ return nil, fmt.Errorf("failed to create logger: %w", err)
71
+ }
72
+ }
73
+
74
+ g := &Gateway{
75
+ config: cfg,
76
+ router: mux.NewRouter(),
77
+ logger: logger,
78
+ tracer: otel.Tracer(cfg.Observability.ServiceName),
79
+ shutdown: make(chan struct{}),
80
+ }
81
+
82
+ // Initialize components
83
+ if err := g.initializeMiddleware(); err != nil {
84
+ return nil, fmt.Errorf("failed to initialize middleware: %w", err)
85
+ }
86
+
87
+ if err := g.initializeServices(); err != nil {
88
+ return nil, fmt.Errorf("failed to initialize services: %w", err)
89
+ }
90
+
91
+ if err := g.initializeHandlers(); err != nil {
92
+ return nil, fmt.Errorf("failed to initialize handlers: %w", err)
93
+ }
94
+
95
+ g.setupRoutes()
96
+
97
+ return g, nil
98
+ }
99
+
100
+ // initializeMiddleware sets up all middleware components
101
+ func (g *Gateway) initializeMiddleware() error {
102
+ // CORS middleware
103
+ g.corsMiddleware = middleware.NewCORSMiddleware(middleware.CORSConfig{
104
+ AllowedOrigins: g.config.CORS.AllowedOrigins,
105
+ AllowedMethods: g.config.CORS.AllowedMethods,
106
+ AllowedHeaders: g.config.CORS.AllowedHeaders,
107
+ ExposedHeaders: g.config.CORS.ExposedHeaders,
108
+ AllowCredentials: g.config.CORS.AllowCredentials,
109
+ MaxAge: int(g.config.CORS.MaxAge.Seconds()),
110
+ })
111
+
112
+ // Rate limiting middleware
113
+ if g.config.RateLimit.Enabled {
114
+ var err error
115
+ g.rateLimitMiddleware, err = middleware.NewRateLimitMiddleware(middleware.RateLimitConfig{
116
+ RequestsPerSec: g.config.RateLimit.RequestsPerSec,
117
+ BurstSize: g.config.RateLimit.BurstSize,
118
+ PerTenant: g.config.RateLimit.PerTenant,
119
+ PerUser: g.config.RateLimit.PerUser,
120
+ RedisAddr: g.config.Cache.RedisAddr,
121
+ RedisEnabled: g.config.RateLimit.RedisEnabled,
122
+ CleanupInterval: g.config.RateLimit.CleanupInterval,
123
+ }, g.logger)
124
+ if err != nil {
125
+ return fmt.Errorf("failed to create rate limit middleware: %w", err)
126
+ }
127
+ }
128
+
129
+ // Auth middleware
130
+ g.authMiddleware = middleware.NewAuthMiddleware(middleware.AuthConfig{
131
+ JWTSecret: g.config.Auth.JWTSecret,
132
+ JWTIssuer: g.config.Auth.JWTIssuer,
133
+ JWTAudience: g.config.Auth.JWTAudience,
134
+ OPAEnabled: g.config.Auth.OPAEnabled,
135
+ OPAAddr: g.config.Auth.OPAAddr,
136
+ OPAPolicy: g.config.Auth.OPAPolicy,
137
+ SkipPaths: g.config.Auth.SkipPaths,
138
+ }, g.logger)
139
+
140
+ // Audit middleware
141
+ if g.config.Observability.AuditLogEnabled {
142
+ g.auditMiddleware = middleware.NewAuditMiddleware(g.logger)
143
+ }
144
+
145
+ // Tracing middleware
146
+ if g.config.Observability.TracingEnabled {
147
+ g.tracingMiddleware = middleware.NewTracingMiddleware(g.tracer)
148
+ }
149
+
150
+ return nil
151
+ }
152
+
153
+ // initializeServices sets up backend service connections
154
+ func (g *Gateway) initializeServices() error {
155
+ var err error
156
+
157
+ // Service registry
158
+ g.serviceRegistry, err = services.NewRegistry(services.RegistryConfig{
159
+ ConsulEnabled: g.config.ServiceDiscovery.Enabled,
160
+ ConsulAddr: g.config.ServiceDiscovery.ConsulAddr,
161
+ ConsulToken: g.config.ServiceDiscovery.ConsulToken,
162
+ RefreshInterval: g.config.ServiceDiscovery.ServiceRefreshInterval,
163
+ AgentAddr: g.config.ServiceDiscovery.AgentServiceAddr,
164
+ RetrieverAddr: g.config.ServiceDiscovery.RetrieverServiceAddr,
165
+ GeneratorAddr: g.config.ServiceDiscovery.GeneratorServiceAddr,
166
+ MemoryAddr: g.config.ServiceDiscovery.MemoryServiceAddr,
167
+ CircuitBreakerCfg: services.CircuitBreakerConfig{
168
+ MaxRequests: g.config.CircuitBreaker.MaxRequests,
169
+ Interval: g.config.CircuitBreaker.Interval,
170
+ Timeout: g.config.CircuitBreaker.Timeout,
171
+ FailureThreshold: g.config.CircuitBreaker.FailureThreshold,
172
+ },
173
+ }, g.logger)
174
+ if err != nil {
175
+ return fmt.Errorf("failed to create service registry: %w", err)
176
+ }
177
+
178
+ // Cache manager
179
+ if g.config.Cache.Enabled {
180
+ g.cacheManager, err = gatewaycache.NewManager(gatewaycache.Config{
181
+ RedisAddr: g.config.Cache.RedisAddr,
182
+ RedisPassword: g.config.Cache.RedisPassword,
183
+ RedisDB: g.config.Cache.RedisDB,
184
+ DefaultTTL: g.config.Cache.DefaultTTL,
185
+ MaxEntrySize: g.config.Cache.MaxEntrySize,
186
+ KeyPrefix: g.config.Cache.KeyPrefix,
187
+ }, g.logger)
188
+ if err != nil {
189
+ g.logger.Warn("Failed to create cache manager, caching disabled", zap.Error(err))
190
+ }
191
+ }
192
+
193
+ return nil
194
+ }
195
+
196
+ // initializeHandlers sets up all request handlers
197
+ func (g *Gateway) initializeHandlers() error {
198
+ // Query handler
199
+ g.queryHandler = handlers.NewQueryHandler(
200
+ g.serviceRegistry,
201
+ g.cacheManager,
202
+ g.logger,
203
+ )
204
+
205
+ // Memory handler
206
+ g.memoryHandler = handlers.NewMemoryHandler(
207
+ g.serviceRegistry,
208
+ g.logger,
209
+ )
210
+
211
+ // Agent handler
212
+ g.agentHandler = handlers.NewAgentHandler(
213
+ g.serviceRegistry,
214
+ g.logger,
215
+ )
216
+
217
+ // WebSocket handler
218
+ g.wsHandler = handlers.NewWebSocketHandler(
219
+ g.serviceRegistry,
220
+ gwtypes.WebSocketConfig{
221
+ ReadBufferSize: g.config.WebSocket.ReadBufferSize,
222
+ WriteBufferSize: g.config.WebSocket.WriteBufferSize,
223
+ PingInterval: g.config.WebSocket.PingInterval,
224
+ PongWait: g.config.WebSocket.PongWait,
225
+ WriteWait: g.config.WebSocket.WriteWait,
226
+ MaxMessageSize: g.config.WebSocket.MaxMessageSize,
227
+ },
228
+ g.logger,
229
+ )
230
+
231
+ // Admin handler
232
+ g.adminHandler = handlers.NewAdminHandler(
233
+ g.serviceRegistry,
234
+ g.logger,
235
+ )
236
+
237
+ // Auth handler
238
+ g.authHandler = handlers.NewAuthHandler(
239
+ middleware.AuthConfig{
240
+ JWTSecret: g.config.Auth.JWTSecret,
241
+ JWTIssuer: g.config.Auth.JWTIssuer,
242
+ JWTAudience: g.config.Auth.JWTAudience,
243
+ },
244
+ g.logger,
245
+ )
246
+
247
+ // GraphQL handler
248
+ g.graphqlHandler = handlers.NewGraphQLHandler(
249
+ g.serviceRegistry,
250
+ g.logger,
251
+ )
252
+
253
+ return nil
254
+ }
255
+
256
+ // setupRoutes configures all API routes
257
+ func (g *Gateway) setupRoutes() {
258
+ // Global middleware
259
+ g.router.Use(middleware.Recovery(g.logger))
260
+ g.router.Use(middleware.RequestID())
261
+ g.router.Use(g.corsMiddleware.Handler)
262
+
263
+ if g.tracingMiddleware != nil {
264
+ g.router.Use(g.tracingMiddleware.Handler)
265
+ }
266
+
267
+ // Public routes (no auth required)
268
+ g.router.HandleFunc("/admin/health", g.adminHandler.HealthCheck).Methods("GET")
269
+ g.router.Handle("/admin/metrics", promhttp.Handler()).Methods("GET")
270
+
271
+ // API v2 routes
272
+ v2 := g.router.PathPrefix("/v2").Subrouter()
273
+
274
+ // Apply rate limiting
275
+ if g.rateLimitMiddleware != nil {
276
+ v2.Use(g.rateLimitMiddleware.Handler)
277
+ }
278
+
279
+ // Apply authentication
280
+ v2.Use(g.authMiddleware.Handler)
281
+
282
+ // Apply audit logging
283
+ if g.auditMiddleware != nil {
284
+ v2.Use(g.auditMiddleware.Handler)
285
+ }
286
+
287
+ // Query routes
288
+ queries := v2.PathPrefix("/queries").Subrouter()
289
+ queries.HandleFunc("", g.queryHandler.ExecuteQuery).Methods("POST")
290
+ queries.HandleFunc("/stream", g.wsHandler.HandleStream)
291
+ queries.HandleFunc("/{queryId}", g.queryHandler.GetQueryResult).Methods("GET")
292
+
293
+ // Memory routes
294
+ memory := v2.PathPrefix("/memory").Subrouter()
295
+ memory.HandleFunc("/context", g.memoryHandler.GetContextWindow).Methods("GET")
296
+ memory.HandleFunc("/sessions/{sessionId}/consolidate", g.memoryHandler.ConsolidateMemory).Methods("POST")
297
+
298
+ // Agent routes
299
+ agents := v2.PathPrefix("/agents").Subrouter()
300
+ agents.HandleFunc("", g.agentHandler.CreateAgent).Methods("POST")
301
+ agents.HandleFunc("/{agentId}", g.agentHandler.GetAgent).Methods("GET")
302
+ agents.HandleFunc("/{agentId}", g.agentHandler.DeleteAgent).Methods("DELETE")
303
+ agents.HandleFunc("/{agentId}/execute", g.agentHandler.ExecutePlan).Methods("POST")
304
+
305
+ // Auth routes
306
+ v2.HandleFunc("/auth/token", g.authHandler.Token).Methods("POST")
307
+
308
+ // GraphQL route
309
+ v2.Handle("/graphql", g.graphqlHandler).Methods("POST", "GET")
310
+
311
+ // CORS preflight handler
312
+ g.router.Methods("OPTIONS").HandlerFunc(g.corsMiddleware.HandlePreflight)
313
+ }
314
+
315
+ // Start starts the gateway server
316
+ func (g *Gateway) Start() error {
317
+ // Build TLS config if enabled
318
+ var tlsConfig *tls.Config
319
+ if g.config.TLS.Enabled {
320
+ var err error
321
+ tlsConfig, err = g.buildTLSConfig()
322
+ if err != nil {
323
+ return fmt.Errorf("failed to build TLS config: %w", err)
324
+ }
325
+ }
326
+
327
+ // Create HTTP server
328
+ g.server = &http.Server{
329
+ Addr: g.config.Server.BindAddr,
330
+ Handler: g.router,
331
+ ReadTimeout: g.config.Server.ReadTimeout,
332
+ WriteTimeout: g.config.Server.WriteTimeout,
333
+ IdleTimeout: g.config.Server.IdleTimeout,
334
+ MaxHeaderBytes: g.config.Server.MaxHeaderBytes,
335
+ TLSConfig: tlsConfig,
336
+ }
337
+
338
+ // Start server in goroutine
339
+ g.wg.Add(1)
340
+ go func() {
341
+ defer g.wg.Done()
342
+
343
+ g.logger.Info("Gateway starting",
344
+ zap.String("addr", g.config.Server.BindAddr),
345
+ zap.Bool("tls", g.config.TLS.Enabled),
346
+ )
347
+
348
+ var err error
349
+ if g.config.TLS.Enabled {
350
+ err = g.server.ListenAndServeTLS(g.config.TLS.CertFile, g.config.TLS.KeyFile)
351
+ } else {
352
+ err = g.server.ListenAndServe()
353
+ }
354
+
355
+ if err != nil && !errors.Is(err, http.ErrServerClosed) {
356
+ g.logger.Error("Server error", zap.Error(err))
357
+ }
358
+ }()
359
+
360
+ // Wait for shutdown signal
361
+ g.handleShutdown()
362
+
363
+ return nil
364
+ }
365
+
366
+ // handleShutdown gracefully stops the server on interrupt signals
367
+ func (g *Gateway) handleShutdown() {
368
+ sigChan := make(chan os.Signal, 1)
369
+ signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
370
+
371
+ select {
372
+ case sig := <-sigChan:
373
+ g.logger.Info("Received shutdown signal", zap.String("signal", sig.String()))
374
+ case <-g.shutdown:
375
+ g.logger.Info("Shutdown requested")
376
+ }
377
+
378
+ g.Stop()
379
+ }
380
+
381
+ // Stop gracefully stops the gateway
382
+ func (g *Gateway) Stop() {
383
+ ctx, cancel := context.WithTimeout(context.Background(), g.config.Server.ShutdownTimeout)
384
+ defer cancel()
385
+
386
+ g.logger.Info("Shutting down gateway...")
387
+
388
+ // Shutdown HTTP server
389
+ if g.server != nil {
390
+ if err := g.server.Shutdown(ctx); err != nil {
391
+ g.logger.Error("Server shutdown error", zap.Error(err))
392
+ }
393
+ }
394
+
395
+ // Close service connections
396
+ if g.serviceRegistry != nil {
397
+ g.serviceRegistry.Close()
398
+ }
399
+
400
+ // Close cache manager
401
+ if g.cacheManager != nil {
402
+ g.cacheManager.Close()
403
+ }
404
+
405
+ // Close WebSocket connections
406
+ if g.wsHandler != nil {
407
+ g.wsHandler.CloseAll()
408
+ }
409
+
410
+ // Wait for goroutines
411
+ g.wg.Wait()
412
+
413
+ g.logger.Info("Gateway stopped")
414
+ }
415
+
416
+ // Shutdown requests a graceful shutdown
417
+ func (g *Gateway) Shutdown() {
418
+ close(g.shutdown)
419
+ }
420
+
421
+ // buildTLSConfig creates TLS configuration
422
+ func (g *Gateway) buildTLSConfig() (*tls.Config, error) {
423
+ tlsConfig := &tls.Config{
424
+ MinVersion: tls.VersionTLS12,
425
+ }
426
+
427
+ // Set minimum TLS version
428
+ switch g.config.TLS.MinVersion {
429
+ case "1.2":
430
+ tlsConfig.MinVersion = tls.VersionTLS12
431
+ case "1.3":
432
+ tlsConfig.MinVersion = tls.VersionTLS13
433
+ }
434
+
435
+ // Load client CA if mTLS is required
436
+ if g.config.TLS.RequireClientCert && g.config.TLS.ClientCAFile != "" {
437
+ caCert, err := os.ReadFile(g.config.TLS.ClientCAFile)
438
+ if err != nil {
439
+ return nil, fmt.Errorf("failed to read client CA file: %w", err)
440
+ }
441
+
442
+ caCertPool := x509.NewCertPool()
443
+ if !caCertPool.AppendCertsFromPEM(caCert) {
444
+ return nil, errors.New("failed to parse client CA certificate")
445
+ }
446
+
447
+ tlsConfig.ClientCAs = caCertPool
448
+ tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
449
+ }
450
+
451
+ return tlsConfig, nil
452
+ }
453
+
454
+ // Router returns the underlying router for testing
455
+ func (g *Gateway) Router() *mux.Router {
456
+ return g.router
457
+ }
internal/gateway/gwtypes/types.go ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package gwtypes provides shared types for the gateway and handlers.
2
+ package gwtypes
3
+
4
+ import "time"
5
+
6
+ // WebSocketConfig for WebSocket connections
7
+ type WebSocketConfig struct {
8
+ ReadBufferSize int `yaml:"readBufferSize"`
9
+ WriteBufferSize int `yaml:"writeBufferSize"`
10
+ PingInterval time.Duration `yaml:"pingInterval"`
11
+ PongWait time.Duration `yaml:"pongWait"`
12
+ WriteWait time.Duration `yaml:"writeWait"`
13
+ MaxMessageSize int64 `yaml:"maxMessageSize"`
14
+ }
15
+
16
+ // REST API request/response types matching OpenAPI spec
17
+
18
+ // QueryRequest represents a RAG query request
19
+ type QueryRequest struct {
20
+ Query string `json:"query"`
21
+ UserID string `json:"userId"`
22
+ SessionID string `json:"sessionId,omitempty"`
23
+ AgentID string `json:"agentId,omitempty"`
24
+ Context *QueryContext `json:"context,omitempty"`
25
+ Streaming bool `json:"streaming,omitempty"`
26
+ Options *QueryOptions `json:"options,omitempty"`
27
+ }
28
+
29
+ // QueryContext specifies context retrieval options
30
+ type QueryContext struct {
31
+ MaxTurns int `json:"maxTurns,omitempty"`
32
+ MemoryTypes []string `json:"memoryTypes,omitempty"`
33
+ }
34
+
35
+ // QueryOptions specifies query processing options
36
+ type QueryOptions struct {
37
+ Temperature float64 `json:"temperature,omitempty"`
38
+ MaxTokens int `json:"maxTokens,omitempty"`
39
+ }
40
+
41
+ // QueryResponse contains the query result
42
+ type QueryResponse struct {
43
+ QueryID string `json:"queryId"`
44
+ Answer string `json:"answer"`
45
+ Sources []Source `json:"sources,omitempty"`
46
+ Metadata *ResponseMetadata `json:"metadata,omitempty"`
47
+ }
48
+
49
+ // Source represents a retrieved document source
50
+ type Source struct {
51
+ DocumentID string `json:"documentId"`
52
+ Score float64 `json:"score"`
53
+ Content string `json:"content"`
54
+ Metadata map[string]string `json:"metadata,omitempty"`
55
+ }
56
+
57
+ // ResponseMetadata contains query processing info
58
+ type ResponseMetadata struct {
59
+ LatencyMs int64 `json:"latencyMs"`
60
+ TokensUsed int `json:"tokensUsed"`
61
+ CacheHit bool `json:"cacheHit"`
62
+ }
63
+
64
+ // StreamChunk represents a streaming response chunk
65
+ type StreamChunk struct {
66
+ Type string `json:"type"` // chunk, sources, metadata, done, error
67
+ Data string `json:"data,omitempty"`
68
+ Timestamp string `json:"timestamp"`
69
+ Metadata map[string]interface{} `json:"metadata,omitempty"`
70
+ }
71
+
72
+ // CreateAgentRequest for creating new agents
73
+ type CreateAgentRequest struct {
74
+ Name string `json:"name"`
75
+ Type string `json:"type"` // rag, agentic, custom
76
+ Config map[string]interface{} `json:"config,omitempty"`
77
+ }
78
+
79
+ // Agent represents an agent instance
80
+ type Agent struct {
81
+ ID string `json:"id"`
82
+ Name string `json:"name"`
83
+ Status string `json:"status"` // active, idle, error
84
+ CreatedAt string `json:"createdAt"`
85
+ Config map[string]interface{} `json:"config,omitempty"`
86
+ }
87
+
88
+ // ExecutionPlan for multi-step execution
89
+ type ExecutionPlan struct {
90
+ ID string `json:"id"`
91
+ Query string `json:"query"`
92
+ Steps []ExecutionStep `json:"steps"`
93
+ }
94
+
95
+ // ExecutionStep represents a single execution step
96
+ type ExecutionStep struct {
97
+ ID string `json:"id"`
98
+ Type string `json:"type"`
99
+ Description string `json:"description"`
100
+ Tool string `json:"tool"`
101
+ Input string `json:"input"`
102
+ Dependencies []string `json:"dependencies,omitempty"`
103
+ }
104
+
105
+ // PlanResult contains execution results
106
+ type PlanResult struct {
107
+ PlanID string `json:"planId"`
108
+ Status string `json:"status"`
109
+ StepResults []StepResult `json:"stepResults"`
110
+ FinalAnswer string `json:"finalAnswer"`
111
+ ExecutionTimeMs int64 `json:"executionTimeMs"`
112
+ }
113
+
114
+ // StepResult contains individual step results
115
+ type StepResult struct {
116
+ StepID string `json:"stepId"`
117
+ Status string `json:"status"`
118
+ Output string `json:"output"`
119
+ Error string `json:"error,omitempty"`
120
+ ExecutionTimeMs int64 `json:"executionTimeMs"`
121
+ }
122
+
123
+ // ContextWindow represents memory context
124
+ type ContextWindow struct {
125
+ SessionID string `json:"sessionId"`
126
+ Entries []ContextEntry `json:"entries"`
127
+ TotalTokens int `json:"totalTokens"`
128
+ }
129
+
130
+ // ContextEntry represents a single context entry
131
+ type ContextEntry struct {
132
+ ID string `json:"id"`
133
+ Type string `json:"type"`
134
+ Content string `json:"content"`
135
+ Timestamp string `json:"timestamp"`
136
+ Score float64 `json:"score,omitempty"`
137
+ }
138
+
139
+ // TokenRequest for OAuth2 token endpoint
140
+ type TokenRequest struct {
141
+ GrantType string `json:"grant_type" form:"grant_type"`
142
+ ClientID string `json:"client_id" form:"client_id"`
143
+ ClientSecret string `json:"client_secret" form:"client_secret"`
144
+ Username string `json:"username,omitempty" form:"username"`
145
+ Password string `json:"password,omitempty" form:"password"`
146
+ RefreshToken string `json:"refresh_token,omitempty" form:"refresh_token"`
147
+ }
148
+
149
+ // TokenResponse for OAuth2 token response
150
+ type TokenResponse struct {
151
+ AccessToken string `json:"access_token"`
152
+ TokenType string `json:"token_type"`
153
+ ExpiresIn int `json:"expires_in"`
154
+ RefreshToken string `json:"refresh_token,omitempty"`
155
+ Scope string `json:"scope,omitempty"`
156
+ }
157
+
158
+ // HealthResponse for health check
159
+ type HealthResponse struct {
160
+ Status string `json:"status"`
161
+ Timestamp string `json:"timestamp"`
162
+ Services map[string]string `json:"services"`
163
+ }
164
+
165
+ // ErrorResponse for API errors
166
+ type ErrorResponse struct {
167
+ Error string `json:"error"`
168
+ Message string `json:"message"`
169
+ Code string `json:"code,omitempty"`
170
+ RequestID string `json:"requestId,omitempty"`
171
+ }
172
+
173
+ // WebSocketMessage represents client WebSocket messages
174
+ type WebSocketMessage struct {
175
+ Type string `json:"type"` // query, ping, cancel
176
+ Payload interface{} `json:"payload,omitempty"`
177
+ }
178
+
179
+ // WebSocketQueryPayload for WebSocket query messages
180
+ type WebSocketQueryPayload struct {
181
+ QueryID string `json:"queryId,omitempty"`
182
+ Query string `json:"query"`
183
+ UserID string `json:"userId"`
184
+ SessionID string `json:"sessionId,omitempty"`
185
+ AgentID string `json:"agentId,omitempty"`
186
+ Streaming bool `json:"streaming"`
187
+ Options *QueryOptions `json:"options,omitempty"`
188
+ }
189
+
190
+ // WebSocketServerMessage represents server WebSocket messages
191
+ type WebSocketServerMessage struct {
192
+ Type string `json:"type"`
193
+ Timestamp string `json:"timestamp"`
194
+ Data interface{} `json:"data,omitempty"`
195
+ Metadata map[string]interface{} `json:"metadata,omitempty"`
196
+ }
internal/gateway/handlers/admin.go ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package handlers
2
+
3
+ import (
4
+ "encoding/json"
5
+ "net/http"
6
+ "time"
7
+
8
+ "go.uber.org/zap"
9
+
10
+ "github.com/AmaniQuery/amaniquery/internal/gateway/gwtypes"
11
+ "github.com/AmaniQuery/amaniquery/internal/gateway/services"
12
+ )
13
+
14
+ // AdminHandler handles admin endpoints
15
+ type AdminHandler struct {
16
+ registry *services.Registry
17
+ logger *zap.Logger
18
+ }
19
+
20
+ // NewAdminHandler creates a new admin handler
21
+ func NewAdminHandler(registry *services.Registry, logger *zap.Logger) *AdminHandler {
22
+ return &AdminHandler{
23
+ registry: registry,
24
+ logger: logger,
25
+ }
26
+ }
27
+
28
+ // HealthCheck handles GET /admin/health
29
+ func (h *AdminHandler) HealthCheck(w http.ResponseWriter, r *http.Request) {
30
+ ctx := r.Context()
31
+
32
+ // Check all backend services
33
+ serviceStatus := make(map[string]string)
34
+
35
+ // Check agent service
36
+ if h.registry != nil {
37
+ if _, err := h.registry.GetAgentClient(ctx); err != nil {
38
+ serviceStatus["agent"] = "unhealthy"
39
+ } else {
40
+ status := h.registry.HealthCheck(ctx, "agent")
41
+ serviceStatus["agent"] = status
42
+ }
43
+
44
+ // Check retriever service
45
+ if _, err := h.registry.GetRetrieverClient(ctx); err != nil {
46
+ serviceStatus["retriever"] = "unhealthy"
47
+ } else {
48
+ status := h.registry.HealthCheck(ctx, "retriever")
49
+ serviceStatus["retriever"] = status
50
+ }
51
+
52
+ // Check generator service
53
+ if _, err := h.registry.GetGeneratorClient(ctx); err != nil {
54
+ serviceStatus["generator"] = "unhealthy"
55
+ } else {
56
+ status := h.registry.HealthCheck(ctx, "generator")
57
+ serviceStatus["generator"] = status
58
+ }
59
+
60
+ // Check memory service
61
+ if _, err := h.registry.GetMemoryClient(ctx); err != nil {
62
+ serviceStatus["memory"] = "unhealthy"
63
+ } else {
64
+ status := h.registry.HealthCheck(ctx, "memory")
65
+ serviceStatus["memory"] = status
66
+ }
67
+ }
68
+
69
+ // Determine overall status
70
+ overallStatus := "healthy"
71
+ for _, status := range serviceStatus {
72
+ if status != "healthy" && status != "SERVING" {
73
+ overallStatus = "degraded"
74
+ break
75
+ }
76
+ }
77
+
78
+ response := gwtypes.HealthResponse{
79
+ Status: overallStatus,
80
+ Timestamp: time.Now().Format(time.RFC3339),
81
+ Services: serviceStatus,
82
+ }
83
+
84
+ // Set status code based on health
85
+ statusCode := http.StatusOK
86
+ if overallStatus != "healthy" {
87
+ statusCode = http.StatusServiceUnavailable
88
+ }
89
+
90
+ w.Header().Set("Content-Type", "application/json")
91
+ w.WriteHeader(statusCode)
92
+ json.NewEncoder(w).Encode(response)
93
+ }
internal/gateway/handlers/agent.go ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package handlers
2
+
3
+ import (
4
+ "encoding/json"
5
+ "net/http"
6
+ "time"
7
+
8
+ "github.com/google/uuid"
9
+ "github.com/gorilla/mux"
10
+ "go.uber.org/zap"
11
+
12
+ "github.com/AmaniQuery/amaniquery/internal/gateway/gwtypes"
13
+ "github.com/AmaniQuery/amaniquery/internal/gateway/middleware"
14
+ "github.com/AmaniQuery/amaniquery/internal/gateway/services"
15
+ )
16
+
17
+ // AgentHandler handles agent-related endpoints
18
+ type AgentHandler struct {
19
+ registry *services.Registry
20
+ logger *zap.Logger
21
+ }
22
+
23
+ // NewAgentHandler creates a new agent handler
24
+ func NewAgentHandler(registry *services.Registry, logger *zap.Logger) *AgentHandler {
25
+ return &AgentHandler{
26
+ registry: registry,
27
+ logger: logger,
28
+ }
29
+ }
30
+
31
+ // CreateAgent handles POST /v2/agents
32
+ func (h *AgentHandler) CreateAgent(w http.ResponseWriter, r *http.Request) {
33
+ ctx := r.Context()
34
+
35
+ // Parse request
36
+ var req gwtypes.CreateAgentRequest
37
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
38
+ h.errorResponse(w, http.StatusBadRequest, "Invalid request body", err.Error())
39
+ return
40
+ }
41
+
42
+ // Validate required fields
43
+ if req.Name == "" {
44
+ h.errorResponse(w, http.StatusBadRequest, "Agent name is required", "")
45
+ return
46
+ }
47
+ if req.Type == "" {
48
+ req.Type = "rag" // Default type
49
+ }
50
+
51
+ // Get user info from context
52
+ userID := middleware.UserIDFromContext(ctx)
53
+ tenantID := middleware.TenantIDFromContext(ctx)
54
+
55
+ // Get agent client
56
+ agentClient, err := h.registry.GetAgentClient(ctx)
57
+ if err != nil {
58
+ h.logger.Error("Failed to get agent client", zap.Error(err))
59
+ h.errorResponse(w, http.StatusServiceUnavailable, "Service unavailable", "")
60
+ return
61
+ }
62
+
63
+ // Generate agent ID
64
+ agentID := uuid.New().String()
65
+
66
+ // Create agent
67
+ agent, err := agentClient.CreateAgent(ctx, &services.CreateAgentRequest{
68
+ ID: agentID,
69
+ Name: req.Name,
70
+ Type: req.Type,
71
+ Config: req.Config,
72
+ UserID: userID,
73
+ TenantID: tenantID,
74
+ })
75
+ if err != nil {
76
+ h.logger.Error("Failed to create agent", zap.Error(err))
77
+ h.errorResponse(w, http.StatusInternalServerError, "Failed to create agent", err.Error())
78
+ return
79
+ }
80
+
81
+ // Build response
82
+ response := gwtypes.Agent{
83
+ ID: agent.ID,
84
+ Name: agent.Name,
85
+ Status: agent.Status,
86
+ CreatedAt: time.Now().Format(time.RFC3339),
87
+ Config: agent.Config,
88
+ }
89
+
90
+ h.jsonResponse(w, http.StatusCreated, response)
91
+ }
92
+
93
+ // GetAgent handles GET /v2/agents/{agentId}
94
+ func (h *AgentHandler) GetAgent(w http.ResponseWriter, r *http.Request) {
95
+ ctx := r.Context()
96
+ vars := mux.Vars(r)
97
+ agentID := vars["agentId"]
98
+
99
+ if agentID == "" {
100
+ h.errorResponse(w, http.StatusBadRequest, "Agent ID is required", "")
101
+ return
102
+ }
103
+
104
+ // Get agent client
105
+ agentClient, err := h.registry.GetAgentClient(ctx)
106
+ if err != nil {
107
+ h.logger.Error("Failed to get agent client", zap.Error(err))
108
+ h.errorResponse(w, http.StatusServiceUnavailable, "Service unavailable", "")
109
+ return
110
+ }
111
+
112
+ // Get agent
113
+ agent, err := agentClient.GetAgent(ctx, agentID)
114
+ if err != nil {
115
+ h.logger.Error("Failed to get agent", zap.Error(err), zap.String("agent_id", agentID))
116
+ h.errorResponse(w, http.StatusNotFound, "Agent not found", "")
117
+ return
118
+ }
119
+
120
+ // Build response
121
+ response := gwtypes.Agent{
122
+ ID: agent.ID,
123
+ Name: agent.Name,
124
+ Status: agent.Status,
125
+ CreatedAt: agent.CreatedAt,
126
+ Config: agent.Config,
127
+ }
128
+
129
+ h.jsonResponse(w, http.StatusOK, response)
130
+ }
131
+
132
+ // DeleteAgent handles DELETE /v2/agents/{agentId}
133
+ func (h *AgentHandler) DeleteAgent(w http.ResponseWriter, r *http.Request) {
134
+ ctx := r.Context()
135
+ vars := mux.Vars(r)
136
+ agentID := vars["agentId"]
137
+
138
+ if agentID == "" {
139
+ h.errorResponse(w, http.StatusBadRequest, "Agent ID is required", "")
140
+ return
141
+ }
142
+
143
+ // Get agent client
144
+ agentClient, err := h.registry.GetAgentClient(ctx)
145
+ if err != nil {
146
+ h.logger.Error("Failed to get agent client", zap.Error(err))
147
+ h.errorResponse(w, http.StatusServiceUnavailable, "Service unavailable", "")
148
+ return
149
+ }
150
+
151
+ // Delete agent
152
+ err = agentClient.DeleteAgent(ctx, agentID)
153
+ if err != nil {
154
+ h.logger.Error("Failed to delete agent", zap.Error(err), zap.String("agent_id", agentID))
155
+ h.errorResponse(w, http.StatusInternalServerError, "Failed to delete agent", err.Error())
156
+ return
157
+ }
158
+
159
+ w.WriteHeader(http.StatusNoContent)
160
+ }
161
+
162
+ // ExecutePlan handles POST /v2/agents/{agentId}/execute
163
+ func (h *AgentHandler) ExecutePlan(w http.ResponseWriter, r *http.Request) {
164
+ ctx := r.Context()
165
+ vars := mux.Vars(r)
166
+ agentID := vars["agentId"]
167
+
168
+ if agentID == "" {
169
+ h.errorResponse(w, http.StatusBadRequest, "Agent ID is required", "")
170
+ return
171
+ }
172
+
173
+ // Parse request
174
+ var plan gwtypes.ExecutionPlan
175
+ if err := json.NewDecoder(r.Body).Decode(&plan); err != nil {
176
+ h.errorResponse(w, http.StatusBadRequest, "Invalid request body", err.Error())
177
+ return
178
+ }
179
+
180
+ // Get user info from context
181
+ userID := middleware.UserIDFromContext(ctx)
182
+
183
+ // Get agent client
184
+ agentClient, err := h.registry.GetAgentClient(ctx)
185
+ if err != nil {
186
+ h.logger.Error("Failed to get agent client", zap.Error(err))
187
+ h.errorResponse(w, http.StatusServiceUnavailable, "Service unavailable", "")
188
+ return
189
+ }
190
+
191
+ // Generate plan ID if not provided
192
+ if plan.ID == "" {
193
+ plan.ID = uuid.New().String()
194
+ }
195
+
196
+ // Execute plan
197
+ start := time.Now()
198
+ result, err := agentClient.ExecutePlan(ctx, &services.ExecutePlanRequest{
199
+ AgentID: agentID,
200
+ PlanID: plan.ID,
201
+ Query: plan.Query,
202
+ Steps: h.convertSteps(plan.Steps),
203
+ UserID: userID,
204
+ })
205
+ if err != nil {
206
+ h.logger.Error("Failed to execute plan", zap.Error(err), zap.String("plan_id", plan.ID))
207
+ h.errorResponse(w, http.StatusInternalServerError, "Plan execution failed", err.Error())
208
+ return
209
+ }
210
+
211
+ // Build response
212
+ response := gwtypes.PlanResult{
213
+ PlanID: plan.ID,
214
+ Status: result.Status,
215
+ StepResults: h.convertStepResults(result.StepResults),
216
+ FinalAnswer: result.FinalAnswer,
217
+ ExecutionTimeMs: time.Since(start).Milliseconds(),
218
+ }
219
+
220
+ h.jsonResponse(w, http.StatusOK, response)
221
+ }
222
+
223
+ func (h *AgentHandler) convertSteps(steps []gwtypes.ExecutionStep) []*services.ExecutionStep {
224
+ result := make([]*services.ExecutionStep, len(steps))
225
+ for i, step := range steps {
226
+ result[i] = &services.ExecutionStep{
227
+ ID: step.ID,
228
+ Type: step.Type,
229
+ Description: step.Description,
230
+ Tool: step.Tool,
231
+ Input: step.Input,
232
+ Dependencies: step.Dependencies,
233
+ }
234
+ }
235
+ return result
236
+ }
237
+
238
+ func (h *AgentHandler) convertStepResults(results []*services.StepResult) []gwtypes.StepResult {
239
+ converted := make([]gwtypes.StepResult, len(results))
240
+ for i, r := range results {
241
+ converted[i] = gwtypes.StepResult{
242
+ StepID: r.StepID,
243
+ Status: r.Status,
244
+ Output: r.Output,
245
+ Error: r.Error,
246
+ ExecutionTimeMs: r.ExecutionTimeMs,
247
+ }
248
+ }
249
+ return converted
250
+ }
251
+
252
+ func (h *AgentHandler) jsonResponse(w http.ResponseWriter, status int, data interface{}) {
253
+ w.Header().Set("Content-Type", "application/json")
254
+ w.WriteHeader(status)
255
+ json.NewEncoder(w).Encode(data)
256
+ }
257
+
258
+ func (h *AgentHandler) errorResponse(w http.ResponseWriter, status int, message, detail string) {
259
+ w.Header().Set("Content-Type", "application/json")
260
+ w.WriteHeader(status)
261
+ resp := gwtypes.ErrorResponse{
262
+ Error: http.StatusText(status),
263
+ Message: message,
264
+ RequestID: w.Header().Get("X-Request-ID"),
265
+ }
266
+ if detail != "" {
267
+ resp.Code = detail
268
+ }
269
+ json.NewEncoder(w).Encode(resp)
270
+ }
internal/gateway/handlers/auth.go ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package handlers
2
+
3
+ import (
4
+ "encoding/json"
5
+ "net/http"
6
+ "time"
7
+
8
+ "github.com/golang-jwt/jwt/v5"
9
+ "go.uber.org/zap"
10
+
11
+ "github.com/AmaniQuery/amaniquery/internal/gateway/gwtypes"
12
+ "github.com/AmaniQuery/amaniquery/internal/gateway/middleware"
13
+ )
14
+
15
+ // AuthHandler handles authentication endpoints
16
+ type AuthHandler struct {
17
+ config middleware.AuthConfig
18
+ logger *zap.Logger
19
+ }
20
+
21
+ // NewAuthHandler creates a new auth handler
22
+ func NewAuthHandler(cfg middleware.AuthConfig, logger *zap.Logger) *AuthHandler {
23
+ return &AuthHandler{
24
+ config: middleware.AuthConfig{
25
+ JWTSecret: cfg.JWTSecret,
26
+ JWTIssuer: cfg.JWTIssuer,
27
+ JWTAudience: cfg.JWTAudience,
28
+ },
29
+ logger: logger,
30
+ }
31
+ }
32
+
33
+ // Token handles POST /v2/auth/token
34
+ func (h *AuthHandler) Token(w http.ResponseWriter, r *http.Request) {
35
+ if err := r.ParseForm(); err != nil {
36
+ h.errorResponse(w, http.StatusBadRequest, "Invalid request")
37
+ return
38
+ }
39
+
40
+ grantType := r.FormValue("grant_type")
41
+ clientID := r.FormValue("client_id")
42
+ clientSecret := r.FormValue("client_secret")
43
+
44
+ if !h.validateClient(clientID, clientSecret) {
45
+ h.errorResponse(w, http.StatusUnauthorized, "Invalid client credentials")
46
+ return
47
+ }
48
+
49
+ switch grantType {
50
+ case "client_credentials":
51
+ token, expiresIn, err := h.generateToken(clientID, []string{"service"})
52
+ if err != nil {
53
+ h.errorResponse(w, http.StatusInternalServerError, "Token generation failed")
54
+ return
55
+ }
56
+ h.tokenResponse(w, token, "", expiresIn)
57
+
58
+ case "password":
59
+ username := r.FormValue("username")
60
+ password := r.FormValue("password")
61
+ if !h.validateUser(username, password) {
62
+ h.errorResponse(w, http.StatusUnauthorized, "Invalid credentials")
63
+ return
64
+ }
65
+ token, expiresIn, _ := h.generateToken(username, []string{"user"})
66
+ refresh, _, _ := h.generateToken(username, []string{"refresh"})
67
+ h.tokenResponse(w, token, refresh, expiresIn)
68
+
69
+ default:
70
+ h.errorResponse(w, http.StatusBadRequest, "Unsupported grant type")
71
+ }
72
+ }
73
+
74
+ func (h *AuthHandler) validateClient(id, secret string) bool {
75
+ return id != "" && secret != ""
76
+ }
77
+
78
+ func (h *AuthHandler) validateUser(username, password string) bool {
79
+ return username != "" && password != ""
80
+ }
81
+
82
+ func (h *AuthHandler) generateToken(subject string, roles []string) (string, int, error) {
83
+ now := time.Now()
84
+ expiresIn := 3600
85
+ claims := &middleware.JWTClaims{
86
+ RegisteredClaims: jwt.RegisteredClaims{
87
+ Issuer: h.config.JWTIssuer,
88
+ Subject: subject,
89
+ IssuedAt: jwt.NewNumericDate(now),
90
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(expiresIn) * time.Second)),
91
+ },
92
+ UserID: subject,
93
+ Roles: roles,
94
+ }
95
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
96
+ tokenString, err := token.SignedString([]byte(h.config.JWTSecret))
97
+ return tokenString, expiresIn, err
98
+ }
99
+
100
+ func (h *AuthHandler) tokenResponse(w http.ResponseWriter, access, refresh string, expiresIn int) {
101
+ w.Header().Set("Content-Type", "application/json")
102
+ w.Header().Set("Cache-Control", "no-store")
103
+ json.NewEncoder(w).Encode(gwtypes.TokenResponse{
104
+ AccessToken: access,
105
+ TokenType: "Bearer",
106
+ ExpiresIn: expiresIn,
107
+ RefreshToken: refresh,
108
+ })
109
+ }
110
+
111
+ func (h *AuthHandler) errorResponse(w http.ResponseWriter, status int, message string) {
112
+ w.Header().Set("Content-Type", "application/json")
113
+ w.WriteHeader(status)
114
+ json.NewEncoder(w).Encode(map[string]string{"error": message})
115
+ }
internal/gateway/handlers/graphql.go ADDED
@@ -0,0 +1,620 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package handlers
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "net/http"
7
+ "time"
8
+
9
+ "github.com/google/uuid"
10
+ graphql "github.com/graph-gophers/graphql-go"
11
+ "go.uber.org/zap"
12
+
13
+ "github.com/AmaniQuery/amaniquery/internal/gateway/middleware"
14
+ "github.com/AmaniQuery/amaniquery/internal/gateway/services"
15
+ )
16
+
17
+ // GraphQL Schema Definition
18
+ const schemaString = `
19
+ schema {
20
+ query: Query
21
+ mutation: Mutation
22
+ }
23
+
24
+ type Query {
25
+ # Execute a RAG query and get the response
26
+ query(input: QueryInput!): QueryResponse!
27
+
28
+ # Get a specific query result by ID
29
+ queryResult(queryId: ID!): QueryResponse
30
+
31
+ # Get an agent by ID
32
+ agent(id: ID!): Agent
33
+
34
+ # List all agents for the current user
35
+ agents: [Agent!]!
36
+
37
+ # Get memory context for a session
38
+ contextWindow(sessionId: ID!, maxTurns: Int): ContextWindow!
39
+
40
+ # Health check
41
+ health: HealthStatus!
42
+ }
43
+
44
+ type Mutation {
45
+ # Execute a RAG query
46
+ executeQuery(input: QueryInput!): QueryResponse!
47
+
48
+ # Create a new agent
49
+ createAgent(input: CreateAgentInput!): Agent!
50
+
51
+ # Delete an agent
52
+ deleteAgent(id: ID!): Boolean!
53
+
54
+ # Execute an agent plan
55
+ executePlan(agentId: ID!, input: ExecutionPlanInput!): PlanResult!
56
+
57
+ # Consolidate memory for a session
58
+ consolidateMemory(sessionId: ID!): Boolean!
59
+ }
60
+
61
+ input QueryInput {
62
+ query: String!
63
+ sessionId: String
64
+ agentId: String
65
+ temperature: Float
66
+ maxTokens: Int
67
+ }
68
+
69
+ input CreateAgentInput {
70
+ name: String!
71
+ type: AgentType!
72
+ systemPrompt: String
73
+ }
74
+
75
+ input ExecutionPlanInput {
76
+ query: String!
77
+ steps: [ExecutionStepInput!]
78
+ }
79
+
80
+ input ExecutionStepInput {
81
+ type: String!
82
+ description: String!
83
+ tool: String
84
+ input: String
85
+ }
86
+
87
+ type QueryResponse {
88
+ queryId: ID!
89
+ answer: String!
90
+ sources: [Source!]!
91
+ metadata: QueryMetadata!
92
+ }
93
+
94
+ type Source {
95
+ documentId: ID!
96
+ score: Float!
97
+ content: String!
98
+ }
99
+
100
+ type QueryMetadata {
101
+ latencyMs: Int!
102
+ tokensUsed: Int!
103
+ cacheHit: Boolean!
104
+ }
105
+
106
+ type Agent {
107
+ id: ID!
108
+ name: String!
109
+ type: AgentType!
110
+ status: AgentStatus!
111
+ createdAt: String!
112
+ }
113
+
114
+ enum AgentType {
115
+ RAG
116
+ AGENTIC
117
+ CUSTOM
118
+ }
119
+
120
+ enum AgentStatus {
121
+ ACTIVE
122
+ IDLE
123
+ ERROR
124
+ }
125
+
126
+ type PlanResult {
127
+ planId: ID!
128
+ status: String!
129
+ finalAnswer: String!
130
+ executionTimeMs: Int!
131
+ stepResults: [StepResult!]!
132
+ }
133
+
134
+ type StepResult {
135
+ stepId: ID!
136
+ status: String!
137
+ output: String!
138
+ error: String
139
+ }
140
+
141
+ type ContextWindow {
142
+ sessionId: ID!
143
+ entries: [ContextEntry!]!
144
+ totalTokens: Int!
145
+ }
146
+
147
+ type ContextEntry {
148
+ id: ID!
149
+ type: String!
150
+ content: String!
151
+ timestamp: String!
152
+ score: Float
153
+ }
154
+
155
+ type HealthStatus {
156
+ status: String!
157
+ timestamp: String!
158
+ services: [ServiceHealth!]!
159
+ }
160
+
161
+ type ServiceHealth {
162
+ name: String!
163
+ status: String!
164
+ }
165
+ `
166
+
167
+ // GraphQLHandler handles GraphQL requests
168
+ type GraphQLHandler struct {
169
+ schema *graphql.Schema
170
+ registry *services.Registry
171
+ logger *zap.Logger
172
+ }
173
+
174
+ // NewGraphQLHandler creates a new GraphQL handler
175
+ func NewGraphQLHandler(registry *services.Registry, logger *zap.Logger) *GraphQLHandler {
176
+ resolver := &Resolver{
177
+ registry: registry,
178
+ logger: logger,
179
+ }
180
+
181
+ schema := graphql.MustParseSchema(schemaString, resolver,
182
+ graphql.UseFieldResolvers(),
183
+ graphql.MaxParallelism(20),
184
+ )
185
+
186
+ return &GraphQLHandler{
187
+ schema: schema,
188
+ registry: registry,
189
+ logger: logger,
190
+ }
191
+ }
192
+
193
+ // ServeHTTP implements http.Handler for GraphQL
194
+ func (h *GraphQLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
195
+ var params struct {
196
+ Query string `json:"query"`
197
+ OperationName string `json:"operationName"`
198
+ Variables map[string]interface{} `json:"variables"`
199
+ }
200
+
201
+ if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
202
+ h.errorResponse(w, "Invalid request body", http.StatusBadRequest)
203
+ return
204
+ }
205
+
206
+ ctx := r.Context()
207
+ response := h.schema.Exec(ctx, params.Query, params.OperationName, params.Variables)
208
+
209
+ w.Header().Set("Content-Type", "application/json")
210
+ if err := json.NewEncoder(w).Encode(response); err != nil {
211
+ h.logger.Error("Failed to encode GraphQL response", zap.Error(err))
212
+ }
213
+ }
214
+
215
+ func (h *GraphQLHandler) errorResponse(w http.ResponseWriter, message string, status int) {
216
+ w.Header().Set("Content-Type", "application/json")
217
+ w.WriteHeader(status)
218
+ json.NewEncoder(w).Encode(map[string]interface{}{
219
+ "errors": []map[string]string{{"message": message}},
220
+ })
221
+ }
222
+
223
+ // Resolver implements GraphQL resolvers
224
+ type Resolver struct {
225
+ registry *services.Registry
226
+ logger *zap.Logger
227
+ }
228
+
229
+ // Query resolvers
230
+
231
+ func (r *Resolver) Query(ctx context.Context, args struct{ Input QueryInputArgs }) (*QueryResponseResolver, error) {
232
+ return r.ExecuteQuery(ctx, args)
233
+ }
234
+
235
+ func (r *Resolver) QueryResult(ctx context.Context, args struct{ QueryId graphql.ID }) (*QueryResponseResolver, error) {
236
+ agentClient, err := r.registry.GetAgentClient(ctx)
237
+ if err != nil {
238
+ return nil, err
239
+ }
240
+
241
+ status, err := agentClient.GetQueryStatus(ctx, string(args.QueryId))
242
+ if err != nil {
243
+ return nil, err
244
+ }
245
+
246
+ return &QueryResponseResolver{
247
+ queryId: string(args.QueryId),
248
+ answer: status.Status,
249
+ }, nil
250
+ }
251
+
252
+ func (r *Resolver) Agent(ctx context.Context, args struct{ Id graphql.ID }) (*AgentResolver, error) {
253
+ agentClient, err := r.registry.GetAgentClient(ctx)
254
+ if err != nil {
255
+ return nil, err
256
+ }
257
+
258
+ agent, err := agentClient.GetAgent(ctx, string(args.Id))
259
+ if err != nil {
260
+ return nil, err
261
+ }
262
+
263
+ return &AgentResolver{agent: agent}, nil
264
+ }
265
+
266
+ func (r *Resolver) Agents(ctx context.Context) ([]*AgentResolver, error) {
267
+ // Return empty list - would query user's agents
268
+ return []*AgentResolver{}, nil
269
+ }
270
+
271
+ func (r *Resolver) ContextWindow(ctx context.Context, args struct {
272
+ SessionId graphql.ID
273
+ MaxTurns *int32
274
+ }) (*ContextWindowResolver, error) {
275
+ memoryClient, err := r.registry.GetMemoryClient(ctx)
276
+ if err != nil {
277
+ return nil, err
278
+ }
279
+
280
+ maxTurns := 50
281
+ if args.MaxTurns != nil {
282
+ maxTurns = int(*args.MaxTurns)
283
+ }
284
+
285
+ userID := middleware.UserIDFromContext(ctx)
286
+ window, err := memoryClient.GetContextWindow(ctx, &services.ContextWindowRequest{
287
+ SessionID: string(args.SessionId),
288
+ UserID: userID,
289
+ MaxTurns: maxTurns,
290
+ })
291
+ if err != nil {
292
+ return nil, err
293
+ }
294
+
295
+ return &ContextWindowResolver{
296
+ sessionId: string(args.SessionId),
297
+ window: window,
298
+ }, nil
299
+ }
300
+
301
+ func (r *Resolver) Health(ctx context.Context) (*HealthStatusResolver, error) {
302
+ serviceStatuses := []ServiceHealthResolver{}
303
+
304
+ for _, svc := range []string{"agent", "retriever", "generator", "memory"} {
305
+ status := r.registry.HealthCheck(ctx, svc)
306
+ serviceStatuses = append(serviceStatuses, ServiceHealthResolver{
307
+ name: svc,
308
+ status: status,
309
+ })
310
+ }
311
+
312
+ return &HealthStatusResolver{
313
+ status: "healthy",
314
+ timestamp: time.Now().Format(time.RFC3339),
315
+ services: serviceStatuses,
316
+ }, nil
317
+ }
318
+
319
+ // Mutation resolvers
320
+
321
+ func (r *Resolver) ExecuteQuery(ctx context.Context, args struct{ Input QueryInputArgs }) (*QueryResponseResolver, error) {
322
+ agentClient, err := r.registry.GetAgentClient(ctx)
323
+ if err != nil {
324
+ return nil, err
325
+ }
326
+
327
+ userID := middleware.UserIDFromContext(ctx)
328
+ queryID := uuid.New().String()
329
+
330
+ req := &services.QueryRequest{
331
+ QueryID: queryID,
332
+ Query: args.Input.Query,
333
+ UserID: userID,
334
+ SessionID: stringVal(args.Input.SessionId),
335
+ AgentID: stringVal(args.Input.AgentId),
336
+ }
337
+
338
+ if args.Input.Temperature != nil {
339
+ req.Temperature = *args.Input.Temperature
340
+ }
341
+ if args.Input.MaxTokens != nil {
342
+ req.MaxTokens = int(*args.Input.MaxTokens)
343
+ }
344
+
345
+ resp, err := agentClient.ProcessQuery(ctx, req)
346
+ if err != nil {
347
+ return nil, err
348
+ }
349
+
350
+ sources := make([]SourceResolver, len(resp.Sources))
351
+ for i, src := range resp.Sources {
352
+ sources[i] = SourceResolver{
353
+ documentId: src.ID,
354
+ score: src.Score,
355
+ content: src.Content,
356
+ }
357
+ }
358
+
359
+ return &QueryResponseResolver{
360
+ queryId: queryID,
361
+ answer: resp.Answer,
362
+ sources: sources,
363
+ tokensUsed: resp.TokensUsed,
364
+ }, nil
365
+ }
366
+
367
+ func (r *Resolver) CreateAgent(ctx context.Context, args struct{ Input CreateAgentInputArgs }) (*AgentResolver, error) {
368
+ agentClient, err := r.registry.GetAgentClient(ctx)
369
+ if err != nil {
370
+ return nil, err
371
+ }
372
+
373
+ userID := middleware.UserIDFromContext(ctx)
374
+ tenantID := middleware.TenantIDFromContext(ctx)
375
+
376
+ agent, err := agentClient.CreateAgent(ctx, &services.CreateAgentRequest{
377
+ ID: uuid.New().String(),
378
+ Name: args.Input.Name,
379
+ Type: string(args.Input.Type),
380
+ UserID: userID,
381
+ TenantID: tenantID,
382
+ })
383
+ if err != nil {
384
+ return nil, err
385
+ }
386
+
387
+ return &AgentResolver{agent: agent}, nil
388
+ }
389
+
390
+ func (r *Resolver) DeleteAgent(ctx context.Context, args struct{ Id graphql.ID }) (bool, error) {
391
+ agentClient, err := r.registry.GetAgentClient(ctx)
392
+ if err != nil {
393
+ return false, err
394
+ }
395
+
396
+ err = agentClient.DeleteAgent(ctx, string(args.Id))
397
+ return err == nil, err
398
+ }
399
+
400
+ func (r *Resolver) ExecutePlan(ctx context.Context, args struct {
401
+ AgentId graphql.ID
402
+ Input ExecutionPlanInputArgs
403
+ }) (*PlanResultResolver, error) {
404
+ agentClient, err := r.registry.GetAgentClient(ctx)
405
+ if err != nil {
406
+ return nil, err
407
+ }
408
+
409
+ userID := middleware.UserIDFromContext(ctx)
410
+ planID := uuid.New().String()
411
+
412
+ steps := make([]*services.ExecutionStep, len(args.Input.Steps))
413
+ for i, s := range args.Input.Steps {
414
+ steps[i] = &services.ExecutionStep{
415
+ ID: uuid.New().String(),
416
+ Type: s.Type,
417
+ Description: s.Description,
418
+ Tool: stringVal(s.Tool),
419
+ Input: stringVal(s.Input),
420
+ }
421
+ }
422
+
423
+ result, err := agentClient.ExecutePlan(ctx, &services.ExecutePlanRequest{
424
+ AgentID: string(args.AgentId),
425
+ PlanID: planID,
426
+ Query: args.Input.Query,
427
+ Steps: steps,
428
+ UserID: userID,
429
+ })
430
+ if err != nil {
431
+ return nil, err
432
+ }
433
+
434
+ stepResults := make([]StepResultResolver, len(result.StepResults))
435
+ for i, sr := range result.StepResults {
436
+ stepResults[i] = StepResultResolver{
437
+ stepId: sr.StepID,
438
+ status: sr.Status,
439
+ output: sr.Output,
440
+ err: sr.Error,
441
+ }
442
+ }
443
+
444
+ return &PlanResultResolver{
445
+ planId: planID,
446
+ status: result.Status,
447
+ finalAnswer: result.FinalAnswer,
448
+ stepResults: stepResults,
449
+ }, nil
450
+ }
451
+
452
+ func (r *Resolver) ConsolidateMemory(ctx context.Context, args struct{ SessionId graphql.ID }) (bool, error) {
453
+ memoryClient, err := r.registry.GetMemoryClient(ctx)
454
+ if err != nil {
455
+ return false, err
456
+ }
457
+
458
+ userID := middleware.UserIDFromContext(ctx)
459
+ err = memoryClient.ConsolidateMemory(ctx, &services.ConsolidateRequest{
460
+ SessionID: string(args.SessionId),
461
+ UserID: userID,
462
+ })
463
+ return err == nil, err
464
+ }
465
+
466
+ // Input args types
467
+
468
+ type QueryInputArgs struct {
469
+ Query string
470
+ SessionId *string
471
+ AgentId *string
472
+ Temperature *float64
473
+ MaxTokens *int32
474
+ }
475
+
476
+ type CreateAgentInputArgs struct {
477
+ Name string
478
+ Type string
479
+ SystemPrompt *string
480
+ }
481
+
482
+ type ExecutionPlanInputArgs struct {
483
+ Query string
484
+ Steps []ExecutionStepInputArgs
485
+ }
486
+
487
+ type ExecutionStepInputArgs struct {
488
+ Type string
489
+ Description string
490
+ Tool *string
491
+ Input *string
492
+ }
493
+
494
+ // Resolver types
495
+
496
+ type QueryResponseResolver struct {
497
+ queryId string
498
+ answer string
499
+ sources []SourceResolver
500
+ tokensUsed int
501
+ }
502
+
503
+ func (r *QueryResponseResolver) QueryId() graphql.ID { return graphql.ID(r.queryId) }
504
+ func (r *QueryResponseResolver) Answer() string { return r.answer }
505
+ func (r *QueryResponseResolver) Sources() []SourceResolver {
506
+ return r.sources
507
+ }
508
+ func (r *QueryResponseResolver) Metadata() *QueryMetadataResolver {
509
+ return &QueryMetadataResolver{latencyMs: 100, tokensUsed: r.tokensUsed}
510
+ }
511
+
512
+ type SourceResolver struct {
513
+ documentId string
514
+ score float64
515
+ content string
516
+ }
517
+
518
+ func (r SourceResolver) DocumentId() graphql.ID { return graphql.ID(r.documentId) }
519
+ func (r SourceResolver) Score() float64 { return r.score }
520
+ func (r SourceResolver) Content() string { return r.content }
521
+
522
+ type QueryMetadataResolver struct {
523
+ latencyMs int
524
+ tokensUsed int
525
+ }
526
+
527
+ func (r *QueryMetadataResolver) LatencyMs() int32 { return int32(r.latencyMs) }
528
+ func (r *QueryMetadataResolver) TokensUsed() int32 { return int32(r.tokensUsed) }
529
+ func (r *QueryMetadataResolver) CacheHit() bool { return false }
530
+
531
+ type AgentResolver struct {
532
+ agent *services.AgentInfo
533
+ }
534
+
535
+ func (r *AgentResolver) Id() graphql.ID { return graphql.ID(r.agent.ID) }
536
+ func (r *AgentResolver) Name() string { return r.agent.Name }
537
+ func (r *AgentResolver) Type() string { return "RAG" }
538
+ func (r *AgentResolver) Status() string { return r.agent.Status }
539
+ func (r *AgentResolver) CreatedAt() string { return r.agent.CreatedAt }
540
+
541
+ type PlanResultResolver struct {
542
+ planId string
543
+ status string
544
+ finalAnswer string
545
+ stepResults []StepResultResolver
546
+ }
547
+
548
+ func (r *PlanResultResolver) PlanId() graphql.ID { return graphql.ID(r.planId) }
549
+ func (r *PlanResultResolver) Status() string { return r.status }
550
+ func (r *PlanResultResolver) FinalAnswer() string { return r.finalAnswer }
551
+ func (r *PlanResultResolver) ExecutionTimeMs() int32 { return 1000 }
552
+ func (r *PlanResultResolver) StepResults() []StepResultResolver { return r.stepResults }
553
+
554
+ type StepResultResolver struct {
555
+ stepId string
556
+ status string
557
+ output string
558
+ err string
559
+ }
560
+
561
+ func (r StepResultResolver) StepId() graphql.ID { return graphql.ID(r.stepId) }
562
+ func (r StepResultResolver) Status() string { return r.status }
563
+ func (r StepResultResolver) Output() string { return r.output }
564
+ func (r StepResultResolver) Error() *string {
565
+ if r.err == "" {
566
+ return nil
567
+ }
568
+ return &r.err
569
+ }
570
+
571
+ type ContextWindowResolver struct {
572
+ sessionId string
573
+ window *services.ContextWindowResponse
574
+ }
575
+
576
+ func (r *ContextWindowResolver) SessionId() graphql.ID { return graphql.ID(r.sessionId) }
577
+ func (r *ContextWindowResolver) TotalTokens() int32 { return int32(r.window.TotalTokens) }
578
+ func (r *ContextWindowResolver) Entries() []ContextEntryResolver {
579
+ entries := make([]ContextEntryResolver, len(r.window.Entries))
580
+ for i, e := range r.window.Entries {
581
+ entries[i] = ContextEntryResolver{entry: e}
582
+ }
583
+ return entries
584
+ }
585
+
586
+ type ContextEntryResolver struct {
587
+ entry *services.ContextEntry
588
+ }
589
+
590
+ func (r ContextEntryResolver) Id() graphql.ID { return graphql.ID(r.entry.ID) }
591
+ func (r ContextEntryResolver) Type() string { return r.entry.Type }
592
+ func (r ContextEntryResolver) Content() string { return r.entry.Content }
593
+ func (r ContextEntryResolver) Timestamp() string { return r.entry.Timestamp }
594
+ func (r ContextEntryResolver) Score() *float64 { return &r.entry.Score }
595
+
596
+ type HealthStatusResolver struct {
597
+ status string
598
+ timestamp string
599
+ services []ServiceHealthResolver
600
+ }
601
+
602
+ func (r *HealthStatusResolver) Status() string { return r.status }
603
+ func (r *HealthStatusResolver) Timestamp() string { return r.timestamp }
604
+ func (r *HealthStatusResolver) Services() []ServiceHealthResolver { return r.services }
605
+
606
+ type ServiceHealthResolver struct {
607
+ name string
608
+ status string
609
+ }
610
+
611
+ func (r ServiceHealthResolver) Name() string { return r.name }
612
+ func (r ServiceHealthResolver) Status() string { return r.status }
613
+
614
+ // Helper functions
615
+ func stringVal(s *string) string {
616
+ if s == nil {
617
+ return ""
618
+ }
619
+ return *s
620
+ }
internal/gateway/handlers/handlers_test.go ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package handlers_test
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "net/http"
7
+ "net/http/httptest"
8
+ "testing"
9
+
10
+ "github.com/AmaniQuery/amaniquery/internal/gateway/gwtypes"
11
+ "go.uber.org/zap"
12
+ )
13
+
14
+ // TestQueryRequest_JSONParsing tests JSON parsing of query requests
15
+ func TestQueryRequest_JSONParsing(t *testing.T) {
16
+ jsonData := `{
17
+ "query": "What is negligence in tort law?",
18
+ "userId": "user-123",
19
+ "sessionId": "session-456"
20
+ }`
21
+
22
+ var req gwtypes.QueryRequest
23
+ err := json.Unmarshal([]byte(jsonData), &req)
24
+ if err != nil {
25
+ t.Fatalf("Failed to parse JSON: %v", err)
26
+ }
27
+
28
+ if req.Query != "What is negligence in tort law?" {
29
+ t.Errorf("Query mismatch: got '%s'", req.Query)
30
+ }
31
+ if req.UserID != "user-123" {
32
+ t.Errorf("UserID mismatch: got '%s'", req.UserID)
33
+ }
34
+ }
35
+
36
+ // TestQueryResponse_JSONSerialization tests JSON serialization of query responses
37
+ func TestQueryResponse_JSONSerialization(t *testing.T) {
38
+ resp := gwtypes.QueryResponse{
39
+ QueryID: "query-123",
40
+ Answer: "Negligence is a failure to exercise reasonable care.",
41
+ Sources: []gwtypes.Source{
42
+ {
43
+ DocumentID: "doc-1",
44
+ Score: 0.95,
45
+ Content: "Source content",
46
+ },
47
+ },
48
+ Metadata: &gwtypes.ResponseMetadata{
49
+ LatencyMs: 150,
50
+ TokensUsed: 200,
51
+ CacheHit: false,
52
+ },
53
+ }
54
+
55
+ data, err := json.Marshal(resp)
56
+ if err != nil {
57
+ t.Fatalf("Failed to serialize JSON: %v", err)
58
+ }
59
+
60
+ if len(data) == 0 {
61
+ t.Error("Expected non-empty JSON")
62
+ }
63
+
64
+ // Verify round-trip
65
+ var parsed gwtypes.QueryResponse
66
+ err = json.Unmarshal(data, &parsed)
67
+ if err != nil {
68
+ t.Fatalf("Failed to parse serialized JSON: %v", err)
69
+ }
70
+
71
+ if parsed.QueryID != resp.QueryID {
72
+ t.Error("QueryID mismatch after round-trip")
73
+ }
74
+ }
75
+
76
+ // TestErrorResponse_Structure tests error response structure
77
+ func TestErrorResponse_Structure(t *testing.T) {
78
+ resp := gwtypes.ErrorResponse{
79
+ Error: "Bad Request",
80
+ Message: "Query is required",
81
+ Code: "VALIDATION_ERROR",
82
+ RequestID: "req-123",
83
+ }
84
+
85
+ data, err := json.Marshal(resp)
86
+ if err != nil {
87
+ t.Fatalf("Failed to serialize: %v", err)
88
+ }
89
+
90
+ if len(data) == 0 {
91
+ t.Error("Expected non-empty JSON")
92
+ }
93
+ }
94
+
95
+ // TestSource_Structure tests source structure
96
+ func TestSource_Structure(t *testing.T) {
97
+ source := gwtypes.Source{
98
+ DocumentID: "doc-123",
99
+ Score: 0.87,
100
+ Content: "This is the source content.",
101
+ Metadata: map[string]string{
102
+ "author": "John Doe",
103
+ "date": "2024-01-15",
104
+ },
105
+ }
106
+
107
+ if source.DocumentID != "doc-123" {
108
+ t.Error("DocumentID mismatch")
109
+ }
110
+ if source.Score != 0.87 {
111
+ t.Error("Score mismatch")
112
+ }
113
+ if source.Metadata["author"] != "John Doe" {
114
+ t.Error("Metadata mismatch")
115
+ }
116
+ }
117
+
118
+ // TestQueryRequest_EmptyValidation tests that empty query is detected
119
+ func TestQueryRequest_EmptyValidation(t *testing.T) {
120
+ req := gwtypes.QueryRequest{
121
+ Query: "",
122
+ UserID: "user-123",
123
+ }
124
+
125
+ if req.Query != "" {
126
+ t.Error("Query should be empty for this test")
127
+ }
128
+
129
+ // This simulates what the handler would check
130
+ isValid := req.Query != ""
131
+ if isValid {
132
+ t.Error("Empty query should be invalid")
133
+ }
134
+ }
135
+
136
+ // TestResponseMetadata_Structure tests response metadata
137
+ func TestResponseMetadata_Structure(t *testing.T) {
138
+ meta := gwtypes.ResponseMetadata{
139
+ LatencyMs: 250,
140
+ TokensUsed: 1500,
141
+ CacheHit: true,
142
+ }
143
+
144
+ if meta.LatencyMs != 250 {
145
+ t.Error("LatencyMs mismatch")
146
+ }
147
+ if meta.TokensUsed != 1500 {
148
+ t.Error("TokensUsed mismatch")
149
+ }
150
+ if !meta.CacheHit {
151
+ t.Error("CacheHit should be true")
152
+ }
153
+ }
154
+
155
+ // TestHTTPStatusCodes tests status codes are correctly used
156
+ func TestHTTPStatusCodes(t *testing.T) {
157
+ testCases := []struct {
158
+ name string
159
+ status int
160
+ expected string
161
+ }{
162
+ {"OK", http.StatusOK, "OK"},
163
+ {"BadRequest", http.StatusBadRequest, "Bad Request"},
164
+ {"NotFound", http.StatusNotFound, "Not Found"},
165
+ {"ServiceUnavailable", http.StatusServiceUnavailable, "Service Unavailable"},
166
+ }
167
+
168
+ for _, tc := range testCases {
169
+ t.Run(tc.name, func(t *testing.T) {
170
+ text := http.StatusText(tc.status)
171
+ if text != tc.expected {
172
+ t.Errorf("Expected '%s', got '%s'", tc.expected, text)
173
+ }
174
+ })
175
+ }
176
+ }
177
+
178
+ // TestJSONContentType tests content type header
179
+ func TestJSONContentType(t *testing.T) {
180
+ rec := httptest.NewRecorder()
181
+ rec.Header().Set("Content-Type", "application/json")
182
+
183
+ if rec.Header().Get("Content-Type") != "application/json" {
184
+ t.Error("Content-Type header not set correctly")
185
+ }
186
+ }
187
+
188
+ // TestQueryOptionsDefaults tests query options defaults
189
+ func TestQueryOptionsDefaults(t *testing.T) {
190
+ opts := gwtypes.QueryOptions{
191
+ Temperature: 0.7,
192
+ MaxTokens: 4096,
193
+ }
194
+
195
+ if opts.Temperature != 0.7 {
196
+ t.Errorf("Temperature mismatch: got %f", opts.Temperature)
197
+ }
198
+ if opts.MaxTokens != 4096 {
199
+ t.Errorf("MaxTokens mismatch: got %d", opts.MaxTokens)
200
+ }
201
+ }
202
+
203
+ // TestQueryContext tests query context options
204
+ func TestQueryContext(t *testing.T) {
205
+ ctx := gwtypes.QueryContext{
206
+ MaxTurns: 5,
207
+ MemoryTypes: []string{"semantic", "episodic"},
208
+ }
209
+
210
+ if ctx.MaxTurns != 5 {
211
+ t.Error("MaxTurns mismatch")
212
+ }
213
+ if len(ctx.MemoryTypes) != 2 {
214
+ t.Error("MemoryTypes length mismatch")
215
+ }
216
+ }
217
+
218
+ // MockHandler for testing HTTP handling patterns
219
+ type MockHandler struct {
220
+ logger *zap.Logger
221
+ }
222
+
223
+ func (h *MockHandler) jsonResponse(w http.ResponseWriter, status int, data interface{}) {
224
+ w.Header().Set("Content-Type", "application/json")
225
+ w.WriteHeader(status)
226
+ json.NewEncoder(w).Encode(data)
227
+ }
228
+
229
+ // TestMockHandler_JSONResponse tests JSON response helper
230
+ func TestMockHandler_JSONResponse(t *testing.T) {
231
+ logger, _ := zap.NewDevelopment()
232
+ h := &MockHandler{logger: logger}
233
+
234
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
235
+ rec := httptest.NewRecorder()
236
+
237
+ h.jsonResponse(rec, http.StatusOK, map[string]string{"status": "ok"})
238
+
239
+ if rec.Code != http.StatusOK {
240
+ t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
241
+ }
242
+
243
+ if rec.Header().Get("Content-Type") != "application/json" {
244
+ t.Error("Expected JSON content type")
245
+ }
246
+ _ = req
247
+ }
248
+
249
+ // TestRequestParsing tests request body parsing
250
+ func TestRequestParsing(t *testing.T) {
251
+ body := bytes.NewBufferString(`{"query": "test query"}`)
252
+ req := httptest.NewRequest(http.MethodPost, "/query", body)
253
+ req.Header.Set("Content-Type", "application/json")
254
+
255
+ var parsed gwtypes.QueryRequest
256
+ err := json.NewDecoder(req.Body).Decode(&parsed)
257
+ if err != nil {
258
+ t.Fatalf("Failed to decode: %v", err)
259
+ }
260
+
261
+ if parsed.Query != "test query" {
262
+ t.Errorf("Query mismatch: got '%s'", parsed.Query)
263
+ }
264
+ }
265
+
266
+ // BenchmarkJSONMarshal benchmarks JSON marshaling
267
+ func BenchmarkJSONMarshal(b *testing.B) {
268
+ resp := gwtypes.QueryResponse{
269
+ QueryID: "query-123",
270
+ Answer: "This is a test answer with some content.",
271
+ Sources: []gwtypes.Source{
272
+ {DocumentID: "doc-1", Score: 0.9, Content: "Content 1"},
273
+ {DocumentID: "doc-2", Score: 0.8, Content: "Content 2"},
274
+ },
275
+ }
276
+
277
+ b.ResetTimer()
278
+ for i := 0; i < b.N; i++ {
279
+ json.Marshal(resp)
280
+ }
281
+ }
282
+
283
+ // BenchmarkJSONUnmarshal benchmarks JSON unmarshaling
284
+ func BenchmarkJSONUnmarshal(b *testing.B) {
285
+ data := []byte(`{"query": "test query", "userId": "user-123", "sessionId": "session-456"}`)
286
+
287
+ b.ResetTimer()
288
+ for i := 0; i < b.N; i++ {
289
+ var req gwtypes.QueryRequest
290
+ json.Unmarshal(data, &req)
291
+ }
292
+ }
internal/gateway/handlers/memory.go ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package handlers
2
+
3
+ import (
4
+ "encoding/json"
5
+ "net/http"
6
+ "strconv"
7
+
8
+ "github.com/gorilla/mux"
9
+ "go.uber.org/zap"
10
+
11
+ "github.com/AmaniQuery/amaniquery/internal/gateway/gwtypes"
12
+ "github.com/AmaniQuery/amaniquery/internal/gateway/middleware"
13
+ "github.com/AmaniQuery/amaniquery/internal/gateway/services"
14
+ )
15
+
16
+ // MemoryHandler handles memory-related endpoints
17
+ type MemoryHandler struct {
18
+ registry *services.Registry
19
+ logger *zap.Logger
20
+ }
21
+
22
+ // NewMemoryHandler creates a new memory handler
23
+ func NewMemoryHandler(registry *services.Registry, logger *zap.Logger) *MemoryHandler {
24
+ return &MemoryHandler{
25
+ registry: registry,
26
+ logger: logger,
27
+ }
28
+ }
29
+
30
+ // GetContextWindow handles GET /v2/memory/context
31
+ func (h *MemoryHandler) GetContextWindow(w http.ResponseWriter, r *http.Request) {
32
+ ctx := r.Context()
33
+
34
+ // Get session ID from query params
35
+ sessionID := r.URL.Query().Get("sessionId")
36
+ if sessionID == "" {
37
+ h.errorResponse(w, http.StatusBadRequest, "Session ID is required", "")
38
+ return
39
+ }
40
+
41
+ // Get max turns
42
+ maxTurns := 50
43
+ if mt := r.URL.Query().Get("maxTurns"); mt != "" {
44
+ if parsed, err := strconv.Atoi(mt); err == nil && parsed > 0 {
45
+ maxTurns = parsed
46
+ }
47
+ }
48
+
49
+ // Get user ID from context
50
+ userID := middleware.UserIDFromContext(ctx)
51
+
52
+ // Get memory client
53
+ memoryClient, err := h.registry.GetMemoryClient(ctx)
54
+ if err != nil {
55
+ h.logger.Error("Failed to get memory client", zap.Error(err))
56
+ h.errorResponse(w, http.StatusServiceUnavailable, "Service unavailable", "")
57
+ return
58
+ }
59
+
60
+ // Get context window from memory service
61
+ contextWindow, err := memoryClient.GetContextWindow(ctx, &services.ContextWindowRequest{
62
+ SessionID: sessionID,
63
+ UserID: userID,
64
+ MaxTurns: maxTurns,
65
+ })
66
+ if err != nil {
67
+ h.logger.Error("Failed to get context window", zap.Error(err))
68
+ h.errorResponse(w, http.StatusInternalServerError, "Failed to retrieve context", err.Error())
69
+ return
70
+ }
71
+
72
+ // Build response
73
+ response := gwtypes.ContextWindow{
74
+ SessionID: sessionID,
75
+ Entries: make([]gwtypes.ContextEntry, len(contextWindow.Entries)),
76
+ TotalTokens: contextWindow.TotalTokens,
77
+ }
78
+
79
+ for i, entry := range contextWindow.Entries {
80
+ response.Entries[i] = gwtypes.ContextEntry{
81
+ ID: entry.ID,
82
+ Type: entry.Type,
83
+ Content: entry.Content,
84
+ Timestamp: entry.Timestamp,
85
+ Score: entry.Score,
86
+ }
87
+ }
88
+
89
+ h.jsonResponse(w, http.StatusOK, response)
90
+ }
91
+
92
+ // ConsolidateMemory handles POST /v2/memory/sessions/{sessionId}/consolidate
93
+ func (h *MemoryHandler) ConsolidateMemory(w http.ResponseWriter, r *http.Request) {
94
+ ctx := r.Context()
95
+ vars := mux.Vars(r)
96
+ sessionID := vars["sessionId"]
97
+
98
+ if sessionID == "" {
99
+ h.errorResponse(w, http.StatusBadRequest, "Session ID is required", "")
100
+ return
101
+ }
102
+
103
+ // Get user ID from context
104
+ userID := middleware.UserIDFromContext(ctx)
105
+
106
+ // Get memory client
107
+ memoryClient, err := h.registry.GetMemoryClient(ctx)
108
+ if err != nil {
109
+ h.logger.Error("Failed to get memory client", zap.Error(err))
110
+ h.errorResponse(w, http.StatusServiceUnavailable, "Service unavailable", "")
111
+ return
112
+ }
113
+
114
+ // Trigger consolidation
115
+ err = memoryClient.ConsolidateMemory(ctx, &services.ConsolidateRequest{
116
+ SessionID: sessionID,
117
+ UserID: userID,
118
+ })
119
+ if err != nil {
120
+ h.logger.Error("Failed to consolidate memory", zap.Error(err))
121
+ h.errorResponse(w, http.StatusInternalServerError, "Consolidation failed", err.Error())
122
+ return
123
+ }
124
+
125
+ // Return 202 Accepted
126
+ w.WriteHeader(http.StatusAccepted)
127
+ json.NewEncoder(w).Encode(map[string]string{
128
+ "status": "accepted",
129
+ "sessionId": sessionID,
130
+ "message": "Memory consolidation started",
131
+ })
132
+ }
133
+
134
+ func (h *MemoryHandler) jsonResponse(w http.ResponseWriter, status int, data interface{}) {
135
+ w.Header().Set("Content-Type", "application/json")
136
+ w.WriteHeader(status)
137
+ json.NewEncoder(w).Encode(data)
138
+ }
139
+
140
+ func (h *MemoryHandler) errorResponse(w http.ResponseWriter, status int, message, detail string) {
141
+ w.Header().Set("Content-Type", "application/json")
142
+ w.WriteHeader(status)
143
+ resp := gwtypes.ErrorResponse{
144
+ Error: http.StatusText(status),
145
+ Message: message,
146
+ RequestID: w.Header().Get("X-Request-ID"),
147
+ }
148
+ if detail != "" {
149
+ resp.Code = detail
150
+ }
151
+ json.NewEncoder(w).Encode(resp)
152
+ }
internal/gateway/handlers/query.go ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package handlers provides HTTP request handlers for the API Gateway.
2
+ package handlers
3
+
4
+ import (
5
+ "context"
6
+ "encoding/json"
7
+ "net/http"
8
+ "time"
9
+
10
+ "github.com/google/uuid"
11
+ "github.com/gorilla/mux"
12
+ "go.uber.org/zap"
13
+
14
+ gatewaycache "github.com/AmaniQuery/amaniquery/internal/gateway/cache"
15
+ "github.com/AmaniQuery/amaniquery/internal/gateway/gwtypes"
16
+ "github.com/AmaniQuery/amaniquery/internal/gateway/middleware"
17
+ "github.com/AmaniQuery/amaniquery/internal/gateway/services"
18
+ )
19
+
20
+ // Ensure context package is used for request handling
21
+ var _ context.Context
22
+
23
+ // QueryHandler handles query-related endpoints
24
+ type QueryHandler struct {
25
+ registry *services.Registry
26
+ cache *gatewaycache.Manager
27
+ logger *zap.Logger
28
+ }
29
+
30
+ // NewQueryHandler creates a new query handler
31
+ func NewQueryHandler(registry *services.Registry, cache *gatewaycache.Manager, logger *zap.Logger) *QueryHandler {
32
+ return &QueryHandler{
33
+ registry: registry,
34
+ cache: cache,
35
+ logger: logger,
36
+ }
37
+ }
38
+
39
+ // ExecuteQuery handles POST /v2/queries
40
+ func (h *QueryHandler) ExecuteQuery(w http.ResponseWriter, r *http.Request) {
41
+ ctx := r.Context()
42
+ start := time.Now()
43
+
44
+ // Parse request
45
+ var req gwtypes.QueryRequest
46
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
47
+ h.errorResponse(w, http.StatusBadRequest, "Invalid request body", err.Error())
48
+ return
49
+ }
50
+
51
+ // Validate required fields
52
+ if req.Query == "" {
53
+ h.errorResponse(w, http.StatusBadRequest, "Query is required", "")
54
+ return
55
+ }
56
+
57
+ // Get user info from context
58
+ userID := middleware.UserIDFromContext(ctx)
59
+ if req.UserID == "" {
60
+ req.UserID = userID
61
+ }
62
+
63
+ // Generate query ID
64
+ queryID := uuid.New().String()
65
+
66
+ // Check cache first
67
+ if h.cache != nil && !req.Streaming {
68
+ if cached, found := h.cache.Get(ctx, h.cacheKey(&req)); found {
69
+ w.Header().Set("X-Cache-Hit", "true")
70
+ h.jsonResponse(w, http.StatusOK, cached)
71
+ return
72
+ }
73
+ }
74
+
75
+ // Get agent service client
76
+ agentClient, err := h.registry.GetAgentClient(ctx)
77
+ if err != nil {
78
+ h.logger.Error("Failed to get agent client", zap.Error(err))
79
+ h.errorResponse(w, http.StatusServiceUnavailable, "Service unavailable", "")
80
+ return
81
+ }
82
+
83
+ // Execute query via gRPC
84
+ grpcReq := h.toGRPCRequest(&req, queryID)
85
+ grpcResp, err := agentClient.ProcessQuery(ctx, grpcReq)
86
+ if err != nil {
87
+ h.logger.Error("Query execution failed", zap.Error(err), zap.String("query_id", queryID))
88
+ h.errorResponse(w, http.StatusInternalServerError, "Query execution failed", err.Error())
89
+ return
90
+ }
91
+
92
+ // Build response
93
+ response := h.fromGRPCResponse(grpcResp, queryID, start)
94
+
95
+ // Cache response
96
+ if h.cache != nil && !req.Streaming {
97
+ h.cache.Set(ctx, h.cacheKey(&req), response, h.cacheTTL(&req))
98
+ }
99
+
100
+ w.Header().Set("X-Cache-Hit", "false")
101
+ h.jsonResponse(w, http.StatusOK, response)
102
+ }
103
+
104
+ // GetQueryResult handles GET /v2/queries/{queryId}
105
+ func (h *QueryHandler) GetQueryResult(w http.ResponseWriter, r *http.Request) {
106
+ ctx := r.Context()
107
+ vars := mux.Vars(r)
108
+ queryID := vars["queryId"]
109
+
110
+ if queryID == "" {
111
+ h.errorResponse(w, http.StatusBadRequest, "Query ID is required", "")
112
+ return
113
+ }
114
+
115
+ // Get agent service client
116
+ agentClient, err := h.registry.GetAgentClient(ctx)
117
+ if err != nil {
118
+ h.logger.Error("Failed to get agent client", zap.Error(err))
119
+ h.errorResponse(w, http.StatusServiceUnavailable, "Service unavailable", "")
120
+ return
121
+ }
122
+
123
+ // Check query status
124
+ status, err := agentClient.GetQueryStatus(ctx, queryID)
125
+ if err != nil {
126
+ h.logger.Error("Failed to get query status", zap.Error(err), zap.String("query_id", queryID))
127
+ h.errorResponse(w, http.StatusNotFound, "Query not found", "")
128
+ return
129
+ }
130
+
131
+ // If still processing, return 202
132
+ if status.Status == "processing" {
133
+ w.Header().Set("Retry-After", "5")
134
+ h.jsonResponse(w, http.StatusAccepted, map[string]interface{}{
135
+ "queryId": queryID,
136
+ "status": status.Status,
137
+ "progress": status.Progress,
138
+ })
139
+ return
140
+ }
141
+
142
+ // Return result
143
+ h.jsonResponse(w, http.StatusOK, status.Result)
144
+ }
145
+
146
+ func (h *QueryHandler) cacheKey(req *gwtypes.QueryRequest) string {
147
+ return req.Query + ":" + req.UserID
148
+ }
149
+
150
+ func (h *QueryHandler) cacheTTL(req *gwtypes.QueryRequest) time.Duration {
151
+ if req.Streaming {
152
+ return 30 * time.Second
153
+ }
154
+ if len(req.Query) < 100 {
155
+ return 5 * time.Minute
156
+ }
157
+ return 2 * time.Minute
158
+ }
159
+
160
+ func (h *QueryHandler) toGRPCRequest(req *gwtypes.QueryRequest, queryID string) *services.QueryRequest {
161
+ grpcReq := &services.QueryRequest{
162
+ QueryID: queryID,
163
+ Query: req.Query,
164
+ UserID: req.UserID,
165
+ SessionID: req.SessionID,
166
+ AgentID: req.AgentID,
167
+ }
168
+
169
+ if req.Options != nil {
170
+ grpcReq.Temperature = req.Options.Temperature
171
+ grpcReq.MaxTokens = req.Options.MaxTokens
172
+ }
173
+
174
+ if req.Context != nil {
175
+ grpcReq.MaxTurns = req.Context.MaxTurns
176
+ grpcReq.MemoryTypes = req.Context.MemoryTypes
177
+ }
178
+
179
+ return grpcReq
180
+ }
181
+
182
+ func (h *QueryHandler) fromGRPCResponse(resp *services.QueryResponse, queryID string, start time.Time) *gwtypes.QueryResponse {
183
+ response := &gwtypes.QueryResponse{
184
+ QueryID: queryID,
185
+ Answer: resp.Answer,
186
+ Sources: make([]gwtypes.Source, len(resp.Sources)),
187
+ Metadata: &gwtypes.ResponseMetadata{
188
+ LatencyMs: time.Since(start).Milliseconds(),
189
+ TokensUsed: resp.TokensUsed,
190
+ CacheHit: false,
191
+ },
192
+ }
193
+
194
+ for i, src := range resp.Sources {
195
+ response.Sources[i] = gwtypes.Source{
196
+ DocumentID: src.ID,
197
+ Score: src.Score,
198
+ Content: src.Content,
199
+ Metadata: src.Metadata,
200
+ }
201
+ }
202
+
203
+ return response
204
+ }
205
+
206
+ func (h *QueryHandler) jsonResponse(w http.ResponseWriter, status int, data interface{}) {
207
+ w.Header().Set("Content-Type", "application/json")
208
+ w.WriteHeader(status)
209
+ json.NewEncoder(w).Encode(data)
210
+ }
211
+
212
+ func (h *QueryHandler) errorResponse(w http.ResponseWriter, status int, message, detail string) {
213
+ w.Header().Set("Content-Type", "application/json")
214
+ w.WriteHeader(status)
215
+ resp := gwtypes.ErrorResponse{
216
+ Error: http.StatusText(status),
217
+ Message: message,
218
+ RequestID: w.Header().Get("X-Request-ID"),
219
+ }
220
+ if detail != "" {
221
+ resp.Code = detail
222
+ }
223
+ json.NewEncoder(w).Encode(resp)
224
+ }
internal/gateway/handlers/voice_files.go ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package handlers provides HTTP handlers for voice and file services
2
+ package handlers
3
+
4
+ import (
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "net/http"
9
+ "strconv"
10
+
11
+ "github.com/gorilla/mux"
12
+ "go.uber.org/zap"
13
+ )
14
+
15
+ // VoiceHandler handles voice-related requests
16
+ type VoiceHandler struct {
17
+ voiceServiceURL string
18
+ logger *zap.Logger
19
+ httpClient *http.Client
20
+ }
21
+
22
+ // NewVoiceHandler creates a new voice handler
23
+ func NewVoiceHandler(voiceServiceURL string, logger *zap.Logger) *VoiceHandler {
24
+ return &VoiceHandler{
25
+ voiceServiceURL: voiceServiceURL,
26
+ logger: logger,
27
+ httpClient: &http.Client{},
28
+ }
29
+ }
30
+
31
+ // RegisterRoutes registers voice routes
32
+ func (h *VoiceHandler) RegisterRoutes(r *mux.Router) {
33
+ r.HandleFunc("/api/v1/voice/sessions", h.ListSessions).Methods("GET")
34
+ r.HandleFunc("/api/v1/voice/sessions/{sessionId}", h.GetSession).Methods("GET")
35
+ r.HandleFunc("/api/v1/voice/sessions/{sessionId}/end", h.EndSession).Methods("POST")
36
+ r.HandleFunc("/api/v1/voice/voices", h.ListVoices).Methods("GET")
37
+ }
38
+
39
+ // ListSessions lists active voice sessions
40
+ func (h *VoiceHandler) ListSessions(w http.ResponseWriter, r *http.Request) {
41
+ h.proxyRequest(w, r, "/api/v1/voice/sessions")
42
+ }
43
+
44
+ // GetSession gets a voice session
45
+ func (h *VoiceHandler) GetSession(w http.ResponseWriter, r *http.Request) {
46
+ vars := mux.Vars(r)
47
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/voice/sessions/%s", vars["sessionId"]))
48
+ }
49
+
50
+ // EndSession ends a voice session
51
+ func (h *VoiceHandler) EndSession(w http.ResponseWriter, r *http.Request) {
52
+ vars := mux.Vars(r)
53
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/voice/sessions/%s/end", vars["sessionId"]))
54
+ }
55
+
56
+ // ListVoices lists available TTS voices
57
+ func (h *VoiceHandler) ListVoices(w http.ResponseWriter, r *http.Request) {
58
+ voices := []map[string]string{
59
+ {"id": "nova", "name": "Nova", "gender": "female", "style": "natural"},
60
+ {"id": "alloy", "name": "Alloy", "gender": "female", "style": "professional"},
61
+ {"id": "echo", "name": "Echo", "gender": "male", "style": "natural"},
62
+ {"id": "onyx", "name": "Onyx", "gender": "male", "style": "deep"},
63
+ {"id": "shimmer", "name": "Shimmer", "gender": "female", "style": "soft"},
64
+ {"id": "swahili", "name": "Rafiki", "gender": "male", "style": "swahili"},
65
+ }
66
+ json.NewEncoder(w).Encode(voices)
67
+ }
68
+
69
+ func (h *VoiceHandler) proxyRequest(w http.ResponseWriter, r *http.Request, path string) {
70
+ req, err := http.NewRequest(r.Method, h.voiceServiceURL+path, r.Body)
71
+ if err != nil {
72
+ http.Error(w, "Failed to create request", http.StatusInternalServerError)
73
+ return
74
+ }
75
+
76
+ // Copy headers
77
+ for key, values := range r.Header {
78
+ for _, value := range values {
79
+ req.Header.Add(key, value)
80
+ }
81
+ }
82
+
83
+ resp, err := h.httpClient.Do(req)
84
+ if err != nil {
85
+ http.Error(w, "Voice service unavailable", http.StatusServiceUnavailable)
86
+ return
87
+ }
88
+ defer resp.Body.Close()
89
+
90
+ // Copy response
91
+ for key, values := range resp.Header {
92
+ for _, value := range values {
93
+ w.Header().Add(key, value)
94
+ }
95
+ }
96
+ w.WriteHeader(resp.StatusCode)
97
+ io.Copy(w, resp.Body)
98
+ }
99
+
100
+ // FileHandler handles file-related requests
101
+ type FileHandler struct {
102
+ fileServiceURL string
103
+ logger *zap.Logger
104
+ httpClient *http.Client
105
+ }
106
+
107
+ // NewFileHandler creates a new file handler
108
+ func NewFileHandler(fileServiceURL string, logger *zap.Logger) *FileHandler {
109
+ return &FileHandler{
110
+ fileServiceURL: fileServiceURL,
111
+ logger: logger,
112
+ httpClient: &http.Client{},
113
+ }
114
+ }
115
+
116
+ // RegisterRoutes registers file routes
117
+ func (h *FileHandler) RegisterRoutes(r *mux.Router) {
118
+ // Upload routes
119
+ r.HandleFunc("/api/v1/files/upload", h.InitiateUpload).Methods("POST")
120
+ r.HandleFunc("/api/v1/files/upload/{fileId}/chunk/{chunkIndex}", h.UploadChunk).Methods("POST")
121
+ r.HandleFunc("/api/v1/files/upload/{fileId}/complete", h.CompleteUpload).Methods("POST")
122
+
123
+ // File management routes
124
+ r.HandleFunc("/api/v1/files", h.ListFiles).Methods("GET")
125
+ r.HandleFunc("/api/v1/files/{fileId}", h.GetFile).Methods("GET")
126
+ r.HandleFunc("/api/v1/files/{fileId}", h.DeleteFile).Methods("DELETE")
127
+ r.HandleFunc("/api/v1/files/{fileId}/download", h.DownloadFile).Methods("GET")
128
+ r.HandleFunc("/api/v1/files/{fileId}/preview", h.PreviewFile).Methods("GET")
129
+ r.HandleFunc("/api/v1/files/{fileId}/share", h.ShareFile).Methods("POST")
130
+
131
+ // Chat integration
132
+ r.HandleFunc("/api/v1/files/{fileId}/chat", h.GetFileChat).Methods("GET")
133
+ r.HandleFunc("/api/v1/files/{fileId}/chat/message", h.SendFileMessage).Methods("POST")
134
+ }
135
+
136
+ // InitiateUpload starts a file upload
137
+ func (h *FileHandler) InitiateUpload(w http.ResponseWriter, r *http.Request) {
138
+ h.proxyRequest(w, r, "/api/v1/files/upload")
139
+ }
140
+
141
+ // UploadChunk handles chunk upload
142
+ func (h *FileHandler) UploadChunk(w http.ResponseWriter, r *http.Request) {
143
+ vars := mux.Vars(r)
144
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/files/upload/%s/chunk/%s", vars["fileId"], vars["chunkIndex"]))
145
+ }
146
+
147
+ // CompleteUpload completes a file upload
148
+ func (h *FileHandler) CompleteUpload(w http.ResponseWriter, r *http.Request) {
149
+ vars := mux.Vars(r)
150
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/files/upload/%s/complete", vars["fileId"]))
151
+ }
152
+
153
+ // ListFiles lists user files
154
+ func (h *FileHandler) ListFiles(w http.ResponseWriter, r *http.Request) {
155
+ h.proxyRequest(w, r, "/api/v1/files")
156
+ }
157
+
158
+ // GetFile gets file details
159
+ func (h *FileHandler) GetFile(w http.ResponseWriter, r *http.Request) {
160
+ vars := mux.Vars(r)
161
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/files/%s", vars["fileId"]))
162
+ }
163
+
164
+ // DeleteFile deletes a file
165
+ func (h *FileHandler) DeleteFile(w http.ResponseWriter, r *http.Request) {
166
+ vars := mux.Vars(r)
167
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/files/%s", vars["fileId"]))
168
+ }
169
+
170
+ // DownloadFile downloads a file
171
+ func (h *FileHandler) DownloadFile(w http.ResponseWriter, r *http.Request) {
172
+ vars := mux.Vars(r)
173
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/files/%s/download", vars["fileId"]))
174
+ }
175
+
176
+ // PreviewFile previews a file
177
+ func (h *FileHandler) PreviewFile(w http.ResponseWriter, r *http.Request) {
178
+ vars := mux.Vars(r)
179
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/files/%s/preview", vars["fileId"]))
180
+ }
181
+
182
+ // ShareFile creates a share link
183
+ func (h *FileHandler) ShareFile(w http.ResponseWriter, r *http.Request) {
184
+ vars := mux.Vars(r)
185
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/files/%s/share", vars["fileId"]))
186
+ }
187
+
188
+ // GetFileChat gets chat messages for a file
189
+ func (h *FileHandler) GetFileChat(w http.ResponseWriter, r *http.Request) {
190
+ vars := mux.Vars(r)
191
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/files/%s/chat", vars["fileId"]))
192
+ }
193
+
194
+ // SendFileMessage sends a message about a file
195
+ func (h *FileHandler) SendFileMessage(w http.ResponseWriter, r *http.Request) {
196
+ vars := mux.Vars(r)
197
+ h.proxyRequest(w, r, fmt.Sprintf("/api/v1/files/%s/chat/message", vars["fileId"]))
198
+ }
199
+
200
+ func (h *FileHandler) proxyRequest(w http.ResponseWriter, r *http.Request, path string) {
201
+ req, err := http.NewRequest(r.Method, h.fileServiceURL+path, r.Body)
202
+ if err != nil {
203
+ http.Error(w, "Failed to create request", http.StatusInternalServerError)
204
+ return
205
+ }
206
+
207
+ // Copy headers
208
+ for key, values := range r.Header {
209
+ for _, value := range values {
210
+ req.Header.Add(key, value)
211
+ }
212
+ }
213
+
214
+ resp, err := h.httpClient.Do(req)
215
+ if err != nil {
216
+ http.Error(w, "File service unavailable", http.StatusServiceUnavailable)
217
+ return
218
+ }
219
+ defer resp.Body.Close()
220
+
221
+ // Copy response
222
+ for key, values := range resp.Header {
223
+ for _, value := range values {
224
+ w.Header().Add(key, value)
225
+ }
226
+ }
227
+ w.WriteHeader(resp.StatusCode)
228
+ io.Copy(w, resp.Body)
229
+ }
230
+
231
+ // Unused variable suppression
232
+ var _ = strconv.Atoi
internal/gateway/handlers/websocket.go ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package handlers
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "net/http"
7
+ "sync"
8
+ "time"
9
+
10
+ "github.com/google/uuid"
11
+ "github.com/gorilla/websocket"
12
+ "go.uber.org/zap"
13
+
14
+ "github.com/AmaniQuery/amaniquery/internal/gateway/gwtypes"
15
+ "github.com/AmaniQuery/amaniquery/internal/gateway/middleware"
16
+ "github.com/AmaniQuery/amaniquery/internal/gateway/services"
17
+ )
18
+
19
+ // WebSocketHandler handles WebSocket connections for streaming queries
20
+ type WebSocketHandler struct {
21
+ registry *services.Registry
22
+ config gwtypes.WebSocketConfig
23
+ upgrader websocket.Upgrader
24
+ connections sync.Map // map[string]*wsConnection
25
+ logger *zap.Logger
26
+ }
27
+
28
+ type wsConnection struct {
29
+ conn *websocket.Conn
30
+ userID string
31
+ sessionID string
32
+ createdAt time.Time
33
+ cancel context.CancelFunc
34
+ }
35
+
36
+ // NewWebSocketHandler creates a new WebSocket handler
37
+ func NewWebSocketHandler(registry *services.Registry, cfg gwtypes.WebSocketConfig, logger *zap.Logger) *WebSocketHandler {
38
+ return &WebSocketHandler{
39
+ registry: registry,
40
+ config: cfg,
41
+ upgrader: websocket.Upgrader{
42
+ CheckOrigin: func(r *http.Request) bool {
43
+ // Allow all origins for now, configure in production
44
+ return true
45
+ },
46
+ ReadBufferSize: cfg.ReadBufferSize,
47
+ WriteBufferSize: cfg.WriteBufferSize,
48
+ Subprotocols: []string{"rag-agent-protocol"},
49
+ },
50
+ logger: logger,
51
+ }
52
+ }
53
+
54
+ // HandleStream handles WebSocket upgrade and streaming
55
+ func (h *WebSocketHandler) HandleStream(w http.ResponseWriter, r *http.Request) {
56
+ // Upgrade connection
57
+ conn, err := h.upgrader.Upgrade(w, r, nil)
58
+ if err != nil {
59
+ h.logger.Error("WebSocket upgrade failed", zap.Error(err))
60
+ return
61
+ }
62
+
63
+ // Create connection context with cancel
64
+ ctx, cancel := context.WithCancel(r.Context())
65
+
66
+ // Generate connection ID
67
+ connID := uuid.New().String()
68
+
69
+ // Get user info from context
70
+ userID := middleware.UserIDFromContext(r.Context())
71
+ sessionID := r.URL.Query().Get("sessionId")
72
+
73
+ // Store connection
74
+ wsConn := &wsConnection{
75
+ conn: conn,
76
+ userID: userID,
77
+ sessionID: sessionID,
78
+ createdAt: time.Now(),
79
+ cancel: cancel,
80
+ }
81
+ h.connections.Store(connID, wsConn)
82
+
83
+ defer func() {
84
+ h.connections.Delete(connID)
85
+ cancel()
86
+ conn.Close()
87
+ }()
88
+
89
+ h.logger.Info("WebSocket connection established",
90
+ zap.String("conn_id", connID),
91
+ zap.String("user_id", userID),
92
+ )
93
+
94
+ // Start ping-pong keepalive
95
+ go h.keepalive(ctx, conn, connID)
96
+
97
+ // Message handling loop
98
+ h.messageLoop(ctx, conn, connID, userID, sessionID)
99
+ }
100
+
101
+ func (h *WebSocketHandler) messageLoop(ctx context.Context, conn *websocket.Conn, connID, userID, sessionID string) {
102
+ for {
103
+ select {
104
+ case <-ctx.Done():
105
+ return
106
+ default:
107
+ // Set read deadline
108
+ conn.SetReadDeadline(time.Now().Add(h.config.PongWait))
109
+
110
+ // Read message
111
+ _, message, err := conn.ReadMessage()
112
+ if err != nil {
113
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
114
+ h.logger.Error("WebSocket read error", zap.Error(err), zap.String("conn_id", connID))
115
+ }
116
+ return
117
+ }
118
+
119
+ // Parse message
120
+ var msg gwtypes.WebSocketMessage
121
+ if err := json.Unmarshal(message, &msg); err != nil {
122
+ h.sendError(conn, "Invalid message format")
123
+ continue
124
+ }
125
+
126
+ // Handle message
127
+ go h.handleMessage(ctx, conn, connID, userID, sessionID, msg)
128
+ }
129
+ }
130
+ }
131
+
132
+ func (h *WebSocketHandler) handleMessage(ctx context.Context, conn *websocket.Conn, connID, userID, sessionID string, msg gwtypes.WebSocketMessage) {
133
+ switch msg.Type {
134
+ case "query":
135
+ h.handleQuery(ctx, conn, connID, userID, sessionID, msg)
136
+ case "ping":
137
+ h.sendMessage(conn, gwtypes.WebSocketServerMessage{
138
+ Type: "pong",
139
+ Timestamp: time.Now().Format(time.RFC3339),
140
+ })
141
+ case "cancel":
142
+ h.handleCancel(connID, msg)
143
+ default:
144
+ h.sendError(conn, "Unknown message type")
145
+ }
146
+ }
147
+
148
+ func (h *WebSocketHandler) handleQuery(ctx context.Context, conn *websocket.Conn, connID, userID, sessionID string, msg gwtypes.WebSocketMessage) {
149
+ // Parse query payload
150
+ payloadBytes, _ := json.Marshal(msg.Payload)
151
+ var payload gwtypes.WebSocketQueryPayload
152
+ if err := json.Unmarshal(payloadBytes, &payload); err != nil {
153
+ h.sendError(conn, "Invalid query payload")
154
+ return
155
+ }
156
+
157
+ // Set defaults
158
+ if payload.UserID == "" {
159
+ payload.UserID = userID
160
+ }
161
+ if payload.SessionID == "" {
162
+ payload.SessionID = sessionID
163
+ }
164
+ if payload.QueryID == "" {
165
+ payload.QueryID = uuid.New().String()
166
+ }
167
+
168
+ h.logger.Info("Processing streaming query",
169
+ zap.String("conn_id", connID),
170
+ zap.String("query_id", payload.QueryID),
171
+ )
172
+
173
+ // Get agent client
174
+ agentClient, err := h.registry.GetAgentClient(ctx)
175
+ if err != nil {
176
+ h.logger.Error("Failed to get agent client", zap.Error(err))
177
+ h.sendError(conn, "Service unavailable")
178
+ return
179
+ }
180
+
181
+ // Create streaming request
182
+ streamReq := &services.StreamQueryRequest{
183
+ QueryID: payload.QueryID,
184
+ Query: payload.Query,
185
+ UserID: payload.UserID,
186
+ SessionID: payload.SessionID,
187
+ AgentID: payload.AgentID,
188
+ }
189
+
190
+ if payload.Options != nil {
191
+ streamReq.Temperature = payload.Options.Temperature
192
+ streamReq.MaxTokens = payload.Options.MaxTokens
193
+ }
194
+
195
+ // Execute streaming query
196
+ streamChan, errChan := agentClient.ProcessQueryStream(ctx, streamReq)
197
+
198
+ // Stream results to client
199
+ for {
200
+ select {
201
+ case <-ctx.Done():
202
+ return
203
+ case err := <-errChan:
204
+ if err != nil {
205
+ h.logger.Error("Stream error", zap.Error(err), zap.String("query_id", payload.QueryID))
206
+ h.sendError(conn, err.Error())
207
+ }
208
+ return
209
+ case chunk, ok := <-streamChan:
210
+ if !ok {
211
+ h.sendMessage(conn, gwtypes.WebSocketServerMessage{
212
+ Type: "done",
213
+ Timestamp: time.Now().Format(time.RFC3339),
214
+ Metadata: map[string]interface{}{
215
+ "queryId": payload.QueryID,
216
+ },
217
+ })
218
+ return
219
+ }
220
+
221
+ // Send chunk
222
+ h.sendMessage(conn, gwtypes.WebSocketServerMessage{
223
+ Type: chunk.Type,
224
+ Timestamp: time.Now().Format(time.RFC3339),
225
+ Data: chunk.Data,
226
+ Metadata: chunk.Metadata,
227
+ })
228
+ }
229
+ }
230
+ }
231
+
232
+ func (h *WebSocketHandler) handleCancel(connID string, msg gwtypes.WebSocketMessage) {
233
+ if wsConn, ok := h.connections.Load(connID); ok {
234
+ wsConn.(*wsConnection).cancel()
235
+ }
236
+ }
237
+
238
+ func (h *WebSocketHandler) keepalive(ctx context.Context, conn *websocket.Conn, connID string) {
239
+ ticker := time.NewTicker(h.config.PingInterval)
240
+ defer ticker.Stop()
241
+
242
+ for {
243
+ select {
244
+ case <-ticker.C:
245
+ conn.SetWriteDeadline(time.Now().Add(h.config.WriteWait))
246
+ if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
247
+ h.logger.Debug("Ping failed", zap.String("conn_id", connID), zap.Error(err))
248
+ return
249
+ }
250
+ case <-ctx.Done():
251
+ return
252
+ }
253
+ }
254
+ }
255
+
256
+ func (h *WebSocketHandler) sendMessage(conn *websocket.Conn, msg gwtypes.WebSocketServerMessage) {
257
+ conn.SetWriteDeadline(time.Now().Add(h.config.WriteWait))
258
+ if err := conn.WriteJSON(msg); err != nil {
259
+ h.logger.Error("Failed to send WebSocket message", zap.Error(err))
260
+ }
261
+ }
262
+
263
+ func (h *WebSocketHandler) sendError(conn *websocket.Conn, message string) {
264
+ h.sendMessage(conn, gwtypes.WebSocketServerMessage{
265
+ Type: "error",
266
+ Timestamp: time.Now().Format(time.RFC3339),
267
+ Data: message,
268
+ })
269
+ }
270
+
271
+ // CloseAll closes all active connections
272
+ func (h *WebSocketHandler) CloseAll() {
273
+ h.connections.Range(func(key, value interface{}) bool {
274
+ wsConn := value.(*wsConnection)
275
+ wsConn.cancel()
276
+ wsConn.conn.Close()
277
+ return true
278
+ })
279
+ }
280
+
281
+ // ActiveConnections returns the number of active connections
282
+ func (h *WebSocketHandler) ActiveConnections() int {
283
+ count := 0
284
+ h.connections.Range(func(key, value interface{}) bool {
285
+ count++
286
+ return true
287
+ })
288
+ return count
289
+ }
internal/gateway/middleware/audit.go ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ "encoding/json"
5
+ "net/http"
6
+ "time"
7
+
8
+ "go.uber.org/zap"
9
+ )
10
+
11
+ // AuditEvent represents an audit log entry
12
+ type AuditEvent struct {
13
+ Timestamp int64 `json:"timestamp"`
14
+ RequestID string `json:"request_id"`
15
+ UserID string `json:"user_id"`
16
+ TenantID string `json:"tenant_id"`
17
+ Method string `json:"method"`
18
+ Path string `json:"path"`
19
+ Query string `json:"query,omitempty"`
20
+ StatusCode int `json:"status_code"`
21
+ LatencyMs float64 `json:"latency_ms"`
22
+ RequestSize int64 `json:"request_size"`
23
+ ResponseSize int `json:"response_size"`
24
+ UserAgent string `json:"user_agent"`
25
+ IP string `json:"ip"`
26
+ Error string `json:"error,omitempty"`
27
+ }
28
+
29
+ // AuditMiddleware logs all API requests for auditing
30
+ type AuditMiddleware struct {
31
+ logger *zap.Logger
32
+ skipPaths map[string]bool
33
+ }
34
+
35
+ // NewAuditMiddleware creates a new audit middleware
36
+ func NewAuditMiddleware(logger *zap.Logger) *AuditMiddleware {
37
+ return &AuditMiddleware{
38
+ logger: logger,
39
+ skipPaths: map[string]bool{
40
+ "/admin/health": true,
41
+ "/admin/metrics": true,
42
+ },
43
+ }
44
+ }
45
+
46
+ // Handler is the middleware handler function
47
+ func (m *AuditMiddleware) Handler(next http.Handler) http.Handler {
48
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
49
+ // Skip audit for certain paths
50
+ if m.skipPaths[r.URL.Path] {
51
+ next.ServeHTTP(w, r)
52
+ return
53
+ }
54
+
55
+ start := time.Now()
56
+
57
+ // Wrap response writer to capture status code and size
58
+ rw := &responseWriter{
59
+ ResponseWriter: w,
60
+ statusCode: http.StatusOK,
61
+ }
62
+
63
+ // Call next handler
64
+ next.ServeHTTP(rw, r)
65
+
66
+ // Calculate latency
67
+ latency := time.Since(start)
68
+
69
+ // Build audit event
70
+ event := AuditEvent{
71
+ Timestamp: time.Now().UnixNano() / int64(time.Millisecond),
72
+ RequestID: w.Header().Get("X-Request-ID"),
73
+ UserID: UserIDFromContext(r.Context()),
74
+ TenantID: TenantIDFromContext(r.Context()),
75
+ Method: r.Method,
76
+ Path: r.URL.Path,
77
+ Query: r.URL.RawQuery,
78
+ StatusCode: rw.statusCode,
79
+ LatencyMs: float64(latency.Nanoseconds()) / float64(time.Millisecond),
80
+ RequestSize: r.ContentLength,
81
+ ResponseSize: rw.size,
82
+ UserAgent: r.UserAgent(),
83
+ IP: getClientIP(r),
84
+ }
85
+
86
+ // Log the audit event
87
+ m.logEvent(event)
88
+ })
89
+ }
90
+
91
+ func (m *AuditMiddleware) logEvent(event AuditEvent) {
92
+ // Log as structured JSON
93
+ eventJSON, err := json.Marshal(event)
94
+ if err != nil {
95
+ m.logger.Error("Failed to marshal audit event", zap.Error(err))
96
+ return
97
+ }
98
+
99
+ m.logger.Info("audit",
100
+ zap.String("event", string(eventJSON)),
101
+ zap.String("request_id", event.RequestID),
102
+ zap.String("user_id", event.UserID),
103
+ zap.String("method", event.Method),
104
+ zap.String("path", event.Path),
105
+ zap.Int("status", event.StatusCode),
106
+ zap.Float64("latency_ms", event.LatencyMs),
107
+ )
108
+ }
109
+
110
+ // responseWriter wraps http.ResponseWriter to capture response details
111
+ type responseWriter struct {
112
+ http.ResponseWriter
113
+ statusCode int
114
+ size int
115
+ }
116
+
117
+ func (rw *responseWriter) WriteHeader(code int) {
118
+ rw.statusCode = code
119
+ rw.ResponseWriter.WriteHeader(code)
120
+ }
121
+
122
+ func (rw *responseWriter) Write(b []byte) (int, error) {
123
+ size, err := rw.ResponseWriter.Write(b)
124
+ rw.size += size
125
+ return size, err
126
+ }
127
+
128
+ // Flush implements http.Flusher
129
+ func (rw *responseWriter) Flush() {
130
+ if f, ok := rw.ResponseWriter.(http.Flusher); ok {
131
+ f.Flush()
132
+ }
133
+ }
internal/gateway/middleware/auth.go ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ "bytes"
5
+ "context"
6
+ "encoding/json"
7
+ "errors"
8
+ "fmt"
9
+ "net/http"
10
+ "strings"
11
+ "time"
12
+
13
+ "github.com/golang-jwt/jwt/v5"
14
+ "go.uber.org/zap"
15
+ )
16
+
17
+ // Context keys for request values
18
+ type contextKey string
19
+
20
+ const (
21
+ ContextKeyUserID contextKey = "userId"
22
+ ContextKeyTenantID contextKey = "tenantId"
23
+ ContextKeyClaims contextKey = "claims"
24
+ ContextKeyRoles contextKey = "roles"
25
+ )
26
+
27
+ // AuthConfig holds authentication configuration
28
+ type AuthConfig struct {
29
+ JWTSecret string
30
+ JWTIssuer string
31
+ JWTAudience string
32
+ OPAEnabled bool
33
+ OPAAddr string
34
+ OPAPolicy string
35
+ SkipPaths []string
36
+ }
37
+
38
+ // JWTClaims represents JWT token claims
39
+ type JWTClaims struct {
40
+ jwt.RegisteredClaims
41
+ UserID string `json:"user_id"`
42
+ Email string `json:"email"`
43
+ Roles []string `json:"roles"`
44
+ TenantID string `json:"tenant_id,omitempty"`
45
+ }
46
+
47
+ // AuthMiddleware handles JWT authentication and OPA authorization
48
+ type AuthMiddleware struct {
49
+ config AuthConfig
50
+ skipPaths map[string]bool
51
+ opaClient *OPAClient
52
+ logger *zap.Logger
53
+ }
54
+
55
+ // NewAuthMiddleware creates a new authentication middleware
56
+ func NewAuthMiddleware(cfg AuthConfig, logger *zap.Logger) *AuthMiddleware {
57
+ skipPaths := make(map[string]bool)
58
+ for _, path := range cfg.SkipPaths {
59
+ skipPaths[path] = true
60
+ }
61
+
62
+ m := &AuthMiddleware{
63
+ config: cfg,
64
+ skipPaths: skipPaths,
65
+ logger: logger,
66
+ }
67
+
68
+ if cfg.OPAEnabled && cfg.OPAAddr != "" {
69
+ m.opaClient = NewOPAClient(cfg.OPAAddr, cfg.OPAPolicy)
70
+ }
71
+
72
+ return m
73
+ }
74
+
75
+ // Handler is the middleware handler function
76
+ func (m *AuthMiddleware) Handler(next http.Handler) http.Handler {
77
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
78
+ // Skip authentication for certain paths
79
+ if m.skipPaths[r.URL.Path] {
80
+ next.ServeHTTP(w, r)
81
+ return
82
+ }
83
+
84
+ // Extract token
85
+ token := m.extractToken(r)
86
+ if token == "" {
87
+ m.unauthorized(w, "Missing authorization token")
88
+ return
89
+ }
90
+
91
+ // Validate JWT
92
+ claims, err := m.validateToken(token)
93
+ if err != nil {
94
+ m.logger.Debug("Token validation failed", zap.Error(err))
95
+ m.unauthorized(w, "Invalid token")
96
+ return
97
+ }
98
+
99
+ // Check token expiration
100
+ if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) {
101
+ m.unauthorized(w, "Token expired")
102
+ return
103
+ }
104
+
105
+ // OPA authorization
106
+ if m.opaClient != nil {
107
+ allowed, err := m.opaClient.Authorize(r.Context(), AuthzInput{
108
+ User: claims.UserID,
109
+ Roles: claims.Roles,
110
+ Action: r.Method,
111
+ Path: r.URL.Path,
112
+ Tenant: claims.TenantID,
113
+ })
114
+ if err != nil {
115
+ m.logger.Error("OPA authorization failed", zap.Error(err))
116
+ m.forbidden(w, "Authorization service unavailable")
117
+ return
118
+ }
119
+ if !allowed {
120
+ m.forbidden(w, "Access denied")
121
+ return
122
+ }
123
+ }
124
+
125
+ // Add claims to context
126
+ ctx := r.Context()
127
+ ctx = context.WithValue(ctx, ContextKeyClaims, claims)
128
+ ctx = context.WithValue(ctx, ContextKeyUserID, claims.UserID)
129
+ ctx = context.WithValue(ctx, ContextKeyTenantID, claims.TenantID)
130
+ ctx = context.WithValue(ctx, ContextKeyRoles, claims.Roles)
131
+
132
+ next.ServeHTTP(w, r.WithContext(ctx))
133
+ })
134
+ }
135
+
136
+ func (m *AuthMiddleware) extractToken(r *http.Request) string {
137
+ // Check Authorization header
138
+ authHeader := r.Header.Get("Authorization")
139
+ if authHeader != "" {
140
+ if strings.HasPrefix(authHeader, "Bearer ") {
141
+ return strings.TrimPrefix(authHeader, "Bearer ")
142
+ }
143
+ }
144
+
145
+ // Check query parameter (for WebSocket connections)
146
+ if token := r.URL.Query().Get("token"); token != "" {
147
+ return token
148
+ }
149
+
150
+ return ""
151
+ }
152
+
153
+ func (m *AuthMiddleware) validateToken(tokenString string) (*JWTClaims, error) {
154
+ claims := &JWTClaims{}
155
+
156
+ token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
157
+ // Validate signing method
158
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
159
+ return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
160
+ }
161
+ return []byte(m.config.JWTSecret), nil
162
+ })
163
+
164
+ if err != nil {
165
+ return nil, fmt.Errorf("failed to parse token: %w", err)
166
+ }
167
+
168
+ if !token.Valid {
169
+ return nil, errors.New("invalid token")
170
+ }
171
+
172
+ // Validate issuer
173
+ if m.config.JWTIssuer != "" && claims.Issuer != m.config.JWTIssuer {
174
+ return nil, fmt.Errorf("invalid issuer: expected %s, got %s", m.config.JWTIssuer, claims.Issuer)
175
+ }
176
+
177
+ // Validate audience
178
+ if m.config.JWTAudience != "" {
179
+ hasAudience := false
180
+ for _, aud := range claims.Audience {
181
+ if aud == m.config.JWTAudience {
182
+ hasAudience = true
183
+ break
184
+ }
185
+ }
186
+ if !hasAudience {
187
+ return nil, errors.New("invalid audience")
188
+ }
189
+ }
190
+
191
+ return claims, nil
192
+ }
193
+
194
+ func (m *AuthMiddleware) unauthorized(w http.ResponseWriter, message string) {
195
+ w.Header().Set("Content-Type", "application/json")
196
+ w.Header().Set("WWW-Authenticate", "Bearer")
197
+ w.WriteHeader(http.StatusUnauthorized)
198
+ json.NewEncoder(w).Encode(map[string]string{
199
+ "error": "unauthorized",
200
+ "message": message,
201
+ })
202
+ }
203
+
204
+ func (m *AuthMiddleware) forbidden(w http.ResponseWriter, message string) {
205
+ w.Header().Set("Content-Type", "application/json")
206
+ w.WriteHeader(http.StatusForbidden)
207
+ json.NewEncoder(w).Encode(map[string]string{
208
+ "error": "forbidden",
209
+ "message": message,
210
+ })
211
+ }
212
+
213
+ // UserIDFromContext extracts user ID from context
214
+ func UserIDFromContext(ctx context.Context) string {
215
+ if userID, ok := ctx.Value(ContextKeyUserID).(string); ok {
216
+ return userID
217
+ }
218
+ return ""
219
+ }
220
+
221
+ // TenantIDFromContext extracts tenant ID from context
222
+ func TenantIDFromContext(ctx context.Context) string {
223
+ if tenantID, ok := ctx.Value(ContextKeyTenantID).(string); ok {
224
+ return tenantID
225
+ }
226
+ return ""
227
+ }
228
+
229
+ // ClaimsFromContext extracts claims from context
230
+ func ClaimsFromContext(ctx context.Context) *JWTClaims {
231
+ if claims, ok := ctx.Value(ContextKeyClaims).(*JWTClaims); ok {
232
+ return claims
233
+ }
234
+ return nil
235
+ }
236
+
237
+ // RolesFromContext extracts roles from context
238
+ func RolesFromContext(ctx context.Context) []string {
239
+ if roles, ok := ctx.Value(ContextKeyRoles).([]string); ok {
240
+ return roles
241
+ }
242
+ return nil
243
+ }
244
+
245
+ // OPAClient is a client for Open Policy Agent
246
+ type OPAClient struct {
247
+ addr string
248
+ policy string
249
+ client *http.Client
250
+ }
251
+
252
+ // NewOPAClient creates a new OPA client
253
+ func NewOPAClient(addr, policy string) *OPAClient {
254
+ return &OPAClient{
255
+ addr: addr,
256
+ policy: policy,
257
+ client: &http.Client{
258
+ Timeout: 5 * time.Second,
259
+ },
260
+ }
261
+ }
262
+
263
+ // AuthzInput represents the input to OPA authorization
264
+ type AuthzInput struct {
265
+ User string `json:"user"`
266
+ Roles []string `json:"roles"`
267
+ Action string `json:"action"`
268
+ Path string `json:"path"`
269
+ Tenant string `json:"tenant,omitempty"`
270
+ }
271
+
272
+ // Authorize checks if the request is authorized
273
+ func (c *OPAClient) Authorize(ctx context.Context, input AuthzInput) (bool, error) {
274
+ // Build OPA query URL
275
+ url := fmt.Sprintf("%s/v1/data/%s", c.addr, strings.ReplaceAll(c.policy, "/", "."))
276
+
277
+ // Create request body
278
+ body := map[string]interface{}{
279
+ "input": input,
280
+ }
281
+
282
+ jsonBody, err := json.Marshal(body)
283
+ if err != nil {
284
+ return false, fmt.Errorf("failed to marshal OPA input: %w", err)
285
+ }
286
+
287
+ // Create request
288
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonBody))
289
+ if err != nil {
290
+ return false, fmt.Errorf("failed to create OPA request: %w", err)
291
+ }
292
+ req.Header.Set("Content-Type", "application/json")
293
+
294
+ // Send request
295
+ resp, err := c.client.Do(req)
296
+ if err != nil {
297
+ return false, fmt.Errorf("OPA request failed: %w", err)
298
+ }
299
+ defer resp.Body.Close()
300
+
301
+ // Parse response
302
+ var result struct {
303
+ Result bool `json:"result"`
304
+ }
305
+
306
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
307
+ return false, fmt.Errorf("failed to decode OPA response: %w", err)
308
+ }
309
+
310
+ return result.Result, nil
311
+ }
internal/gateway/middleware/common.go ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ "net/http"
5
+ "runtime/debug"
6
+
7
+ "github.com/google/uuid"
8
+ "go.uber.org/zap"
9
+ )
10
+
11
+ // Recovery returns a middleware that recovers from panics
12
+ func Recovery(logger *zap.Logger) func(http.Handler) http.Handler {
13
+ return func(next http.Handler) http.Handler {
14
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15
+ defer func() {
16
+ if rec := recover(); rec != nil {
17
+ // Log the panic
18
+ logger.Error("Panic recovered",
19
+ zap.Any("panic", rec),
20
+ zap.String("path", r.URL.Path),
21
+ zap.String("method", r.Method),
22
+ zap.String("stack", string(debug.Stack())),
23
+ )
24
+
25
+ // Return 500 error
26
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
27
+ }
28
+ }()
29
+
30
+ next.ServeHTTP(w, r)
31
+ })
32
+ }
33
+ }
34
+
35
+ // RequestID returns a middleware that adds a unique request ID
36
+ func RequestID() func(http.Handler) http.Handler {
37
+ return func(next http.Handler) http.Handler {
38
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39
+ // Check if request ID already exists
40
+ requestID := r.Header.Get("X-Request-ID")
41
+ if requestID == "" {
42
+ requestID = uuid.New().String()
43
+ }
44
+
45
+ // Set request ID in response header
46
+ w.Header().Set("X-Request-ID", requestID)
47
+
48
+ next.ServeHTTP(w, r)
49
+ })
50
+ }
51
+ }
internal/gateway/middleware/cors.go ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package middleware provides HTTP middleware for the API Gateway.
2
+ package middleware
3
+
4
+ import (
5
+ "net/http"
6
+ "strconv"
7
+ "strings"
8
+ )
9
+
10
+ // CORSConfig holds CORS middleware configuration
11
+ type CORSConfig struct {
12
+ AllowedOrigins []string
13
+ AllowedMethods []string
14
+ AllowedHeaders []string
15
+ ExposedHeaders []string
16
+ AllowCredentials bool
17
+ MaxAge int
18
+ }
19
+
20
+ // CORSMiddleware handles Cross-Origin Resource Sharing
21
+ type CORSMiddleware struct {
22
+ config CORSConfig
23
+ allowedOrigins map[string]bool
24
+ allowAllOrigins bool
25
+ }
26
+
27
+ // NewCORSMiddleware creates a new CORS middleware
28
+ func NewCORSMiddleware(cfg CORSConfig) *CORSMiddleware {
29
+ m := &CORSMiddleware{
30
+ config: cfg,
31
+ allowedOrigins: make(map[string]bool),
32
+ }
33
+
34
+ for _, origin := range cfg.AllowedOrigins {
35
+ if origin == "*" {
36
+ m.allowAllOrigins = true
37
+ break
38
+ }
39
+ m.allowedOrigins[origin] = true
40
+ }
41
+
42
+ return m
43
+ }
44
+
45
+ // Handler is the middleware handler function
46
+ func (m *CORSMiddleware) Handler(next http.Handler) http.Handler {
47
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
48
+ origin := r.Header.Get("Origin")
49
+
50
+ // Check if origin is allowed
51
+ if m.isOriginAllowed(origin) {
52
+ w.Header().Set("Access-Control-Allow-Origin", origin)
53
+ }
54
+
55
+ // Set CORS headers
56
+ if m.config.AllowCredentials {
57
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
58
+ }
59
+
60
+ if len(m.config.ExposedHeaders) > 0 {
61
+ w.Header().Set("Access-Control-Expose-Headers", strings.Join(m.config.ExposedHeaders, ", "))
62
+ }
63
+
64
+ // Security headers
65
+ m.setSecurityHeaders(w)
66
+
67
+ // Handle preflight
68
+ if r.Method == http.MethodOptions {
69
+ m.handlePreflight(w, r)
70
+ return
71
+ }
72
+
73
+ next.ServeHTTP(w, r)
74
+ })
75
+ }
76
+
77
+ // HandlePreflight handles OPTIONS preflight requests
78
+ func (m *CORSMiddleware) HandlePreflight(w http.ResponseWriter, r *http.Request) {
79
+ origin := r.Header.Get("Origin")
80
+
81
+ if m.isOriginAllowed(origin) {
82
+ w.Header().Set("Access-Control-Allow-Origin", origin)
83
+ }
84
+
85
+ m.handlePreflight(w, r)
86
+ }
87
+
88
+ func (m *CORSMiddleware) handlePreflight(w http.ResponseWriter, r *http.Request) {
89
+ // Allow methods
90
+ if len(m.config.AllowedMethods) > 0 {
91
+ w.Header().Set("Access-Control-Allow-Methods", strings.Join(m.config.AllowedMethods, ", "))
92
+ }
93
+
94
+ // Allow headers
95
+ if len(m.config.AllowedHeaders) > 0 {
96
+ w.Header().Set("Access-Control-Allow-Headers", strings.Join(m.config.AllowedHeaders, ", "))
97
+ }
98
+
99
+ // Max age
100
+ if m.config.MaxAge > 0 {
101
+ w.Header().Set("Access-Control-Max-Age", strconv.Itoa(m.config.MaxAge))
102
+ }
103
+
104
+ // Credentials
105
+ if m.config.AllowCredentials {
106
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
107
+ }
108
+
109
+ // Security headers
110
+ m.setSecurityHeaders(w)
111
+
112
+ w.WriteHeader(http.StatusNoContent)
113
+ }
114
+
115
+ func (m *CORSMiddleware) isOriginAllowed(origin string) bool {
116
+ if origin == "" {
117
+ return false
118
+ }
119
+ if m.allowAllOrigins {
120
+ return true
121
+ }
122
+ return m.allowedOrigins[origin]
123
+ }
124
+
125
+ func (m *CORSMiddleware) setSecurityHeaders(w http.ResponseWriter) {
126
+ // Prevent MIME type sniffing
127
+ w.Header().Set("X-Content-Type-Options", "nosniff")
128
+
129
+ // Prevent clickjacking
130
+ w.Header().Set("X-Frame-Options", "DENY")
131
+
132
+ // XSS protection
133
+ w.Header().Set("X-XSS-Protection", "1; mode=block")
134
+
135
+ // HSTS - force HTTPS
136
+ w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
137
+
138
+ // Referrer policy
139
+ w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
140
+
141
+ // Content Security Policy
142
+ w.Header().Set("Content-Security-Policy", "default-src 'self'")
143
+ }
internal/gateway/middleware/middleware_test.go ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware_test
2
+
3
+ import (
4
+ "net/http"
5
+ "net/http/httptest"
6
+ "testing"
7
+ "time"
8
+
9
+ "github.com/AmaniQuery/amaniquery/internal/gateway/middleware"
10
+ "go.uber.org/zap"
11
+ )
12
+
13
+ // TestCORSMiddleware_AllowAllOrigins tests wildcard origin support
14
+ func TestCORSMiddleware_AllowAllOrigins(t *testing.T) {
15
+ cfg := middleware.CORSConfig{
16
+ AllowedOrigins: []string{"*"},
17
+ AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
18
+ AllowedHeaders: []string{"Content-Type", "Authorization"},
19
+ AllowCredentials: false,
20
+ MaxAge: 3600,
21
+ }
22
+
23
+ cors := middleware.NewCORSMiddleware(cfg)
24
+
25
+ handler := cors.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
26
+ w.WriteHeader(http.StatusOK)
27
+ }))
28
+
29
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
30
+ req.Header.Set("Origin", "https://example.com")
31
+ rec := httptest.NewRecorder()
32
+
33
+ handler.ServeHTTP(rec, req)
34
+
35
+ if rec.Header().Get("Access-Control-Allow-Origin") != "https://example.com" {
36
+ t.Errorf("Expected origin to be allowed, got: %s", rec.Header().Get("Access-Control-Allow-Origin"))
37
+ }
38
+ }
39
+
40
+ // TestCORSMiddleware_SpecificOrigins tests specific origin allowlist
41
+ func TestCORSMiddleware_SpecificOrigins(t *testing.T) {
42
+ cfg := middleware.CORSConfig{
43
+ AllowedOrigins: []string{"https://allowed.com", "https://another.com"},
44
+ AllowedMethods: []string{"GET", "POST"},
45
+ AllowedHeaders: []string{"Content-Type"},
46
+ AllowCredentials: true,
47
+ }
48
+
49
+ cors := middleware.NewCORSMiddleware(cfg)
50
+
51
+ handler := cors.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
52
+ w.WriteHeader(http.StatusOK)
53
+ }))
54
+
55
+ testCases := []struct {
56
+ name string
57
+ origin string
58
+ shouldAllow bool
59
+ }{
60
+ {"allowed origin", "https://allowed.com", true},
61
+ {"another allowed", "https://another.com", true},
62
+ {"not allowed", "https://evil.com", false},
63
+ {"no origin", "", false},
64
+ }
65
+
66
+ for _, tc := range testCases {
67
+ t.Run(tc.name, func(t *testing.T) {
68
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
69
+ if tc.origin != "" {
70
+ req.Header.Set("Origin", tc.origin)
71
+ }
72
+ rec := httptest.NewRecorder()
73
+
74
+ handler.ServeHTTP(rec, req)
75
+
76
+ hasOrigin := rec.Header().Get("Access-Control-Allow-Origin") != ""
77
+ if hasOrigin != tc.shouldAllow {
78
+ t.Errorf("Origin %s: expected allowed=%v, got allowed=%v", tc.origin, tc.shouldAllow, hasOrigin)
79
+ }
80
+ })
81
+ }
82
+ }
83
+
84
+ // TestCORSMiddleware_Preflight tests OPTIONS preflight handling
85
+ func TestCORSMiddleware_Preflight(t *testing.T) {
86
+ cfg := middleware.CORSConfig{
87
+ AllowedOrigins: []string{"https://allowed.com"},
88
+ AllowedMethods: []string{"GET", "POST", "PUT"},
89
+ AllowedHeaders: []string{"Content-Type", "Authorization"},
90
+ AllowCredentials: true,
91
+ MaxAge: 7200,
92
+ }
93
+
94
+ cors := middleware.NewCORSMiddleware(cfg)
95
+
96
+ handler := cors.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
97
+ t.Error("Handler should not be called for OPTIONS request")
98
+ }))
99
+
100
+ req := httptest.NewRequest(http.MethodOptions, "/test", nil)
101
+ req.Header.Set("Origin", "https://allowed.com")
102
+ req.Header.Set("Access-Control-Request-Method", "POST")
103
+ rec := httptest.NewRecorder()
104
+
105
+ handler.ServeHTTP(rec, req)
106
+
107
+ if rec.Code != http.StatusNoContent {
108
+ t.Errorf("Expected status %d, got %d", http.StatusNoContent, rec.Code)
109
+ }
110
+
111
+ // Check preflight headers
112
+ if rec.Header().Get("Access-Control-Allow-Methods") == "" {
113
+ t.Error("Expected Access-Control-Allow-Methods header")
114
+ }
115
+ if rec.Header().Get("Access-Control-Allow-Headers") == "" {
116
+ t.Error("Expected Access-Control-Allow-Headers header")
117
+ }
118
+ if rec.Header().Get("Access-Control-Max-Age") != "7200" {
119
+ t.Errorf("Expected Max-Age 7200, got %s", rec.Header().Get("Access-Control-Max-Age"))
120
+ }
121
+ }
122
+
123
+ // TestCORSMiddleware_SecurityHeaders tests security headers are set
124
+ func TestCORSMiddleware_SecurityHeaders(t *testing.T) {
125
+ cfg := middleware.CORSConfig{
126
+ AllowedOrigins: []string{"*"},
127
+ }
128
+
129
+ cors := middleware.NewCORSMiddleware(cfg)
130
+
131
+ handler := cors.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
132
+ w.WriteHeader(http.StatusOK)
133
+ }))
134
+
135
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
136
+ req.Header.Set("Origin", "https://example.com")
137
+ rec := httptest.NewRecorder()
138
+
139
+ handler.ServeHTTP(rec, req)
140
+
141
+ securityHeaders := []string{
142
+ "X-Content-Type-Options",
143
+ "X-Frame-Options",
144
+ "X-XSS-Protection",
145
+ "Strict-Transport-Security",
146
+ "Referrer-Policy",
147
+ "Content-Security-Policy",
148
+ }
149
+
150
+ for _, header := range securityHeaders {
151
+ if rec.Header().Get(header) == "" {
152
+ t.Errorf("Expected security header %s to be set", header)
153
+ }
154
+ }
155
+ }
156
+
157
+ // TestCORSMiddleware_Credentials tests credentials header
158
+ func TestCORSMiddleware_Credentials(t *testing.T) {
159
+ cfg := middleware.CORSConfig{
160
+ AllowedOrigins: []string{"https://allowed.com"},
161
+ AllowCredentials: true,
162
+ }
163
+
164
+ cors := middleware.NewCORSMiddleware(cfg)
165
+
166
+ handler := cors.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
167
+ w.WriteHeader(http.StatusOK)
168
+ }))
169
+
170
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
171
+ req.Header.Set("Origin", "https://allowed.com")
172
+ rec := httptest.NewRecorder()
173
+
174
+ handler.ServeHTTP(rec, req)
175
+
176
+ if rec.Header().Get("Access-Control-Allow-Credentials") != "true" {
177
+ t.Error("Expected Access-Control-Allow-Credentials to be true")
178
+ }
179
+ }
180
+
181
+ // TestRateLimitMiddleware_LocalRateLimit tests local rate limiting
182
+ func TestRateLimitMiddleware_LocalRateLimit(t *testing.T) {
183
+ logger, _ := zap.NewDevelopment()
184
+ cfg := middleware.RateLimitConfig{
185
+ RequestsPerSec: 1, // 1 request per second
186
+ BurstSize: 1, // Allow 1 request burst
187
+ CleanupInterval: time.Minute,
188
+ }
189
+
190
+ rl, err := middleware.NewRateLimitMiddleware(cfg, logger)
191
+ if err != nil {
192
+ t.Fatalf("Failed to create rate limit middleware: %v", err)
193
+ }
194
+ defer rl.Close()
195
+
196
+ handler := rl.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
197
+ w.WriteHeader(http.StatusOK)
198
+ }))
199
+
200
+ // First request should succeed (uses burst token)
201
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
202
+ req.RemoteAddr = "127.0.0.1:12345"
203
+ rec := httptest.NewRecorder()
204
+ handler.ServeHTTP(rec, req)
205
+
206
+ if rec.Code != http.StatusOK {
207
+ t.Errorf("First request should be allowed, got status %d", rec.Code)
208
+ }
209
+
210
+ // Second request immediately after should be rate limited
211
+ req2 := httptest.NewRequest(http.MethodGet, "/test", nil)
212
+ req2.RemoteAddr = "127.0.0.1:12345"
213
+ rec2 := httptest.NewRecorder()
214
+ handler.ServeHTTP(rec2, req2)
215
+
216
+ if rec2.Code != http.StatusTooManyRequests {
217
+ t.Errorf("Expected status %d (rate limited), got %d", http.StatusTooManyRequests, rec2.Code)
218
+ }
219
+ }
220
+
221
+ // TestRateLimitMiddleware_Headers tests rate limit headers
222
+ func TestRateLimitMiddleware_Headers(t *testing.T) {
223
+ logger, _ := zap.NewDevelopment()
224
+ cfg := middleware.RateLimitConfig{
225
+ RequestsPerSec: 10,
226
+ BurstSize: 10,
227
+ CleanupInterval: time.Minute,
228
+ }
229
+
230
+ rl, err := middleware.NewRateLimitMiddleware(cfg, logger)
231
+ if err != nil {
232
+ t.Fatalf("Failed to create rate limit middleware: %v", err)
233
+ }
234
+ defer rl.Close()
235
+
236
+ handler := rl.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
237
+ w.WriteHeader(http.StatusOK)
238
+ }))
239
+
240
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
241
+ req.RemoteAddr = "127.0.0.1:12345"
242
+ rec := httptest.NewRecorder()
243
+
244
+ handler.ServeHTTP(rec, req)
245
+
246
+ // Check rate limit headers
247
+ if rec.Header().Get("X-RateLimit-Limit") == "" {
248
+ t.Error("Expected X-RateLimit-Limit header")
249
+ }
250
+ if rec.Header().Get("X-RateLimit-Remaining") == "" {
251
+ t.Error("Expected X-RateLimit-Remaining header")
252
+ }
253
+ if rec.Header().Get("X-RateLimit-Reset") == "" {
254
+ t.Error("Expected X-RateLimit-Reset header")
255
+ }
256
+ }
257
+
258
+ // TestRateLimitMiddleware_DifferentIPs tests per-IP rate limiting
259
+ func TestRateLimitMiddleware_DifferentIPs(t *testing.T) {
260
+ logger, _ := zap.NewDevelopment()
261
+ cfg := middleware.RateLimitConfig{
262
+ RequestsPerSec: 1,
263
+ BurstSize: 1,
264
+ CleanupInterval: time.Minute,
265
+ }
266
+
267
+ rl, err := middleware.NewRateLimitMiddleware(cfg, logger)
268
+ if err != nil {
269
+ t.Fatalf("Failed to create rate limit middleware: %v", err)
270
+ }
271
+ defer rl.Close()
272
+
273
+ handler := rl.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
274
+ w.WriteHeader(http.StatusOK)
275
+ }))
276
+
277
+ // First IP exhausts its limit
278
+ req1 := httptest.NewRequest(http.MethodGet, "/test", nil)
279
+ req1.RemoteAddr = "10.0.0.1:12345"
280
+ rec1 := httptest.NewRecorder()
281
+ handler.ServeHTTP(rec1, req1)
282
+
283
+ if rec1.Code != http.StatusOK {
284
+ t.Errorf("First request from IP1 should succeed, got %d", rec1.Code)
285
+ }
286
+
287
+ // Second request from same IP should be limited
288
+ req2 := httptest.NewRequest(http.MethodGet, "/test", nil)
289
+ req2.RemoteAddr = "10.0.0.1:12345"
290
+ rec2 := httptest.NewRecorder()
291
+ handler.ServeHTTP(rec2, req2)
292
+
293
+ if rec2.Code != http.StatusTooManyRequests {
294
+ t.Errorf("Second request from IP1 should be limited, got %d", rec2.Code)
295
+ }
296
+
297
+ // Different IP should still be allowed
298
+ req3 := httptest.NewRequest(http.MethodGet, "/test", nil)
299
+ req3.RemoteAddr = "10.0.0.2:12345"
300
+ rec3 := httptest.NewRecorder()
301
+ handler.ServeHTTP(rec3, req3)
302
+
303
+ if rec3.Code != http.StatusOK {
304
+ t.Errorf("First request from IP2 should succeed, got %d", rec3.Code)
305
+ }
306
+ }
307
+
308
+ // TestRateLimitMiddleware_XForwardedFor tests X-Forwarded-For handling
309
+ func TestRateLimitMiddleware_XForwardedFor(t *testing.T) {
310
+ logger, _ := zap.NewDevelopment()
311
+ cfg := middleware.RateLimitConfig{
312
+ RequestsPerSec: 1,
313
+ BurstSize: 1,
314
+ CleanupInterval: time.Minute,
315
+ }
316
+
317
+ rl, err := middleware.NewRateLimitMiddleware(cfg, logger)
318
+ if err != nil {
319
+ t.Fatalf("Failed to create rate limit middleware: %v", err)
320
+ }
321
+ defer rl.Close()
322
+
323
+ handler := rl.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
324
+ w.WriteHeader(http.StatusOK)
325
+ }))
326
+
327
+ // Request with X-Forwarded-For header
328
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
329
+ req.RemoteAddr = "127.0.0.1:12345"
330
+ req.Header.Set("X-Forwarded-For", "203.0.113.195, 70.41.3.18, 150.172.238.178")
331
+ rec := httptest.NewRecorder()
332
+
333
+ handler.ServeHTTP(rec, req)
334
+
335
+ if rec.Code != http.StatusOK {
336
+ t.Errorf("Request should succeed, got %d", rec.Code)
337
+ }
338
+ }
339
+
340
+ // BenchmarkCORSMiddleware benchmarks CORS middleware
341
+ func BenchmarkCORSMiddleware(b *testing.B) {
342
+ cfg := middleware.CORSConfig{
343
+ AllowedOrigins: []string{"https://example.com", "https://test.com"},
344
+ AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
345
+ AllowedHeaders: []string{"Content-Type", "Authorization"},
346
+ }
347
+
348
+ cors := middleware.NewCORSMiddleware(cfg)
349
+
350
+ handler := cors.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
351
+ w.WriteHeader(http.StatusOK)
352
+ }))
353
+
354
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
355
+ req.Header.Set("Origin", "https://example.com")
356
+
357
+ b.ResetTimer()
358
+ for i := 0; i < b.N; i++ {
359
+ rec := httptest.NewRecorder()
360
+ handler.ServeHTTP(rec, req)
361
+ }
362
+ }
363
+
364
+ // BenchmarkRateLimitMiddleware benchmarks rate limit middleware
365
+ func BenchmarkRateLimitMiddleware(b *testing.B) {
366
+ logger, _ := zap.NewProduction()
367
+ cfg := middleware.RateLimitConfig{
368
+ RequestsPerSec: 1000000, // High limit for benchmarking
369
+ BurstSize: 1000000,
370
+ CleanupInterval: time.Minute,
371
+ }
372
+
373
+ rl, _ := middleware.NewRateLimitMiddleware(cfg, logger)
374
+ defer rl.Close()
375
+
376
+ handler := rl.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
377
+ w.WriteHeader(http.StatusOK)
378
+ }))
379
+
380
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
381
+ req.RemoteAddr = "127.0.0.1:12345"
382
+
383
+ b.ResetTimer()
384
+ for i := 0; i < b.N; i++ {
385
+ rec := httptest.NewRecorder()
386
+ handler.ServeHTTP(rec, req)
387
+ }
388
+ }
internal/gateway/middleware/ratelimit.go ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "net/http"
7
+ "sync"
8
+ "time"
9
+
10
+ "github.com/redis/go-redis/v9"
11
+ "go.uber.org/zap"
12
+ "golang.org/x/time/rate"
13
+ )
14
+
15
+ // RateLimitConfig holds rate limiting configuration
16
+ type RateLimitConfig struct {
17
+ RequestsPerSec float64
18
+ BurstSize int
19
+ PerTenant bool
20
+ PerUser bool
21
+ RedisAddr string
22
+ RedisEnabled bool
23
+ CleanupInterval time.Duration
24
+ }
25
+
26
+ // RateLimitMiddleware implements token bucket rate limiting
27
+ type RateLimitMiddleware struct {
28
+ config RateLimitConfig
29
+ limiters sync.Map // map[string]*rate.Limiter
30
+ redis *redis.Client
31
+ logger *zap.Logger
32
+ stop chan struct{}
33
+ }
34
+
35
+ // NewRateLimitMiddleware creates a new rate limit middleware
36
+ func NewRateLimitMiddleware(cfg RateLimitConfig, logger *zap.Logger) (*RateLimitMiddleware, error) {
37
+ m := &RateLimitMiddleware{
38
+ config: cfg,
39
+ logger: logger,
40
+ stop: make(chan struct{}),
41
+ }
42
+
43
+ // Initialize Redis if enabled
44
+ if cfg.RedisEnabled && cfg.RedisAddr != "" {
45
+ m.redis = redis.NewClient(&redis.Options{
46
+ Addr: cfg.RedisAddr,
47
+ })
48
+
49
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
50
+ defer cancel()
51
+
52
+ if err := m.redis.Ping(ctx).Err(); err != nil {
53
+ logger.Warn("Redis ping failed, using local rate limiting", zap.Error(err))
54
+ m.redis = nil
55
+ }
56
+ }
57
+
58
+ // Start cleanup goroutine for local limiters
59
+ if cfg.CleanupInterval > 0 {
60
+ go m.cleanupLoop()
61
+ }
62
+
63
+ return m, nil
64
+ }
65
+
66
+ // Handler is the middleware handler function
67
+ func (m *RateLimitMiddleware) Handler(next http.Handler) http.Handler {
68
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
69
+ // Get rate limit key
70
+ key := m.getKey(r)
71
+
72
+ // Check rate limit
73
+ allowed, remaining, reset := m.checkLimit(r.Context(), key)
74
+
75
+ // Set rate limit headers
76
+ w.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%.0f", m.config.RequestsPerSec))
77
+ w.Header().Set("X-RateLimit-Remaining", fmt.Sprintf("%d", remaining))
78
+ w.Header().Set("X-RateLimit-Reset", fmt.Sprintf("%d", reset))
79
+
80
+ if !allowed {
81
+ w.Header().Set("Retry-After", fmt.Sprintf("%d", reset-time.Now().Unix()))
82
+ http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
83
+ return
84
+ }
85
+
86
+ next.ServeHTTP(w, r)
87
+ })
88
+ }
89
+
90
+ func (m *RateLimitMiddleware) getKey(r *http.Request) string {
91
+ var parts []string
92
+
93
+ // Get tenant from context
94
+ if m.config.PerTenant {
95
+ if tenantID := r.Context().Value(ContextKeyTenantID); tenantID != nil {
96
+ parts = append(parts, fmt.Sprintf("tenant:%s", tenantID))
97
+ }
98
+ }
99
+
100
+ // Get user from context
101
+ if m.config.PerUser {
102
+ if userID := r.Context().Value(ContextKeyUserID); userID != nil {
103
+ parts = append(parts, fmt.Sprintf("user:%s", userID))
104
+ }
105
+ }
106
+
107
+ // Fall back to IP if no tenant/user
108
+ if len(parts) == 0 {
109
+ parts = append(parts, fmt.Sprintf("ip:%s", getClientIP(r)))
110
+ }
111
+
112
+ key := ""
113
+ for i, part := range parts {
114
+ if i > 0 {
115
+ key += ":"
116
+ }
117
+ key += part
118
+ }
119
+
120
+ return key
121
+ }
122
+
123
+ func (m *RateLimitMiddleware) checkLimit(ctx context.Context, key string) (allowed bool, remaining int, reset int64) {
124
+ // Use Redis for distributed rate limiting if available
125
+ if m.redis != nil {
126
+ return m.checkRedisLimit(ctx, key)
127
+ }
128
+
129
+ // Fall back to local rate limiting
130
+ return m.checkLocalLimit(key)
131
+ }
132
+
133
+ func (m *RateLimitMiddleware) checkLocalLimit(key string) (allowed bool, remaining int, reset int64) {
134
+ // Get or create limiter for this key
135
+ limiterI, _ := m.limiters.LoadOrStore(key, &limiterEntry{
136
+ limiter: rate.NewLimiter(rate.Limit(m.config.RequestsPerSec), m.config.BurstSize),
137
+ lastAccess: time.Now(),
138
+ })
139
+
140
+ entry := limiterI.(*limiterEntry)
141
+ entry.lastAccess = time.Now()
142
+
143
+ // Check if request is allowed
144
+ allowed = entry.limiter.Allow()
145
+
146
+ // Calculate remaining tokens (approximate)
147
+ tokens := entry.limiter.Tokens()
148
+ if tokens < 0 {
149
+ remaining = 0
150
+ } else {
151
+ remaining = int(tokens)
152
+ }
153
+
154
+ // Reset time is when a token will be available
155
+ reset = time.Now().Add(entry.limiter.Reserve().Delay()).Unix()
156
+
157
+ return allowed, remaining, reset
158
+ }
159
+
160
+ func (m *RateLimitMiddleware) checkRedisLimit(ctx context.Context, key string) (allowed bool, remaining int, reset int64) {
161
+ redisKey := fmt.Sprintf("ratelimit:%s", key)
162
+ now := time.Now()
163
+ windowStart := now.Truncate(time.Second)
164
+ windowEnd := windowStart.Add(time.Second)
165
+
166
+ // Use Redis MULTI/EXEC for atomic operations
167
+ pipe := m.redis.Pipeline()
168
+
169
+ // Increment counter
170
+ incrCmd := pipe.Incr(ctx, redisKey)
171
+ pipe.ExpireAt(ctx, redisKey, windowEnd.Add(time.Second))
172
+
173
+ _, err := pipe.Exec(ctx)
174
+ if err != nil {
175
+ m.logger.Warn("Redis rate limit check failed", zap.Error(err))
176
+ // Fall back to local limiting on Redis error
177
+ return m.checkLocalLimit(key)
178
+ }
179
+
180
+ count := incrCmd.Val()
181
+ limit := int64(m.config.RequestsPerSec)
182
+
183
+ allowed = count <= limit
184
+ remaining = int(limit - count)
185
+ if remaining < 0 {
186
+ remaining = 0
187
+ }
188
+ reset = windowEnd.Unix()
189
+
190
+ return allowed, remaining, reset
191
+ }
192
+
193
+ type limiterEntry struct {
194
+ limiter *rate.Limiter
195
+ lastAccess time.Time
196
+ }
197
+
198
+ func (m *RateLimitMiddleware) cleanupLoop() {
199
+ ticker := time.NewTicker(m.config.CleanupInterval)
200
+ defer ticker.Stop()
201
+
202
+ for {
203
+ select {
204
+ case <-ticker.C:
205
+ m.cleanup()
206
+ case <-m.stop:
207
+ return
208
+ }
209
+ }
210
+ }
211
+
212
+ func (m *RateLimitMiddleware) cleanup() {
213
+ expiry := time.Now().Add(-m.config.CleanupInterval * 2)
214
+
215
+ m.limiters.Range(func(key, value interface{}) bool {
216
+ entry := value.(*limiterEntry)
217
+ if entry.lastAccess.Before(expiry) {
218
+ m.limiters.Delete(key)
219
+ }
220
+ return true
221
+ })
222
+ }
223
+
224
+ // Close stops the cleanup goroutine
225
+ func (m *RateLimitMiddleware) Close() {
226
+ close(m.stop)
227
+ if m.redis != nil {
228
+ m.redis.Close()
229
+ }
230
+ }
231
+
232
+ func getClientIP(r *http.Request) string {
233
+ // Check X-Forwarded-For header
234
+ if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
235
+ // Take the first IP in the chain
236
+ if idx := len(xff); idx > 0 {
237
+ for i, c := range xff {
238
+ if c == ',' {
239
+ return xff[:i]
240
+ }
241
+ }
242
+ return xff
243
+ }
244
+ }
245
+
246
+ // Check X-Real-IP header
247
+ if xrip := r.Header.Get("X-Real-IP"); xrip != "" {
248
+ return xrip
249
+ }
250
+
251
+ // Fall back to remote address
252
+ return r.RemoteAddr
253
+ }
internal/gateway/middleware/tracing.go ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ "net/http"
5
+
6
+ "go.opentelemetry.io/otel/attribute"
7
+ "go.opentelemetry.io/otel/trace"
8
+ )
9
+
10
+ // TracingMiddleware adds OpenTelemetry tracing to requests
11
+ type TracingMiddleware struct {
12
+ tracer trace.Tracer
13
+ }
14
+
15
+ // NewTracingMiddleware creates a new tracing middleware
16
+ func NewTracingMiddleware(tracer trace.Tracer) *TracingMiddleware {
17
+ return &TracingMiddleware{
18
+ tracer: tracer,
19
+ }
20
+ }
21
+
22
+ // Handler is the middleware handler function
23
+ func (m *TracingMiddleware) Handler(next http.Handler) http.Handler {
24
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
25
+ // Start span
26
+ ctx, span := m.tracer.Start(r.Context(), r.URL.Path,
27
+ trace.WithSpanKind(trace.SpanKindServer),
28
+ )
29
+ defer span.End()
30
+
31
+ // Add request attributes
32
+ span.SetAttributes(
33
+ attribute.String("http.method", r.Method),
34
+ attribute.String("http.url", r.URL.String()),
35
+ attribute.String("http.host", r.Host),
36
+ attribute.String("http.user_agent", r.UserAgent()),
37
+ attribute.String("http.remote_addr", r.RemoteAddr),
38
+ )
39
+
40
+ // Add request ID if present
41
+ if requestID := r.Header.Get("X-Request-ID"); requestID != "" {
42
+ span.SetAttributes(attribute.String("http.request_id", requestID))
43
+ }
44
+
45
+ // Wrap response writer to capture status
46
+ rw := &tracingResponseWriter{
47
+ ResponseWriter: w,
48
+ statusCode: http.StatusOK,
49
+ }
50
+
51
+ // Call next handler with traced context
52
+ next.ServeHTTP(rw, r.WithContext(ctx))
53
+
54
+ // Add response attributes
55
+ span.SetAttributes(
56
+ attribute.Int("http.status_code", rw.statusCode),
57
+ attribute.Int("http.response_size", rw.size),
58
+ )
59
+
60
+ // Add user context if available
61
+ if userID := UserIDFromContext(ctx); userID != "" {
62
+ span.SetAttributes(attribute.String("user.id", userID))
63
+ }
64
+ if tenantID := TenantIDFromContext(ctx); tenantID != "" {
65
+ span.SetAttributes(attribute.String("tenant.id", tenantID))
66
+ }
67
+
68
+ // Mark error status
69
+ if rw.statusCode >= 400 {
70
+ span.SetAttributes(attribute.Bool("error", true))
71
+ }
72
+ })
73
+ }
74
+
75
+ type tracingResponseWriter struct {
76
+ http.ResponseWriter
77
+ statusCode int
78
+ size int
79
+ }
80
+
81
+ func (rw *tracingResponseWriter) WriteHeader(code int) {
82
+ rw.statusCode = code
83
+ rw.ResponseWriter.WriteHeader(code)
84
+ }
85
+
86
+ func (rw *tracingResponseWriter) Write(b []byte) (int, error) {
87
+ size, err := rw.ResponseWriter.Write(b)
88
+ rw.size += size
89
+ return size, err
90
+ }
91
+
92
+ func (rw *tracingResponseWriter) Flush() {
93
+ if f, ok := rw.ResponseWriter.(http.Flusher); ok {
94
+ f.Flush()
95
+ }
96
+ }
internal/gateway/observability/metrics.go ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package observability provides metrics for the API Gateway.
2
+ package observability
3
+
4
+ import (
5
+ "github.com/prometheus/client_golang/prometheus"
6
+ "github.com/prometheus/client_golang/prometheus/promauto"
7
+ )
8
+
9
+ var (
10
+ // HTTPRequestsTotal counts total HTTP requests
11
+ HTTPRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
12
+ Name: "gateway_http_requests_total",
13
+ Help: "Total HTTP requests",
14
+ }, []string{"method", "path", "status", "cached"})
15
+
16
+ // HTTPRequestDuration tracks request latency
17
+ HTTPRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
18
+ Name: "gateway_http_request_duration_seconds",
19
+ Help: "HTTP request duration in seconds",
20
+ Buckets: prometheus.DefBuckets,
21
+ }, []string{"method", "path"})
22
+
23
+ // WebSocketConnections tracks active connections
24
+ WebSocketConnections = promauto.NewGauge(prometheus.GaugeOpts{
25
+ Name: "gateway_websocket_connections_active",
26
+ Help: "Active WebSocket connections",
27
+ })
28
+
29
+ // RateLimitHits counts rate limit violations
30
+ RateLimitHits = promauto.NewCounterVec(prometheus.CounterOpts{
31
+ Name: "gateway_rate_limit_hits_total",
32
+ Help: "Total rate limit hits",
33
+ }, []string{"tenant", "user"})
34
+
35
+ // CacheHits tracks cache performance
36
+ CacheHits = promauto.NewCounterVec(prometheus.CounterOpts{
37
+ Name: "gateway_cache_hits_total",
38
+ Help: "Cache hit/miss counts",
39
+ }, []string{"hit"})
40
+
41
+ // CircuitBreakerState tracks circuit breaker states
42
+ CircuitBreakerState = promauto.NewGaugeVec(prometheus.GaugeOpts{
43
+ Name: "gateway_circuit_breaker_state",
44
+ Help: "Circuit breaker state (0=closed, 1=open, 2=half-open)",
45
+ }, []string{"service"})
46
+
47
+ // JWTValidationErrors counts auth failures
48
+ JWTValidationErrors = promauto.NewCounter(prometheus.CounterOpts{
49
+ Name: "gateway_jwt_validation_errors_total",
50
+ Help: "JWT validation errors",
51
+ })
52
+
53
+ // QueryLatency tracks query processing time
54
+ QueryLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
55
+ Name: "gateway_query_latency_seconds",
56
+ Help: "Query processing latency",
57
+ Buckets: []float64{.01, .05, .1, .25, .5, 1, 2.5, 5, 10},
58
+ }, []string{"agent_id", "cached"})
59
+
60
+ // StreamingChunks counts streamed chunks
61
+ StreamingChunks = promauto.NewCounterVec(prometheus.CounterOpts{
62
+ Name: "gateway_streaming_chunks_total",
63
+ Help: "Total streaming chunks sent",
64
+ }, []string{"type"})
65
+ )
66
+
67
+ // RecordRequest records HTTP request metrics
68
+ func RecordRequest(method, path, status string, cached bool, duration float64) {
69
+ cachedStr := "false"
70
+ if cached {
71
+ cachedStr = "true"
72
+ }
73
+ HTTPRequestsTotal.WithLabelValues(method, path, status, cachedStr).Inc()
74
+ HTTPRequestDuration.WithLabelValues(method, path).Observe(duration)
75
+ }
76
+
77
+ // RecordCacheHit records cache hit/miss
78
+ func RecordCacheHit(hit bool) {
79
+ hitStr := "false"
80
+ if hit {
81
+ hitStr = "true"
82
+ }
83
+ CacheHits.WithLabelValues(hitStr).Inc()
84
+ }
85
+
86
+ // RecordRateLimit records rate limit event
87
+ func RecordRateLimit(tenant, user string) {
88
+ RateLimitHits.WithLabelValues(tenant, user).Inc()
89
+ }
90
+
91
+ // SetCircuitBreakerState sets circuit breaker gauge
92
+ func SetCircuitBreakerState(service string, state int) {
93
+ CircuitBreakerState.WithLabelValues(service).Set(float64(state))
94
+ }
95
+
96
+ // IncrementWSConnections increments active WS connections
97
+ func IncrementWSConnections() {
98
+ WebSocketConnections.Inc()
99
+ }
100
+
101
+ // DecrementWSConnections decrements active WS connections
102
+ func DecrementWSConnections() {
103
+ WebSocketConnections.Dec()
104
+ }