mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 09:29:38 +02:00
Merge commit '74cc8a4685' as 'ai-context/trustgraph-templates'
This commit is contained in:
commit
c386f68743
1216 changed files with 116347 additions and 0 deletions
25
ai-context/trustgraph-templates/.github/workflows/cla.yml
vendored
Normal file
25
ai-context/trustgraph-templates/.github/workflows/cla.yml
vendored
Normal 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]'
|
||||
76
ai-context/trustgraph-templates/.github/workflows/deploy-prod.yaml
vendored
Normal file
76
ai-context/trustgraph-templates/.github/workflows/deploy-prod.yaml
vendored
Normal 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 }}
|
||||
|
||||
31
ai-context/trustgraph-templates/.github/workflows/pull-request.yaml
vendored
Normal file
31
ai-context/trustgraph-templates/.github/workflows/pull-request.yaml
vendored
Normal 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
|
||||
|
||||
45
ai-context/trustgraph-templates/.github/workflows/undeploy-prod.yaml
vendored
Normal file
45
ai-context/trustgraph-templates/.github/workflows/undeploy-prod.yaml
vendored
Normal 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: ""
|
||||
|
||||
22
ai-context/trustgraph-templates/.gitignore
vendored
Normal file
22
ai-context/trustgraph-templates/.gitignore
vendored
Normal 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
|
||||
38
ai-context/trustgraph-templates/Containerfile
Normal file
38
ai-context/trustgraph-templates/Containerfile
Normal 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
|
||||
|
||||
168
ai-context/trustgraph-templates/DIALOG-FLOW.md
Normal file
168
ai-context/trustgraph-templates/DIALOG-FLOW.md
Normal 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
|
||||
22
ai-context/trustgraph-templates/Makefile
Normal file
22
ai-context/trustgraph-templates/Makefile
Normal 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}
|
||||
311
ai-context/trustgraph-templates/README.md
Normal file
311
ai-context/trustgraph-templates/README.md
Normal 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.
|
||||
320
ai-context/trustgraph-templates/TEST_STRATEGY.md
Normal file
320
ai-context/trustgraph-templates/TEST_STRATEGY.md
Normal 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.
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
244
ai-context/trustgraph-templates/docs/garage-deployment.md
Normal file
244
ai-context/trustgraph-templates/docs/garage-deployment.md
Normal 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/)
|
||||
416
ai-context/trustgraph-templates/docs/tech-specs/tests.md
Normal file
416
ai-context/trustgraph-templates/docs/tech-specs/tests.md
Normal 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
|
||||
```
|
||||
188
ai-context/trustgraph-templates/docs/templates-structure.md
Normal file
188
ai-context/trustgraph-templates/docs/templates-structure.md
Normal 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
|
||||
|
|
@ -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>",
|
||||
}
|
||||
},
|
||||
]
|
||||
14
ai-context/trustgraph-templates/pulumi/Pulumi.prod.yaml
Normal file
14
ai-context/trustgraph-templates/pulumi/Pulumi.prod.yaml
Normal 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"
|
||||
3
ai-context/trustgraph-templates/pulumi/Pulumi.yaml
Normal file
3
ai-context/trustgraph-templates/pulumi/Pulumi.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
name: config-svc
|
||||
runtime: nodejs
|
||||
description: Config service
|
||||
323
ai-context/trustgraph-templates/pulumi/index.ts
Normal file
323
ai-context/trustgraph-templates/pulumi/index.ts
Normal 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,
|
||||
}
|
||||
);
|
||||
|
||||
4695
ai-context/trustgraph-templates/pulumi/package-lock.json
generated
Normal file
4695
ai-context/trustgraph-templates/pulumi/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
7
ai-context/trustgraph-templates/pulumi/package.json
Normal file
7
ai-context/trustgraph-templates/pulumi/package.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@pulumi/command": "^1.1.3",
|
||||
"@pulumi/gcp": "^9.10.0",
|
||||
"@pulumi/pulumi": "^3.216.0"
|
||||
}
|
||||
}
|
||||
18
ai-context/trustgraph-templates/pulumi/tsconfig.json
Normal file
18
ai-context/trustgraph-templates/pulumi/tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
68
ai-context/trustgraph-templates/pyproject.toml
Normal file
68
ai-context/trustgraph-templates/pyproject.toml
Normal 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",
|
||||
]
|
||||
|
||||
493
ai-context/trustgraph-templates/test-dialog-flow.py
Normal file
493
ai-context/trustgraph-templates/test-dialog-flow.py
Normal 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()
|
||||
416
ai-context/trustgraph-templates/test-docs-flow.py
Normal file
416
ai-context/trustgraph-templates/test-docs-flow.py
Normal 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()
|
||||
189
ai-context/trustgraph-templates/tests/README.md
Normal file
189
ai-context/trustgraph-templates/tests/README.md
Normal 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.
|
||||
26
ai-context/trustgraph-templates/tests/configs/cloud-aws.json
Normal file
26
ai-context/trustgraph-templates/tests/configs/cloud-aws.json
Normal 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": {}
|
||||
}
|
||||
]
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
]
|
||||
23
ai-context/trustgraph-templates/tests/configs/minimal.json
Normal file
23
ai-context/trustgraph-templates/tests/configs/minimal.json
Normal 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": {}
|
||||
}
|
||||
]
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
]
|
||||
125
ai-context/trustgraph-templates/tests/conftest.py
Normal file
125
ai-context/trustgraph-templates/tests/conftest.py
Normal 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
|
||||
|
|
@ -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}"
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
0
ai-context/trustgraph-templates/tests/unit/__init__.py
Normal file
0
ai-context/trustgraph-templates/tests/unit/__init__.py
Normal file
38
ai-context/trustgraph-templates/tests/unit/test_api.py
Normal file
38
ai-context/trustgraph-templates/tests/unit/test_api.py
Normal 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')
|
||||
101
ai-context/trustgraph-templates/tests/unit/test_generator.py
Normal file
101
ai-context/trustgraph-templates/tests/unit/test_generator.py
Normal 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
|
||||
60
ai-context/trustgraph-templates/tests/unit/test_packager.py
Normal file
60
ai-context/trustgraph-templates/tests/unit/test_packager.py
Normal 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
|
||||
)
|
||||
62
ai-context/trustgraph-templates/tests/unit/test_run.py
Normal file
62
ai-context/trustgraph-templates/tests/unit/test_run.py
Normal 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
|
||||
111
ai-context/trustgraph-templates/tests/validation/test_schema.py
Normal file
111
ai-context/trustgraph-templates/tests/validation/test_schema.py
Normal 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}")
|
||||
|
|
@ -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))
|
||||
|
|
@ -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))
|
||||
|
|
@ -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))
|
||||
|
|
@ -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}")
|
||||
|
|
@ -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
|
||||
269
ai-context/trustgraph-templates/tests/validators/kubernetes.py
Normal file
269
ai-context/trustgraph-templates/tests/validators/kubernetes.py
Normal 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
|
||||
235
ai-context/trustgraph-templates/tests/validators/trustgraph.py
Normal file
235
ai-context/trustgraph-templates/tests/validators/trustgraph.py
Normal 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
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
]
|
||||
125
ai-context/trustgraph-templates/tiber/config-BM-ICP-GAUDI2.json
Normal file
125
ai-context/trustgraph-templates/tiber/config-BM-ICP-GAUDI2.json
Normal 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": {}
|
||||
}
|
||||
]
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
]
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
]
|
||||
124
ai-context/trustgraph-templates/tiber/config-VM-SPR-LRG.json
Normal file
124
ai-context/trustgraph-templates/tiber/config-VM-SPR-LRG.json
Normal 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": {}
|
||||
}
|
||||
]
|
||||
124
ai-context/trustgraph-templates/tiber/config-VM-SPR-MED.json
Normal file
124
ai-context/trustgraph-templates/tiber/config-VM-SPR-MED.json
Normal 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": {}
|
||||
}
|
||||
]
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
]
|
||||
123
ai-context/trustgraph-templates/tiber/config-VM-SPR-SML.json
Normal file
123
ai-context/trustgraph-templates/tiber/config-VM-SPR-SML.json
Normal 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": {}
|
||||
}
|
||||
]
|
||||
127
ai-context/trustgraph-templates/tiber/config-gaudi2-tgi.json
Normal file
127
ai-context/trustgraph-templates/tiber/config-gaudi2-tgi.json
Normal 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": {}
|
||||
}
|
||||
]
|
||||
129
ai-context/trustgraph-templates/tiber/config-gaudi2-vllm.json
Normal file
129
ai-context/trustgraph-templates/tiber/config-gaudi2-vllm.json
Normal 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": {}
|
||||
}
|
||||
]
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from . import run
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
|
||||
206
ai-context/trustgraph-templates/trustgraph_configurator/api.py
Normal file
206
ai-context/trustgraph-templates/trustgraph_configurator/api.py
Normal 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)
|
||||
|
||||
23
ai-context/trustgraph-templates/trustgraph_configurator/generator.py
Executable file
23
ai-context/trustgraph-templates/trustgraph_configurator/generator.py
Executable 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)
|
||||
121
ai-context/trustgraph-templates/trustgraph_configurator/index.py
Normal file
121
ai-context/trustgraph-templates/trustgraph_configurator/index.py
Normal 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]
|
||||
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
@ -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()
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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'
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue