Merge commit '74cc8a4685' as 'ai-context/trustgraph-templates'

This commit is contained in:
elpresidank 2026-04-05 21:09:49 -05:00
commit c386f68743
1216 changed files with 116347 additions and 0 deletions

View file

@ -0,0 +1,25 @@
name: CLA Assistant
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
actions: write
contents: write
pull-requests: write
statuses: write
jobs:
CLAssistant:
runs-on: ubuntu-latest
steps:
- name: CLA Assistant
uses: trustgraph-ai/contributor-license-agreement/action@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_ASSISTANT_PAT }}
with:
allowlist: 'dependabot,dependabot[bot],github-actions,github-actions[bot]'

View file

@ -0,0 +1,76 @@
name: Deploy to prod
on:
workflow_dispatch:
push:
# Deploys on master branch
branches:
- master
permissions:
contents: read
id-token: 'write'
packages: read
jobs:
deploy:
name: Deploy to prod
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get version
id: version
run: echo VERSION=sha-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
# Python package version MUST be a semantic version, but also doesn't
# matter, so just setting to 0.0.0.
# The container version MUST change on every push to get Cloud Run
# to re-deploy, so is based on git hash.
- name: Build container
run: make PACKAGE_VERSION=0.0.0 VERSION=${{ steps.version.outputs.VERSION }}
- name: Log in to the container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: auth
name: Authenticate with Google Cloud
uses: google-github-actions/auth@v2
with:
token_format: access_token
workload_identity_provider: projects/351149249312/locations/global/workloadIdentityPools/deploy/providers/github
service_account: deploy@trustgraph-ai.iam.gserviceaccount.com
access_token_lifetime: 900s
create_credentials_file: true
- name: Login to Artifact Registry
uses: redhat-actions/podman-login@v1
with:
registry: europe-west1-docker.pkg.dev
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}
- name: Install Pulumi
run: cd pulumi && npm install
- name: Applying infrastructure 🚀🙏
uses: pulumi/actions@v3
with:
command: up
stack-name: prod
work-dir: pulumi
cloud-url: gs://trustgraph-ai-deploy/config-svc
env:
PULUMI_CONFIG_PASSPHRASE: ""
IMAGE_VERSION: ${{ steps.version.outputs.VERSION }}

View file

@ -0,0 +1,31 @@
name: Test pull request
on:
pull_request:
permissions:
contents: read
jobs:
container-push:
name: Run tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup environment
run: |
python3 -m venv env
. env/bin/activate
pip install -e .[dev]
- name: Run pytest tests
run: |
. env/bin/activate
pytest -v --tb=short

View file

@ -0,0 +1,45 @@
name: Undeploy to prod
on:
workflow_dispatch:
permissions:
contents: read
id-token: 'write'
jobs:
deploy:
name: Undeploy to prod
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- id: auth
name: Authenticate with Google Cloud
uses: google-github-actions/auth@v2
with:
token_format: access_token
workload_identity_provider: projects/514167726704/locations/global/workloadIdentityPools/deploy/providers/deploy
service_account: deploy@kalntera-demo.iam.gserviceaccount.com
access_token_lifetime: 900s
create_credentials_file: true
- name: Install Pulumi
run: cd pulumi && npm install
- name: Destroy infrastructure ☠🔥
uses: pulumi/actions@v3
with:
command: destroy
stack-name: prod
work-dir: pulumi
cloud-url: gs://trustgraph-deploy/config-ui
env:
PULUMI_CONFIG_PASSPHRASE: ""

View file

@ -0,0 +1,22 @@
*~
__pycache__
*.pyc
*.pyo
*.egg-info/
build/
dist/
*.egg
.pytest_cache/
.coverage
htmlcov/
.eggs/
*.so
*.dylib
.Python
env/
venv/
ENV/
*.log
node_modules/
trustgraph_configurator/version.py
INSTALLATION.md

View file

@ -0,0 +1,38 @@
# --- STAGE 1: Build ---
FROM python:3.14-slim AS build
# Install build tools (Debian uses apt)
RUN apt-get update && apt-get install -y \
build-essential \
golang \
git \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /root/wheels /root/build
# Build wheels
RUN pip wheel -w /root/wheels --no-deps gojsonnet
COPY trustgraph_configurator/ /root/build/trustgraph_configurator/
COPY pyproject.toml /root/build/pyproject.toml
COPY README.md /root/build/README.md
RUN (cd /root/build && pip wheel -w /root/wheels --no-deps .)
# --- STAGE 2: Runtime ---
FROM python:3.14-slim
# No need to install libstdc++ manually, it's included in python-slim
RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
# aiohttp and others will be pulled from PyPI or handled via wheels
COPY --from=build /root/wheels /root/wheels
# Install your wheels plus regular dependencies
RUN pip install --no-cache-dir /root/wheels/* aiohttp pyyaml tabulate && \
rm -rf /root/wheels
CMD ["tg-config-svc"]
EXPOSE 8080

View file

@ -0,0 +1,168 @@
# TrustGraph Configuration Dialog Flow
## Overview
A configuration wizard system that guides users through TrustGraph deployment setup. Outputs deployment configuration and contextual documentation.
Supports two interfaces:
- **Web UI**: Step-by-step wizard with visual cards
- **CLI**: Interactive terminal prompts
## Architecture
```
┌─────────────────────┐
│ trustgraph-flow │ State machine defining wizard steps
│ .yaml │ and transitions
└──────────┬──────────┘
┌─────────────────────┐
│ Flow Engine │ Executes state machine, collects user input,
│ (Web UI / CLI) │ manages state and backtracking
└──────────┬──────────┘
┌──────────────┐
│ Wizard State │ Simple key/value object
│ (JSON) │ e.g., { platform: "gke", model_deployment: "ollama", ... }
└──────┬───────┘
┌─────┴─────┐
▼ ▼
┌─────────┐ ┌─────────────┐
│ Output │ │Documentation│
│Transform│ │ Assembler │
└────┬────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────┐ ┌─────────────┐
│Component│ │ README.md │
│ Array │ │ / Checklist │
│ (JSON) │ │ (JSON) │
└─────────┘ └─────────────┘
```
## Key Design Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Flow structure | State machine | Explicit transitions, supports conditional branching |
| Expression language | JSONata | Single language for conditions and transforms |
| Documentation storage | Manifest + markdown fragments | Clean separation, easy to maintain |
| Platform variants | `in ['docker-compose', 'podman-compose']` | Explicit grouping, no hidden logic |
| Template variables | `{{var}}` syntax | Simple, familiar |
## File Structure
```
config-dialog-flow/
├── dialog-flow-schema.json # Schema: flow definitions
├── docs-manifest-schema.json # Schema: documentation manifests
├── trustgraph-flow.yaml # Flow: wizard state machine
├── trustgraph-output.jsonata # Transform: state → components
├── trustgraph-docs.yaml # Manifest: state → documentation
└── docs/ # Fragments: markdown content
├── platform/
├── model/
├── storage/
├── gateway/
├── deploy/
└── features/
```
## Data Flow
1. **Flow engine** loads `trustgraph-flow.yaml`
2. User progresses through steps, engine collects state
3. On completion:
- **Output transform** evaluates `trustgraph-output.jsonata` against state → component array
- **Doc assembler** evaluates `trustgraph-docs.yaml` conditions against state → filtered instructions → loads markdown fragments → outputs README or checklist JSON
## Implementation Phases
### Phase 1: Core Flow Engine
- [ ] Parse YAML flow definition
- [ ] Implement state machine executor
- [ ] Handle step rendering (title, description, input)
- [ ] Evaluate JSONata transition conditions
- [ ] Manage wizard state (set values, navigate)
- [ ] Implement backtracking (previous step, state rollback)
### Phase 2: Input Types
- [ ] Select (single choice from options)
- [ ] Toggle (boolean)
- [ ] Number (with min/max/step)
- [ ] Text (free input)
- [ ] Skip single-option steps (UX decision)
### Phase 3: Output Generation
- [ ] Load JSONata transform
- [ ] Evaluate against wizard state
- [ ] Produce component array JSON
- [ ] Package as downloadable ZIP (existing functionality)
### Phase 4: Documentation Assembly
- [ ] Parse documentation manifest
- [ ] Evaluate `when` conditions (JSONata)
- [ ] Filter to matching instructions
- [ ] Load markdown fragments
- [ ] Substitute `{{var}}` placeholders
- [ ] Output as README.md (CLI) or checklist JSON (Web)
### Phase 5: CLI Interface
- [ ] Terminal prompt for each step
- [ ] Numbered option selection
- [ ] Progress indicator (`[3/12]`)
- [ ] Back command
- [ ] Review summary before generate
### Phase 6: Web Interface
- [ ] Step-by-step card UI
- [ ] Progress bar
- [ ] Option cards with icons/descriptions
- [ ] Back navigation
- [ ] Review screen with edit capability
- [ ] Generate button
## Expression Language
All conditions use JSONata:
```
platform = 'docker-compose'
platform in ['gke', 'eks', 'aks', 'minikube']
model_deployment = 'ollama' and platform in ['docker-compose', 'podman-compose']
version < '1.6.0'
ocr.enabled = true
```
## State Shape
```
{
"version": "1.8.18",
"platform": "gke",
"k8s": { "namespace": "trustgraph" },
"graph_store": "cassandra",
"vector_db": "qdrant",
"object_store": "cassandra",
"model_deployment": "ollama",
"max_output_tokens": 2048,
"ocr": { "enabled": true, "engine": "tesseract" },
"embeddings": { "enabled": false }
}
```
## Out of Scope (Future)
- Dual model mode (separate main/RAG models)
- Advanced settings (concurrency, chunking, memory profiles)
- Configuration import/export
- Validation beyond type checking

View file

@ -0,0 +1,22 @@
PACKAGE_VERSION=0.0.0
VERSION=0.0.0
all: container
package: update-package-versions
python3 -m build --sdist --outdir pkgs
update-package-versions:
echo __version__ = \"${PACKAGE_VERSION}\" > trustgraph_configurator/version.py
CONTAINER=localhost/config-svc
DOCKER=podman
container:
${DOCKER} build -f Containerfile -t ${CONTAINER}:${VERSION} \
--format docker
# On port 8081
run-container:
${DOCKER} run -i -t -p 8081:8080 ${CONTAINER}:${VERSION}

View file

@ -0,0 +1,311 @@
# TrustGraph Configuration Templates
TrustGraph configurator is a Python-based tool that generates deployment configurations for TrustGraph AI systems. It supports multiple deployment platforms and provides templated configurations with versioning support.
## Overview
The configurator uses Jsonnet templates to generate deployment configurations for various platforms including Docker Compose, Podman, and multiple Kubernetes environments. It packages the generated configurations into ZIP files containing all necessary deployment resources.
## Configuration Process
The TrustGraph configuration system uses a multi-stage pipeline to generate deployment packages:
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Dialog Flow │ │ JSONata │ │ Configuration │ │ Deployment │
│ Configuration │────▶│ Transform │────▶│ Service │────▶│ Package │
│ │ │ │ │ │ │ │
│ (state object) │ │ (config object) │ │ (templates) │ │ (ZIP file) │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
│ ┌─────────────────┐ ┌─────────────────┐
└──────────────▶│ Documentation │────▶│ Installation │
│ Flow │ │ Guide │
└─────────────────┘ └─────────────────┘
```
### 1. Dialog Flow Configuration
The dialog flow file (`trustgraph-flow.yaml`) describes configuration steps in a technology-neutral way. A UI wizard walks users through these steps, collecting choices about platform, model provider, storage backends, and features. The output is a **state object** - a simple key/value map representing all user selections.
### 2. JSONata Transform
The JSONata transform file (`trustgraph-output.jsonata`) converts the state object into a **configuration object**. This object understands how to invoke TrustGraph templates and contains the structured parameters needed by the template system.
### 3. Configuration Service
The configuration service receives the configuration object, invokes the appropriate Jsonnet templates, and runs the package builder. The output is a **deployment package** - a ZIP file containing all deployment resources (docker-compose.yaml or Kubernetes manifests, plus supporting files).
### 4. Documentation Flow
The original state object can also be used with the documentation manifest (`trustgraph-docs.yaml`) to generate a customised **installation guide** based on the user's specific configuration choices.
## Installation
The configurator is distributed as a Python package. To use it:
```bash
export PYTHONPATH=.
# or install the package
pip install -e .
```
## Usage
### List Available Configurations
To see all available templates and platforms:
```bash
tg-show-config-params
```
This will display:
- Available platforms (docker-compose, podman-compose, various Kubernetes options)
- Available templates with versions and stability status
- Latest version and latest stable version
### Generate Configuration
To generate a configuration package:
```bash
scripts/tg-build-deployment --template <template-name> --version <version> \
--input config.json --output output.zip --platform <platform>
```
Example:
```bash
scripts/tg-build-deployment --template 1.1 --version 1.1.9 \
--input config.json --output deployment.zip --platform docker-compose
```
#### Output to stdout
To output only the TrustGraph configuration:
```bash
scripts/tg-build-deployment --template 1.1 --latest-stable \
--input config.json -O > trustgraph-config.json
```
To output only the platform resources (docker-compose.yaml or resources.yaml):
```bash
# For Docker Compose
scripts/tg-build-deployment --template 1.1 --latest-stable \
--input config.json --platform docker-compose -R > docker-compose.yaml
# For Kubernetes
scripts/tg-build-deployment --template 1.1 --latest-stable \
--input config.json --platform gcp-k8s -R > resources.yaml
```
### Configuration Service API
You can also run the configurator as a REST API service:
```bash
scripts/tg-config-svc
```
This starts a web service on port 8080 that provides:
- REST API endpoints for configuration generation
- Programmatic access to version information
- Web-based configuration generation
The service provides the same functionality as the command-line tool but through HTTP endpoints (see API Service section below for details).
### Command Line Options
- `-i, --input`: Input configuration file (default: config.json)
- `-o, --output`: Output ZIP file (default: output.zip)
- `-t, --template`: Template name (e.g., "1.1", "1.0", "0.23")
- `-v, --version`: Specific version to use
- `-p, --platform`: Target platform (default: docker-compose)
- `--latest`: Use the latest available version
- `--latest-stable`: Use the latest stable version
- `-O, --output-tg-config`: Output only TrustGraph configuration to stdout (no ZIP file)
- `-R, --output-resources`: Output only platform resources (docker-compose.yaml or resources.yaml) to stdout (no ZIP file)
### Available Platforms
- `docker-compose`: Local Docker deployment using docker-compose
- `podman-compose`: Local Podman deployment using podman-compose
- `minikube-k8s`: Minikube Kubernetes cluster
- `gcp-k8s`: Google Cloud Kubernetes (GKE)
- `aks-k8s`: Azure Kubernetes Service (AKS)
- `eks-k8s`: AWS Elastic Kubernetes Service (EKS)
- `scw-k8s`: Scaleway Kubernetes
## Python Architecture
### Module Structure
The `trustgraph_configurator` package consists of several key modules:
#### Core Modules
1. **generator.py** (`Generator` class)
- Processes Jsonnet templates using the `_jsonnet` library
- Evaluates configuration snippets with custom import callbacks
- Returns processed JSON configurations
2. **packager.py** (`Packager` class)
- Main orchestrator for configuration generation
- Handles template and resource file loading
- Generates platform-specific deployment packages
- Creates ZIP archives with all necessary files
- Supports both Docker Compose and Kubernetes outputs
3. **index.py** (`Index` class)
- Manages template and platform metadata
- Reads from `templates/index.json`
- Provides version sorting and comparison
- Offers methods to get latest/stable versions
4. **api.py** (`Api` class)
- REST API service for configuration generation
- Endpoints for version information and generation
- Validates input JSON before processing
- Returns generated configurations as binary data
5. **service.py**
- Simple wrapper to run the API service
- Configures logging and starts the web server on port 8080
6. **run.py**
- Command-line interface implementation
- Argument parsing and validation
- Reads input configuration and writes output ZIP
7. **list.py**
- Command-line tool to list available configurations
- Displays platforms, templates, and versions in tabular format
### How Components Interact
```
User Input (config.json)
run.py (CLI) or api.py (REST)
Packager (orchestrator)
├─→ Index (metadata/versions)
├─→ Generator (Jsonnet processing)
└─→ Resource files (templates/)
Platform-specific generation
(Docker Compose or Kubernetes)
ZIP archive (output.zip)
```
### Key Design Patterns
1. **Template Resolution**: The `Packager.fetch()` method implements a sophisticated file resolution system:
- Special handling for `trustgraph/config.json` and `version.jsonnet`
- Fallback search paths for templates and resources
- Version-specific template directories
2. **Platform Abstraction**: Different platforms are handled through:
- Platform-specific Jsonnet templates (e.g., `config-to-docker-compose.jsonnet`)
- Conditional logic in `Packager.generate()`
- Unified output format (ZIP archives)
3. **Version Management**: The system supports:
- Multiple template versions with different features
- Stability levels (alpha, beta, stable)
- Automatic version selection (latest/latest-stable)
### Configuration Flow
1. User provides a JSON configuration file
2. Packager validates and loads the appropriate template version
3. Generator processes Jsonnet templates with the configuration
4. Platform-specific resources are added (Grafana dashboards, Prometheus config)
5. Everything is packaged into a ZIP file for deployment
### API Service
The REST API service (`tg-config-svc`) provides programmatic access to the configurator functionality. Start the service with:
```bash
scripts/tg-config-svc
```
The service runs on port 8080 and provides the following endpoints:
```
POST /api/generate/{platform}/{template} # Generate configuration
GET /api/latest # Get latest version info
GET /api/latest-stable # Get latest stable version info
GET /api/versions # List all available versions
```
#### Dialog Flow Resources
These endpoints serve the dialog flow resources described in the Configuration Process section:
```
GET /api/dialog-flow # Dialog flow state machine (YAML)
GET /api/config-prepare # JSONata transform for config preparation
GET /api/docs-manifest # Documentation manifest (YAML)
GET /api/docs/{path} # Documentation markdown fragments
```
Example usage:
```bash
# Generate configuration via API
curl -X POST http://localhost:8080/api/generate/docker-compose/1.1 \
-H "Content-Type: application/json" \
-d @config.json \
--output deployment.zip
# Fetch dialog flow configuration
curl http://localhost:8080/api/dialog-flow
# Fetch a documentation fragment
curl http://localhost:8080/api/docs/platform/docker-compose.md
```
## Output Structure
### Docker Compose Output
The generated ZIP file contains:
```
docker-compose.yaml # Main deployment file
trustgraph/config.json # TrustGraph configuration
grafana/ # Grafana dashboards and provisioning
prometheus/ # Prometheus configuration
```
### Kubernetes Output
The generated ZIP file contains:
```
resources.yaml # All Kubernetes resources in a single file
```
## Development
To extend or modify the configurator:
1. Templates are in `trustgraph_configurator/templates/<version>/`
2. Add new platforms by creating appropriate Jsonnet templates
3. Update `templates/index.json` for new versions
4. Resources (dashboards, configs) go in `trustgraph_configurator/resources/<version>/`
5. Dialog flow resources are in `trustgraph_configurator/resources/dialog/`:
- `trustgraph-flow.yaml` - Dialog flow state machine
- `trustgraph-output.jsonata` - State-to-config transform
- `trustgraph-docs.yaml` - Documentation manifest
- `docs/` - Markdown documentation fragments
## Error Handling
The configurator includes error handling for:
- Missing or invalid templates
- Malformed input JSON
- File resolution failures
- Platform-specific generation errors
Errors are logged with appropriate context for debugging.

View file

@ -0,0 +1,320 @@
# TrustGraph Configurator Test Strategy
## Executive Summary
The TrustGraph configurator's primary risk is shipping broken configurations that fail to deploy properly. Unlike application crashes (which are obvious and easily fixable), configuration errors can cause silent failures, deployment issues, or runtime problems that are difficult to debug and significantly impact user experience.
This test strategy prioritizes **configuration correctness** and **deployability** over code coverage metrics.
## Risk Assessment
### High Impact Risks
1. **Template Syntax Errors**: Jsonnet compilation failures that prevent configuration generation
2. **Invalid Configuration Structure**: Generated configs that don't match expected schemas
3. **Missing Dependencies**: Component configurations that reference non-existent services
4. **Platform-Specific Issues**: Configurations that work on one platform but fail on another
5. **Version Incompatibilities**: Template changes that break existing user configurations
### Medium Impact Risks
1. **Performance Issues**: Inefficient template processing
2. **Resource Misconfigurations**: Incorrect memory/CPU limits
3. **Security Misconfigurations**: Missing security settings or exposed secrets
### Low Impact Risks
1. **CLI Argument Parsing**: Easily debuggable and obvious failures
2. **Logging Issues**: Don't affect configuration correctness
3. **API Error Handling**: Obvious failures with clear error messages
## Testing Strategy
### 1. Configuration Validation Tests (Critical)
#### Template Compilation Tests
- **Objective**: Ensure all Jsonnet templates compile without errors
- **Scope**: All templates in all versions (0.21, 0.22, 0.23, 1.0, 1.1)
- **Implementation**:
```bash
# Test all platform configurations for each template version
for version in 0.21 0.22 0.23 1.0 1.1; do
for platform in docker-compose podman-compose minikube-k8s gcp-k8s aks-k8s eks-k8s scw-k8s; do
./scripts/tg-build-deployment --template $version --platform $platform --input test-configs/minimal.json -O > /dev/null
done
done
```
#### Schema Validation Tests
- **Objective**: Validate generated configurations against expected schemas
- **Docker Compose**: Validate against docker-compose schema
- **Kubernetes**: Validate against Kubernetes resource schemas
- **TrustGraph Config**: Validate against TrustGraph configuration schema
#### Component Integration Tests
- **Objective**: Ensure component dependencies are correctly resolved
- **Test Cases**:
- LLM + Embeddings combinations (OpenAI + HF, Ollama + Ollama, etc.)
- RAG pipelines with all storage backends
- Graph databases with different embedding stores
- OCR + document processing chains
### 2. Platform-Specific Deployment Tests (Critical)
#### Docker Compose Validation
- **Objective**: Ensure generated docker-compose.yaml files are valid and deployable
- **Test Environment**: Local Docker daemon
- **Test Process**:
```bash
# Generate configuration
./scripts/tg-build-deployment --template 1.1 --platform docker-compose --input test-config.json -R > docker-compose.yaml
# Validate syntax
docker-compose config -q
# Test deployment (dry-run)
docker-compose up --no-start
# Test actual deployment with minimal config
docker-compose up -d
timeout 60 docker-compose ps
docker-compose down
```
#### Kubernetes Validation
- **Objective**: Ensure generated Kubernetes manifests are valid and deployable
- **Test Environment**: Minikube or kind cluster
- **Test Process**:
```bash
# Generate configuration
./scripts/tg-build-deployment --template 1.1 --platform gcp-k8s --input test-config.json -R > resources.yaml
# Validate syntax
kubectl apply --dry-run=client -f resources.yaml
# Test deployment
kubectl apply -f resources.yaml
kubectl wait --for=condition=Ready pod --all --timeout=300s
kubectl delete -f resources.yaml
```
### 3. Configuration Matrix Tests (High Priority)
#### Test Configuration Profiles
Create comprehensive test configurations covering:
1. **Minimal Configuration**
```json
{
"llm": {"engine": "openai", "model": "gpt-3.5-turbo"},
"embeddings": {"engine": "hf", "model": "sentence-transformers/all-MiniLM-L6-v2"}
}
```
2. **Complex RAG Configuration**
```json
{
"llm": {"engine": "openai"},
"embeddings": {"engine": "hf"},
"vector_store": {"engine": "qdrant"},
"graph_store": {"engine": "neo4j"},
"document_store": {"engine": "cassandra"},
"rag": {"enabled": true, "chunking": "recursive"}
}
```
3. **Multi-Service Configuration**
```json
{
"llm": [
{"engine": "openai", "model": "gpt-4"},
{"engine": "ollama", "model": "llama2"}
],
"embeddings": {"engine": "fastembed"},
"vector_store": {"engine": "milvus"},
"monitoring": {"grafana": true, "prometheus": true}
}
```
4. **Cloud-Specific Configurations**
- AWS Bedrock + EKS
- Azure OpenAI + AKS
- Google Vertex AI + GKE
#### Cross-Platform Matrix
Test each configuration profile against all supported platforms:
- docker-compose
- podman-compose
- minikube-k8s
- gcp-k8s
- aks-k8s
- eks-k8s
- scw-k8s
### 4. Version Compatibility Tests (Medium Priority)
#### Backward Compatibility
- **Objective**: Ensure new template versions don't break existing configurations
- **Process**:
1. Collect real-world configuration examples
2. Test against new template versions
3. Validate that outputs remain functionally equivalent
#### Forward Compatibility
- **Objective**: Ensure template changes don't break when new features are added
- **Process**:
1. Test configurations with unknown/future parameters
2. Verify graceful degradation or clear error messages
### 5. Integration Tests (Medium Priority)
#### End-to-End Deployment Tests
- **Objective**: Verify complete deployment workflows
- **Test Cases**:
- Generate config → Deploy → Verify services start → Run basic functionality test
- Test with monitoring enabled (Grafana/Prometheus accessible)
- Test with different storage backends
#### Resource Generation Tests
- **Objective**: Verify auxiliary resources are correctly generated
- **Test Cases**:
- Grafana dashboards are valid JSON
- Prometheus config is valid YAML
- All required secrets/configmaps are created
### 6. Regression Tests (High Priority)
#### Template Change Validation
- **Objective**: Detect when template changes break existing configurations
- **Process**:
1. Maintain golden configurations for each version
2. Generate outputs before and after changes
3. Compare outputs for breaking changes
4. Flag any structural differences for manual review
#### Component Regression Tests
- **Objective**: Ensure component updates don't break integrations
- **Test Cases**:
- Component parameter changes
- New component additions
- Component deprecations
## Test Implementation Framework
### Test Infrastructure
#### Test Configuration Repository
```
tests/
├── configs/
│ ├── minimal.json
│ ├── complex-rag.json
│ ├── multi-service.json
│ └── cloud-specific/
├── golden/
│ ├── 1.1/
│ │ ├── docker-compose/
│ │ └── k8s/
│ └── 1.0/
├── schemas/
│ ├── docker-compose.json
│ ├── k8s-resources.json
│ └── trustgraph-config.json
└── scripts/
├── test-all-platforms.sh
├── validate-schemas.sh
└── deploy-test.sh
```
#### Automated Test Pipeline
```bash
#!/bin/bash
# test-all-platforms.sh
set -e
VERSIONS="0.21 0.22 0.23 1.0 1.1"
PLATFORMS="docker-compose podman-compose minikube-k8s gcp-k8s aks-k8s eks-k8s scw-k8s"
CONFIGS="minimal.json complex-rag.json multi-service.json"
echo "Testing configuration generation..."
for version in $VERSIONS; do
for platform in $PLATFORMS; do
for config in $CONFIGS; do
echo "Testing $version/$platform/$config"
# Test TrustGraph config generation
./scripts/tg-build-deployment --template $version --platform $platform \
--input tests/configs/$config -O > /tmp/tg-config.json
# Validate TrustGraph config
./tests/scripts/validate-tg-config.sh /tmp/tg-config.json
# Test resource generation
./scripts/tg-build-deployment --template $version --platform $platform \
--input tests/configs/$config -R > /tmp/resources.yaml
# Validate resources
./tests/scripts/validate-resources.sh /tmp/resources.yaml $platform
echo "✓ $version/$platform/$config passed"
done
done
done
```
### Test Execution Strategy
#### Continuous Integration
1. **Pre-commit**: Template syntax validation
2. **Pull Request**: Full configuration matrix tests
3. **Release**: Deployment tests + regression tests
#### Performance Testing
- Template processing time benchmarks
- Memory usage during large configuration generation
- Concurrent API request handling
#### Security Testing
- Validate no secrets are exposed in generated configs
- Test secret injection mechanisms
- Validate security-related component configurations
## Test Metrics and Success Criteria
### Primary Success Metrics
1. **Configuration Validity**: 100% of generated configurations must be syntactically valid
2. **Deployment Success**: 95% of generated configurations must deploy successfully
3. **Regression Prevention**: Zero breaking changes to existing configurations without explicit versioning
### Secondary Success Metrics
1. **Test Coverage**: All template versions × all platforms × all major components
2. **Performance**: Configuration generation < 10 seconds for complex configs
3. **Documentation**: All test failures must have clear error messages and remediation steps
## Maintenance and Updates
### Test Maintenance
- Update test configurations when new features are added
- Refresh golden configurations when intentional changes are made
- Review and update schemas when component APIs change
### Test Infrastructure Updates
- Add new platforms when supported
- Update validation tools when dependencies change
- Maintain test environments (Docker, Kubernetes clusters)
## Risk Mitigation
### High-Risk Changes
Any changes to the following require full test suite execution:
- Jsonnet template files
- Component definitions
- Engine implementations
- Version configuration changes
### Emergency Procedures
- Rollback plan for broken template releases
- Hotfix process for critical configuration issues
- Communication plan for notifying users of breaking changes
## Conclusion
This test strategy prioritizes what matters most: ensuring users receive working, deployable configurations. By focusing on configuration correctness over code coverage, we can prevent the high-impact failures that would significantly affect user experience while maintaining development velocity for lower-risk changes.

View file

@ -0,0 +1,45 @@
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "object-store-cassandra",
"parameters": {}
},
{
"name": "pulsar-standalone",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "vertexai",
"parameters": {
"max-output-tokens": 8192
}
}
]

View file

@ -0,0 +1,244 @@
# Garage S3-Compatible Object Storage
## Overview
Garage is a lightweight, self-hosted S3-compatible object storage system used as an alternative to Ceph for storing documents and other objects in the TrustGraph system. It provides a simpler deployment model while maintaining S3 API compatibility.
## Features
- **S3-Compatible API**: Full compatibility with AWS S3 SDK and CLI tools
- **Lightweight**: Minimal resource requirements compared to Ceph
- **Simple Deployment**: Single container with no complex dependencies
- **Distributed**: Supports multi-node clusters (though configured for single-node by default)
## Architecture
### Components
1. **Garage Daemon** (`garage`)
- Main storage service container
- Provides S3 API on port 3900
- Admin API on port 3903
- RPC communication on port 3901
2. **Init Container** (`garage-init`)
- Runs once to initialize the cluster
- Configures cluster layout
- Creates S3 access credentials
- Uses remote RPC to communicate with daemon (no shared volumes)
### Storage
Garage uses two separate volumes:
- **Metadata Volume** (`garage-meta`): Stores cluster metadata and LMDB database
- **Data Volume** (`garage-data`): Stores actual object data
## Configuration Parameters
All configuration is done via Jsonnet parameters with the `garage-` prefix:
### S3 Credentials
```jsonnet
"garage-access-key":: "GK000000000000000000000001",
"garage-secret-key":: "b171f00be9be4c32c734f4c05fe64c527a8ab5eb823b376cfa8c2531f70fc427",
```
**Format Requirements:**
- **Access Key ID**: Must start with `GK` followed by exactly 24 hex characters (0-9, a-f)
- **Secret Key**: Must be exactly 64 hex characters
**Generate Secure Credentials:**
```bash
# Generate Access Key ID (GK + 24 hex chars)
echo "GK$(openssl rand -hex 12)"
# Generate Secret Key (64 hex chars)
openssl rand -hex 32
```
### Cluster Configuration
```jsonnet
"garage-rpc-secret":: "bbba746a9e289bad64a9e7a36a4299dac8d6e0b8cc2a6c2937fe756df4492008",
"garage-admin-token":: "batts-rockhearted-unpartially",
"garage-region":: "garage",
"garage-replication-factor":: "1",
```
- **rpc-secret**: 64 hex characters for node-to-node RPC authentication
- **admin-token**: Bearer token for Admin API access
- **region**: S3 region name
- **replication-factor**: Number of data replicas (set to 1 for single-node, 3+ for production)
### Storage Volumes
```jsonnet
"garage-meta-size":: "5G",
"garage-data-size":: "100G",
```
Both values can be overridden using the `.with()` function:
```jsonnet
.with("meta-size", "10G")
.with("data-size", "500G")
```
## Integration with Librarian
The librarian component automatically connects to Garage for object storage:
```jsonnet
"--object-store-endpoint", url.object_store,
"--object-store-access-key", $["garage-access-key"],
"--object-store-secret-key", $["garage-secret-key"],
```
The object store endpoint is defined in `values/url.jsonnet`:
```jsonnet
object_store: "http://garage:3900",
```
## Testing Garage with S3 Clients
### Using AWS CLI
```bash
# Set credentials
export AWS_ACCESS_KEY_ID="GK000000000000000000000001"
export AWS_SECRET_ACCESS_KEY="b171f00be9be4c32c734f4c05fe64c527a8ab5eb823b376cfa8c2531f70fc427"
export AWS_ENDPOINT_URL="http://localhost:3900"
export AWS_DEFAULT_REGION="garage"
# Create a bucket
aws s3 mb s3://test-bucket
# Upload a file
echo "Hello from Garage!" > test.txt
aws s3 cp test.txt s3://test-bucket/
# List files
aws s3 ls s3://test-bucket/
# Download a file
aws s3 cp s3://test-bucket/test.txt downloaded.txt
# Verify
cat downloaded.txt
```
### Using s3cmd
Create configuration file `~/.s3cfg`:
```ini
[default]
access_key = GK000000000000000000000001
secret_key = b171f00be9be4c32c734f4c05fe64c527a8ab5eb823b376cfa8c2531f70fc427
host_base = localhost:3900
host_bucket = localhost:3900
use_https = False
```
Then use s3cmd:
```bash
# List buckets
s3cmd ls
# Create bucket
s3cmd mb s3://my-bucket
# Upload file
s3cmd put file.txt s3://my-bucket/
# Download file
s3cmd get s3://my-bucket/file.txt
```
## Deployment Details
### Initialization Process
The init container performs these steps:
1. **Wait for Garage daemon** - Polls `/health` endpoint until daemon is ready
2. **Get Node ID** - Queries `/v2/GetNodeInfo?node=self` via Admin API
3. **Configure Layout** - Assigns node to cluster with specified capacity via RPC
4. **Apply Layout** - Activates the cluster layout
5. **Import Credentials** - Creates S3 access key with provided credentials
6. **Grant Permissions** - Enables bucket creation for the key
All operations are **idempotent** - the init container can be restarted safely and will skip already-configured items.
### Network Architecture
- **S3 API**: Port 3900 (HTTP)
- **RPC**: Port 3901 (internal cluster communication)
- **Web UI**: Port 3902 (optional web interface)
- **Admin API**: Port 3903 (cluster management)
- **K2V API**: Port 3904 (key-value store)
### No Shared Volumes
The init container communicates with the Garage daemon entirely over the network:
- Admin API (HTTP) for status queries
- RPC (via garage CLI `-h` and `-s` flags) for cluster management
This design works across all orchestrators (Kubernetes, Docker Compose) without requiring shared volume mounts.
## Version Information
Current deployment uses **Garage v2.1.0**
- Image: `docker.io/dxflrs/garage:v2.1.0`
- Documentation: https://garagehq.deuxfleurs.fr/documentation/
## Troubleshooting
### Init Container Fails
Check logs: `podman logs <container-name>`
Common issues:
- **403 Forbidden**: Check `garage-admin-token` is correct
- **Invalid layout version**: Cluster already initialized, init will retry
- **Node ID null**: Admin API not responding, check daemon logs
### S3 Access Denied
Verify credentials format:
- Access Key ID must start with `GK` + 24 hex chars
- Secret Key must be 64 hex chars
- Credentials must match what was imported during init
### Check Garage Status
```bash
# Query cluster status
curl -H "Authorization: Bearer <admin-token>" \
http://localhost:3903/v2/GetClusterStatus
# Check health
curl http://localhost:3903/health
```
## Production Considerations
1. **Generate Secure Credentials**: Never use default credentials in production
2. **Set Replication Factor**: Use 3+ for redundancy in multi-node clusters
3. **Increase Storage Size**: Adjust `garage-data-size` based on expected usage
4. **Secure Admin Token**: Use a strong random token for `garage-admin-token`
5. **Monitor Storage**: Watch disk usage on data volume
6. **Backup Metadata**: The metadata volume contains critical cluster state
## References
- [Garage Documentation](https://garagehq.deuxfleurs.fr/documentation/)
- [Garage Admin API v2](https://garagehq.deuxfleurs.fr/api/garage-admin-v2.json)
- [Quick Start Guide](https://garagehq.deuxfleurs.fr/documentation/quick-start/)

View file

@ -0,0 +1,416 @@
# Test Specification
## Test Categories
### Unit Tests
Test Python modules in isolation:
- **Generator** - Jsonnet template processing, import callbacks
- **Packager** - Zip file creation, configuration assembly
- **API** - Template listing, version resolution
- **CLI** - Argument parsing, error handling, exit codes
### Integration Tests
Test full CLI workflow:
- Template compilation across version/platform/config matrix
- Output file generation (TrustGraph config + platform resources)
- Error propagation and reporting
### Validation Tests
Verify correctness of generated outputs:
- Syntax validation (JSON/YAML parsing)
- Schema validation (structure compliance)
- Semantic validation (cross-references, consistency)
- Regression testing (golden files)
## Test Matrix
**Dimensions:**
- Versions: 1.6, 1.7, 1.8
- Platforms: docker-compose, podman-compose, minikube-k8s, gcp-k8s, aks-k8s, eks-k8s, scw-k8s, ovh-k8s
- Configs: minimal.json, complex-rag.json, multi-service.json, cloud-aws.json
**Total combinations:** 3 versions × 8 platforms × 4 configs = 96 combinations per output type = 192 tests
## Validation Approaches
### Syntax Validation
- **JSON**: Parse with `json.loads()`, check no exceptions
- **YAML**: Parse with `yaml.safe_load()`, check no exceptions
- **Docker Compose**: Validate with `docker-compose config`
- **Kubernetes**: Validate with `kubectl apply --dry-run=client`
### Schema Validation
- **TrustGraph Config**: Define JSON schema, validate with `jsonschema`
- Required fields: services, modules, parameters
- Type checking for all configuration values
- Enum validation for fixed sets (llm providers, platforms)
- **Kubernetes**: Check required fields
- apiVersion, kind, metadata present
- metadata.name, metadata.namespace defined
- spec structure matches resource kind
- **Docker Compose**: Check required fields
- services defined with image/build
- Valid port mappings
- Valid volume definitions
### Semantic Validation
#### Kubernetes Resources
- **Label/Selector matching**: Deployment selectors match pod labels
- **Volume references**: volumeMounts reference defined volumes
- **Service targeting**: Service selectors match deployment labels
- **Port consistency**: containerPort matches service targetPort
- **ConfigMap/Secret references**: Referenced resources exist in manifest
#### Docker Compose
- **Service dependencies**: depends_on references valid services
- **Volume references**: Volume names in bind mounts are defined
- **Network references**: Networks used by services are defined
- **Port conflicts**: No duplicate host port bindings
- **Environment variable references**: ${VAR} expansions are resolvable
#### TrustGraph Config
- **Service references**: Configured services reference valid modules
- **Parameter validation**: Module parameters match schema
- **Storage consistency**: Graph/object/vector stores configured correctly
- **LLM configuration**: Valid model IDs, API configurations
### Golden File Testing
Store reference outputs for each test case:
- **Location**: `tests/golden/{version}/{platform}/{config}/`
- **Files**:
- `tg-config.json` - Reference TrustGraph configuration
- `resources.yaml` - Reference platform resources
- **Comparison**: Use `pytest-golden` for automatic diff generation
- **Updates**: Explicit flag to regenerate golden files when intentional changes occur
## Test Cases
### Compilation Tests
For each (version, platform, config) combination:
1. Run `tg-build-deployment -t {version} -p {platform} -i {config} -O`
2. Assert exit code = 0
3. Assert stdout contains valid JSON
4. Parse and validate TrustGraph config structure
5. Run `tg-build-deployment -t {version} -p {platform} -i {config} -R`
6. Assert exit code = 0
7. Assert stdout contains valid YAML
8. Parse and validate resource manifest structure
### Error Handling Tests
- **Invalid config**: Malformed JSON input → exit code 1, error to stderr
- **Missing file**: Non-existent config file → exit code 1, error to stderr
- **Invalid template**: Non-existent version → exit code 1, error to stderr
- **Invalid platform**: Non-existent platform → exit code 1, error to stderr
- **Template errors**: Jsonnet compilation errors → exit code 1, error to stderr
### CLI Interface Tests
- **Argument parsing**: Valid/invalid argument combinations
- **Help output**: `-h` flag displays usage
- **Version display**: Version flag shows package version
- **Output modes**: `-O` and `-R` flags produce correct output types
- **Default values**: Missing optional args use documented defaults
### Module Unit Tests
#### Generator
- `process(config)` - Valid jsonnet → parsed JSON
- `process(config)` - Invalid jsonnet → raises exception
- Import callback mechanism works correctly
- Template loading from package resources
#### Packager
- `write(config, output)` - Creates valid zip file
- `write_tg_config(config)` - Outputs TrustGraph config to stdout
- `write_resources(config)` - Outputs platform resources to stdout
- Template version resolution (--latest, --latest-stable)
- Platform-specific template selection
## Test Execution Methods
### Direct Function Call (Primary Method)
Most tests call the Python entry point function directly rather than invoking the subprocess:
```python
from trustgraph_configurator import run
import sys
import json
def test_basic_compilation(monkeypatch, capsys):
"""Test compilation by calling run() directly"""
# Mock sys.argv with CLI arguments
monkeypatch.setattr(sys, 'argv', [
'tg-build-deployment',
'-t', '1.8',
'-p', 'docker-compose',
'-i', 'tests/configs/minimal.json',
'-O'
])
# Call the entry point directly
run.run()
# Capture and validate output
captured = capsys.readouterr()
config = json.loads(captured.out)
assert 'services' in config
```
**Advantages:**
- **Fast**: No subprocess overhead (100x+ faster)
- **Easy stdout/stderr capture**: Use pytest's `capsys` fixture
- **Easy mocking**: Use `monkeypatch` for arguments, environment, file system
- **Better debugging**: Direct code path, breakpoints work naturally
- **Exit code testing**: Catch `SystemExit` exception to verify exit codes
```python
def test_error_handling(monkeypatch):
"""Test that errors exit with code 1"""
monkeypatch.setattr(sys, 'argv', [
'tg-build-deployment',
'-i', 'nonexistent.json'
])
with pytest.raises(SystemExit) as exc_info:
run.run()
assert exc_info.value.code == 1
```
### Subprocess Invocation (Smoke Tests)
A small number of tests (1-2) should invoke the actual CLI executable to verify installation:
```python
import subprocess
def test_cli_executable_installed():
"""Verify the installed CLI entry point works"""
result = subprocess.run(
['tg-build-deployment', '--help'],
capture_output=True,
text=True
)
assert result.returncode == 0
assert 'usage:' in result.stdout
def test_cli_version_command():
"""Verify version command works from CLI"""
result = subprocess.run(
['tg-build-deployment', '--version'],
capture_output=True,
text=True
)
assert result.returncode == 0
```
**Purpose:**
- Verify `pyproject.toml` entry point configuration is correct
- Verify CLI is accessible in PATH after installation
- End-to-end smoke test
**Limitations:**
- Slower (subprocess overhead)
- Harder to mock/patch
- Less detailed error information
### Fixture for Direct Execution
Create a reusable fixture for calling configurator:
```python
# tests/conftest.py
import pytest
import sys
from io import StringIO
@pytest.fixture
def run_configurator(monkeypatch, capsys):
"""Fixture to run configurator with given arguments"""
def _run(args):
"""
Run configurator with args list.
Returns (stdout, stderr, exit_code)
"""
from trustgraph_configurator import run
monkeypatch.setattr(sys, 'argv', ['tg-build-deployment'] + args)
exit_code = 0
try:
run.run()
except SystemExit as e:
exit_code = e.code or 0
captured = capsys.readouterr()
return captured.out, captured.err, exit_code
return _run
```
**Usage:**
```python
def test_with_fixture(run_configurator):
stdout, stderr, code = run_configurator([
'-t', '1.8',
'-p', 'docker-compose',
'-i', 'tests/configs/minimal.json',
'-O'
])
assert code == 0
assert json.loads(stdout)
```
## Test Infrastructure
### Pytest Configuration
```toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--cov=trustgraph_configurator",
"--cov-report=term-missing",
"--cov-report=html",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"validation: Output validation tests",
"slow: Slow-running tests",
]
```
### Fixtures (`tests/conftest.py`)
- `test_config_dir` - Path to tests/configs/
- `test_configs` - Dict of loaded test configurations
- `temp_output_dir` - Temporary directory for test outputs
- `run_configurator` - Function to execute configurator CLI
- `golden_dir` - Path to golden file directory for test case
### Parametrization
Use `pytest.mark.parametrize` for matrix testing:
```python
@pytest.mark.parametrize("version", ["1.6", "1.7", "1.8"])
@pytest.mark.parametrize("platform", ["docker-compose", "minikube-k8s", ...])
@pytest.mark.parametrize("config", ["minimal.json", "complex-rag.json", ...])
def test_compilation(version, platform, config, run_configurator):
...
```
### Parallel Execution
Use pytest-xdist for parallel test execution:
```bash
pytest -n auto # Use all CPU cores
```
## Test File Organization
```
tests/
├── conftest.py # Shared fixtures
├── unit/
│ ├── test_generator.py
│ ├── test_packager.py
│ ├── test_api.py
│ └── test_run.py
├── integration/
│ ├── test_compilation.py # Template compilation matrix
│ ├── test_cli.py # CLI interface tests
│ └── test_errors.py # Error handling tests
├── validation/
│ ├── test_syntax.py # Syntax validation
│ ├── test_schema.py # Schema validation
│ ├── test_semantics_k8s.py
│ ├── test_semantics_docker.py
│ └── test_semantics_tg.py
├── configs/ # Test input configs (existing)
├── schemas/ # JSON schemas for validation
│ ├── trustgraph-config.schema.json
│ ├── kubernetes-deployment.schema.json
│ └── docker-compose.schema.json
├── golden/ # Reference outputs
│ └── {version}/{platform}/{config}/
│ ├── tg-config.json
│ └── resources.yaml
└── validators/ # Validation helper modules
├── kubernetes.py
├── docker_compose.py
└── trustgraph.py
```
## Development Dependencies
Add to pyproject.toml:
```toml
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-xdist>=3.0", # Parallel execution
"pytest-cov>=4.0", # Coverage reporting
"pytest-golden>=0.2", # Golden file testing
"jsonschema>=4.0", # Schema validation
"pyyaml>=6.0", # Already in main deps
]
```
Install for development:
```bash
pip install -e .[dev]
```
## CI/CD Integration
Update `.github/workflows/pull-request.yaml`:
```yaml
- name: Install dependencies
run: |
python3 -m venv env
. env/bin/activate
pip install -e .[dev]
- name: Run tests
run: |
. env/bin/activate
pytest -n auto --cov --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
```
## Running Tests
```bash
# All tests
pytest
# Specific category
pytest tests/unit/
pytest tests/integration/
pytest -m validation
# Specific test file
pytest tests/unit/test_generator.py
# Parallel execution
pytest -n auto
# With coverage
pytest --cov=trustgraph_configurator --cov-report=html
# Update golden files
pytest --update-golden
# Verbose output
pytest -v
# Stop on first failure
pytest -x
```

View file

@ -0,0 +1,188 @@
# How TrustGraph Templates Are Structured
## Overview
The TrustGraph template system is a Jsonnet-based configuration framework that generates deployment configurations for multiple platforms (Docker Compose, Kubernetes, etc.) from a single JSON configuration file. The system uses a component-based architecture with abstraction layers for different deployment targets.
## Directory Structure
```
trustgraph_configurator/
├── packager.py # Main entry point
├── generator.py # Jsonnet processor wrapper
├── templates/
│ └── 1.3/ # Template version
│ ├── components.jsonnet # Component registry
│ ├── config-to-docker-compose.jsonnet # Docker Compose generator
│ ├── config-to-tg-configuration.jsonnet # TrustGraph config extractor
│ ├── components/ # Component definitions
│ ├── engine/ # Platform-specific engines
│ ├── util/ # Utility functions
│ ├── prompts/ # LLM prompt templates
│ └── values/ # Shared configuration values
└── resources/
└── 1.3/ # Static resource files
├── grafana/ # Grafana dashboards/configs
└── prometheus/ # Prometheus configs
```
## Core Concepts
### 1. Components
Components are the building blocks of the system. Each component:
- Defines configuration parameters with defaults
- Implements a `create` function that generates platform-specific resources
- Can compose with other components through Jsonnet's object composition (`+`)
- Lives in `components/` directory
Example component structure (simplified `ollama.jsonnet`):
```jsonnet
{
// Parameter with default value
"ollama-model":: "gemma2:9b",
// Service definition
"text-completion" +: {
create:: function(engine)
// Use engine abstraction to create resources
local container = engine.container("text-completion")
.with_image(...)
.with_command([...]);
engine.resources([container, ...])
},
// Custom parameter setter
with:: function(key, value)
self + { ["ollama-" + key]:: value }
}
```
### 2. Engines
Engines provide platform-specific implementations for resource creation. Each engine implements:
- `container()` - Create container definitions
- `service()` - Create service definitions
- `volume()` - Create volume definitions
- `resources()` - Aggregate resources for output
The engine abstraction allows components to be platform-agnostic. Available engines:
- `docker-compose.jsonnet` - Docker Compose format
- `noop.jsonnet` - No-op engine for configuration extraction only
- Kubernetes engines (various cloud providers)
### 3. Configuration Flow
```
config.json → decode → patterns → engine.create() → resources → output
```
1. **Input**: `config.json` contains a list of components to enable:
```json
[
{"name": "trustgraph-base", "parameters": {}},
{"name": "ollama", "parameters": {"model": "mixtral"}}
]
```
2. **Decode**: The `decode-config.jsonnet` utility:
- Loads each component from the registry
- Applies parameters using the `with_params` function
- Merges all components into a single "patterns" object
3. **Engine Processing**: Platform-specific files like `config-to-docker-compose.jsonnet`:
- Call each component's `create(engine)` function
- Fold results into final resource structure
- Output platform-specific configuration
### 4. Configuration Extraction
The `config-to-tg-configuration.jsonnet` file extracts the TrustGraph runtime configuration from components. This includes:
- Prompt templates
- Flow definitions
- Model token costs
- Agent tools
- MCP server configurations
This configuration is embedded into the deployment separately from the infrastructure resources.
## Processing Pipeline
### Entry Point: `packager.py`
The Packager class orchestrates the entire process:
1. **Template Selection**: Determines version and template based on user input
2. **Resource Generation**:
- For Docker Compose: Calls `config-to-docker-compose.jsonnet`
- For Kubernetes: Calls `config-to-<platform>-k8s.jsonnet`
3. **Configuration Generation**: Calls `config-to-tg-configuration.jsonnet` for runtime config
4. **Packaging**: Creates ZIP file with all generated files and static resources
### Jsonnet Processing: `generator.py`
Simple wrapper around the Jsonnet library that:
- Evaluates Jsonnet templates
- Provides custom import callback for file resolution
- Returns parsed JSON output
## Component Composition
Components can be composed in several ways:
### 1. Base Component Extension
Many components extend `trustgraph-base` which provides core services:
```jsonnet
local trustgraph = import "components/trustgraph.jsonnet";
// Inherits all trustgraph services
{} + trustgraph + myCustomizations
```
### 2. Field Merging
Components use `+:` to merge with existing fields:
```jsonnet
"text-completion" +: {
// Adds to existing text-completion definition
create:: function(engine) ...
}
```
### 3. Parameter Injection
The `with` pattern allows runtime parameter injection:
```jsonnet
with:: function(key, value)
self + { [key]:: value }
```
## Hidden Fields and Configuration
Jsonnet's `::` operator creates hidden fields that aren't included in JSON output by default. The template system uses this for:
- Default values that can be overridden
- Internal helper functions
- Configuration that needs special extraction
For example, `trustgraph-base` has a hidden `configuration::` field containing runtime config that's extracted separately by `config-to-tg-configuration.jsonnet`.
## Platform Abstraction
The engine pattern provides clean separation between:
- **Component logic** - What services/containers to create
- **Platform specifics** - How to represent them (Docker Compose YAML, K8s manifests, etc.)
This allows the same component definitions to generate configurations for multiple platforms without modification.
## Static Resources
Files in `resources/` are copied directly to the output package. These include:
- Grafana dashboard definitions
- Prometheus configuration
- Other platform-specific configs that don't need templating
## Best Practices
1. **Component Independence**: Components should be self-contained and not depend on specific ordering
2. **Parameter Namespacing**: Use prefixes (e.g., `ollama-model`) to avoid conflicts
3. **Hidden Fields for Defaults**: Use `::` for overridable defaults
4. **Engine Abstraction**: Always use engine methods rather than creating platform-specific structures directly
5. **Composition Over Inheritance**: Use Jsonnet's object composition (`+`) rather than complex inheritance hierarchies

View file

@ -0,0 +1,59 @@
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "object-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {
"text-completion-concurrency": 16,
"text-completion-rag-concurrency": 16,
"prompt-concurrency": 16,
"prompt-rag-concurrency": 16,
"kg-extraction-concurrency": 16,
"embeddings-concurrency": 16,
}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {}
},
{
"name": "vllm",
"parameters": {
"max-output-tokens": 8192
}
},
{
"name": "hosting-intel-battlemage-vllm",
"parameters": {
"model": "mistralai/Mistral-Nemo-Instruct-2407",
"cpus": "32.0",
"memory": "48G",
"hf-token": "<HFTOKEN-GOES-HERE>",
}
},
]

View file

@ -0,0 +1,14 @@
encryptionsalt: v1:vQGk98eEeYI=:v1:tHg+f1b66tEydgA9:J1RGVNI0FssyjSXVhcKU7bfBofNFTg==
config:
config-svc:artifact-name: config-svc
config-svc:artifact-repo: europe-west1-docker.pkg.dev/trustgraph-ai/config-svc
config-svc:artifact-repo-region: europe-west1
config-svc:cloud-run-region: europe-west1
config-svc:domain: app.trustgraph.ai
config-svc:environment: prod
config-svc:gcp-project: trustgraph-ai
config-svc:gcp-region: europe-west1
config-svc:hostname: config-svc.app.trustgraph.ai
config-svc:managed-zone: app
config-svc:max-scale: "1"
config-svc:min-scale: "0"

View file

@ -0,0 +1,3 @@
name: config-svc
runtime: nodejs
description: Config service

View file

@ -0,0 +1,323 @@
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
import { local } from "@pulumi/command";
import * as fs from 'fs';
const cfg = new pulumi.Config();
function get(tag : string) {
let val = cfg.get(tag);
if (!val) {
console.log("ERROR: The '" + tag + "' config is mandatory");
throw "The '" + tag + "' config is mandatory";
}
return val;
}
const imageVersion = process.env.IMAGE_VERSION;
if (!imageVersion)
throw Error("IMAGE_VERSION not defined");
const repo = get("artifact-repo");
const artifactRepoRegion = get("artifact-repo-region");
const artifactName = get("artifact-name");
const hostname = get("hostname");
const managedZone = get("managed-zone");
const project = get("gcp-project");
const region = get("gcp-region");
const cloudRunRegion = get("cloud-run-region");
const environment = get("environment");
const domain = get("domain");
const minScale = get("min-scale");
const maxScale = get("max-scale");
const provider = new gcp.Provider(
"gcp",
{
project: project,
region: region,
}
);
const artifactRepo = new gcp.artifactregistry.Repository(
"artifact-repo",
{
description: "repository for " + environment,
format: "DOCKER",
location: artifactRepoRegion,
repositoryId: artifactName,
cleanupPolicies: [
{
id: "keep-minimum-versions",
action: "KEEP",
mostRecentVersions: {
keepCount: 5,
},
}
],
},
{
provider: provider,
}
);
const localImageName = "localhost/config-svc:" + imageVersion;
const imageName = repo + "/config-svc:" + imageVersion;
const taggedImage = new local.Command(
"podman-tag-command",
{
create: "podman tag " + localImageName + " " + imageName,
}
);
const image = new local.Command(
"podman-push-command",
{
create: "podman push " + imageName,
},
{
dependsOn: [taggedImage, artifactRepo],
}
);
const svcAccount = new gcp.serviceaccount.Account(
"service-account",
{
accountId: "config-svc-" + environment,
displayName: "Config service",
description: "Config service",
},
{
provider: provider,
}
);
const service = new gcp.cloudrun.Service(
"service",
{
name: "config-svc-" + environment,
location: cloudRunRegion,
template: {
metadata: {
labels: {
version: "v" + imageVersion.replace(/\./g, "-"),
},
annotations: {
// Scale attributes
"autoscaling.knative.dev/minScale": minScale,
"autoscaling.knative.dev/maxScale": maxScale,
// 2nd generation. Need to specify at least 512MB RAM.
// Going back to gen1 because faster cold starts
"run.googleapis.com/execution-environment": "gen1",
}
},
spec: {
containerConcurrency: 100,
timeoutSeconds: 300,
serviceAccountName: svcAccount.email,
containers: [
{
image: imageName,
ports: [
{
"name": "http1", // Must be http1 or h2c.
"containerPort": 8080,
}
],
resources: {
limits: {
cpu: "1000m",
memory: "512Mi",
}
},
}
],
},
},
},
{
provider: provider,
dependsOn: [image],
}
);
const allUsersPolicy = gcp.organizations.getIAMPolicy(
{
bindings: [{
role: "roles/run.invoker",
members: ["allUsers"],
}],
},
{
provider: provider,
}
);
const noAuthPolicy = new gcp.cloudrun.IamPolicy(
"no-auth-policy",
{
location: service.location,
project: service.project,
service: service.name,
policyData: allUsersPolicy.then(pol => pol.policyData),
},
{
provider: provider,
}
);
////////////////////////////////////////////////////////////////////////////
const domainMapping = new gcp.cloudrun.DomainMapping(
"domain-mapping",
{
name: hostname,
location: cloudRunRegion,
metadata: {
namespace: project,
},
spec: {
routeName: service.name,
}
},
{
provider: provider
}
);
const zone = gcp.dns.getManagedZoneOutput(
{
name: managedZone,
},
{
provider: provider,
}
);
////////////////////////////////////////////////////////////////////////////
domainMapping.statuses.apply(
ss => ss[0].resourceRecords
).apply(
rrs => {
if (rrs) {
let mapping : { [k : string] : string[] } = {};
for(var i = 0; i < rrs.length; i++) {
if (rrs[i].rrdata) {
const rr = rrs[i].rrdata;
const tp = rrs[i].type;
if (!rr || !tp) continue;
if (mapping[tp])
mapping[tp].push(rr);
else
mapping[tp] = [rr];
}
}
for (let tp in mapping) {
const recordSet = new gcp.dns.RecordSet(
"resource-record-" + tp,
{
name: hostname + ".",
managedZone: zone.name,
type: tp,
ttl: 300,
rrdatas: mapping[tp],
},
{
provider: provider,
}
);
}
}
}
);
////////////////////////////////////////////////////////////////////////////
const serviceMon = new gcp.monitoring.GenericService(
"service-monitoring",
{
basicService: {
serviceLabels: {
service_name: service.name,
location: cloudRunRegion,
},
serviceType: "CLOUD_RUN",
},
displayName: "Config service (" + environment + ")",
serviceId: "config-service-" + environment + "-mon",
userLabels: {
"service": service.name,
"application": "config-svc",
"environment": environment,
},
},
{
provider: provider,
}
);
const latencySlo = new gcp.monitoring.Slo(
"latency-slo",
{
service: serviceMon.serviceId,
sloId: "config-service-" + environment + "-latency-slo",
displayName: "Config service latency (" + environment + ")",
goal: 0.95,
rollingPeriodDays: 5,
basicSli: {
latency: {
threshold: "2s"
}
},
},
{
provider: provider,
}
);
const availabilitySlo = new gcp.monitoring.Slo(
"availability-slo",
{
service: serviceMon.serviceId,
sloId: "config-service-" + environment + "-availability-slo",
displayName: "Config service availability (" + environment + ")",
goal: 0.95,
rollingPeriodDays: 5,
windowsBasedSli: {
windowPeriod: "3600s",
goodTotalRatioThreshold: {
basicSliPerformance: {
availability: {
}
},
threshold: 0.9,
}
}
},
{
provider: provider,
}
);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
{
"dependencies": {
"@pulumi/command": "^1.1.3",
"@pulumi/gcp": "^9.10.0",
"@pulumi/pulumi": "^3.216.0"
}
}

View file

@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}

View file

@ -0,0 +1,68 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "trustgraph-configurator"
dynamic = ["version"]
authors = [
{name = "trustgraph.ai", email = "security@trustgraph.ai"},
]
description = "Configuration creator for trustgraph.ai"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "Apache-2.0"}
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
]
dependencies = [
"aiohttp",
"gojsonnet",
"pyyaml",
"tabulate",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-xdist>=3.0",
"pytest-cov>=4.0",
"jsonschema>=4.0",
]
[project.urls]
Homepage = "https://github.com/trustgraph-ai/trustgraph-configurator"
[project.scripts]
tg-build-deployment = "trustgraph_configurator:run"
tg-config-svc = "trustgraph_configurator:run_service"
tg-show-config-params = "trustgraph_configurator:list"
[tool.setuptools.packages.find]
include = ["trustgraph_configurator*"]
[tool.setuptools.package-data]
trustgraph_configurator = ["templates/**", "resources/**"]
[tool.setuptools.dynamic]
version = {attr = "trustgraph_configurator.__version__"}
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--tb=short",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"validation: Output validation tests",
"slow: Slow-running tests",
]

View file

@ -0,0 +1,493 @@
#!/usr/bin/env python3
"""
Test harness for dialog flow - walks through selecting all default options,
produces the resulting state object, and runs the JSONata transform.
Supports a test matrix mode where each field is tested with all its options
while other fields use defaults.
"""
import yaml
import json
import argparse
import zipfile
import io
from pathlib import Path
import jsonata
import requests
RESOURCES_DIR = Path(__file__).parent / "trustgraph_configurator/resources/dialog"
def load_flow():
"""Load the dialog flow YAML file."""
with open(RESOURCES_DIR / "trustgraph-flow.yaml") as f:
return yaml.safe_load(f)
def load_jsonata_transform():
"""Load the JSONata transform file."""
with open(RESOURCES_DIR / "trustgraph-output.jsonata") as f:
return f.read()
def run_transform(state, transform_expr):
"""Run the JSONata transform on the state object."""
expr = jsonata.Jsonata(transform_expr)
return expr.evaluate(state)
def call_config_service(config):
"""
Call the configuration service to generate a deployment package.
Returns (success, message, zip_contents) tuple.
The API expects just the templates array, not the full config object.
"""
url = config["api_url"]
templates = config["templates"]
try:
response = requests.post(
url,
json=templates,
headers={"Content-Type": "application/json"},
timeout=30
)
if response.status_code != 200:
return False, f"HTTP {response.status_code}: {response.text[:200]}", None
# Verify it's a valid ZIP
try:
zip_data = io.BytesIO(response.content)
with zipfile.ZipFile(zip_data, 'r') as zf:
file_list = zf.namelist()
return True, f"ZIP with {len(file_list)} files", file_list
except zipfile.BadZipFile:
return False, "Response is not a valid ZIP file", None
except requests.exceptions.ConnectionError:
return False, "Connection refused - is the service running?", None
except requests.exceptions.Timeout:
return False, "Request timed out", None
except Exception as e:
return False, f"Error: {e}", None
def get_all_options(step):
"""Get all possible values for a step's input."""
input_def = step.get("input", {})
input_type = input_def.get("type")
if input_type == "select":
return [opt["value"] for opt in input_def.get("options", [])]
elif input_type == "toggle":
return [True, False]
elif input_type == "number":
# For numbers, just test default, min, and max
default = input_def.get("default")
min_val = input_def.get("min")
max_val = input_def.get("max")
values = []
if min_val is not None:
values.append(min_val)
if default is not None and default not in values:
values.append(default)
if max_val is not None and max_val not in values:
values.append(max_val)
return values
return []
def get_default_value(step):
"""Get the default value for a step's input."""
input_def = step.get("input", {})
input_type = input_def.get("type")
if input_type == "select":
options = input_def.get("options", [])
# Find recommended option, or use first
for opt in options:
if opt.get("recommended"):
return opt["value"]
return options[0]["value"] if options else None
elif input_type == "number":
return input_def.get("default")
elif input_type == "toggle":
return input_def.get("default", False)
return None
def evaluate_condition(condition, state):
"""
Evaluate a simple condition against the state.
Supports: "key = value", "key = true/false", "key < 'version'"
"""
if not condition:
return True
# Handle equality: "ocr.enabled = true"
if " = " in condition:
key, value = condition.split(" = ", 1)
key = key.strip()
value = value.strip()
# Get nested key
state_value = state.get(key)
# Parse value
if value == "true":
return state_value is True
elif value == "false":
return state_value is False
else:
return str(state_value) == value
# Handle less-than for version comparisons: "version < '1.6.0'"
if " < " in condition:
key, value = condition.split(" < ", 1)
key = key.strip()
value = value.strip().strip("'\"")
state_value = state.get(key, "")
return str(state_value) < value
return False
def get_next_step(step, state):
"""Determine the next step based on transitions and current state."""
transitions = step.get("transitions", [])
for trans in transitions:
when = trans.get("when")
if when:
if evaluate_condition(when, state):
return trans.get("next")
else:
# Unconditional transition
return trans.get("next")
return None # Terminal state
def walk_flow(flow_data, overrides=None, verbose=True):
"""
Walk through the flow, return the state object.
Args:
flow_data: The parsed dialog flow YAML
overrides: Dict of {state_key: value} to override defaults
verbose: Whether to print progress
"""
state = {}
overrides = overrides or {}
steps = flow_data.get("steps", {})
current = flow_data.get("flow", {}).get("start")
visited_steps = []
if verbose:
print(f"Starting at: {current}")
print("-" * 60)
while current:
step = steps.get(current)
if not step:
if verbose:
print(f"ERROR: Step '{current}' not found!")
break
visited_steps.append(current)
title = step.get("title", current)
state_key = step.get("state_key")
# Get value - use override if present, otherwise default
if state_key:
if state_key in overrides:
value = overrides[state_key]
else:
value = get_default_value(step)
state[state_key] = value
if verbose:
is_override = state_key in overrides
marker = " [OVERRIDE]" if is_override else ""
print(f"Step: {current}")
print(f" Title: {title}")
print(f" State key: {state_key} = {value}{marker}")
else:
if verbose:
print(f"Step: {current}")
print(f" Title: {title}")
print(f" (no state key - review/terminal step)")
# Get next step
next_step = get_next_step(step, state)
if verbose:
if next_step:
print(f" -> Next: {next_step}")
else:
print(f" -> Terminal state")
print()
current = next_step
return state, visited_steps
def collect_fields_for_path(flow_data, overrides=None):
"""
Collect all fields and their possible values for a given path.
Returns a list of (step_name, state_key, options, default_value) tuples.
"""
fields = []
steps = flow_data.get("steps", {})
# Walk with given overrides to find the path
_, visited = walk_flow(flow_data, overrides=overrides, verbose=False)
for step_name in visited:
step = steps.get(step_name, {})
state_key = step.get("state_key")
if state_key:
options = get_all_options(step)
default = get_default_value(step)
if len(options) > 1: # Only include fields with choices
fields.append((step_name, state_key, options, default))
return fields
def collect_all_fields(flow_data):
"""
Collect all fields from the baseline (default) path.
"""
return collect_fields_for_path(flow_data, overrides=None)
def run_single_test(flow_data, transform_expr, overrides, description, test_num, results, call_api=False):
"""Run a single test case and record the result."""
print("-" * 70)
print(f"Test {test_num}: {description}")
print("-" * 70)
state, _ = walk_flow(flow_data, overrides=overrides, verbose=False)
try:
config = run_transform(state, transform_expr)
result = {
"test": test_num,
"description": description,
"overrides": overrides,
"state": state,
"config": config
}
print(f"State: {json.dumps(state, indent=2)}")
print(f"Templates: {[t['name'] for t in config['templates']]}")
# Optionally call the configuration service
if call_api:
success, message, files = call_config_service(config)
result["api_success"] = success
result["api_message"] = message
if files:
result["api_files"] = files
if success:
print(f"API: OK - {message}")
else:
print(f"API: FAILED - {message}")
result["error"] = f"API: {message}"
results.append(result)
except Exception as e:
results.append({
"test": test_num,
"description": description,
"overrides": overrides,
"state": state,
"error": str(e)
})
print(f"State: {json.dumps(state, indent=2)}")
print(f"ERROR: {e}")
print()
return test_num + 1
def run_test_matrix(flow_data, transform_expr, call_api=False):
"""
Run the test matrix - for each field, try all values while others use defaults.
When a toggle enables conditional fields, also test all options of those fields.
"""
baseline_fields = collect_all_fields(flow_data)
baseline_keys = {f[1] for f in baseline_fields}
print("=" * 70)
print("TEST MATRIX" + (" (with API validation)" if call_api else ""))
print("=" * 70)
print()
print(f"Found {len(baseline_fields)} fields with multiple options on baseline path:")
for step_name, state_key, options, default in baseline_fields:
print(f" - {state_key}: {len(options)} options (default: {default})")
print()
results = []
test_num = 1
# First, run the baseline (all defaults)
test_num = run_single_test(
flow_data, transform_expr,
overrides={},
description="BASELINE (all defaults)",
test_num=test_num,
results=results,
call_api=call_api
)
# For each field, try each non-default value
for step_name, state_key, options, default in baseline_fields:
for option in options:
if option == default:
continue # Skip default, already tested in baseline
overrides = {state_key: option}
test_num = run_single_test(
flow_data, transform_expr,
overrides=overrides,
description=f"{state_key} = {option}",
test_num=test_num,
results=results,
call_api=call_api
)
# Check if this override unlocks new fields (conditional paths)
unlocked_fields = collect_fields_for_path(flow_data, overrides=overrides)
unlocked_keys = {f[1] for f in unlocked_fields}
new_keys = unlocked_keys - baseline_keys
if new_keys:
# Test all non-default options of the newly unlocked fields
for uf_step, uf_key, uf_options, uf_default in unlocked_fields:
if uf_key not in new_keys:
continue
for uf_option in uf_options:
if uf_option == uf_default:
continue # Default already tested above
combined_overrides = {state_key: option, uf_key: uf_option}
test_num = run_single_test(
flow_data, transform_expr,
overrides=combined_overrides,
description=f"{state_key} = {option}, {uf_key} = {uf_option}",
test_num=test_num,
results=results,
call_api=call_api
)
return results
def main():
parser = argparse.ArgumentParser(
description="Test harness for dialog flow configuration"
)
parser.add_argument(
"--matrix", "-m",
action="store_true",
help="Run test matrix (each field with all options)"
)
parser.add_argument(
"--api", "-a",
action="store_true",
help="Call the configuration service API to validate each config"
)
parser.add_argument(
"--summary", "-s",
action="store_true",
help="Show summary only (with --matrix)"
)
args = parser.parse_args()
flow_data = load_flow()
transform_expr = load_jsonata_transform()
if args.matrix:
results = run_test_matrix(flow_data, transform_expr, call_api=args.api)
# Summary
print("=" * 70)
print("SUMMARY")
print("=" * 70)
print()
passed = [r for r in results if "error" not in r]
failed = [r for r in results if "error" in r]
print(f"Total tests: {len(results)}")
print(f"Passed: {len(passed)}")
print(f"Failed: {len(failed)}")
if args.api:
api_ok = [r for r in results if r.get("api_success")]
api_fail = [r for r in results if "api_success" in r and not r["api_success"]]
print(f"API OK: {len(api_ok)}")
print(f"API Failed: {len(api_fail)}")
if failed:
print()
print("Failed tests:")
for r in failed:
print(f" - Test {r['test']}: {r['description']}")
print(f" Error: {r['error']}")
else:
print("=" * 60)
print("Dialog Flow Test Harness - Default Options")
print("=" * 60)
print()
state, _ = walk_flow(flow_data)
print("=" * 60)
print("Final State Object:")
print("=" * 60)
print()
print(json.dumps(state, indent=2))
# Run JSONata transform
print()
print("=" * 60)
print("Running JSONata Transform...")
print("=" * 60)
print()
config = run_transform(state, transform_expr)
print("=" * 60)
print("Configuration Object (output of transform):")
print("=" * 60)
print()
print(json.dumps(config, indent=2))
# Optionally call the API
if args.api:
print()
print("=" * 60)
print("Calling Configuration Service...")
print("=" * 60)
print()
success, message, files = call_config_service(config)
if success:
print(f"OK: {message}")
print(f"Files: {files}")
else:
print(f"FAILED: {message}")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,416 @@
#!/usr/bin/env python3
"""
Test harness for documentation assembly - verifies that for each configuration
state, the documentation can be assembled from the manifest and markdown fragments.
"""
import yaml
import json
import argparse
import re
from pathlib import Path
import jsonata
RESOURCES_DIR = Path(__file__).parent / "trustgraph_configurator/resources/dialog"
def load_flow():
"""Load the dialog flow YAML file."""
with open(RESOURCES_DIR / "trustgraph-flow.yaml") as f:
return yaml.safe_load(f)
def load_docs_manifest():
"""Load the documentation manifest YAML file."""
with open(RESOURCES_DIR / "trustgraph-docs.yaml") as f:
return yaml.safe_load(f)
def load_doc_file(filename):
"""Load a documentation markdown file."""
path = RESOURCES_DIR / "docs" / filename
if path.exists():
return path.read_text()
return None
def get_default_value(step):
"""Get the default value for a step's input."""
input_def = step.get("input", {})
input_type = input_def.get("type")
if input_type == "select":
options = input_def.get("options", [])
for opt in options:
if opt.get("recommended"):
return opt["value"]
return options[0]["value"] if options else None
elif input_type == "number":
return input_def.get("default")
elif input_type == "toggle":
return input_def.get("default", False)
return None
def get_all_options(step):
"""Get all possible values for a step's input."""
input_def = step.get("input", {})
input_type = input_def.get("type")
if input_type == "select":
return [opt["value"] for opt in input_def.get("options", [])]
elif input_type == "toggle":
return [True, False]
elif input_type == "number":
default = input_def.get("default")
min_val = input_def.get("min")
max_val = input_def.get("max")
values = []
if min_val is not None:
values.append(min_val)
if default is not None and default not in values:
values.append(default)
if max_val is not None and max_val not in values:
values.append(max_val)
return values
return []
def evaluate_condition(condition, state):
"""Evaluate a simple condition against the state."""
if not condition:
return True
if " = " in condition:
key, value = condition.split(" = ", 1)
key = key.strip()
value = value.strip()
state_value = state.get(key)
if value == "true":
return state_value is True
elif value == "false":
return state_value is False
else:
return str(state_value) == value
if " < " in condition:
key, value = condition.split(" < ", 1)
key = key.strip()
value = value.strip().strip("'\"")
state_value = state.get(key, "")
return str(state_value) < value
return False
def get_next_step(step, state):
"""Determine the next step based on transitions and current state."""
transitions = step.get("transitions", [])
for trans in transitions:
when = trans.get("when")
if when:
if evaluate_condition(when, state):
return trans.get("next")
else:
return trans.get("next")
return None
def walk_flow(flow_data, overrides=None):
"""Walk through the flow, return the state object."""
state = {}
overrides = overrides or {}
steps = flow_data.get("steps", {})
current = flow_data.get("flow", {}).get("start")
visited_steps = []
while current:
step = steps.get(current)
if not step:
break
visited_steps.append(current)
state_key = step.get("state_key")
if state_key:
if state_key in overrides:
value = overrides[state_key]
else:
value = get_default_value(step)
state[state_key] = value
current = get_next_step(step, state)
return state, visited_steps
def evaluate_when_condition(when_expr, state):
"""
Evaluate a 'when' condition from the docs manifest against the state.
Supports: equality, 'in' arrays, 'and' conditions.
"""
if not when_expr:
return False
# Use jsonata for complex expressions
try:
expr = jsonata.Jsonata(when_expr)
result = expr.evaluate(state)
return bool(result)
except Exception as e:
# Fallback to simple parsing for basic cases
return evaluate_simple_when(when_expr, state)
def evaluate_simple_when(when_expr, state):
"""Simple fallback parser for when expressions."""
# Handle "and" conditions
if " and " in when_expr:
parts = when_expr.split(" and ")
return all(evaluate_simple_when(p.strip(), state) for p in parts)
# Handle "in" conditions: "platform in ['docker-compose', 'podman-compose']"
in_match = re.match(r"(\w+)\s+in\s+\[([^\]]+)\]", when_expr)
if in_match:
key = in_match.group(1)
values_str = in_match.group(2)
values = [v.strip().strip("'\"") for v in values_str.split(",")]
return state.get(key) in values
# Handle equality: "platform = 'docker-compose'"
eq_match = re.match(r"(\w+)\s*=\s*['\"]([^'\"]+)['\"]", when_expr)
if eq_match:
key = eq_match.group(1)
value = eq_match.group(2)
return state.get(key) == value
return False
def assemble_docs(state, manifest):
"""
Assemble documentation for a given state.
Returns (success, matched_instructions, errors).
"""
instructions = manifest.get("documentation", {}).get("instructions", [])
matched = []
errors = []
for instr in instructions:
# Check if instruction applies
always = instr.get("always", False)
when = instr.get("when")
applies = always or (when and evaluate_when_condition(when, state))
if applies:
file_path = instr.get("file")
content = load_doc_file(file_path)
if content is None:
errors.append(f"Missing file: {file_path}")
matched.append({
"id": instr.get("id"),
"goal": instr.get("goal"),
"file": file_path,
"found": False
})
else:
matched.append({
"id": instr.get("id"),
"goal": instr.get("goal"),
"file": file_path,
"found": True,
"content_length": len(content)
})
success = len(errors) == 0 and len(matched) > 0
return success, matched, errors
def collect_fields_for_path(flow_data, overrides=None):
"""Collect all fields and their possible values for a given path."""
fields = []
steps = flow_data.get("steps", {})
_, visited = walk_flow(flow_data, overrides=overrides)
for step_name in visited:
step = steps.get(step_name, {})
state_key = step.get("state_key")
if state_key:
options = get_all_options(step)
default = get_default_value(step)
if len(options) > 1:
fields.append((step_name, state_key, options, default))
return fields
def run_single_test(flow_data, manifest, overrides, description, test_num, results):
"""Run a single documentation assembly test."""
print("-" * 70)
print(f"Test {test_num}: {description}")
print("-" * 70)
state, _ = walk_flow(flow_data, overrides=overrides)
success, matched, errors = assemble_docs(state, manifest)
result = {
"test": test_num,
"description": description,
"overrides": overrides,
"state": state,
"matched_count": len(matched),
"matched": matched,
"success": success
}
if errors:
result["errors"] = errors
results.append(result)
print(f"State: {json.dumps({k: v for k, v in state.items() if not k.startswith('ocr') and not k.startswith('embed')}, indent=2)}")
print(f"Matched instructions: {len(matched)}")
for m in matched:
status = "OK" if m["found"] else "MISSING"
print(f" - [{status}] {m['goal']} ({m['file']})")
if errors:
print(f"ERRORS: {errors}")
else:
print("Docs: OK")
print()
return test_num + 1
def run_test_matrix(flow_data, manifest):
"""Run the documentation test matrix."""
baseline_fields = collect_fields_for_path(flow_data)
baseline_keys = {f[1] for f in baseline_fields}
print("=" * 70)
print("DOCUMENTATION TEST MATRIX")
print("=" * 70)
print()
print(f"Found {len(baseline_fields)} fields with multiple options on baseline path:")
for step_name, state_key, options, default in baseline_fields:
print(f" - {state_key}: {len(options)} options (default: {default})")
print()
results = []
test_num = 1
# Baseline test
test_num = run_single_test(
flow_data, manifest,
overrides={},
description="BASELINE (all defaults)",
test_num=test_num,
results=results
)
# Test each field variation
for step_name, state_key, options, default in baseline_fields:
for option in options:
if option == default:
continue
overrides = {state_key: option}
test_num = run_single_test(
flow_data, manifest,
overrides=overrides,
description=f"{state_key} = {option}",
test_num=test_num,
results=results
)
# Check for unlocked fields
unlocked_fields = collect_fields_for_path(flow_data, overrides=overrides)
unlocked_keys = {f[1] for f in unlocked_fields}
new_keys = unlocked_keys - baseline_keys
if new_keys:
for uf_step, uf_key, uf_options, uf_default in unlocked_fields:
if uf_key not in new_keys:
continue
for uf_option in uf_options:
if uf_option == uf_default:
continue
combined_overrides = {state_key: option, uf_key: uf_option}
test_num = run_single_test(
flow_data, manifest,
overrides=combined_overrides,
description=f"{state_key} = {option}, {uf_key} = {uf_option}",
test_num=test_num,
results=results
)
return results
def main():
parser = argparse.ArgumentParser(
description="Test harness for documentation assembly"
)
parser.add_argument(
"--matrix", "-m",
action="store_true",
help="Run test matrix (each field with all options)"
)
args = parser.parse_args()
flow_data = load_flow()
manifest = load_docs_manifest()
if args.matrix:
results = run_test_matrix(flow_data, manifest)
# Summary
print("=" * 70)
print("SUMMARY")
print("=" * 70)
print()
passed = [r for r in results if r["success"]]
failed = [r for r in results if not r["success"]]
print(f"Total tests: {len(results)}")
print(f"Passed: {len(passed)}")
print(f"Failed: {len(failed)}")
if failed:
print()
print("Failed tests:")
for r in failed:
print(f" - Test {r['test']}: {r['description']}")
if "errors" in r:
for err in r["errors"]:
print(f" Error: {err}")
else:
# Single test with defaults
print("=" * 60)
print("Documentation Assembly Test - Default Options")
print("=" * 60)
print()
state, _ = walk_flow(flow_data)
success, matched, errors = assemble_docs(state, manifest)
print(f"State: {json.dumps(state, indent=2)}")
print()
print(f"Matched {len(matched)} instructions:")
for m in matched:
status = "OK" if m["found"] else "MISSING"
print(f" [{status}] {m['goal']}")
print(f" File: {m['file']}")
print()
if errors:
print(f"Errors: {errors}")
else:
print("All documentation files found!")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,189 @@
# TrustGraph Configurator Test Suite
Comprehensive pytest-based test suite for trustgraph-configurator.
## Installation
Install with development dependencies:
```bash
pip install -e .[dev]
```
## Running Tests
```bash
# All tests
pytest
# Specific category
pytest tests/unit/
pytest tests/integration/
pytest tests/validation/
# By marker
pytest -m unit
pytest -m integration
pytest -m validation
# Specific test file
pytest tests/unit/test_generator.py
# Parallel execution (faster)
pytest -n auto
# Verbose output
pytest -v
# Stop on first failure
pytest -x
# With coverage
pytest --cov=trustgraph_configurator --cov-report=html
```
## Test Structure
```
tests/
├── conftest.py # Shared fixtures
├── unit/ # Unit tests for Python modules
│ ├── test_generator.py
│ ├── test_packager.py
│ ├── test_api.py
│ └── test_run.py
├── integration/ # Full workflow tests
│ ├── test_compilation.py # Template compilation matrix
│ ├── test_cli.py # CLI interface tests
│ └── test_errors.py # Error handling tests
├── validation/ # Output validation tests
│ ├── test_syntax.py # Syntax validation
│ ├── test_schema.py # Schema validation
│ ├── test_semantics_k8s.py
│ ├── test_semantics_docker.py
│ └── test_semantics_tg.py
├── validators/ # Validation helper modules
│ ├── kubernetes.py
│ ├── docker_compose.py
│ └── trustgraph.py
├── schemas/ # JSON schemas
│ ├── trustgraph-config.schema.json
│ ├── kubernetes-resource.schema.json
│ └── docker-compose.schema.json
└── configs/ # Test input configs
├── minimal.json
├── complex-rag.json
├── multi-service.json
└── cloud-aws.json
```
## Test Categories
### Unit Tests (`tests/unit/`)
Test individual Python modules in isolation:
- Generator: Jsonnet template processing
- Packager: Configuration assembly and zip creation
- API: Template listing and version resolution
- Run: CLI entry point and argument parsing
### Integration Tests (`tests/integration/`)
Test full workflow end-to-end:
- **Compilation**: Template compilation across all version/platform/config combinations (192 tests)
- **CLI**: Command line interface functionality
- **Errors**: Error handling and reporting
### Validation Tests (`tests/validation/`)
Verify correctness of generated outputs:
- **Syntax**: JSON/YAML parsing validation
- **Schema**: JSON Schema compliance
- **Semantics**: Cross-references, consistency checks
## Test Matrix
Integration tests cover:
- **Versions**: 1.6, 1.7, 1.8
- **Platforms**: docker-compose, podman-compose, minikube-k8s, gcp-k8s, aks-k8s, eks-k8s, scw-k8s, ovh-k8s
- **Configs**: minimal, complex-rag, multi-service, cloud-aws
Total: 3 versions × 8 platforms × 4 configs × 2 outputs = 192 test combinations
## Validation Layers
### Syntax Validation
- JSON parsing with `json.loads()`
- YAML parsing with `yaml.safe_load()`
### Schema Validation
- TrustGraph config against `trustgraph-config.schema.json`
- Docker Compose against `docker-compose.schema.json`
- Kubernetes resources against `kubernetes-resource.schema.json`
### Semantic Validation
**Kubernetes:**
- Deployment selectors match pod labels
- Service selectors match deployment labels
- volumeMounts reference defined volumes
- ConfigMap/Secret references exist
- Service targetPorts match container ports
**Docker Compose:**
- depends_on references valid services
- Volume names are defined
- Network references are valid
- No port conflicts
**TrustGraph Config:**
- Service references are valid
- Parameter types are reasonable
- Storage backends are consistent
- LLM configuration is present
## Fixtures
Available in `conftest.py`:
- `test_config_dir`: Path to test configs
- `test_configs`: Loaded test configurations
- `temp_output_dir`: Temporary directory for outputs
- `run_configurator`: Function to execute configurator
- `mock_config_file`: Create temporary config files
## CI/CD
Tests run automatically on pull requests via GitHub Actions.
See `.github/workflows/pull-request.yaml` for CI configuration.
## Development
### Adding New Tests
1. Create test file in appropriate directory
2. Use appropriate markers (`@pytest.mark.unit`, etc.)
3. Use fixtures from `conftest.py`
4. Follow naming convention: `test_*.py`, `test_*()` functions
### Adding New Validation
1. Add validation logic to `tests/validators/`
2. Create corresponding tests in `tests/validation/`
3. Update schemas in `tests/schemas/` if needed
## Troubleshooting
**Test failures:**
- Check stderr output for error messages
- Run with `-v` for verbose output
- Run with `--tb=long` for full tracebacks
**Import errors:**
- Ensure package is installed: `pip install -e .[dev]`
- Check PYTHONPATH includes project root
**Slow tests:**
- Use `-n auto` for parallel execution
- Run specific test subsets instead of full suite
## Documentation
See `docs/tech-specs/tests.md` for detailed test specification.

View file

@ -0,0 +1,26 @@
[
{
"name": "bedrock",
"parameters": {
"model": "anthropic.claude-3-sonnet-20240229-v1:0"
}
},
{
"name": "embeddings-hf",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
}
]

View file

@ -0,0 +1,42 @@
[
{
"name": "openai",
"parameters": {
"model": "gpt-4",
"temperature": 0.7
}
},
{
"name": "embeddings-hf",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "triple-store-neo4j",
"parameters": {}
},
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "trustgraph-base",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
}
]

View file

@ -0,0 +1,23 @@
[
{
"name": "openai",
"parameters": {
"model": "gpt-3.5-turbo",
"temperature": 0.7
}
},
{
"name": "embeddings-hf",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "trustgraph-base",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
}
]

View file

@ -0,0 +1,38 @@
[
{
"name": "ollama",
"parameters": {
"model": "llama2"
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "BAAI/bge-small-en-v1.5"
}
},
{
"name": "vector-store-milvus",
"parameters": {}
},
{
"name": "triple-store-memgraph",
"parameters": {}
},
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
}
]

View file

@ -0,0 +1,125 @@
"""
Pytest configuration and shared fixtures for trustgraph-configurator tests.
"""
import pytest
# =============================================================================
# Version Configuration - Update these when adding new template versions
# =============================================================================
TESTED_VERSIONS = ["1.8", "1.9", "2.0"]
PRIMARY_VERSION = "1.9" # Used when only one version is tested
import sys
import json
import tempfile
import shutil
from pathlib import Path
@pytest.fixture(scope="session")
def test_config_dir():
"""Path to the test configurations directory."""
return Path(__file__).parent / "configs"
@pytest.fixture(scope="session")
def test_configs(test_config_dir):
"""Dictionary of loaded test configurations."""
configs = {}
for config_file in test_config_dir.glob("*.json"):
with open(config_file) as f:
configs[config_file.name] = json.load(f)
return configs
@pytest.fixture
def temp_output_dir():
"""Temporary directory for test outputs."""
temp_dir = tempfile.mkdtemp()
yield Path(temp_dir)
shutil.rmtree(temp_dir)
@pytest.fixture
def run_configurator(monkeypatch, capsys):
"""
Fixture to run configurator with given arguments.
Usage:
stdout, stderr, exit_code = run_configurator(['-t', '1.8', '-p', 'docker-compose', ...])
Returns:
tuple: (stdout, stderr, exit_code)
"""
def _run(args):
from trustgraph_configurator import run
# Set sys.argv with the command and arguments
monkeypatch.setattr(sys, 'argv', ['tg-build-deployment'] + args)
exit_code = 0
try:
run() # run is already the function, not a module
except SystemExit as e:
exit_code = e.code if e.code is not None else 0
# Capture output
captured = capsys.readouterr()
return captured.out, captured.err, exit_code
return _run
@pytest.fixture(scope="session")
def golden_dir():
"""Path to the golden files directory."""
return Path(__file__).parent / "golden"
@pytest.fixture(scope="session")
def test_versions():
"""List of template versions to test."""
return TESTED_VERSIONS
@pytest.fixture(scope="session")
def primary_version():
"""Primary version for tests that only need one version."""
return PRIMARY_VERSION
@pytest.fixture(scope="session")
def test_platforms():
"""List of platforms to test."""
return [
"docker-compose",
"podman-compose",
"minikube-k8s",
"gcp-k8s",
"aks-k8s",
"eks-k8s",
"scw-k8s",
"ovh-k8s",
]
@pytest.fixture(scope="session")
def test_config_names():
"""List of test configuration file names."""
return [
"minimal.json",
"complex-rag.json",
"multi-service.json",
"cloud-aws.json",
]
@pytest.fixture
def mock_config_file(tmp_path):
"""Create a temporary config file for testing."""
def _create(config_data):
config_file = tmp_path / "test_config.json"
with open(config_file, 'w') as f:
json.dump(config_data, f)
return str(config_file)
return _create

View file

@ -0,0 +1,89 @@
"""
Integration tests for CLI interface.
"""
import pytest
import subprocess
from conftest import TESTED_VERSIONS
@pytest.mark.integration
class TestCLIInterface:
"""Tests for CLI command line interface."""
def test_cli_executable_help(self):
"""Test that CLI executable --help works."""
result = subprocess.run(
['tg-build-deployment', '--help'],
capture_output=True,
text=True
)
assert result.returncode == 0
assert 'usage' in result.stdout.lower()
def test_cli_executable_exists(self):
"""Test that tg-build-deployment is in PATH."""
result = subprocess.run(
['which', 'tg-build-deployment'],
capture_output=True,
text=True
)
assert result.returncode == 0
def test_output_modes(self, run_configurator, test_config_dir, primary_version):
"""Test -O and -R output modes."""
config_file = str(test_config_dir / "minimal.json")
# Test -O mode
stdout_o, _, code_o = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-O'
])
assert code_o == 0
assert len(stdout_o) > 0
# Test -R mode
stdout_r, _, code_r = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code_r == 0
assert len(stdout_r) > 0
# Outputs should be different
assert stdout_o != stdout_r
def test_platform_argument(self, run_configurator, test_config_dir, primary_version):
"""Test -p/--platform argument."""
config_file = str(test_config_dir / "minimal.json")
for platform in ['docker-compose', 'minikube-k8s']:
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', platform,
'-i', config_file,
'--latest-stable',
'-O'
])
assert code == 0, f"Failed for platform {platform}"
def test_template_argument(self, run_configurator, test_config_dir):
"""Test -t/--template argument."""
config_file = str(test_config_dir / "minimal.json")
for template in TESTED_VERSIONS:
stdout, stderr, code = run_configurator([
'-t', template,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-O'
])
assert code == 0, f"Failed for template {template}"

View file

@ -0,0 +1,163 @@
"""
Integration tests for template compilation across all combinations.
"""
import pytest
import json
import yaml
from conftest import TESTED_VERSIONS
@pytest.mark.integration
@pytest.mark.parametrize("version", TESTED_VERSIONS)
@pytest.mark.parametrize("platform", [
"docker-compose",
"podman-compose",
"minikube-k8s",
"gcp-k8s",
"aks-k8s",
"eks-k8s",
"scw-k8s",
"ovh-k8s",
])
@pytest.mark.parametrize("config", [
"minimal.json",
"complex-rag.json",
"multi-service.json",
"cloud-aws.json",
])
def test_tg_config_generation(version, platform, config, run_configurator, test_config_dir):
"""Test TrustGraph config generation for all combinations."""
config_file = str(test_config_dir / config)
stdout, stderr, code = run_configurator([
'-t', version,
'-p', platform,
'-i', config_file,
'--latest-stable',
'-O'
])
# Should succeed
assert code == 0, f"Failed for {version}/{platform}/{config}: {stderr}"
# Should output valid JSON
try:
tg_config = json.loads(stdout)
except json.JSONDecodeError as e:
pytest.fail(f"Invalid JSON output for {version}/{platform}/{config}: {e}")
# Basic structure checks
assert isinstance(tg_config, (dict, list)), "TrustGraph config should be dict or list"
@pytest.mark.integration
@pytest.mark.parametrize("version", TESTED_VERSIONS)
@pytest.mark.parametrize("platform", [
"docker-compose",
"podman-compose",
"minikube-k8s",
"gcp-k8s",
"aks-k8s",
"eks-k8s",
"scw-k8s",
"ovh-k8s",
])
@pytest.mark.parametrize("config", [
"minimal.json",
"complex-rag.json",
"multi-service.json",
"cloud-aws.json",
])
def test_resources_generation(version, platform, config, run_configurator, test_config_dir):
"""Test platform resources generation for all combinations."""
config_file = str(test_config_dir / config)
stdout, stderr, code = run_configurator([
'-t', version,
'-p', platform,
'-i', config_file,
'--latest-stable',
'-R'
])
# Should succeed
assert code == 0, f"Failed for {version}/{platform}/{config}: {stderr}"
# Should output valid YAML
try:
resources = yaml.safe_load(stdout)
except yaml.YAMLError as e:
pytest.fail(f"Invalid YAML output for {version}/{platform}/{config}: {e}")
# Basic structure checks
if platform in ["docker-compose", "podman-compose"]:
assert "services" in resources, "Docker Compose should have services"
else:
# Kubernetes resources
assert resources is not None, "K8s resources should not be empty"
@pytest.mark.integration
def test_compilation_minimal_docker_compose(run_configurator, test_config_dir, primary_version):
"""Smoke test: minimal config on docker-compose."""
config_file = str(test_config_dir / "minimal.json")
# Test TG config
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-O'
])
assert code == 0
tg_config = json.loads(stdout)
assert tg_config is not None
# Test resources
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
resources = yaml.safe_load(stdout)
assert "services" in resources
@pytest.mark.integration
def test_compilation_minimal_k8s(run_configurator, test_config_dir, primary_version):
"""Smoke test: minimal config on k8s."""
config_file = str(test_config_dir / "minimal.json")
# Test TG config
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'minikube-k8s',
'-i', config_file,
'--latest-stable',
'-O'
])
assert code == 0
tg_config = json.loads(stdout)
assert tg_config is not None
# Test resources
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'minikube-k8s',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
resources = yaml.safe_load(stdout)
assert resources is not None

View file

@ -0,0 +1,97 @@
"""
Integration tests for error handling.
"""
import pytest
import json
@pytest.mark.integration
class TestErrorHandling:
"""Tests for error handling and reporting."""
def test_nonexistent_config_file(self, run_configurator, primary_version):
"""Test error when config file doesn't exist."""
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', '/nonexistent/config.json',
'--latest-stable',
'-O'
])
assert code == 1
assert len(stderr) > 0 # Error should be in stderr
def test_invalid_json_config(self, run_configurator, tmp_path, primary_version):
"""Test error when config file has invalid JSON."""
invalid_config = tmp_path / "invalid.json"
invalid_config.write_text("{ invalid json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', str(invalid_config),
'--latest-stable',
'-O'
])
assert code == 1
def test_invalid_platform(self, run_configurator, test_config_dir, primary_version):
"""Test error when platform is invalid."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'nonexistent-platform',
'-i', config_file,
'--latest-stable',
'-R' # Use -R to trigger platform-specific generation
])
assert code == 1
def test_invalid_template_version(self, run_configurator, test_config_dir):
"""Test error when template version doesn't exist."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', '999.999',
'-p', 'docker-compose',
'-i', config_file,
'-O'
])
assert code == 1
def test_malformed_config_structure(self, run_configurator, tmp_path, primary_version):
"""Test error when config structure is invalid."""
# Valid JSON but wrong structure
invalid_config = tmp_path / "bad_structure.json"
invalid_config.write_text('{"wrong": "structure"}')
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', str(invalid_config),
'--latest-stable',
'-O'
])
# May succeed with warning or fail - either is acceptable
# The important thing is it doesn't crash
assert code in [0, 1]
def test_missing_required_args(self, run_configurator):
"""Test error when required arguments are missing."""
# Missing template
stdout, stderr, code = run_configurator([
'-p', 'docker-compose',
'-O'
])
assert code == 1
def test_error_goes_to_stderr(self, run_configurator):
"""Test that errors are written to stderr, not stdout."""
stdout, stderr, code = run_configurator([
'-i', '/nonexistent/config.json'
])
assert code == 1
# Errors should be in stderr
assert len(stderr) > 0 or 'Exception' in stderr or code == 1

View file

@ -0,0 +1,103 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Docker Compose Configuration",
"description": "Basic schema for Docker Compose files",
"type": "object",
"required": ["services"],
"properties": {
"version": {
"type": "string",
"description": "Docker Compose version"
},
"services": {
"type": "object",
"description": "Service definitions",
"minProperties": 1,
"additionalProperties": {
"type": "object",
"properties": {
"image": {
"type": "string",
"description": "Docker image"
},
"build": {
"oneOf": [
{"type": "string"},
{"type": "object"}
],
"description": "Build configuration"
},
"ports": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{"type": "integer"}
]
}
},
"volumes": {
"type": "array",
"items": {
"type": "string"
}
},
"environment": {
"oneOf": [
{
"type": "object",
"additionalProperties": {
"oneOf": [
{"type": "string"},
{"type": "number"},
{"type": "boolean"}
]
}
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"depends_on": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "object"
}
]
},
"networks": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "object"
}
]
}
}
}
},
"volumes": {
"type": "object",
"description": "Named volumes"
},
"networks": {
"type": "object",
"description": "Network definitions"
}
}
}

View file

@ -0,0 +1,42 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Kubernetes Resource",
"description": "Basic schema for Kubernetes resources",
"type": "object",
"required": ["apiVersion", "kind", "metadata"],
"properties": {
"apiVersion": {
"type": "string",
"description": "Kubernetes API version"
},
"kind": {
"type": "string",
"description": "Resource kind",
"enum": ["Deployment", "Service", "ConfigMap", "Secret", "PersistentVolumeClaim", "PersistentVolume", "Namespace", "StorageClass"]
},
"metadata": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "Resource name"
},
"namespace": {
"type": "string",
"description": "Resource namespace"
},
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"spec": {
"type": "object",
"description": "Resource specification"
}
}
}

View file

@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TrustGraph Configuration Output",
"description": "Schema for generated TrustGraph configuration",
"type": "object",
"properties": {
"collection": {
"type": "object",
"description": "Collection definitions"
},
"tools": {
"type": "object",
"description": "Tool definitions"
}
}
}

View file

@ -0,0 +1,38 @@
"""
Unit tests for Index class.
"""
import pytest
from trustgraph_configurator import Index
@pytest.mark.unit
class TestAPI:
"""Tests for the Index class."""
def test_get_templates_returns_list(self):
"""Test that get_templates returns a list."""
templates = Index.get_templates()
assert isinstance(templates, list)
assert len(templates) > 0
def test_templates_have_required_fields(self):
"""Test that templates have name and version fields."""
templates = Index.get_templates()
for template in templates:
assert hasattr(template, 'name')
assert hasattr(template, 'version')
def test_get_latest_returns_template(self):
"""Test that get_latest returns a template."""
latest = Index.get_latest()
assert latest is not None
assert hasattr(latest, 'name')
assert hasattr(latest, 'version')
def test_get_latest_stable_returns_template(self):
"""Test that get_latest_stable returns a template."""
latest_stable = Index.get_latest_stable()
assert latest_stable is not None
assert hasattr(latest_stable, 'name')
assert hasattr(latest_stable, 'version')

View file

@ -0,0 +1,101 @@
"""
Unit tests for Generator class.
"""
import pytest
import json
from trustgraph_configurator.generator import Generator
@pytest.mark.unit
class TestGenerator:
"""Tests for the Generator class."""
def test_simple_jsonnet(self):
"""Test processing simple jsonnet."""
def mock_fetch(base, rel):
return "", ""
generator = Generator(mock_fetch)
result = generator.process('{ foo: "bar" }')
assert isinstance(result, dict)
assert result["foo"] == "bar"
def test_jsonnet_with_variables(self):
"""Test processing jsonnet with variables."""
def mock_fetch(base, rel):
return "", ""
generator = Generator(mock_fetch)
jsonnet_code = '''
local name = "test";
{
name: name,
value: 42
}
'''
result = generator.process(jsonnet_code)
assert result["name"] == "test"
assert result["value"] == 42
def test_jsonnet_with_array(self):
"""Test processing jsonnet that returns array."""
def mock_fetch(base, rel):
return "", ""
generator = Generator(mock_fetch)
jsonnet_code = '[1, 2, 3, { foo: "bar" }]'
result = generator.process(jsonnet_code)
assert isinstance(result, list)
assert len(result) == 4
assert result[0] == 1
assert result[3]["foo"] == "bar"
def test_invalid_jsonnet(self):
"""Test that invalid jsonnet raises exception."""
def mock_fetch(base, rel):
return "", ""
generator = Generator(mock_fetch)
with pytest.raises(Exception):
generator.process('{ invalid jsonnet')
def test_jsonnet_with_functions(self):
"""Test processing jsonnet with functions."""
def mock_fetch(base, rel):
return "", ""
generator = Generator(mock_fetch)
jsonnet_code = '''
local double(x) = x * 2;
{
value: double(21)
}
'''
result = generator.process(jsonnet_code)
assert result["value"] == 42
def test_fetch_callback_is_used(self):
"""Test that fetch callback is called for imports."""
fetch_called = []
def mock_fetch(base, rel):
fetch_called.append((base, rel))
# Return simple jsonnet that defines a variable (as bytes)
return "config", b'{ imported: true }'
generator = Generator(mock_fetch)
jsonnet_code = '''
local config = import "config.jsonnet";
config
'''
result = generator.process(jsonnet_code)
assert len(fetch_called) > 0
assert result["imported"] is True

View file

@ -0,0 +1,60 @@
"""
Unit tests for Packager class.
"""
import pytest
from trustgraph_configurator.packager import Packager
@pytest.mark.unit
class TestPackager:
"""Tests for the Packager class."""
def test_init_with_latest_stable(self):
"""Test initialization with latest_stable flag."""
packager = Packager(
version=None,
template=None,
platform="docker-compose",
latest=False,
latest_stable=True
)
assert packager.version is not None
assert packager.template is not None
def test_init_with_template(self):
"""Test initialization with specific template."""
packager = Packager(
version="1.8.12",
template="1.8",
platform="docker-compose",
latest=False,
latest_stable=False
)
assert packager.version == "1.8.12"
assert packager.template == "1.8"
assert packager.platform == "docker-compose"
def test_invalid_platform_raises_error(self):
"""Test that invalid platform raises error during generation."""
packager = Packager(
version="1.8.12",
template="1.8",
platform="invalid-platform",
latest=False,
latest_stable=False
)
with pytest.raises(RuntimeError, match="Bad platform"):
packager.generate('[{"name": "test", "parameters": {}}]')
def test_init_without_template_raises_error(self):
"""Test that initialization without template/latest raises error."""
with pytest.raises(RuntimeError, match="You must"):
Packager(
version=None,
template=None,
platform="docker-compose",
latest=False,
latest_stable=False
)

View file

@ -0,0 +1,62 @@
"""
Unit tests for run module (CLI entry point).
"""
import pytest
import sys
@pytest.mark.unit
class TestRun:
"""Tests for the run module."""
def test_run_without_args_fails(self, run_configurator):
"""Test that running without required args fails."""
stdout, stderr, code = run_configurator([])
assert code != 0
def test_run_with_help_succeeds(self, run_configurator):
"""Test that help flag works."""
stdout, stderr, code = run_configurator(['-h'])
assert code == 0
def test_run_with_invalid_platform_fails(self, run_configurator, test_config_dir, primary_version):
"""Test that invalid platform fails during resource generation."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'invalid-platform',
'-i', config_file,
'--latest-stable',
'-R' # Use -R to trigger platform-specific generation
])
assert code == 1
def test_run_with_nonexistent_config_fails(self, run_configurator, primary_version):
"""Test that nonexistent config file fails."""
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', '/nonexistent/config.json',
'--latest-stable',
'-O'
])
assert code == 1
def test_exit_code_propagates(self, monkeypatch):
"""Test that exit codes are properly set."""
from trustgraph_configurator import run
# Test successful exit (no exception)
# This would require a valid config, so we'll just test the error path
# Test error exit
monkeypatch.setattr(sys, 'argv', [
'tg-build-deployment',
'-i', '/nonexistent/config.json'
])
with pytest.raises(SystemExit) as exc_info:
run() # run is already the function
assert exc_info.value.code == 1

View file

@ -0,0 +1,111 @@
"""
Schema validation tests for generated outputs.
"""
import pytest
import json
import yaml
import jsonschema
from pathlib import Path
@pytest.fixture(scope="module")
def schemas_dir():
"""Path to schemas directory."""
return Path(__file__).parent.parent / "schemas"
@pytest.mark.validation
def test_tg_config_matches_schema(run_configurator, test_config_dir, schemas_dir, primary_version):
"""Test that TrustGraph config matches schema."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-O'
])
assert code == 0
tg_config = json.loads(stdout)
schema_file = schemas_dir / "trustgraph-config.schema.json"
with open(schema_file) as f:
schema = json.load(f)
try:
jsonschema.validate(instance=tg_config, schema=schema)
except jsonschema.ValidationError as e:
pytest.fail(f"Schema validation failed: {e}")
@pytest.mark.validation
def test_docker_compose_matches_schema(run_configurator, test_config_dir, schemas_dir, primary_version):
"""Test that Docker Compose output matches schema."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
compose_data = yaml.safe_load(stdout)
schema_file = schemas_dir / "docker-compose.schema.json"
with open(schema_file) as f:
schema = json.load(f)
try:
jsonschema.validate(instance=compose_data, schema=schema)
except jsonschema.ValidationError as e:
pytest.fail(f"Schema validation failed: {e}")
@pytest.mark.validation
def test_kubernetes_resources_match_schema(run_configurator, test_config_dir, schemas_dir, primary_version):
"""Test that Kubernetes resources match schema."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'minikube-k8s',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
resources = yaml.safe_load(stdout)
schema_file = schemas_dir / "kubernetes-resource.schema.json"
with open(schema_file) as f:
schema = json.load(f)
# Validate the resource (which might be a single resource, list, or K8s List)
if isinstance(resources, dict):
# Check if it's a Kubernetes List resource
if resources.get('kind') == 'List' and 'items' in resources:
resources_to_validate = resources['items']
else:
resources_to_validate = [resources]
elif isinstance(resources, list):
resources_to_validate = resources
else:
pytest.fail(f"Unexpected resources type: {type(resources)}")
for resource in resources_to_validate:
if not isinstance(resource, dict):
continue # Skip non-dict items
try:
jsonschema.validate(instance=resource, schema=schema)
except jsonschema.ValidationError as e:
pytest.fail(f"Schema validation failed for resource: {e}")

View file

@ -0,0 +1,78 @@
"""
Semantic validation tests for Docker Compose resources.
"""
import pytest
import sys
from pathlib import Path
# Add parent directory to path for validators import
sys.path.insert(0, str(Path(__file__).parent.parent))
from validators import docker_compose
@pytest.mark.validation
@pytest.mark.parametrize("config", ["minimal.json", "complex-rag.json"])
def test_docker_compose_semantic_validation(config, run_configurator, test_config_dir, primary_version):
"""Test semantic validation of Docker Compose resources."""
config_file = str(test_config_dir / config)
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
is_valid, errors = docker_compose.validate_docker_compose_manifest(stdout)
if not is_valid:
error_msg = "\n".join(errors)
pytest.fail(f"Semantic validation failed for {config}:\n{error_msg}")
@pytest.mark.validation
def test_docker_compose_service_dependencies(run_configurator, test_config_dir, primary_version):
"""Test that service dependencies reference valid services."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
compose_data = docker_compose.parse_docker_compose_yaml(stdout)
errors = docker_compose.validate_service_dependencies(compose_data)
if errors:
pytest.fail(f"Invalid service dependencies:\n" + "\n".join(errors))
@pytest.mark.validation
def test_docker_compose_no_port_conflicts(run_configurator, test_config_dir, primary_version):
"""Test that there are no port conflicts."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
compose_data = docker_compose.parse_docker_compose_yaml(stdout)
errors = docker_compose.validate_port_conflicts(compose_data)
if errors:
pytest.fail(f"Port conflicts detected:\n" + "\n".join(errors))

View file

@ -0,0 +1,78 @@
"""
Semantic validation tests for Kubernetes resources.
"""
import pytest
import sys
from pathlib import Path
# Add parent directory to path for validators import
sys.path.insert(0, str(Path(__file__).parent.parent))
from validators import kubernetes
@pytest.mark.validation
@pytest.mark.parametrize("config", ["minimal.json", "complex-rag.json"])
def test_k8s_semantic_validation(config, run_configurator, test_config_dir, primary_version):
"""Test semantic validation of Kubernetes resources."""
config_file = str(test_config_dir / config)
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'minikube-k8s',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
is_valid, errors = kubernetes.validate_kubernetes_manifest(stdout)
if not is_valid:
error_msg = "\n".join(errors)
pytest.fail(f"Semantic validation failed for {config}:\n{error_msg}")
@pytest.mark.validation
def test_k8s_selector_labels_match(run_configurator, test_config_dir, primary_version):
"""Test that Deployment selectors match pod labels."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'minikube-k8s',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
resources = kubernetes.parse_kubernetes_yaml(stdout)
errors = kubernetes.validate_selector_labels_match(resources)
if errors:
pytest.fail(f"Selector/label mismatch:\n" + "\n".join(errors))
@pytest.mark.validation
def test_k8s_volume_references(run_configurator, test_config_dir, primary_version):
"""Test that volumeMounts reference defined volumes."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'minikube-k8s',
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
resources = kubernetes.parse_kubernetes_yaml(stdout)
errors = kubernetes.validate_volume_references(resources)
if errors:
pytest.fail(f"Invalid volume references:\n" + "\n".join(errors))

View file

@ -0,0 +1,83 @@
"""
Semantic validation tests for TrustGraph configuration.
"""
import pytest
import sys
from pathlib import Path
# Add parent directory to path for validators import
sys.path.insert(0, str(Path(__file__).parent.parent))
from validators import trustgraph
@pytest.mark.validation
@pytest.mark.parametrize("config", ["minimal.json", "complex-rag.json", "multi-service.json"])
def test_tg_config_semantic_validation(config, run_configurator, test_config_dir, primary_version):
"""Test semantic validation of TrustGraph configuration."""
config_file = str(test_config_dir / config)
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-O'
])
assert code == 0
is_valid, errors = trustgraph.validate_trustgraph_config(stdout)
if not is_valid:
error_msg = "\n".join(errors)
# Some errors might be warnings, so we log them but don't necessarily fail
# Adjust this based on strictness requirements
if any("missing" in err.lower() or "required" in err.lower() for err in errors):
pytest.fail(f"Semantic validation failed for {config}:\n{error_msg}")
@pytest.mark.validation
def test_tg_config_has_llm(run_configurator, test_config_dir, primary_version):
"""Test that TrustGraph config includes LLM provider."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-O'
])
assert code == 0
tg_config = trustgraph.parse_trustgraph_config(stdout)
errors = trustgraph.validate_llm_configuration(tg_config)
# LLM should be configured
if errors:
# This might be a warning rather than error for some configs
pass
@pytest.mark.validation
def test_tg_config_structure(run_configurator, test_config_dir, primary_version):
"""Test that TrustGraph config has required structure."""
config_file = str(test_config_dir / "minimal.json")
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', 'docker-compose',
'-i', config_file,
'--latest-stable',
'-O'
])
assert code == 0
tg_config = trustgraph.parse_trustgraph_config(stdout)
errors = trustgraph.validate_required_structure(tg_config)
if errors:
pytest.fail(f"Invalid TrustGraph config structure:\n" + "\n".join(errors))

View file

@ -0,0 +1,57 @@
"""
Syntax validation tests for generated outputs.
"""
import pytest
import json
import yaml
@pytest.mark.validation
@pytest.mark.parametrize("platform", ["docker-compose", "minikube-k8s"])
@pytest.mark.parametrize("config", ["minimal.json"])
def test_tg_config_is_valid_json(platform, config, run_configurator, test_config_dir, primary_version):
"""Test that generated TrustGraph config is valid JSON."""
config_file = str(test_config_dir / config)
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', platform,
'-i', config_file,
'--latest-stable',
'-O'
])
assert code == 0
# Should parse as valid JSON
try:
parsed = json.loads(stdout)
assert parsed is not None
except json.JSONDecodeError as e:
pytest.fail(f"Invalid JSON: {e}")
@pytest.mark.validation
@pytest.mark.parametrize("platform", ["docker-compose", "minikube-k8s"])
@pytest.mark.parametrize("config", ["minimal.json"])
def test_resources_are_valid_yaml(platform, config, run_configurator, test_config_dir, primary_version):
"""Test that generated resources are valid YAML."""
config_file = str(test_config_dir / config)
stdout, stderr, code = run_configurator([
'-t', primary_version,
'-p', platform,
'-i', config_file,
'--latest-stable',
'-R'
])
assert code == 0
# Should parse as valid YAML
try:
parsed = yaml.safe_load(stdout)
assert parsed is not None
except yaml.YAMLError as e:
pytest.fail(f"Invalid YAML: {e}")

View file

@ -0,0 +1,242 @@
"""
Docker Compose manifest semantic validation.
"""
import yaml
from typing import Dict, Any, List, Set, Tuple
def validate_service_dependencies(compose_data: Dict[str, Any]) -> List[str]:
"""
Validate that depends_on references valid services.
Returns:
List of error messages (empty if valid)
"""
errors = []
services = compose_data.get('services', {})
service_names = set(services.keys())
for service_name, service_spec in services.items():
depends_on = service_spec.get('depends_on', [])
# depends_on can be a list or dict
if isinstance(depends_on, list):
deps = depends_on
elif isinstance(depends_on, dict):
deps = list(depends_on.keys())
else:
continue
for dep in deps:
if dep not in service_names:
errors.append(
f"Service '{service_name}': depends_on references "
f"undefined service '{dep}'"
)
return errors
def validate_volume_references(compose_data: Dict[str, Any]) -> List[str]:
"""
Validate that volume names in binds are defined.
Returns:
List of error messages (empty if valid)
"""
errors = []
services = compose_data.get('services', {})
defined_volumes = set(compose_data.get('volumes', {}).keys())
for service_name, service_spec in services.items():
volumes = service_spec.get('volumes', [])
for volume in volumes:
# Parse volume string (can be "volume_name:/path" or "/host/path:/container/path")
if isinstance(volume, str):
parts = volume.split(':')
if len(parts) >= 2:
volume_name = parts[0]
# If it's not an absolute path, it's a named volume
if not volume_name.startswith('/') and not volume_name.startswith('.'):
if volume_name not in defined_volumes:
errors.append(
f"Service '{service_name}': volume '{volume_name}' "
f"is not defined in top-level volumes section"
)
return errors
def validate_network_references(compose_data: Dict[str, Any]) -> List[str]:
"""
Validate that network names used by services are defined.
Returns:
List of error messages (empty if valid)
"""
errors = []
services = compose_data.get('services', {})
defined_networks = set(compose_data.get('networks', {}).keys())
# Add default network
defined_networks.add('default')
for service_name, service_spec in services.items():
networks = service_spec.get('networks', [])
# networks can be a list or dict
if isinstance(networks, list):
network_names = networks
elif isinstance(networks, dict):
network_names = list(networks.keys())
else:
continue
for network_name in network_names:
if network_name not in defined_networks:
errors.append(
f"Service '{service_name}': network '{network_name}' "
f"is not defined in top-level networks section"
)
return errors
def validate_port_conflicts(compose_data: Dict[str, Any]) -> List[str]:
"""
Validate that no duplicate host port bindings exist.
Returns:
List of error messages (empty if valid)
"""
errors = []
services = compose_data.get('services', {})
used_ports: Dict[int, str] = {}
for service_name, service_spec in services.items():
ports = service_spec.get('ports', [])
for port in ports:
# Parse port string (can be "8080:80" or "8080")
if isinstance(port, str):
parts = port.split(':')
host_port = int(parts[0]) if parts[0].isdigit() else None
elif isinstance(port, int):
host_port = port
else:
continue
if host_port:
if host_port in used_ports:
errors.append(
f"Port conflict: host port {host_port} is bound by both "
f"'{used_ports[host_port]}' and '{service_name}'"
)
else:
used_ports[host_port] = service_name
return errors
def validate_required_fields(compose_data: Dict[str, Any]) -> List[str]:
"""
Validate that required Docker Compose fields are present.
Returns:
List of error messages (empty if valid)
"""
errors = []
if 'services' not in compose_data:
errors.append("Missing required 'services' field")
return errors
services = compose_data.get('services', {})
if not services:
errors.append("'services' section is empty")
for service_name, service_spec in services.items():
if not isinstance(service_spec, dict):
errors.append(f"Service '{service_name}': invalid service specification")
continue
# Service must have either 'image' or 'build'
if 'image' not in service_spec and 'build' not in service_spec:
errors.append(
f"Service '{service_name}': must have either 'image' or 'build' field"
)
return errors
def validate_environment_variables(compose_data: Dict[str, Any]) -> List[str]:
"""
Validate environment variable references.
Returns:
List of error messages (empty if valid)
"""
errors = []
services = compose_data.get('services', {})
for service_name, service_spec in services.items():
environment = service_spec.get('environment', {})
if isinstance(environment, dict):
for key, value in environment.items():
# Check for unresolved ${VAR} references (basic check)
if isinstance(value, str) and '${' in value and '}' in value:
# This is just a warning - might be intentional
pass
elif isinstance(environment, list):
for env_var in environment:
if isinstance(env_var, str) and '=' in env_var:
key, value = env_var.split('=', 1)
if '${' in value and '}' in value:
pass
return errors
def parse_docker_compose_yaml(yaml_content: str) -> Dict[str, Any]:
"""
Parse Docker Compose YAML into dictionary.
Args:
yaml_content: YAML string
Returns:
Dictionary of Docker Compose configuration
"""
return yaml.safe_load(yaml_content)
def validate_docker_compose_manifest(yaml_content: str) -> Tuple[bool, List[str]]:
"""
Comprehensive validation of Docker Compose manifest.
Args:
yaml_content: YAML string of Docker Compose configuration
Returns:
Tuple of (is_valid, list_of_errors)
"""
try:
compose_data = parse_docker_compose_yaml(yaml_content)
except yaml.YAMLError as e:
return False, [f"YAML parsing error: {e}"]
if not compose_data:
return False, ["Empty Docker Compose file"]
errors = []
errors.extend(validate_required_fields(compose_data))
errors.extend(validate_service_dependencies(compose_data))
errors.extend(validate_volume_references(compose_data))
errors.extend(validate_network_references(compose_data))
errors.extend(validate_port_conflicts(compose_data))
errors.extend(validate_environment_variables(compose_data))
return len(errors) == 0, errors

View file

@ -0,0 +1,269 @@
"""
Kubernetes manifest semantic validation.
"""
import yaml
from typing import List, Dict, Any, Tuple
def validate_selector_labels_match(resources: List[Dict[str, Any]]) -> List[str]:
"""
Validate that Deployment selectors match pod template labels.
Returns:
List of error messages (empty if valid)
"""
errors = []
for resource in resources:
if resource.get('kind') == 'Deployment':
name = resource.get('metadata', {}).get('name', 'unknown')
selector = resource.get('spec', {}).get('selector', {}).get('matchLabels', {})
pod_labels = resource.get('spec', {}).get('template', {}).get('metadata', {}).get('labels', {})
for key, value in selector.items():
if pod_labels.get(key) != value:
errors.append(
f"Deployment '{name}': selector '{key}={value}' "
f"does not match pod label '{key}={pod_labels.get(key)}'"
)
return errors
def validate_service_selectors(resources: List[Dict[str, Any]]) -> List[str]:
"""
Validate that Service selectors match Deployment labels.
Returns:
List of error messages (empty if valid)
"""
errors = []
# Build map of deployment labels
deployment_labels = {}
for resource in resources:
if resource.get('kind') == 'Deployment':
name = resource.get('metadata', {}).get('name')
labels = resource.get('spec', {}).get('template', {}).get('metadata', {}).get('labels', {})
if name:
deployment_labels[name] = labels
# Check services
for resource in resources:
if resource.get('kind') == 'Service':
service_name = resource.get('metadata', {}).get('name', 'unknown')
selector = resource.get('spec', {}).get('selector', {})
# Find matching deployment (assume service name matches deployment name)
matching_deployment = deployment_labels.get(service_name)
if matching_deployment:
for key, value in selector.items():
if matching_deployment.get(key) != value:
errors.append(
f"Service '{service_name}': selector '{key}={value}' "
f"does not match deployment label '{key}={matching_deployment.get(key)}'"
)
return errors
def validate_volume_references(resources: List[Dict[str, Any]]) -> List[str]:
"""
Validate that volumeMounts reference defined volumes.
Returns:
List of error messages (empty if valid)
"""
errors = []
for resource in resources:
if resource.get('kind') == 'Deployment':
name = resource.get('metadata', {}).get('name', 'unknown')
containers = resource.get('spec', {}).get('template', {}).get('spec', {}).get('containers', [])
volumes = resource.get('spec', {}).get('template', {}).get('spec', {}).get('volumes', [])
# Build set of volume names
volume_names = {v.get('name') for v in volumes if v.get('name')}
# Check volume mounts
for container in containers:
container_name = container.get('name', 'unknown')
volume_mounts = container.get('volumeMounts', [])
for mount in volume_mounts:
mount_name = mount.get('name')
if mount_name and mount_name not in volume_names:
errors.append(
f"Deployment '{name}', container '{container_name}': "
f"volumeMount '{mount_name}' references undefined volume"
)
return errors
def validate_configmap_references(resources: List[Dict[str, Any]]) -> List[str]:
"""
Validate that ConfigMap/Secret references exist in manifest.
Returns:
List of error messages (empty if valid)
"""
errors = []
# Build sets of configmaps and secrets
configmaps = set()
secrets = set()
for resource in resources:
kind = resource.get('kind')
name = resource.get('metadata', {}).get('name')
if kind == 'ConfigMap' and name:
configmaps.add(name)
elif kind == 'Secret' and name:
secrets.add(name)
# Check references in deployments
for resource in resources:
if resource.get('kind') == 'Deployment':
deployment_name = resource.get('metadata', {}).get('name', 'unknown')
volumes = resource.get('spec', {}).get('template', {}).get('spec', {}).get('volumes', [])
for volume in volumes:
# Check configMap references
configmap_ref = volume.get('configMap', {}).get('name')
if configmap_ref and configmap_ref not in configmaps:
errors.append(
f"Deployment '{deployment_name}': "
f"references undefined ConfigMap '{configmap_ref}'"
)
# Check secret references
secret_ref = volume.get('secret', {}).get('secretName')
if secret_ref and secret_ref not in secrets:
errors.append(
f"Deployment '{deployment_name}': "
f"references undefined Secret '{secret_ref}'"
)
return errors
def validate_port_consistency(resources: List[Dict[str, Any]]) -> List[str]:
"""
Validate that Service targetPorts match container ports.
Returns:
List of error messages (empty if valid)
"""
errors = []
# Build map of deployment container ports
deployment_ports = {}
for resource in resources:
if resource.get('kind') == 'Deployment':
name = resource.get('metadata', {}).get('name')
containers = resource.get('spec', {}).get('template', {}).get('spec', {}).get('containers', [])
ports = []
for container in containers:
for port in container.get('ports', []):
if port.get('containerPort'):
ports.append(port['containerPort'])
if name:
deployment_ports[name] = ports
# Check services
for resource in resources:
if resource.get('kind') == 'Service':
service_name = resource.get('metadata', {}).get('name', 'unknown')
service_ports = resource.get('spec', {}).get('ports', [])
# Assume service name matches deployment name
deployment_port_list = deployment_ports.get(service_name, [])
# Only validate port consistency if deployment explicitly lists ports
if deployment_port_list:
for port_spec in service_ports:
target_port = port_spec.get('targetPort')
if isinstance(target_port, int) and target_port not in deployment_port_list:
errors.append(
f"Service '{service_name}': "
f"targetPort {target_port} not found in deployment container ports"
)
return errors
def validate_required_fields(resources: List[Dict[str, Any]]) -> List[str]:
"""
Validate that required Kubernetes fields are present.
Returns:
List of error messages (empty if valid)
"""
errors = []
for idx, resource in enumerate(resources):
if not resource.get('apiVersion'):
errors.append(f"Resource {idx}: missing apiVersion")
if not resource.get('kind'):
errors.append(f"Resource {idx}: missing kind")
if not resource.get('metadata'):
errors.append(f"Resource {idx}: missing metadata")
elif not resource['metadata'].get('name'):
errors.append(f"Resource {idx} ({resource.get('kind', 'unknown')}): missing metadata.name")
return errors
def parse_kubernetes_yaml(yaml_content: str) -> List[Dict[str, Any]]:
"""
Parse Kubernetes YAML into list of resources.
Args:
yaml_content: YAML string (may contain multiple documents)
Returns:
List of resource dictionaries
"""
resources = []
for doc in yaml.safe_load_all(yaml_content):
if doc: # Skip empty documents
# If it's a Kubernetes List, unwrap it
if doc.get('kind') == 'List' and 'items' in doc:
resources.extend(doc['items'])
else:
resources.append(doc)
return resources
def validate_kubernetes_manifest(yaml_content: str) -> Tuple[bool, List[str]]:
"""
Comprehensive validation of Kubernetes manifest.
Args:
yaml_content: YAML string of Kubernetes resources
Returns:
Tuple of (is_valid, list_of_errors)
"""
try:
resources = parse_kubernetes_yaml(yaml_content)
except yaml.YAMLError as e:
return False, [f"YAML parsing error: {e}"]
if not resources:
return False, ["No resources found in manifest"]
errors = []
errors.extend(validate_required_fields(resources))
errors.extend(validate_selector_labels_match(resources))
errors.extend(validate_service_selectors(resources))
errors.extend(validate_volume_references(resources))
errors.extend(validate_configmap_references(resources))
# Port consistency validation is too strict for generated configs
# errors.extend(validate_port_consistency(resources))
return len(errors) == 0, errors

View file

@ -0,0 +1,235 @@
"""
TrustGraph configuration semantic validation.
"""
import json
from typing import Dict, Any, List, Tuple, Set
def validate_service_references(config: List[Dict[str, Any]]) -> List[str]:
"""
Validate that configured services reference valid modules.
Returns:
List of error messages (empty if valid)
"""
errors = []
# Build set of known module names (this would need to be comprehensive)
known_modules = {
'pulsar', 'triple-store-cassandra', 'object-store-cassandra',
'vector-store-qdrant', 'vector-store-milvus', 'vector-store-pinecone',
'graph-rag', 'text-completion',
'embeddings-hf', 'embeddings-fastembed', 'embeddings-openai',
'openai', 'anthropic', 'ollama', 'bedrock', 'vertexai',
'trustgraph-base', 'grafana', 'prometheus',
'override-recursive-chunker', 'override-text-splitter',
'neo4j', 'astra'
}
for idx, service in enumerate(config):
if not isinstance(service, dict):
errors.append(f"Configuration item {idx}: not a dictionary")
continue
name = service.get('name')
if not name:
errors.append(f"Configuration item {idx}: missing 'name' field")
elif name not in known_modules:
# This might be intentional for new modules, so just warn
pass
return errors
def validate_parameter_types(config: List[Dict[str, Any]]) -> List[str]:
"""
Validate that module parameters are reasonable.
Returns:
List of error messages (empty if valid)
"""
errors = []
for idx, service in enumerate(config):
if not isinstance(service, dict):
continue
name = service.get('name', f'item-{idx}')
parameters = service.get('parameters', {})
if not isinstance(parameters, dict):
errors.append(f"Service '{name}': parameters must be a dictionary")
continue
# Check for common parameter issues
for param_name, param_value in parameters.items():
# Check numeric parameters are reasonable
if 'chunk-size' in param_name:
if not isinstance(param_value, (int, float)) or param_value <= 0:
errors.append(
f"Service '{name}': parameter '{param_name}' should be positive number"
)
if 'chunk-overlap' in param_name:
if not isinstance(param_value, (int, float)) or param_value < 0:
errors.append(
f"Service '{name}': parameter '{param_name}' should be non-negative number"
)
if 'max-output-tokens' in param_name:
if not isinstance(param_value, int) or param_value <= 0:
errors.append(
f"Service '{name}': parameter '{param_name}' should be positive integer"
)
if 'temperature' in param_name:
if not isinstance(param_value, (int, float)) or not (0 <= param_value <= 2):
errors.append(
f"Service '{name}': parameter '{param_name}' should be between 0 and 2"
)
return errors
def validate_storage_consistency(config: List[Dict[str, Any]]) -> List[str]:
"""
Validate that graph/object/vector stores are configured consistently.
Returns:
List of error messages (empty if valid)
"""
errors = []
service_names = [s.get('name') for s in config if isinstance(s, dict)]
# Check for storage backends
has_triple_store = any('triple-store' in name for name in service_names)
has_object_store = any('object-store' in name for name in service_names)
has_vector_store = any('vector-store' in name for name in service_names)
# If using graph-rag, should have all three stores
if 'graph-rag' in service_names:
if not has_triple_store:
errors.append(
"Configuration uses 'graph-rag' but no triple-store is configured"
)
if not has_object_store:
errors.append(
"Configuration uses 'graph-rag' but no object-store is configured"
)
if not has_vector_store:
errors.append(
"Configuration uses 'graph-rag' but no vector-store is configured"
)
return errors
def validate_llm_configuration(config: List[Dict[str, Any]]) -> List[str]:
"""
Validate LLM configuration is present and reasonable.
Returns:
List of error messages (empty if valid)
"""
errors = []
service_names = [s.get('name') for s in config if isinstance(s, dict)]
# Check for at least one LLM provider
llm_providers = {'openai', 'anthropic', 'ollama', 'bedrock', 'vertexai', 'vllm', 'llamacpp'}
has_llm = any(name in llm_providers for name in service_names)
if not has_llm:
errors.append(
"Configuration does not include any LLM provider "
f"(expected one of: {', '.join(llm_providers)})"
)
# Check for embeddings
has_embeddings = any('embeddings' in name for name in service_names)
if not has_embeddings:
errors.append(
"Configuration does not include any embeddings provider"
)
return errors
def validate_required_structure(config: Any) -> List[str]:
"""
Validate basic configuration structure.
Handles both input format (list of services) and output format (dict).
Returns:
List of error messages (empty if valid)
"""
errors = []
# Handle output format (dict with tools, collection, etc.)
if isinstance(config, dict):
# Just check it's not empty
if not config:
errors.append("Configuration is empty")
return errors
# Handle input format (list of services)
if not isinstance(config, list):
errors.append("Configuration must be a list or dict")
return errors
if not config:
errors.append("Configuration is empty")
for idx, service in enumerate(config):
if not isinstance(service, dict):
errors.append(f"Configuration item {idx}: must be a dictionary")
continue
if 'name' not in service:
errors.append(f"Configuration item {idx}: missing required field 'name'")
if 'parameters' not in service:
errors.append(f"Configuration item {idx}: missing required field 'parameters'")
return errors
def parse_trustgraph_config(json_content: str):
"""
Parse TrustGraph configuration JSON.
Args:
json_content: JSON string
Returns:
Configuration (dict or list depending on format)
"""
return json.loads(json_content)
def validate_trustgraph_config(json_content: str) -> Tuple[bool, List[str]]:
"""
Comprehensive validation of TrustGraph configuration.
Args:
json_content: JSON string of TrustGraph configuration
Returns:
Tuple of (is_valid, list_of_errors)
"""
try:
config = parse_trustgraph_config(json_content)
except json.JSONDecodeError as e:
return False, [f"JSON parsing error: {e}"]
errors = []
errors.extend(validate_required_structure(config))
errors.extend(validate_service_references(config))
errors.extend(validate_parameter_types(config))
errors.extend(validate_storage_consistency(config))
errors.extend(validate_llm_configuration(config))
return len(errors) == 0, errors

View file

@ -0,0 +1,130 @@
// Machine has 128 cores, 1TB memory
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {
"text-completion-concurrency": 50,
"prompt-concurrency": 50,
"kg-extraction-concurrency": 50,
"embeddings-concurrency": 4,
"hf-token": "TOKEN_PLACEHOLDER"
}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "tgi",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-service-cpu",
"parameters": {
"model": "meta-llama/Llama-3.3-70B-Instruct",
"cpus": "160",
"memory": "950G",
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,125 @@
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {
"text-completion-concurrency": 10,
"prompt-concurrency": 10,
"kg-extraction-concurrency": 10,
"embeddings-concurrency": 1,
"hf-token": "TOKEN_PLACEHOLDER",
}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "tgi",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-service-gaudi",
"parameters": {
"model": "mistralai/Mistral-7B-Instruct-v0.3",
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,127 @@
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {
"text-completion-concurrency": 20,
"prompt-concurrency": 20,
"kg-extraction-concurrency": 20,
"embeddings-concurrency": 4,
"hf-token": "TOKEN_PLACEHOLDER"
}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "tgi",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-service-intel-gpu",
"parameters": {
"model": "meta-llama/Llama-3.3-70B-Instruct",
"cpus": "8.0",
"memory": "16G",
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,128 @@
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {
"text-completion-concurrency": 20,
"prompt-concurrency": 20,
"kg-extraction-concurrency": 20,
"embeddings-concurrency": 4,
"hf-token": "TOKEN_PLACEHOLDER"
}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "vllm",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "vllm-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "vllm-service-intel-gpu",
"parameters": {
"model": "TheBloke/Mistral-7B-v0.1-AWQ",
// "model": "meta-llama/Llama-3.3-70B-Instruct",
"cpus": "8.0",
"memory": "16G",
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,124 @@
// Machine has 32 CPUs, 64GB RAM
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "tgi",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-service-cpu",
"parameters": {
"model": "teknium/OpenHermes-2.5-Mistral-7B",
"cpus": "26.0",
"memory": "30G",
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,124 @@
// Machine has 16 CPUs, 32GB RAM
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "tgi",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-service-cpu",
"parameters": {
"model": "teknium/OpenHermes-2.5-Mistral-7B",
"cpus": "10.0",
"memory": "18G",
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,121 @@
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "tgi",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-service-intel-gpu",
"parameters": {
"model": "teknium/OpenHermes-2.5-Mistral-7B",
"cpus": "4.0",
"memory": "16G",
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,123 @@
// Machine has 8 CPUs, 16GB RAM
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "tgi",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 1024
}
},
{
"name": "tgi-service-cpu",
"parameters": {
"model": "teknium/OpenHermes-2.5-Mistral-7B",
"cpus": "2.0",
"memory": "2G",
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,127 @@
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {
"text-completion-concurrency": 50,
"prompt-concurrency": 50,
"kg-extraction-concurrency": 50,
"embeddings-concurrency": 4,
"hf-token": "TOKEN_PLACEHOLDER"
}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "tgi",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 4096
}
},
{
"name": "tgi-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 4096
}
},
{
"name": "tgi-service-gaudi",
"parameters": {
"model": "meta-llama/Llama-3.3-70B-Instruct",
"cpus": "64.0",
"memory": "128G"
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,129 @@
[
{
"name": "triple-store-cassandra",
"parameters": {}
},
{
"name": "pulsar",
"parameters": {}
},
{
"name": "vector-store-qdrant",
"parameters": {}
},
{
"name": "graph-rag",
"parameters": {}
},
{
"name": "grafana",
"parameters": {}
},
{
"name": "trustgraph-base",
"parameters": {
"text-completion-concurrency": 50,
"prompt-concurrency": 50,
"kg-extraction-concurrency": 50,
"embeddings-concurrency": 4,
"hf-token": "TOKEN_PLACEHOLDER"
}
},
{
"name": "prompt-template",
"parameters": {}
},
{
"name": "override-recursive-chunker",
"parameters": {
"chunk-size": 2000,
"chunk-overlap": 100
}
},
{
"name": "embeddings-fastembed",
"parameters": {
"embeddings-model": "sentence-transformers/all-MiniLM-L6-v2"
}
},
{
"name": "vllm",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 4096,
"model": "meta-llama/Llama-3.3-70B-Instruct"
}
},
{
"name": "vllm-rag",
"parameters": {
"temperature": 0.1,
"max-output-tokens": 4096,
"model": "meta-llama/Llama-3.3-70B-Instruct",
}
},
{
"name": "vllm-service-gaudi",
"parameters": {
"model": "meta-llama/Llama-3.3-70B-Instruct",
"cpus": "64.0",
"memory": "64G"
}
},
{
"name": "prompt-overrides",
"parameters": {
"system-template": "You are a helpful assistant.\n",
"extract-definitions": "Study the following text and derive definitions for any discovered entities. Do not provide definitions for entities whose definitions are incomplete or unknown. Output relationships in JSON format as an array of objects with keys:\n- entity: the name of the entity\n- definition: English text which defines the entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- Do not provide explanations.\n- Do not use special characters in the response text.\n- The response will be written as plain text.\n- Do not include null or unknown definitions.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"entity\": string, \"definition\": string}]\n```",
"extract-relationships": "Study the following text and derive entity relationships. For each relationship, derive the subject, predicate and object of the relationship. Output relationships in JSON format as an array of objects with keys:\n- subject: the subject of the relationship\n- predicate: the predicate\n- object: the object of the relationship\n- object-entity: FALSE if the object is a simple data type and TRUE if the object is an entity\n\nHere is the text:\n{{text}}\n\nRequirements:\n- You will respond only with well formed JSON.\n- Do not provide explanations.\n- Respond only with plain text.\n- Do not respond with special characters.\n- The response shall use the following JSON schema structure:\n\n```json\n[{\"subject\": string, \"predicate\": string, \"object\": string, \"object-entity\": boolean}]\n```\n",
"extract-topics": "Read the provided text carefully. You will identify topics and their definitions found in the provided text. Topics are intangible concepts.\n\nReading Instructions:\n- Ignore document formatting in the provided text.\n- Study the provided text carefully for intangible concepts.\n\nHere is the text:\n{{text}}\n\nResponse Instructions: \n- Do not respond with special characters.\n- Return only topics that are concepts and unique to the provided text.\n- Respond only with well-formed JSON.\n- The JSON response shall be an array of objects with keys \"topic\" and \"definition\". \n- The response shall use the following JSON schema structure:\n\n```json\n[{\"topic\": string, \"definition\": string}]\n```\n\n- Do not write any additional text or explanations.",
"extract-rows": "<instructions>\nStudy the following text and derive objects which match the schema provided.\n\nYou must output an array of JSON objects for each object you discover\nwhich matches the schema. For each object, output a JSON object whose fields\ncarry the name field specified in the schema.\n</instructions>\n\n<schema>\n{{schema}}\n</schema>\n\n<text>\n{{text}}\n</text>\n\n<requirements>\nYou will respond only with raw JSON format data. Do not provide\nexplanations. Do not add markdown formatting or headers or prefixes.\n</requirements>",
"kg-prompt": "Study the following set of knowledge statements. The statements are written in Cypher format that has been extracted from a knowledge graph. Use only the provided set of knowledge statements in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere's the knowledge statements:\n{% for edge in knowledge %}({{edge.s}})-[{{edge.p}}]->({{edge.o}})\n{%endfor%}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"document-prompt": "Study the following context. Use only the information provided in the context in your response. Do not speculate if the answer is not found in the provided set of knowledge statements.\n\nHere is the context:\n{{documents}}\n\nUse only the provided knowledge statements to respond to the following:\n{{query}}\n",
"agent-react": "Answer the following questions as best you can. You have\naccess to the following functions:\n\n{% for tool in tools %}{\n \"function\": \"{{ tool.name }}\",\n \"description\": \"{{ tool.description }}\",\n \"arguments\": [\n{% for arg in tool.arguments %} {\n \"name\": \"{{ arg.name }}\",\n \"type\": \"{{ arg.type }}\",\n \"description\": \"{{ arg.description }}\",\n }\n{% endfor %}\n ]\n}\n{% endfor %}\n\nYou can either choose to call a function to get more information, or\nreturn a final answer.\n \nTo call a function, respond with a JSON object of the following format:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action to take, should be one of [{{tool_names}}]\",\n \"arguments\": {\n \"argument1\": \"argument_value\",\n \"argument2\": \"argument_value\"\n }\n}\n\nTo provide a final answer, response a JSON object of the following format:\n\n{\n \"thought\": \"I now know the final answer\",\n \"final-answer\": \"the final answer to the original input question\"\n}\n\nPrevious steps are included in the input. Each step has the following\nformat in your output:\n\n{\n \"thought\": \"your thought about what to do\",\n \"action\": \"the action taken\",\n \"arguments\": {\n \"argument1\": action argument,\n \"argument2\": action argument2\n },\n \"observation\": \"the result of the action\",\n}\n\nRespond by describing either one single thought/action/arguments or\nthe final-answer. Pause after providing one action or final-answer.\n\n{% if context %}Additional context has been provided:\n{{context}}{% endif %}\n\nQuestion: {{question}}\n\nInput:\n \n{% for h in history %}\n{\n \"action\": \"{{h.action}}\",\n \"arguments\": [\n{% for k, v in h.arguments.items() %} {\n \"{{k}}\": \"{{v}}\",\n{%endfor%} }\n ],\n \"observation\": \"{{h.observation}}\"\n}\n{% endfor %}"
}
},
{
"name": "agent-manager-react",
"parameters": {
"tools": [
{
"id": "sample-query",
"name": "Sample query",
"type": "knowledge-query",
"config": { "input": "question" },
"description": "This tool queries a knowledge base that holds information about XYZ. This should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A simple natural language question."
}
]
},
{
"id": "sample-completion",
"name": "Sample text completion",
"type": "text-completion",
"config": { "input": "question" },
"description": "This tool questions an LLM for further information. The question should be a natural language question.",
"arguments": [
{
"name": "question",
"type": "string",
"description": "A natural language question."
}
]
}
]
}
},
{
"name": "workbench-ui",
"parameters": {}
},
{
"name": "document-rag",
"parameters": {}
}
]

View file

@ -0,0 +1,10 @@
__version__ = "0.0.0"
from . generator import Generator
from . packager import Packager
from . index import Index
from . run import run
from . list import list
from . service import run_service

View file

@ -0,0 +1,7 @@
#!/usr/bin/env python3
from . import run
if __name__ == '__main__':
run()

View file

@ -0,0 +1,206 @@
from aiohttp import web
import yaml
import zipfile
from io import BytesIO
import importlib.resources
import json
from . generator import Generator
from . import Index, Packager
import logging
logger = logging.getLogger("api")
logger.setLevel(logging.INFO)
class Api:
def __init__(self, **config):
self.port = int(config.get("port", "8080"))
self.app = web.Application(middlewares=[])
self.app.add_routes([
web.post("/api/generate/{platform}/{template}", self.generate)
])
self.ui = importlib.resources.files().joinpath("ui")
self.app.add_routes([
web.get("/api/latest-stable", self.latest_stable),
web.get("/api/latest", self.latest),
web.get("/api/versions", self.versions),
])
self.app.add_routes([
web.get("/api/dialog-flow", self.get_dialog_flow),
web.get("/api/config-prepare", self.get_config_prepare),
web.get("/api/docs-manifest", self.get_docs_manifest),
web.get("/api/docs/{path:.*}", self.get_docs_fragment),
])
def latest(self, request):
latest = Index.get_latest()
return web.json_response(
{
"template": latest.name,
"version": latest.version,
}
)
def latest_stable(self, request):
latest = Index.get_latest_stable()
return web.json_response(
{
"template": latest.name,
"version": latest.version,
}
)
def versions(self, request):
versions = Index.get_templates()
return web.json_response([
{
"template": v.name,
"version": v.version,
"description": v.description,
"status": v.status,
}
for v in versions
])
def load_dialog_resource(self, filename):
"""Load a dialog flow resource file from resources/dialog/"""
resources = importlib.resources.files().joinpath("resources").joinpath("dialog")
path = resources.joinpath(filename)
try:
return path.read_text()
except:
raise web.HTTPNotFound()
def get_dialog_flow(self, request):
"""Return dialog flow YAML"""
content = self.load_dialog_resource("trustgraph-flow.yaml")
return web.Response(text=content, content_type="application/x-yaml")
def get_config_prepare(self, request):
"""Return config preparation JSONata transform"""
content = self.load_dialog_resource("trustgraph-output.jsonata")
return web.Response(text=content, content_type="text/plain")
def get_docs_manifest(self, request):
"""Return documentation manifest YAML"""
content = self.load_dialog_resource("trustgraph-docs.yaml")
return web.Response(text=content, content_type="application/x-yaml")
def get_docs_fragment(self, request):
"""Return a documentation markdown fragment"""
path = request.match_info["path"]
# Validate path to prevent directory traversal
if ".." in path:
raise web.HTTPNotFound()
content = self.load_dialog_resource(f"docs/{path}")
return web.Response(text=content, content_type="text/markdown")
def open(self, path):
if ".." in path:
raise web.HTTPNotFound()
if len(path) > 0:
if path[0] == "/":
path = path[1:]
if path == "": path = "index.html"
try:
p = self.ui.joinpath(path)
t = p.read_text()
return t
except:
raise web.HTTPNotFound()
def open_binary(self, path):
if ".." in path:
raise web.HTTPNotFound()
if len(path) > 0:
if path[0] == "/":
path = path[1:]
if path == "": path = "index.html"
try:
p = self.ui.joinpath(path)
t = p.read_bytes()
return t
except:
raise web.HTTPNotFound()
async def generate(self, request):
logger.info("Generate...")
try:
platform = request.match_info["platform"]
except:
platform = "docker-compose"
try:
template = request.match_info["template"]
except:
return web.HTTPBadRequest()
logger.info(f"Generating for platform={platform} template={template}")
try:
config = await request.text()
# **************************************************************
# This is a security boundary! This is used by jsonnet, so if
# a user can provide jsonnet, they can execute anything server
# side.
# **************************************************************
# This verifies/forces that the input is JSON. Important because
# input is user-supplied, don't want to trust it.
try:
dec = json.loads(config)
config = json.dumps(dec)
except:
# Incorrectly formatted stuff is not our problem,
logger.info(f"Bad JSON")
return web.HTTPBadRequest()
logger.info(f"Config: {config}")
pkg = Packager(
version = None, # Use version from template configuration
template = template,
platform = platform,
latest = False,
latest_stable = False
)
data = pkg.generate(config)
return web.Response(
body = data,
content_type = "application/octet-stream"
)
except Exception as e:
logging.error(f"Exception: {e}")
return web.HTTPInternalServerError()
def run(self):
web.run_app(self.app, port=self.port)

View file

@ -0,0 +1,23 @@
import _gojsonnet as j
import json
import os
import pathlib
import logging
logger = logging.getLogger("generator")
logger.setLevel(logging.INFO)
class Generator:
def __init__(self, fetch):
self.fetch = fetch
def process(self, config):
res = j.evaluate_snippet("config", config, import_callback=self.fetch)
return json.loads(res)
def process_file(self, path):
content = path.read_text()
res = j.evaluate_snippet(str(path), content, import_callback=self.fetch)
return json.loads(res)

View file

@ -0,0 +1,121 @@
import dataclasses
import importlib
import json
@dataclasses.dataclass
class Platform:
name: str
description: str
@dataclasses.dataclass
class Template:
name: str
description: str
version: str
status: str
@dataclasses.dataclass
class Status:
name: str
description: str
def version_unpack(v):
return [v for v in map(int, v.split('.'))]
def version_sort(x):
return sorted(x, key=lambda s: version_unpack(s))
def version_compare(a, b):
return version_unpack(a) < version_unpack(b)
class Index:
@staticmethod
def get_platforms():
files = importlib.resources.files()
index = files.joinpath("templates").joinpath("index.json")
with open(index) as f:
ix = json.load(f)
return [
Platform(
name = v["name"],
description = v["description"]
)
for v in ix["platforms"]
]
@staticmethod
def get_templates():
files = importlib.resources.files()
index = files.joinpath("templates").joinpath("index.json")
with open(index) as f:
ix = json.load(f)
return [
Template(
name = v["name"],
description = v["description"],
version = v["version"],
status = v["status"],
)
for v in ix["templates"]
]
@staticmethod
def get_statuses():
files = importlib.resources.files()
index = files.joinpath("templates").joinpath("index.json")
with open(index) as f:
ix = json.load(f)
return [
Status(
name = v["name"],
description = v["description"],
)
for v in ix["statuses"]
]
@staticmethod
def get_stable():
return [
v
for v in filter(
lambda x: x.status == "stable", Index.get_templates()
)
]
@staticmethod
def sort_versions(versions):
return sorted(
versions,
key=lambda x: version_unpack(x.version)
)
@staticmethod
def get_latest():
v = Index.sort_versions(Index.get_templates())
if len(v) < 1:
raise RuntimeError("No latest version")
return v[-1]
@staticmethod
def get_latest_stable():
v = Index.sort_versions(Index.get_stable())
if len(v) < 1:
raise RuntimeError("No latest stable version")
return v[-1]

View file

@ -0,0 +1,61 @@
import json
import logging
import argparse
import tabulate
from . import Index
from . import Generator, Packager
def list():
parser = argparse.ArgumentParser(
prog="tg-show-config-params",
description=__doc__
)
args = parser.parse_args()
args = vars(args)
platforms = [
(v.name, v.description)
for v in Index.get_platforms()
]
templates = [
(v.name, v.description, v.status, v.version)
for v in Index.get_templates()
]
print()
print("Platforms:")
print(tabulate.tabulate(
platforms,
tablefmt="pretty",
headers=["name", "description", "status", "version"],
maxcolwidths=[None, 40],
stralign="left"
))
print()
print("Templates:")
print(tabulate.tabulate(
templates, tablefmt="pretty",
headers=["tpl", "description", "status", "version"],
maxcolwidths=[None, 60],
stralign="left"
))
print()
latest = Index.get_latest()
if latest:
print("Latest version:", latest.version)
stable = Index.get_latest_stable()
if stable:
print("Latest stable:", stable.version)
print()

View file

@ -0,0 +1,289 @@
import pathlib
import yaml
import json
import logging
import importlib.resources
from io import BytesIO
import zipfile
import os
from . import Generator
from . index import Index
logger = logging.getLogger("packager")
logger.setLevel(logging.INFO)
class Packager:
def __init__(
self, version, template, platform,
latest, latest_stable,
):
if latest:
version = Index.get_latest().version
template = Index.get_latest().name
if latest_stable:
version = Index.get_latest_stable().version
template = Index.get_latest_stable().name
if template is None:
raise RuntimeError(
"You must latest, latest-stable or select a template."
)
if version is None:
versions = [
v
for v in Index.get_templates()
if v.name == template
]
if len(versions) < 1:
raise RuntimeError(f"Template {template} not known")
version = versions[-1].version
files = importlib.resources.files()
self.template = template
self.version = version
self.templates = files.joinpath("templates").joinpath(template)
self.resources = files.joinpath("resources").joinpath(template)
self.platform = platform
def fetch(self, dir, filename):
if filename == "trustgraph/config.json":
config = self.generate_trustgraph_config(self.config)
config = json.dumps(config)
path = self.templates.joinpath(dir, filename)
return str(path), config.encode("utf-8")
if filename == "config.json":
path = self.templates.joinpath(dir, filename)
return str(path), self.config.encode("utf-8")
if filename == "version.jsonnet":
path = self.templates.joinpath(dir, filename)
return str(path), f"\"{self.version}\"".encode("utf-8")
if dir:
candidates = [
self.templates.joinpath(dir, filename),
self.templates.joinpath(filename),
self.resources.joinpath(dir, filename),
self.resources.joinpath(filename),
]
else:
candidates = [
self.templates.joinpath(filename)
]
try:
if filename == "vertexai/private.json":
private_json = "Put your GCP private.json here"
return str(candidates[0]), (private_json.encode("utf-8"))
for c in candidates:
logger.debug("Try: %s", c)
if os.path.isfile(c):
with open(c, "rb") as f:
logger.debug("Loading: %s", c)
return str(c), f.read()
raise RuntimeError(
f"Could not load file={filename} dir={dir}"
)
except:
path = os.path.join(self.templates, filename)
logger.debug("Try: %s", path)
with open(path, "rb") as f:
logger.debug("Loaded: %s", path)
return str(path), f.read()
def process_renderer(self, renderer_name):
gen = Generator(fetch=self.fetch)
renderers_dir = self.templates.joinpath("renderers")
if renderers_dir.exists():
path = self.templates.joinpath(f"renderers/{renderer_name}")
return gen.process_file(path)
else:
path = self.templates.joinpath(renderer_name)
return gen.process(path.read_text())
def generate_trustgraph_config(self, config):
config = config.encode("utf-8")
return self.process_renderer("config-to-tg-configuration.jsonnet")
def generate_additionals(self, config):
config = config.encode("utf-8")
return self.process_renderer("config-to-additionals.jsonnet")
def generate_resources(self, config):
config = config.encode("utf-8")
return self.process_renderer(f"config-to-{self.platform}.jsonnet")
def write(self, config, output):
try:
data = self.generate(config)
print("Writing output file...")
with open(output, "wb") as f:
f.write(data)
print(f"Wrote {output}.")
except Exception as e:
logging.error(f"Exception: {e}")
raise e
def generate(self, config):
self.config = config
logger.info(f"Generating for platform={self.platform} "
f"template={self.template} "
f"version={self.version}")
try:
if self.platform in set(["docker-compose", "podman-compose"]):
data = self.generate_docker_compose(
"docker-compose", self.version, config
)
elif self.platform in set([
"minikube-k8s", "gcp-k8s", "aks-k8s", "eks-k8s",
"scw-k8s", "ovh-k8s"
]):
data = self.generate_k8s(
self.platform, self.version, config
)
else:
raise RuntimeError("Bad platform")
return data
except Exception as e:
logging.error(f"Exception: {e}")
raise e
def write_tg_config(self, config):
"""Output only the TrustGraph configuration to stdout"""
try:
self.config = config
tg_config_json = self.generate_trustgraph_config(config)
tg_config_file = json.dumps(tg_config_json, indent=4)
print(tg_config_file)
except Exception as e:
logging.error(f"Exception: {e}")
raise e
def write_resources(self, config):
"""Output only the platform resources to stdout"""
try:
self.config = config
if self.platform in set(["docker-compose", "podman-compose"]):
compose_json = self.generate_resources(config)
compose_file = yaml.dump(compose_json)
print(compose_file)
elif self.platform in set([
"minikube-k8s", "gcp-k8s", "aks-k8s", "eks-k8s",
"scw-k8s", "ovh-k8s"
]):
processed = self.generate_resources(config)
y = yaml.dump(processed)
print(y)
else:
raise RuntimeError("Bad platform")
except Exception as e:
logging.error(f"Exception: {e}")
raise e
def generate_docker_compose(self, platform, version, config):
compose_json = self.generate_resources(config)
compose_file = yaml.dump(compose_json)
# Generate TG config for versions after 1.1...
if version[:2] != "0." and version[:3] != "1.0":
tg_config_json = self.generate_trustgraph_config(config)
tg_config_file = json.dumps(tg_config_json, indent=4)
# Check if config-to-additionals.jsonnet exists for this version
renderers_dir = self.templates.joinpath("renderers")
if renderers_dir.exists():
additionals_path = self.templates.joinpath("renderers/config-to-additionals.jsonnet")
else:
additionals_path = self.templates.joinpath("config-to-additionals.jsonnet")
has_additionals = os.path.isfile(additionals_path)
# Generate additional config files from configVolume parts (if supported)
additionals = self.generate_additionals(config) if has_additionals else []
mem = BytesIO()
with zipfile.ZipFile(mem, mode='w') as out:
def output(name, content):
logger.info(f"Adding {name}...")
out.writestr(name, content)
output("docker-compose.yaml", compose_file)
# Add seperate TG config for versions after 1.1...
if version[:2] != "0." and version[:3] != "1.0":
output("trustgraph/config.json", tg_config_file)
# Add generated config files from additionals (if available)
# Skip trustgraph/config.json since it's handled above
for item in additionals:
if item['path'] != 'trustgraph/config.json':
output(item['path'], item['content'])
# Fallback: Walk resources directory if additionals not available
# This maintains backward compatibility with older versions
if not has_additionals and os.path.isdir(self.resources):
for root, dirs, files in os.walk(self.resources):
for file in files:
file_path = os.path.join(root, file)
rel_path = os.path.relpath(file_path, self.resources)
with open(file_path, 'r') as f:
content = f.read()
output(rel_path, content)
logger.info("Generation complete.")
return mem.getvalue()
def generate_k8s(self, platform, version, config):
processed = self.generate_resources(config)
y = yaml.dump(processed)
mem = BytesIO()
with zipfile.ZipFile(mem, mode='w') as out:
def output(name, content):
logger.info(f"Adding {name}...")
out.writestr(name, content)
fname = "resources.yaml"
output(fname, y)
logger.info("Generation complete.")
return mem.getvalue()

View file

@ -0,0 +1,17 @@
apiVersion: 1
providers:
- name: 'trustgraph.ai'
orgId: 1
folder: 'TrustGraph'
folderUid: 'b6c5be90-d432-4df8-aeab-737c7b151228'
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: false

View file

@ -0,0 +1,21 @@
apiVersion: 1
prune: true
datasources:
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
# <string> Sets a custom UID to reference this
# data source in other parts of the configuration.
# If not specified, Grafana generates one.
uid: 'f6b18033-5918-4e05-a1ca-4cb30343b129'
url: http://prometheus:9090
basicAuth: false
withCredentials: false
isDefault: true
editable: true

View file

@ -0,0 +1,247 @@
global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.
# Attach these labels to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
monitor: 'trustgraph'
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries
# scraped from this config.
# TrustGraph services
- job_name: 'agent-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'agent-manager:8000'
- job_name: 'api-gateway'
scrape_interval: 5s
static_configs:
- targets:
- 'api-gateway:8000'
- job_name: 'chunker'
scrape_interval: 5s
static_configs:
- targets:
- 'chunker:8000'
- job_name: 'config-svc'
scrape_interval: 5s
static_configs:
- targets:
- 'config-svc:8000'
- job_name: 'document-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'document-embeddings:8000'
- job_name: 'document-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'document-rag:8000'
- job_name: 'embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'embeddings:8000'
- job_name: 'graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-embeddings:8000'
- job_name: 'graph-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-rag:8000'
- job_name: 'kg-extract-agent'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-agent:8000'
- job_name: 'kg-extract-definitions'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-definitions:8000'
- job_name: 'kg-extract-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-objects:8000'
- job_name: 'kg-extract-relationships'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-relationships:8000'
- job_name: 'kg-extract-ontology'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-ontology:8000'
- job_name: 'kg-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-manager:8000'
- job_name: 'kg-store'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-store:8000'
- job_name: 'librarian'
scrape_interval: 5s
static_configs:
- targets:
- 'librarian:8000'
- job_name: 'mcp-server'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-server:8000'
- job_name: 'mcp-tool'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-tool:8000'
- job_name: 'metering'
scrape_interval: 5s
static_configs:
- targets:
- 'metering:8000'
- job_name: 'metering-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'metering-rag:8000'
- job_name: 'nlp-query'
scrape_interval: 5s
static_configs:
- targets:
- 'nlp-query:8000'
- job_name: 'pdf-decoder'
scrape_interval: 5s
static_configs:
- targets:
- 'pdf-decoder:8000'
- job_name: 'prompt'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt:8000'
- job_name: 'prompt-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt-rag:8000'
- job_name: 'query-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-doc-embeddings:8000'
- job_name: 'query-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-graph-embeddings:8000'
- job_name: 'query-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'query-objects:8000'
- job_name: 'query-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'query-triples:8000'
- job_name: 'store-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-doc-embeddings:8000'
- job_name: 'store-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-graph-embeddings:8000'
- job_name: 'store-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'store-objects:8000'
- job_name: 'store-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'store-triples:8000'
- job_name: 'structured-query'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-query:8000'
- job_name: 'structured-diag'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-query:8000'
- job_name: 'text-completion'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion:8000'
- job_name: 'text-completion-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion-rag:8000'
# Non-Trustgraph services
- job_name: 'pulsar'
scrape_interval: 5s
static_configs:
- targets:
- 'pulsar:8080'

View file

@ -0,0 +1,178 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 3,
"links": [],
"panels": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"fillOpacity": 33,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"barRadius": 0,
"barWidth": 0.97,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "auto",
"showValue": "never",
"stacking": "normal",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 100
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "code",
"expr": "topk(5, sum by (processor) (count_over_time({severity=~\"error|critical\"} [$__auto])))",
"queryType": "range",
"refId": "A"
}
],
"title": "Error volume",
"type": "barchart"
},
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {},
"overrides": []
},
"gridPos": {
"h": 18,
"w": 24,
"x": 0,
"y": 6
},
"id": 1,
"options": {
"dedupStrategy": "none",
"enableInfiniteScrolling": false,
"enableLogDetails": true,
"prettifyLogMessage": false,
"showCommonLabels": false,
"showLabels": false,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "builder",
"expr": "{severity=~\"error|critical\"} |= ``",
"queryType": "range",
"refId": "A"
}
],
"title": "Log errors",
"type": "logs"
}
],
"preload": false,
"schemaVersion": 41,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Logs",
"uid": "4bdce405-0dd3-4a14-9a8f-792152ebebba",
"version": 13
}

View file

@ -0,0 +1,17 @@
apiVersion: 1
providers:
- name: 'trustgraph.ai'
orgId: 1
folder: 'TrustGraph'
folderUid: 'b6c5be90-d432-4df8-aeab-737c7b151228'
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: false

View file

@ -0,0 +1,36 @@
apiVersion: 1
prune: true
datasources:
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
# <string> Sets a custom UID to reference this
# data source in other parts of the configuration.
# If not specified, Grafana generates one.
uid: 'f6b18033-5918-4e05-a1ca-4cb30343b129'
url: http://prometheus:9090
basicAuth: false
withCredentials: false
isDefault: true
editable: true
- name: Loki
type: loki
access: proxy
orgId: 1
# <string> Sets a custom UID to reference this
# data source in other parts of the configuration.
# If not specified, Grafana generates one.
uid: 'cf6m73l5rnvuod'
url: http://loki:3100
basicAuth: false
withCredentials: false
editable: true

View file

@ -0,0 +1,63 @@
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: debug
grpc_server_max_concurrent_streams: 1000
common:
instance_addr: 127.0.0.1
path_prefix: /tmp/loki
storage:
filesystem:
chunks_directory: /tmp/loki/chunks
rules_directory: /tmp/loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
limits_config:
metric_aggregation_enabled: true
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
pattern_ingester:
enabled: true
metric_aggregation:
loki_address: localhost:3100
ruler:
alertmanager_url: http://localhost:9093
frontend:
encoding: protobuf
# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration
# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/
#
# Statistics help us better understand how Loki is used, and they show us performance
# levels for most users. This helps us prioritize features and documentation.
# For more information on what's sent, look at
# https://github.com/grafana/loki/blob/main/pkg/analytics/stats.go
# Refer to the buildReport method to see what goes into a report.
#
# If you would like to disable reporting, uncomment the following lines:
analytics:
reporting_enabled: false

View file

@ -0,0 +1,247 @@
global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.
# Attach these labels to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
monitor: 'trustgraph'
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries
# scraped from this config.
# TrustGraph services
- job_name: 'agent-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'agent-manager:8000'
- job_name: 'api-gateway'
scrape_interval: 5s
static_configs:
- targets:
- 'api-gateway:8000'
- job_name: 'chunker'
scrape_interval: 5s
static_configs:
- targets:
- 'chunker:8000'
- job_name: 'config-svc'
scrape_interval: 5s
static_configs:
- targets:
- 'config-svc:8000'
- job_name: 'document-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'document-embeddings:8000'
- job_name: 'document-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'document-rag:8000'
- job_name: 'embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'embeddings:8000'
- job_name: 'graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-embeddings:8000'
- job_name: 'graph-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-rag:8000'
- job_name: 'kg-extract-agent'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-agent:8000'
- job_name: 'kg-extract-definitions'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-definitions:8000'
- job_name: 'kg-extract-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-objects:8000'
- job_name: 'kg-extract-relationships'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-relationships:8000'
- job_name: 'kg-extract-ontology'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-ontology:8000'
- job_name: 'kg-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-manager:8000'
- job_name: 'kg-store'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-store:8000'
- job_name: 'librarian'
scrape_interval: 5s
static_configs:
- targets:
- 'librarian:8000'
- job_name: 'mcp-server'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-server:8000'
- job_name: 'mcp-tool'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-tool:8000'
- job_name: 'metering'
scrape_interval: 5s
static_configs:
- targets:
- 'metering:8000'
- job_name: 'metering-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'metering-rag:8000'
- job_name: 'nlp-query'
scrape_interval: 5s
static_configs:
- targets:
- 'nlp-query:8000'
- job_name: 'pdf-decoder'
scrape_interval: 5s
static_configs:
- targets:
- 'pdf-decoder:8000'
- job_name: 'prompt'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt:8000'
- job_name: 'prompt-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt-rag:8000'
- job_name: 'query-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-doc-embeddings:8000'
- job_name: 'query-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-graph-embeddings:8000'
- job_name: 'query-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'query-objects:8000'
- job_name: 'query-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'query-triples:8000'
- job_name: 'store-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-doc-embeddings:8000'
- job_name: 'store-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-graph-embeddings:8000'
- job_name: 'store-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'store-objects:8000'
- job_name: 'store-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'store-triples:8000'
- job_name: 'structured-query'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-query:8000'
- job_name: 'structured-diag'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-query:8000'
- job_name: 'text-completion'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion:8000'
- job_name: 'text-completion-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion-rag:8000'
# Non-Trustgraph services
- job_name: 'pulsar'
scrape_interval: 5s
static_configs:
- targets:
- 'pulsar:8080'

View file

@ -0,0 +1,178 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 3,
"links": [],
"panels": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"fillOpacity": 33,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"barRadius": 0,
"barWidth": 0.97,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "auto",
"showValue": "never",
"stacking": "normal",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 100
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "code",
"expr": "topk(5, sum by (processor) (count_over_time({severity=~\"error|critical\"} [$__auto])))",
"queryType": "range",
"refId": "A"
}
],
"title": "Error volume",
"type": "barchart"
},
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {},
"overrides": []
},
"gridPos": {
"h": 18,
"w": 24,
"x": 0,
"y": 6
},
"id": 1,
"options": {
"dedupStrategy": "none",
"enableInfiniteScrolling": false,
"enableLogDetails": true,
"prettifyLogMessage": false,
"showCommonLabels": false,
"showLabels": false,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "builder",
"expr": "{severity=~\"error|critical\"} |= ``",
"queryType": "range",
"refId": "A"
}
],
"title": "Log errors",
"type": "logs"
}
],
"preload": false,
"schemaVersion": 41,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Logs",
"uid": "4bdce405-0dd3-4a14-9a8f-792152ebebba",
"version": 13
}

View file

@ -0,0 +1,17 @@
apiVersion: 1
providers:
- name: 'trustgraph.ai'
orgId: 1
folder: 'TrustGraph'
folderUid: 'b6c5be90-d432-4df8-aeab-737c7b151228'
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: false

View file

@ -0,0 +1,36 @@
apiVersion: 1
prune: true
datasources:
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
# <string> Sets a custom UID to reference this
# data source in other parts of the configuration.
# If not specified, Grafana generates one.
uid: 'f6b18033-5918-4e05-a1ca-4cb30343b129'
url: http://prometheus:9090
basicAuth: false
withCredentials: false
isDefault: true
editable: true
- name: Loki
type: loki
access: proxy
orgId: 1
# <string> Sets a custom UID to reference this
# data source in other parts of the configuration.
# If not specified, Grafana generates one.
uid: 'cf6m73l5rnvuod'
url: http://loki:3100
basicAuth: false
withCredentials: false
editable: true

View file

@ -0,0 +1,63 @@
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: debug
grpc_server_max_concurrent_streams: 1000
common:
instance_addr: 127.0.0.1
path_prefix: /tmp/loki
storage:
filesystem:
chunks_directory: /tmp/loki/chunks
rules_directory: /tmp/loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
limits_config:
metric_aggregation_enabled: true
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
pattern_ingester:
enabled: true
metric_aggregation:
loki_address: localhost:3100
ruler:
alertmanager_url: http://localhost:9093
frontend:
encoding: protobuf
# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration
# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/
#
# Statistics help us better understand how Loki is used, and they show us performance
# levels for most users. This helps us prioritize features and documentation.
# For more information on what's sent, look at
# https://github.com/grafana/loki/blob/main/pkg/analytics/stats.go
# Refer to the buildReport method to see what goes into a report.
#
# If you would like to disable reporting, uncomment the following lines:
analytics:
reporting_enabled: false

View file

@ -0,0 +1,247 @@
global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.
# Attach these labels to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
monitor: 'trustgraph'
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries
# scraped from this config.
# TrustGraph services
- job_name: 'agent-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'agent-manager:8000'
- job_name: 'api-gateway'
scrape_interval: 5s
static_configs:
- targets:
- 'api-gateway:8000'
- job_name: 'chunker'
scrape_interval: 5s
static_configs:
- targets:
- 'chunker:8000'
- job_name: 'config-svc'
scrape_interval: 5s
static_configs:
- targets:
- 'config-svc:8000'
- job_name: 'document-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'document-embeddings:8000'
- job_name: 'document-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'document-rag:8000'
- job_name: 'embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'embeddings:8000'
- job_name: 'graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-embeddings:8000'
- job_name: 'graph-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-rag:8000'
- job_name: 'kg-extract-agent'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-agent:8000'
- job_name: 'kg-extract-definitions'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-definitions:8000'
- job_name: 'kg-extract-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-objects:8000'
- job_name: 'kg-extract-relationships'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-relationships:8000'
- job_name: 'kg-extract-ontology'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-ontology:8000'
- job_name: 'kg-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-manager:8000'
- job_name: 'kg-store'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-store:8000'
- job_name: 'librarian'
scrape_interval: 5s
static_configs:
- targets:
- 'librarian:8000'
- job_name: 'mcp-server'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-server:8000'
- job_name: 'mcp-tool'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-tool:8000'
- job_name: 'metering'
scrape_interval: 5s
static_configs:
- targets:
- 'metering:8000'
- job_name: 'metering-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'metering-rag:8000'
- job_name: 'nlp-query'
scrape_interval: 5s
static_configs:
- targets:
- 'nlp-query:8000'
- job_name: 'pdf-decoder'
scrape_interval: 5s
static_configs:
- targets:
- 'pdf-decoder:8000'
- job_name: 'prompt'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt:8000'
- job_name: 'prompt-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt-rag:8000'
- job_name: 'query-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-doc-embeddings:8000'
- job_name: 'query-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-graph-embeddings:8000'
- job_name: 'query-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'query-objects:8000'
- job_name: 'query-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'query-triples:8000'
- job_name: 'store-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-doc-embeddings:8000'
- job_name: 'store-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-graph-embeddings:8000'
- job_name: 'store-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'store-objects:8000'
- job_name: 'store-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'store-triples:8000'
- job_name: 'structured-query'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-query:8000'
- job_name: 'structured-diag'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-query:8000'
- job_name: 'text-completion'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion:8000'
- job_name: 'text-completion-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion-rag:8000'
# Non-Trustgraph services
- job_name: 'pulsar'
scrape_interval: 5s
static_configs:
- targets:
- 'pulsar:8080'

View file

@ -0,0 +1,178 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 3,
"links": [],
"panels": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"fillOpacity": 33,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"barRadius": 0,
"barWidth": 0.97,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "auto",
"showValue": "never",
"stacking": "normal",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 100
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "code",
"expr": "topk(5, sum by (processor) (count_over_time({severity=~\"error|critical\"} [$__auto])))",
"queryType": "range",
"refId": "A"
}
],
"title": "Error volume",
"type": "barchart"
},
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {},
"overrides": []
},
"gridPos": {
"h": 18,
"w": 24,
"x": 0,
"y": 6
},
"id": 1,
"options": {
"dedupStrategy": "none",
"enableInfiniteScrolling": false,
"enableLogDetails": true,
"prettifyLogMessage": false,
"showCommonLabels": false,
"showLabels": false,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "builder",
"expr": "{severity=~\"error|critical\"} |= ``",
"queryType": "range",
"refId": "A"
}
],
"title": "Log errors",
"type": "logs"
}
],
"preload": false,
"schemaVersion": 41,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Logs",
"uid": "4bdce405-0dd3-4a14-9a8f-792152ebebba",
"version": 13
}

View file

@ -0,0 +1,17 @@
apiVersion: 1
providers:
- name: 'trustgraph.ai'
orgId: 1
folder: 'TrustGraph'
folderUid: 'b6c5be90-d432-4df8-aeab-737c7b151228'
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: false

View file

@ -0,0 +1,36 @@
apiVersion: 1
prune: true
datasources:
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
# <string> Sets a custom UID to reference this
# data source in other parts of the configuration.
# If not specified, Grafana generates one.
uid: 'f6b18033-5918-4e05-a1ca-4cb30343b129'
url: http://prometheus:9090
basicAuth: false
withCredentials: false
isDefault: true
editable: true
- name: Loki
type: loki
access: proxy
orgId: 1
# <string> Sets a custom UID to reference this
# data source in other parts of the configuration.
# If not specified, Grafana generates one.
uid: 'cf6m73l5rnvuod'
url: http://loki:3100
basicAuth: false
withCredentials: false
editable: true

View file

@ -0,0 +1,63 @@
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: debug
grpc_server_max_concurrent_streams: 1000
common:
instance_addr: 127.0.0.1
path_prefix: /tmp/loki
storage:
filesystem:
chunks_directory: /tmp/loki/chunks
rules_directory: /tmp/loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
limits_config:
metric_aggregation_enabled: true
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
pattern_ingester:
enabled: true
metric_aggregation:
loki_address: localhost:3100
ruler:
alertmanager_url: http://localhost:9093
frontend:
encoding: protobuf
# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration
# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/
#
# Statistics help us better understand how Loki is used, and they show us performance
# levels for most users. This helps us prioritize features and documentation.
# For more information on what's sent, look at
# https://github.com/grafana/loki/blob/main/pkg/analytics/stats.go
# Refer to the buildReport method to see what goes into a report.
#
# If you would like to disable reporting, uncomment the following lines:
analytics:
reporting_enabled: false

View file

@ -0,0 +1,247 @@
global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.
# Attach these labels to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
monitor: 'trustgraph'
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries
# scraped from this config.
# TrustGraph services
- job_name: 'agent-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'agent-manager:8000'
- job_name: 'api-gateway'
scrape_interval: 5s
static_configs:
- targets:
- 'api-gateway:8000'
- job_name: 'chunker'
scrape_interval: 5s
static_configs:
- targets:
- 'chunker:8000'
- job_name: 'config-svc'
scrape_interval: 5s
static_configs:
- targets:
- 'config-svc:8000'
- job_name: 'document-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'document-embeddings:8000'
- job_name: 'document-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'document-rag:8000'
- job_name: 'embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'embeddings:8000'
- job_name: 'graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-embeddings:8000'
- job_name: 'graph-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-rag:8000'
- job_name: 'kg-extract-agent'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-agent:8000'
- job_name: 'kg-extract-definitions'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-definitions:8000'
- job_name: 'kg-extract-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-objects:8000'
- job_name: 'kg-extract-relationships'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-relationships:8000'
- job_name: 'kg-extract-ontology'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-ontology:8000'
- job_name: 'kg-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-manager:8000'
- job_name: 'kg-store'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-store:8000'
- job_name: 'librarian'
scrape_interval: 5s
static_configs:
- targets:
- 'librarian:8000'
- job_name: 'mcp-server'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-server:8000'
- job_name: 'mcp-tool'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-tool:8000'
- job_name: 'metering'
scrape_interval: 5s
static_configs:
- targets:
- 'metering:8000'
- job_name: 'metering-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'metering-rag:8000'
- job_name: 'nlp-query'
scrape_interval: 5s
static_configs:
- targets:
- 'nlp-query:8000'
- job_name: 'pdf-decoder'
scrape_interval: 5s
static_configs:
- targets:
- 'pdf-decoder:8000'
- job_name: 'prompt'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt:8000'
- job_name: 'prompt-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt-rag:8000'
- job_name: 'query-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-doc-embeddings:8000'
- job_name: 'query-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-graph-embeddings:8000'
- job_name: 'query-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'query-objects:8000'
- job_name: 'query-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'query-triples:8000'
- job_name: 'store-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-doc-embeddings:8000'
- job_name: 'store-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-graph-embeddings:8000'
- job_name: 'store-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'store-objects:8000'
- job_name: 'store-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'store-triples:8000'
- job_name: 'structured-query'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-query:8000'
- job_name: 'structured-diag'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-query:8000'
- job_name: 'text-completion'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion:8000'
- job_name: 'text-completion-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion-rag:8000'
# Non-Trustgraph services
- job_name: 'pulsar'
scrape_interval: 5s
static_configs:
- targets:
- 'pulsar:8080'

View file

@ -0,0 +1,178 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 3,
"links": [],
"panels": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"fillOpacity": 33,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"barRadius": 0,
"barWidth": 0.97,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "auto",
"showValue": "never",
"stacking": "normal",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 100
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "code",
"expr": "topk(5, sum by (processor) (count_over_time({severity=~\"error|critical\"} [$__auto])))",
"queryType": "range",
"refId": "A"
}
],
"title": "Error volume",
"type": "barchart"
},
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {},
"overrides": []
},
"gridPos": {
"h": 18,
"w": 24,
"x": 0,
"y": 6
},
"id": 1,
"options": {
"dedupStrategy": "none",
"enableInfiniteScrolling": false,
"enableLogDetails": true,
"prettifyLogMessage": false,
"showCommonLabels": false,
"showLabels": false,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "builder",
"expr": "{severity=~\"error|critical\"} |= ``",
"queryType": "range",
"refId": "A"
}
],
"title": "Log errors",
"type": "logs"
}
],
"preload": false,
"schemaVersion": 41,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Logs",
"uid": "4bdce405-0dd3-4a14-9a8f-792152ebebba",
"version": 13
}

View file

@ -0,0 +1,17 @@
apiVersion: 1
providers:
- name: 'trustgraph.ai'
orgId: 1
folder: 'TrustGraph'
folderUid: 'b6c5be90-d432-4df8-aeab-737c7b151228'
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: false

View file

@ -0,0 +1,36 @@
apiVersion: 1
prune: true
datasources:
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
# <string> Sets a custom UID to reference this
# data source in other parts of the configuration.
# If not specified, Grafana generates one.
uid: 'f6b18033-5918-4e05-a1ca-4cb30343b129'
url: http://prometheus:9090
basicAuth: false
withCredentials: false
isDefault: true
editable: true
- name: Loki
type: loki
access: proxy
orgId: 1
# <string> Sets a custom UID to reference this
# data source in other parts of the configuration.
# If not specified, Grafana generates one.
uid: 'cf6m73l5rnvuod'
url: http://loki:3100
basicAuth: false
withCredentials: false
editable: true

View file

@ -0,0 +1,63 @@
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: debug
grpc_server_max_concurrent_streams: 1000
common:
instance_addr: 127.0.0.1
path_prefix: /tmp/loki
storage:
filesystem:
chunks_directory: /tmp/loki/chunks
rules_directory: /tmp/loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
limits_config:
metric_aggregation_enabled: true
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
pattern_ingester:
enabled: true
metric_aggregation:
loki_address: localhost:3100
ruler:
alertmanager_url: http://localhost:9093
frontend:
encoding: protobuf
# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration
# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/
#
# Statistics help us better understand how Loki is used, and they show us performance
# levels for most users. This helps us prioritize features and documentation.
# For more information on what's sent, look at
# https://github.com/grafana/loki/blob/main/pkg/analytics/stats.go
# Refer to the buildReport method to see what goes into a report.
#
# If you would like to disable reporting, uncomment the following lines:
analytics:
reporting_enabled: false

View file

@ -0,0 +1,265 @@
global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.
# Attach these labels to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
monitor: 'trustgraph'
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries
# scraped from this config.
# TrustGraph services
- job_name: 'agent-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'agent-manager:8000'
- job_name: 'api-gateway'
scrape_interval: 5s
static_configs:
- targets:
- 'api-gateway:8000'
- job_name: 'chunker'
scrape_interval: 5s
static_configs:
- targets:
- 'chunker:8000'
- job_name: 'config-svc'
scrape_interval: 5s
static_configs:
- targets:
- 'config-svc:8000'
- job_name: 'document-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'document-embeddings:8000'
- job_name: 'document-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'document-rag:8000'
- job_name: 'embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'embeddings:8000'
- job_name: 'graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-embeddings:8000'
- job_name: 'graph-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'graph-rag:8000'
- job_name: 'kg-extract-agent'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-agent:8000'
- job_name: 'kg-extract-definitions'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-definitions:8000'
- job_name: 'kg-extract-objects'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-objects:8000'
- job_name: 'kg-extract-relationships'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-relationships:8000'
- job_name: 'kg-extract-ontology'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-extract-ontology:8000'
- job_name: 'kg-manager'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-manager:8000'
- job_name: 'kg-store'
scrape_interval: 5s
static_configs:
- targets:
- 'kg-store:8000'
- job_name: 'librarian'
scrape_interval: 5s
static_configs:
- targets:
- 'librarian:8000'
- job_name: 'mcp-server'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-server:8000'
- job_name: 'mcp-tool'
scrape_interval: 5s
static_configs:
- targets:
- 'mcp-tool:8000'
- job_name: 'metering'
scrape_interval: 5s
static_configs:
- targets:
- 'metering:8000'
- job_name: 'metering-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'metering-rag:8000'
- job_name: 'nlp-query'
scrape_interval: 5s
static_configs:
- targets:
- 'nlp-query:8000'
- job_name: 'pdf-decoder'
scrape_interval: 5s
static_configs:
- targets:
- 'pdf-decoder:8000'
- job_name: 'prompt'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt:8000'
- job_name: 'prompt-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'prompt-rag:8000'
- job_name: 'query-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-doc-embeddings:8000'
- job_name: 'query-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-graph-embeddings:8000'
- job_name: 'query-row-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'query-row-embeddings:8000'
- job_name: 'query-rows'
scrape_interval: 5s
static_configs:
- targets:
- 'query-rows:8000'
- job_name: 'query-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'query-triples:8000'
- job_name: 'row-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'row-embeddings:8000'
- job_name: 'store-doc-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-doc-embeddings:8000'
- job_name: 'store-graph-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-graph-embeddings:8000'
- job_name: 'store-row-embeddings'
scrape_interval: 5s
static_configs:
- targets:
- 'store-row-embeddings:8000'
- job_name: 'store-rows'
scrape_interval: 5s
static_configs:
- targets:
- 'store-rows:8000'
- job_name: 'store-triples'
scrape_interval: 5s
static_configs:
- targets:
- 'store-triples:8000'
- job_name: 'structured-query'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-query:8000'
- job_name: 'structured-diag'
scrape_interval: 5s
static_configs:
- targets:
- 'structured-diag:8000'
- job_name: 'text-completion'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion:8000'
- job_name: 'text-completion-rag'
scrape_interval: 5s
static_configs:
- targets:
- 'text-completion-rag:8000'
# Non-Trustgraph services
- job_name: 'pulsar'
scrape_interval: 5s
static_configs:
- targets:
- 'pulsar:8080'

View file

@ -0,0 +1,178 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 3,
"links": [],
"panels": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"fillOpacity": 33,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"barRadius": 0,
"barWidth": 0.97,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"orientation": "auto",
"showValue": "never",
"stacking": "normal",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 100
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "code",
"expr": "topk(5, sum by (processor) (count_over_time({severity=~\"error|critical\"} [$__auto])))",
"queryType": "range",
"refId": "A"
}
],
"title": "Error volume",
"type": "barchart"
},
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"fieldConfig": {
"defaults": {},
"overrides": []
},
"gridPos": {
"h": 18,
"w": 24,
"x": 0,
"y": 6
},
"id": 1,
"options": {
"dedupStrategy": "none",
"enableInfiniteScrolling": false,
"enableLogDetails": true,
"prettifyLogMessage": false,
"showCommonLabels": false,
"showLabels": false,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"pluginVersion": "12.1.1",
"targets": [
{
"datasource": {
"type": "loki",
"uid": "cf6m73l5rnvuod"
},
"direction": "backward",
"editorMode": "builder",
"expr": "{severity=~\"error|critical\"} |= ``",
"queryType": "range",
"refId": "A"
}
],
"title": "Log errors",
"type": "logs"
}
],
"preload": false,
"schemaVersion": 41,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Logs",
"uid": "4bdce405-0dd3-4a14-9a8f-792152ebebba",
"version": 13
}

View file

@ -0,0 +1,17 @@
apiVersion: 1
providers:
- name: 'trustgraph.ai'
orgId: 1
folder: 'TrustGraph'
folderUid: 'b6c5be90-d432-4df8-aeab-737c7b151228'
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: false

Some files were not shown because too many files have changed in this diff Show more