Synthetic Data Generation System¶
Package: cogniverse-synthetic (Implementation Layer) Location: libs/synthetic/cogniverse_synthetic
The synthetic data generation system creates training examples for Cogniverse optimizers by sampling tenant-scoped backend content. Production agent callbacks supervise entity extraction, query enhancement, profile selection, and routing; routing query generation also uses a validated DSPy module. Workflow plans are source-grounded local generation.
Overview¶
The service selects from configured backend profiles, samples content through the backend interface, and emits schema-validated examples for training.
Supported Optimizers¶
The OPTIMIZER_REGISTRY (registry.py) currently registers seven optimizer names. Two of them (unified, cross_modal) reuse the generator and schema of another entry rather than shipping a dedicated generator:
- query_enhancement (
QueryEnhancementGenerator/QueryEnhancementExampleSchema) - Query expansions, synonyms, and reasoning - entity_extraction (
EntityExtractionGenerator/EntityExtractionExampleSchema) - Typed entities and relationships - profile (
ProfileGenerator/ProfileSelectionExampleSchema) - Per-query backend profile classification (modality, complexity, canonical intent) forProfileSelectionAgent - routing (
RoutingGenerator/RoutingExperienceSchema) - Entity-based advanced routing - workflow (
WorkflowGenerator/WorkflowExecutionSchema) - Multi-agent workflow orchestration - unified (
WorkflowGenerator/WorkflowExecutionSchema, same asworkflow) - Combines routing decisions with workflow planning for end-to-end optimization - cross_modal (
ProfileGenerator/ProfileSelectionExampleSchema, same asprofile) - Generates profile-selection examples from multi-modal samples
Architecture¶
System Overview¶
flowchart TB
subgraph SyntheticService["<span style='color:#000'>Synthetic Data Generation Service</span>"]
Service["<span style='color:#000'>SyntheticDataService<br/>Main Orchestrator</span>"]
Service --> ProfileSelector
Service --> BackendQuerier
Service --> Generators
ProfileSelector["<span style='color:#000'>ProfileSelector<br/>LLM or Rule-based</span>"]
BackendQuerier["<span style='color:#000'>BackendQuerier<br/>Backend Sampling</span>"]
subgraph Generators["<span style='color:#000'>Generators</span>"]
QueryEnhancementGen["<span style='color:#000'>QueryEnhancementGenerator</span>"]
EntityExtractionGen["<span style='color:#000'>EntityExtractionGenerator</span>"]
ProfileGen["<span style='color:#000'>ProfileGenerator</span>"]
RoutingGen["<span style='color:#000'>RoutingGenerator</span>"]
WorkflowGen["<span style='color:#000'>WorkflowGenerator</span>"]
end
subgraph Utilities["<span style='color:#000'>Utilities</span>"]
TopicExtraction["<span style='color:#000'>Topic extraction<br/>Topics, entities</span>"]
AgentInferrer["<span style='color:#000'>AgentInferrer<br/>Agent Mapping</span>"]
end
RoutingGen --> TopicExtraction
RoutingGen --> AgentInferrer
end
subgraph ExternalSystems["<span style='color:#000'>External Systems</span>"]
Backend[("<span style='color:#000'>Backend<br/>Vespa/Other</span>")]
LLM["<span style='color:#000'>LLM Client<br/>Optional</span>"]
BackendConfig["<span style='color:#000'>Backend Config<br/>Profiles</span>"]
end
ProfileSelector -.-> LLM
Service --> BackendConfig
BackendQuerier --> Backend
subgraph DataFlow["<span style='color:#000'>Data Flow</span>"]
Request["<span style='color:#000'>SyntheticDataRequest</span>"] --> Service
Service --> Response["<span style='color:#000'>SyntheticDataResponse<br/>Generated Examples</span>"]
end
style SyntheticService fill:#ce93d8,stroke:#7b1fa2,color:#000
style Service fill:#ba68c8,stroke:#7b1fa2,color:#000
style ProfileSelector fill:#ffb74d,stroke:#ef6c00,color:#000
style BackendQuerier fill:#ffb74d,stroke:#ef6c00,color:#000
style Generators fill:#a5d6a7,stroke:#388e3c,color:#000
style QueryEnhancementGen fill:#81c784,stroke:#388e3c,color:#000
style EntityExtractionGen fill:#81c784,stroke:#388e3c,color:#000
style ProfileGen fill:#81c784,stroke:#388e3c,color:#000
style RoutingGen fill:#81c784,stroke:#388e3c,color:#000
style WorkflowGen fill:#81c784,stroke:#388e3c,color:#000
style Utilities fill:#ffcc80,stroke:#ef6c00,color:#000
style TopicExtraction fill:#ffb74d,stroke:#ef6c00,color:#000
style AgentInferrer fill:#ffb74d,stroke:#ef6c00,color:#000
style ExternalSystems fill:#90caf9,stroke:#1565c0,color:#000
style Backend fill:#64b5f6,stroke:#1565c0,color:#000
style LLM fill:#b0bec5,stroke:#546e7a,color:#000
style BackendConfig fill:#b0bec5,stroke:#546e7a,color:#000
style DataFlow fill:#a5d6a7,stroke:#388e3c,color:#000
style Request fill:#81c784,stroke:#388e3c,color:#000
style Response fill:#81c784,stroke:#388e3c,color:#000 Generation Pipeline¶
Steps 1, 2, and 4 are the same for every optimizer. Step 3 is shown here for the routing optimizer, which combines canonical topic extraction, the production entity extractor, a validated DSPy query module, and the production routing decision. Workflow generation uses AgentInferrer; profile generation uses its production profile-selection callback (see "Generators" below).
%%{init: {"theme": "base", "themeVariables": {"actorBkg": "#90caf9", "actorBorder": "#1565c0", "actorTextColor": "#000000", "lineColor": "#546e7a", "noteBkgColor": "#ffcc80", "noteBorderColor": "#ef6c00", "noteTextColor": "#000000", "signalTextColor": "#000000"}}}%%
sequenceDiagram
participant API as REST API / Python
participant Service as SyntheticDataService
participant PS as ProfileSelector
participant BQ as BackendQuerier
participant Gen as RoutingGenerator
participant TE as Topic extraction
participant Entity as Entity Agent
participant Gateway as Routing Gateway
participant Backend as Backend DB
participant DSPy as ValidatedEntityQueryGenerator
API->>Service: generate(request)
Note over Service: Deployed Profile Discovery and Selection
Service->>Backend: schema_exists(base_schema, tenant_id) per candidate
Backend-->>Service: tenant-deployed profile configs
Service->>PS: select_profiles(optimizer_name, optimizer_task, available_profiles, max_profiles)
PS->>PS: LLM reasoning or<br/>rule-based scoring
PS-->>Service: [profiles, reasoning]
Note over Service: Step 2: Content Sampling
Service->>BQ: query_profiles(profile_configs, sample_size, strategy, tenant_id)
BQ->>Backend: get_tenant_schema_name(tenant_id, base_schema)
Backend-->>BQ: concrete tenant schema
BQ->>Backend: query_metadata_documents(concrete_schema, tenant YQL, hits, tenant_id)
Backend-->>BQ: sampled documents
BQ-->>Service: sampled_content
Note over Service: Step 3: Data Generation (routing example)
Service->>Gen: generate(sampled_content, target_count)
Gen->>PE: extract(sampled_content)
PE-->>Gen: patterns (topics, entities,<br/>temporal, content_types)
Gen->>Entity: extract typed entities from current source
Entity-->>Gen: entities and relationships
Gen->>DSPy: forward(topics, entities, entity_types)
DSPy-->>Gen: validated query or<br/>contextual error
Gen->>Gateway: route(query, tenant_id)
Gateway-->>Gen: chosen_agent and exact confidence
Gen-->>Service: List[RoutingExperienceSchema]
Note over Service: Step 4: Response
Service->>Service: validate exact count, schema,<br/>canonical unique queries
Service-->>API: SyntheticDataResponse Core Components¶
1. Registry (registry.py)¶
Central configuration mapping optimizers to generators and schemas:
from cogniverse_synthetic.registry import OPTIMIZER_REGISTRY, get_optimizer_config
# Get optimizer configuration
config = get_optimizer_config("profile")
print(config.schema_class) # ProfileSelectionExampleSchema
print(config.backend_query_strategy) # "diverse"
2. Schemas (schemas.py)¶
Pydantic models for all optimizer training data:
ProfileSelectionExampleSchema- ProfileSelectionAgent training examplesQueryEnhancementExampleSchema- Query-expansion training examplesEntityExtractionExampleSchema- Typed-entity training examplesRoutingExperienceSchema- Entity-based routingWorkflowExecutionSchema- Workflow execution patternsSyntheticDataRequest/SyntheticDataResponse- API contracts
Every public model forbids unknown fields. Obsolete request, generated-example, and response keys fail validation instead of being silently discarded.
SyntheticDataRequest requires a canonical tenant ID, count in 1..10000, vespa_sample_size in 1..10000, and max_profiles in 1..10. The optional singular strategy override must be one of diverse, temporal_recent, entity_rich, and multi_modal_sequences. When it is omitted, the service uses the optimizer registry value: entity_rich for entity extraction and routing, multi_modal_sequences for workflow, unified, and cross-modal generation, and diverse for profile and query enhancement. Explicit JSON null is invalid; omit the field to use the registry value. RoutingExperienceSchema.timestamp and WorkflowExecutionSchema.timestamp default to timezone-aware UTC datetimes.
3. ProfileSelector (profile_selector.py)¶
Selects optimal backend profiles for data generation:
import asyncio
from cogniverse_synthetic.profile_selector import ProfileSelector
async def main() -> None:
selector = ProfileSelector() # No LM client selects the rule-based path.
profiles, reasoning = await selector.select_profiles(
optimizer_name="profile",
optimizer_task="Choose a backend profile for each search query",
available_profiles={
"video_colpali_smol500_mv_frame": {
"embedding_type": "multi_vector",
"pipeline_config": {"chunk_strategy": "frame"},
},
"video_xclip_sv_chunk_6s": {
"embedding_type": "global",
"pipeline_config": {"chunk_strategy": "temporal"},
},
},
max_profiles=2,
)
print(profiles, reasoning)
asyncio.run(main())
Selection Strategies:
- LM-based: Used when a compatible LM client is explicitly configured; invalid output or a transport failure raises
- Rule-based: Heuristic scoring with model-family diversity when no LM client is configured
An LM client must implement async generate(prompt: str) -> str. Its response must be one JSON object with a non-empty selected list of unique, known profile names, no more than max_profiles, plus non-empty reasoning. Unknown or duplicate names, invalid JSON, empty fields, excess selections, and transport errors raise; the selector never silently switches to rule-based selection.
4. BackendQuerier (backend_querier.py)¶
Samples content from backend storage (Vespa or other) using Backend interface:
import asyncio
from cogniverse_synthetic.backend_querier import BackendQuerier
from cogniverse_foundation.config.unified_config import (
BackendConfig,
BackendProfileConfig,
FieldMappingConfig,
)
from cogniverse_vespa import VespaBackend
TENANT_ID = "your_org:production"
PROFILE_NAME = "video_colpali_smol500_mv_frame"
backend_config = BackendConfig(
tenant_id=TENANT_ID,
profiles={
PROFILE_NAME: BackendProfileConfig(
profile_name=PROFILE_NAME,
schema_name=PROFILE_NAME,
type="video",
)
},
)
querier = BackendQuerier(
backend=VespaBackend(
backend_config=backend_config,
schema_loader=schema_loader,
config_manager=config_manager,
),
backend_config=backend_config,
field_mappings=FieldMappingConfig(),
)
async def main() -> None:
samples = await querier.query_profiles(
profile_configs=[
{
"profile_name": PROFILE_NAME,
**backend_config.profiles[PROFILE_NAME].to_dict(),
}
],
sample_size=2,
strategy="diverse",
tenant_id=TENANT_ID,
)
print(samples)
asyncio.run(main())
Sampling Strategies:
diverse- Broad unfiltered sampling across the selected profilestemporal_recent- Content created within the last 90 days, using an exact epoch-millisecond cutoff and descendingcreation_timestamporder so the newest matching document is returned firstentity_rich- Schema-safe sampling followed by a non-empty check of the descriptions and/or transcripts the selected profile's pipeline produces; a profile with neither capability is invalid for this strategy. Results are paged in descending creation-time order until the requested number of qualifying documents is found or the tenant schema is exhausted.multi_modal_sequences- Content from different modalities
Backend Abstraction: Uses the Backend interface. VespaBackend is the workspace implementation available today. Keep base schema names in profile configuration. Before selection, SyntheticDataService checks those schemas for the request tenant and gives selection and generation the same deployed-profile set. BackendQuerier then resolves each selected tenant-specific schema and uses that concrete name for both the backend query and its YQL source. Schema lookup, resolution, and query failures propagate instead of being treated as an empty content set.
query_profiles(profile_configs, sample_size, strategy="diverse", *, tenant_id) distributes the requested sample count across profiles. query_by_modality(modality, sample_size, *, tenant_id) selects configured profiles whose type matches VIDEO, DOCUMENT, IMAGE, or AUDIO. Both public methods require the tenant as a keyword-only argument. Direct real-backend queries reject unsupported strategies and profiles without a non-empty schema_name; entity_rich additionally requires a description-producing or audio-transcribing pipeline. Backend lookup and query errors propagate.
5. Generators (generators/)¶
Five concrete generators implement the BaseGenerator interface.
QueryEnhancementGenerator (generators/query_enhancement.py) enumerates unique source-template queries and invokes the production query-enhancement callback for each one with the query, tenant, and exact sampled source text. It copies the changed enhanced query, expansion terms, synonyms, and reasoning exactly, and rejects malformed or mismatched output. expansion_terms must be literal terms drawn from the supplied source text, while synonyms remain free-form.
EntityExtractionGenerator (generators/entity_extraction.py) produces typed entity and relationship examples from sampled content.
ProfileGenerator (generators/profile.py):
import asyncio
from cogniverse_synthetic.generators.profile import ProfileGenerator
async def main() -> None:
generator = ProfileGenerator(profile_labeler=production_profile_labeler)
examples = await generator.generate(
sampled_content=[{"title": "transformer architecture tutorial"}],
target_count=1,
profile_configs={
"video_colpali_smol500_mv_frame": {
"type": "video",
"schema_name": "video_colpali_smol500_mv_frame",
"embedding_type": "multi_vector",
"pipeline_config": {"extract_keyframes": True},
},
"document_text": {
"type": "document",
"schema_name": "document_text",
"embedding_type": "single_vector",
"pipeline_config": {},
},
},
tenant_id="acme:production",
)
print([example.model_dump() for example in examples])
asyncio.run(main())
RoutingGenerator (generators/routing.py):
Routing examples require the production entity-extraction and routing callbacks plus an explicit tenant. The generator never replaces either label with a local heuristic.
import dspy
from cogniverse_foundation.config.unified_config import (
DSPyModuleConfig,
OptimizerGenerationConfig,
)
from cogniverse_synthetic.generators.routing import RoutingGenerator
async def generate_routing_examples(lm: dspy.LM):
optimizer_config = OptimizerGenerationConfig(
optimizer_type="routing",
dspy_modules={
"query_generator": DSPyModuleConfig(
signature_class=(
"cogniverse_synthetic.dspy_signatures.GenerateEntityQuery"
)
)
},
)
generator = RoutingGenerator(
entity_extractor=production_entity_extractor,
routing_decider=production_routing_decider,
optimizer_config=optimizer_config,
)
with dspy.context(lm=lm):
return await generator.generate(
sampled_content=[
{
"topic": "TensorFlow object detection tutorial",
"description": "TensorFlow trains a neural network.",
"schema_name": "video_colpali_smol500_mv_frame",
}
],
target_count=1,
tenant_id="acme:production",
)
The caller supplies a configured DSPy LM to generate_routing_examples. Routing requires optimizer_config.dspy_modules["query_generator"]; missing configuration, empty entity sets, invalid query/reasoning/retry metadata, and exhausted entity-validation retries raise rather than producing an example. Enhanced queries annotate matches in one pass against the original query. When entity names overlap, the longest complete match wins, and annotation type text inserted into the output is never scanned as another entity.
WorkflowGenerator (generators/workflow.py):
import asyncio
import json
from pathlib import Path
from cogniverse_foundation.config.unified_config import SyntheticGeneratorConfig
from cogniverse_synthetic.generators.workflow import WorkflowGenerator
from cogniverse_synthetic.utils.agent_inference import AgentInferrer
async def main() -> None:
raw_config = json.loads(Path("configs/config.json").read_text())
generator_config = SyntheticGeneratorConfig.from_dict(
{"tenant_id": "acme:production", **raw_config["synthetic"]}
)
agent_inferrer = AgentInferrer(
agents_config=raw_config["agents"],
agent_mappings=(
generator_config.optimizer_configs["modality"].agent_mappings
),
)
generator = WorkflowGenerator(agent_inferrer=agent_inferrer)
examples = await generator.generate(
sampled_content=[
{
"topic": "machine learning deployment",
"schema_name": "video_colpali_smol500_mv_frame",
}
],
target_count=2,
)
print([example.model_dump() for example in examples])
asyncio.run(main())
6. Utilities (utils/)¶
Canonical topic extraction (generators/base.py:extract_topic):
- Extract a grounded topic from the shipped schema text roles
- Reject content hashes and invisible markers
-
Apply an optional
max_wordsbudget when callers need a shorter topic -
Extract entities — capitalized terms, CamelCase/technical terms (
extract_entities) -
Extract temporal patterns — years, recency modifiers (
extract_temporal_patterns) -
Extract content types — tutorial, guide, overview, etc. (
extract_content_types) -
Extract relationships — entity co-occurrence (
extract_relationships) -
extract(content_samples)runs the topic, entity, temporal, and content-type extractors and returns{topics, entities, temporal, content_types}.RoutingGeneratorcallsextract_relationships(entities)separately.
For creation_timestamp, extract_temporal_patterns accepts a timezone-aware datetime, an ISO-8601 string with an explicit offset (including Z), or a numeric Unix value. Numeric magnitudes are interpreted as seconds below 1e11, milliseconds from 1e11, microseconds from 1e14, and nanoseconds from 1e17. Values are normalized to UTC. Naive datetime values and strings are rejected instead of silently assuming UTC; any present value that cannot be parsed raises ValueError. Valid values are bucketed as recent/latest (under 30 days), from this quarter (under 90), from this year (under 365), or from YYYY.
AgentInferrer (utils/agent_inference.py):
Builds modality→agent mappings from configured modalities plus search/analysis capabilities, and role→agent mappings from configured roles or semantic capabilities. The caller passes the active configuration's top-level agents object explicitly. Missing modalities or required roles raise instead of selecting an implicit agent.
-
Map modality → agent (
infer_from_modality) -
Infer agent from content characteristics — schema/embedding type, then description keywords (
infer_from_characteristics) -
Infer agent from a natural-language task description (
get_agent_for_task) -
Generate a workflow agent sequence for a complexity/modality/task combination (
infer_workflow_sequence) -
List agents compatible with a modality (
get_compatible_agents) -
Validate that an agent sequence is well-formed (
validate_agent_sequence)
7. DSPy Signatures and Modules¶
DSPy Signatures (dspy_signatures.py):
Defines the interface between generators and LLMs for query generation. Signatures guide LLM behavior through field descriptions.
Available Signatures:
GenerateModalityQuery(modality, topics, context) -> queryGenerateEntityQuery(topics, entities, entity_types) -> reasoning, queryInferAgentFromModality(modality, query, available_agents) -> agent_name, reasoning
DSPy Modules (dspy_modules.py):
Validated query generators with built-in quality checks and retry logic.
import dspy
from cogniverse_synthetic.dspy_modules import ValidatedEntityQueryGenerator
def generate_entity_query(lm: dspy.LM) -> dspy.Prediction:
generator = ValidatedEntityQueryGenerator(max_retries=3)
with dspy.context(lm=lm):
return generator(
topics="object detection, neural networks",
entities=["TensorFlow", "PyTorch"],
entity_types=["TECHNOLOGY", "TECHNOLOGY"],
)
Key Features:
- ChainOfThought: LLM reasons before generating (better quality)
- Validation: Ensures output meets requirements (e.g., entity presence)
- Retry Logic: Up to the explicitly configured
max_retriesattempts to generate a query containing every supplied entity as a complete span - Explicit Failure: If every retry fails validation (including empty LM output), raises with the retry count and attempted entities instead of emitting fabricated training data
- Optimization Ready: Can be compiled with DSPy optimizers (BootstrapFewShot, MIPRO, etc.)
8. Approval System (approval/)¶
Human-in-the-loop review and feedback loop for low-confidence synthetic examples. Implements the domain-agnostic ConfidenceExtractor and FeedbackHandler interfaces from cogniverse_core.approval.interfaces.
SyntheticDataConfidenceExtractor (approval/confidence_extractor.py): Strictly validates the canonical schema, then returns the schema's native observed confidence. Profile-selection, query-enhancement, entity-extraction, and generated routing records have no observed confidence, so they return the explicit 0.0 review sentinel. It does not infer confidence from query length, entity presence, reasoning text, or retry count.
from cogniverse_synthetic.approval.confidence_extractor import (
SyntheticDataConfidenceExtractor,
)
from cogniverse_synthetic.schemas import ProfileSelectionExampleSchema
example = ProfileSelectionExampleSchema(
query="Find a TensorFlow deployment tutorial",
available_profiles="document_text",
selected_profile="document_text",
reasoning="The query requests searchable document text.",
query_intent="document_search",
modality="document",
complexity="simple",
).model_dump()
extractor = SyntheticDataConfidenceExtractor()
assert extractor.extract(example) == 0.0
assert extractor.get_confidence_breakdown(example)["requires_human_review"] is True
SyntheticDataFeedbackHandler (approval/feedback_handler.py): Regenerates a rejected entity, routing, profile-selection, or query-enhancement example outside the event-loop thread. The schema-aware DSPy regenerator receives the complete source record, freeform reviewer instruction, exact structured corrections, and Pydantic JSON Schema. Each call uses the configured request deadline. Workflow records instead apply explicit observed-value corrections. It returns a new ReviewItem with status=ApprovalStatus.REGENERATED; unchanged, invalid, timed-out, and exhausted generations raise a contextual RuntimeError.
import dspy
from cogniverse_core.approval.interfaces import ReviewDecision, ReviewItem
from cogniverse_synthetic.approval.feedback_handler import SyntheticDataFeedbackHandler
from cogniverse_synthetic.dspy_modules import ValidatedSyntheticExampleRegenerator
async def regenerate_rejected_item(lm: dspy.LM) -> ReviewItem:
item = ReviewItem(
item_id="synthetic-001",
confidence=0.4,
data={
"query": "find TensorFlow tutorials",
"entities": [{"text": "TensorFlow", "type": "TECHNOLOGY"}],
"entity_types": "TECHNOLOGY",
"relationships": [],
},
)
decision = ReviewDecision(
item_id=item.item_id,
approved=False,
feedback="entity should be PyTorch, not TensorFlow",
corrections={
"entities": [{"text": "PyTorch", "type": "TECHNOLOGY"}],
"topics": ["beginner tutorials"],
},
)
generator = ValidatedSyntheticExampleRegenerator(max_retries=3)
generator.lm = lm
handler = SyntheticDataFeedbackHandler(
generator=generator,
generation_timeout_seconds=120.0,
max_regeneration_attempts=2,
)
return await handler.process_rejection(item, decision)
Usage¶
Python API¶
import asyncio
import json
from pathlib import Path
from cogniverse_foundation.config.unified_config import (
BackendConfig,
BackendProfileConfig,
SyntheticGeneratorConfig,
)
from cogniverse_agents.profile_selection_agent import (
ProfileSelectionAgent,
ProfileSelectionDeps,
ProfileSelectionInput,
)
from cogniverse_synthetic import SyntheticDataService
from cogniverse_synthetic.schemas import SyntheticDataRequest
from cogniverse_vespa import VespaBackend
TENANT_ID = "your_org:production"
def video_generator_config(tenant_id: str) -> SyntheticGeneratorConfig:
raw_synthetic = json.loads(Path("configs/config.json").read_text())["synthetic"]
return SyntheticGeneratorConfig.from_dict(
{"tenant_id": tenant_id, **raw_synthetic}
)
async def main() -> None:
profile_name = "video_colpali_smol500_mv_frame"
backend_config = BackendConfig(
tenant_id=TENANT_ID,
profiles={
profile_name: BackendProfileConfig(
profile_name=profile_name,
type="video",
schema_name=profile_name,
embedding_type="multi_vector",
pipeline_config={"extract_keyframes": True},
)
},
)
backend = VespaBackend(
backend_config=backend_config,
schema_loader=schema_loader,
config_manager=config_manager,
)
profile_agent = ProfileSelectionAgent(
deps=ProfileSelectionDeps(available_profiles=[profile_name])
)
async def label_profile(query: str, profiles: list[str], tenant_id: str):
return await profile_agent.process(
ProfileSelectionInput(
query=query,
available_profiles=profiles,
tenant_id=tenant_id,
)
)
service = SyntheticDataService(
backend=backend,
backend_config=backend_config,
generator_config=video_generator_config(TENANT_ID),
agents_config=agents_config,
profile_labeler=label_profile,
llm_client=None, # Rule-based profile selection.
)
response = await service.generate(
SyntheticDataRequest(
optimizer="profile",
count=3,
vespa_sample_size=4,
strategy="diverse",
max_profiles=2,
tenant_id=TENANT_ID,
)
)
print(response.count, response.selected_profiles)
print([example["query"] for example in response.data])
asyncio.run(main())
REST API¶
import json
from pathlib import Path
from fastapi import FastAPI
from cogniverse_agents.entity_extraction_agent import (
EntityExtractionAgent,
EntityExtractionDeps,
EntityExtractionInput,
)
from cogniverse_agents.profile_selection_agent import (
ProfileSelectionAgent,
ProfileSelectionDeps,
ProfileSelectionInput,
)
from cogniverse_foundation.config.unified_config import (
BackendConfig,
BackendProfileConfig,
SyntheticGeneratorConfig,
)
from cogniverse_synthetic import router, configure_service
from cogniverse_vespa import VespaBackend
TENANT_ID = "your_org:production"
PROFILE_NAME = "video_colpali_smol500_mv_frame"
raw_synthetic = json.loads(Path("configs/config.json").read_text())["synthetic"]
generator_config = SyntheticGeneratorConfig.from_dict(
{"tenant_id": TENANT_ID, **raw_synthetic}
)
backend_config = BackendConfig(
tenant_id=TENANT_ID,
profiles={
PROFILE_NAME: BackendProfileConfig(
profile_name=PROFILE_NAME,
type="video",
schema_name=PROFILE_NAME,
embedding_type="multi_vector",
pipeline_config={"extract_keyframes": True},
)
},
)
backend = VespaBackend(
backend_config=backend_config,
schema_loader=schema_loader,
config_manager=config_manager,
)
entity_agent = EntityExtractionAgent(deps=EntityExtractionDeps())
profile_agent = ProfileSelectionAgent(
deps=ProfileSelectionDeps(available_profiles=[PROFILE_NAME])
)
async def extract_entities(text: str, tenant_id: str):
return await entity_agent.process(
EntityExtractionInput(query=text, tenant_id=tenant_id)
)
async def label_profile(query: str, profiles: list[str], tenant_id: str):
return await profile_agent.process(
ProfileSelectionInput(
query=query,
available_profiles=profiles,
tenant_id=tenant_id,
)
)
configure_service(
backend=backend,
backend_config=backend_config,
generator_config=generator_config,
agents_config=agents_config,
entity_extractor=extract_entities,
profile_labeler=label_profile,
llm_client=None,
)
app = FastAPI()
app.include_router(router)
Endpoints:
POST /synthetic/generate
curl -X POST http://localhost:8000/synthetic/generate \
-H "Content-Type: application/json" \
-d '{
"optimizer": "profile",
"count": 50,
"vespa_sample_size": 100,
"max_profiles": 2,
"tenant_id": "your_org:production"
}'
GET /synthetic/optimizers
curl http://localhost:8000/synthetic/optimizers
# Returns: {"profile": "ProfileSelectionAgent optimization...", ...}
GET /synthetic/optimizers/{name}
curl http://localhost:8000/synthetic/optimizers/profile
# Returns: Detailed optimizer info with schema, generator, etc.
GET /synthetic/health
curl http://localhost:8000/synthetic/health
# Returns: {"status": "healthy", "service": "synthetic-data-generation",
# "generators": <lazy-initialized count>, "optimizers": 7}
POST /synthetic/batch/generate
curl -X POST "http://localhost:8000/synthetic/batch/generate?optimizer=profile&count_per_batch=100&num_batches=5&tenant_id=your_org:production&strategy=temporal_recent"
# Generates one globally unique pool of 500 examples in one service call,
# then reports five contiguous batch partitions
The product of count_per_batch and num_batches must not exceed 10,000. Duplicate queries are rejected across the complete pool even when volatile identifiers, timestamps, or metadata differ.
The batch endpoint accepts the same optional singular strategy override and forwards it to the one total-count generation request. Omit the query parameter to use the selected optimizer's registered strategy.
CLI¶
cogniverse_runtime.optimization_cli exposes a synthetic mode that wraps SyntheticDataService directly (run_synthetic_generation in optimization_cli.py), then submits each optimizer's examples for review: examples at or above the auto-approval threshold are approved at once, the rest wait for a reviewer (--options '{"human_review": false}' approves them all). --options also sets count (default 50), vespa_sample_size, strategy and max_profiles. Approved examples become input to the matching optimizer:
uv run python -m cogniverse_runtime.optimization_cli \
--mode synthetic --tenant-id your_org:production \
--agents query_enhancement,profile,routing,entity_extraction
--agents is a comma-separated list of optimizer types (defaults to query_enhancement,profile,routing,entity_extraction if omitted). Each type has an active training-data consumer; unconsumed outputs such as workflow are rejected. RoutingGenerator's DSPy module runs under the tenant's LM, bound with dspy.context(lm=...) around each generate call, since the mode executes inside an asyncio task where dspy.configure cannot be called.
Integration with Optimizers¶
ProfileSelectionAgent¶
import asyncio
import json
from pathlib import Path
from cogniverse_foundation.config.unified_config import SyntheticGeneratorConfig
from cogniverse_synthetic import SyntheticDataService
from cogniverse_synthetic.schemas import SyntheticDataRequest
TENANT_ID = "your_org:production"
raw_synthetic = json.loads(Path("configs/config.json").read_text())["synthetic"]
generator_config = SyntheticGeneratorConfig.from_dict(
{"tenant_id": TENANT_ID, **raw_synthetic}
)
async def main() -> None:
service = SyntheticDataService(
backend=backend,
backend_config=backend_config,
generator_config=generator_config,
agents_config=agents_config,
)
response = await service.generate(
SyntheticDataRequest(
optimizer="profile", count=3, tenant_id=TENANT_ID
)
)
# Pass response.data to run_profile_optimization in
# cogniverse_runtime.optimization_cli.
print(f"Generated {response.count} ProfileSelectionExampleSchema examples")
asyncio.run(main())
Workflow Intelligence¶
import asyncio
import json
from pathlib import Path
from cogniverse_foundation.config.unified_config import SyntheticGeneratorConfig
from cogniverse_sdk.interfaces.workflow_store import WorkflowExecution
from cogniverse_synthetic import SyntheticDataService
from cogniverse_synthetic.schemas import SyntheticDataRequest
TENANT_ID = "your_org:production"
raw_synthetic = json.loads(Path("configs/config.json").read_text())["synthetic"]
generator_config = SyntheticGeneratorConfig.from_dict(
{"tenant_id": TENANT_ID, **raw_synthetic}
)
async def main() -> None:
service = SyntheticDataService(
backend=backend,
backend_config=backend_config,
generator_config=generator_config,
agents_config=agents_config,
)
response = await service.generate(
SyntheticDataRequest(
optimizer="workflow", count=3, tenant_id=TENANT_ID
)
)
executions = [WorkflowExecution(**example) for example in response.data]
print([execution.workflow_id for execution in executions])
asyncio.run(main())
response.data from the Python API contains aware UTC datetime values and can construct WorkflowExecution directly as shown. A JSON round trip changes the timestamp to text; WorkflowExecution.from_dict then requires the exact field set and canonical UTC ISO-8601 form such as 2026-08-05T10:30:00+00:00. It rejects missing or extra fields, non-string or non-canonical timestamps, and naive timestamps. Feed the resulting records to WorkflowIntelligence.record_execution in the tenant's batch workflow.
Configuration¶
Backend Configuration¶
The service accepts a BackendConfig instance with backend profiles. Every explicit profile requires a non-empty string schema_name; missing, blank, and non-string values fail before backend access, and profile_name is never used as a substitute.
import json
from pathlib import Path
from cogniverse_foundation.config.unified_config import (
BackendConfig,
BackendProfileConfig,
SyntheticGeneratorConfig,
)
from cogniverse_synthetic import SyntheticDataService
backend_config = BackendConfig(
tenant_id="acme:production",
backend_type="vespa",
url="http://localhost",
port=8080,
profiles={
"video_colpali_smol500_mv_frame": BackendProfileConfig(
profile_name="video_colpali_smol500_mv_frame",
type="video",
schema_name="video_colpali_smol500_mv_frame",
embedding_model="TomoroAI/tomoro-colqwen3-embed-4b",
pipeline_config={"chunk_strategy": "frame"},
),
"video_xclip_sv_chunk_6s": BackendProfileConfig(
profile_name="video_xclip_sv_chunk_6s",
type="video",
schema_name="video_xclip_sv_chunk_6s",
embedding_model="microsoft/xclip-large-patch14",
pipeline_config={"chunk_strategy": "temporal"},
),
},
)
raw_synthetic = json.loads(Path("configs/config.json").read_text())["synthetic"]
generator_config = SyntheticGeneratorConfig.from_dict(
{"tenant_id": "acme:production", **raw_synthetic}
)
service = SyntheticDataService(
backend=backend,
backend_config=backend_config,
generator_config=generator_config,
agents_config=agents_config,
)
BackendConfig and SyntheticGeneratorConfig require tenant_id both when constructed directly and through from_dict. BackendConfig.from_dict uses the serialized key type for backend_type and hydrates nested profile objects. SyntheticGeneratorConfig.from_dict hydrates nested OptimizerGenerationConfig values. BackendProfileConfig.profile_name, OptimizerGenerationConfig.optimizer_type, and DSPyModuleConfig.signature_class are required. A routing optimizer configuration must contain a query_generator DSPy module entry. The synthetic generator configuration has no compiled_path field.
Profile Selection¶
With an LM client implementing async generate(prompt: str) -> str:
import json
from cogniverse_synthetic import SyntheticDataService
class ProfileSelectionLMClient:
async def generate(self, prompt: str) -> str:
del prompt
return json.dumps(
{
"selected": ["video_colpali_smol500_mv_frame"],
"reasoning": "Frame-level visual retrieval matches the task.",
}
)
service = SyntheticDataService(
backend=backend,
backend_config=backend_config,
generator_config=generator_config,
agents_config=agents_config,
llm_client=ProfileSelectionLMClient(),
)
Rule-based (no LM call):
from cogniverse_synthetic import SyntheticDataService
service = SyntheticDataService(
backend=backend,
backend_config=backend_config,
generator_config=generator_config,
agents_config=agents_config,
) # No llm_client means rule-based selection.
Testing¶
# Run all synthetic data tests
JAX_PLATFORM_NAME=cpu uv run pytest \
tests/routing/unit/synthetic/ tests/synthetic/ \
tests/agents/integration/test_replacement_record_store_real_redis.py \
tests/runtime/integration/test_backend_querier_real_vespa.py \
-v --tb=long > /tmp/synthetic-tests.log 2>&1
# Run specific test file
uv run pytest tests/routing/unit/synthetic/test_service.py -v --tb=long \
> /tmp/synthetic-service-tests.log 2>&1
# Test generator
uv run pytest tests/routing/unit/synthetic/test_generators_integration.py \
-v --tb=long > /tmp/synthetic-generator-tests.log 2>&1
Test Coverage:
- Base generator tests (
test_base_generator.py) - Generator integration tests (
test_generators_integration.py) - Registry tests (
test_registry.py) - Schema tests (
test_schemas.py) - Service tests (
test_service.py) - Approval system tests (
test_approval_system.py) - Backend querier tests (
test_backend_querier.py)
Development¶
Adding a New Optimizer¶
- Define a Pydantic output model in
schemas.py, including concrete field constraints and a representative schema example. - Add one
OptimizerConfigentry inregistry.pywith its schema, generator class name, sampling strategy, agent-mapping requirement, and defaults. - Add a module under
generators/that implementsBaseGenerator.generate(sampled_content, target_count, **kwargs); return exactlytarget_countvalidated schema instances and reject invalid inputs or boundary output. - Export the class from
generators/__init__.py, import it inservice.py, and add its lazy_get_generatordispatch branch. - Add exact-output generator tests, service dispatch coverage, schema tests, and real-boundary concurrency and failure-path checks when the generator calls a shared service or external boundary. Update this guide and the package README with the new public surface in the same change.
Performance Considerations¶
- Batch Size: Use
batch/generateendpoint for large datasets - Profile Selection: Rule-based selection avoids an LM call; LM-based selection uses the configured client's reasoning
- Backend Sampling: Larger
sample_size= more diverse patterns - Caching: Profile selection reasoning is not cached (stateless)
- Concurrency: Synchronous backend and routing-LM calls run outside the event-loop thread
- DSPy Optimization: Configure each DSPy module by signature and module type; the synthetic generator configuration has no
compiled_pathfield
Troubleshooting¶
Issue: ValueError: Unknown optimizer 'xyz' - Fix: Check OPTIMIZER_REGISTRY.keys() for valid names
Issue: Empty sampled_content from BackendQuerier - Fix: Ensure a live Backend instance is configured, initialized, and passed to the service, and that the tenant schema contains documents - Note: A backend is required. Query failures propagate instead of being converted into fabricated or partial samples.
Issue: Profile selection returns unexpected profiles - Fix: Provide backend_config with the exact deployed profile definitions, including canonical type and non-empty schema_name values - Note: A non-empty configured profile map is required. Invalid profiles are rejected rather than repaired.
Issue: ValueError: RoutingGenerator requires optimizer configuration - Fix: Provide OptimizerGenerationConfig with DSPy modules configuration - Note: Configuration is required - no defaults or fallbacks
Issue: Generated routing queries don't mention any of the requested entities - Fix: Check DSPy LM is configured correctly (use create_dspy_lm() and dspy.context(lm=...)); a misconfigured or unreliable LM causes every retry to fail validation - Note: ValidatedEntityQueryGenerator raises after exhausting max_retries; no synthetic example is returned from invalid LM output
Issue: Tests fail with import errors - Fix: Reinstall package: uv pip install -e libs/synthetic
Package Location¶
The synthetic data generation package is part of the Implementation Layer:
libs/
└── synthetic/ # cogniverse-synthetic package
├── README.md # Package usage and contracts
├── cogniverse_synthetic/
│ ├── __init__.py # Public package exports
│ ├── service.py # Main service orchestrator
│ ├── api.py # FastAPI router
│ ├── schemas.py # Pydantic models
│ ├── registry.py # Optimizer registry
│ ├── profile_selector.py # Profile selection logic
│ ├── backend_querier.py # Backend content sampling
│ ├── dspy_signatures.py # DSPy signature definitions
│ ├── dspy_modules.py # Validated query generators
│ ├── generators/ # Concrete generators
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── entity_extraction.py
│ │ ├── profile.py
│ │ ├── query_enhancement.py
│ │ ├── routing.py
│ │ └── workflow.py
│ ├── utils/ # Pattern extraction and agent inference
│ │ ├── __init__.py
│ │ └── agent_inference.py
│ └── approval/ # Human-in-loop approval system
│ ├── __init__.py
│ ├── confidence_extractor.py
│ └── feedback_handler.py
└── pyproject.toml
# Tests are located at project root:
tests/
├── synthetic/
│ ├── integration/ # Integration tests (test_profile_synthetic_service.py, etc.)
│ └── unit/ # Unit tests (test_profile_generator.py, etc.)
└── routing/unit/synthetic/ # Routing-focused synthetic unit tests (7 test files + conftest.py)
Related Documentation¶
- 13-Package Architecture - Overall system architecture
- Routing Module - Query routing module (uses this system)
- Optimization Module - DSPy optimization integration
Public Python Surface¶
| Module | Public surface |
|---|---|
cogniverse_synthetic | OPTIMIZER_REGISTRY, OptimizerConfig, ProfileSelectionExampleSchema, RoutingExperienceSchema, WorkflowExecutionSchema, SyntheticDataRequest, SyntheticDataResponse, SyntheticDataService, router, configure_service |
schemas | The five optimizer schemas above, SyntheticDataRequest, SyntheticDataResponse, and SAMPLING_STRATEGIES |
registry | OptimizerConfig, OPTIMIZER_REGISTRY, get_optimizer_config, list_optimizers, get_optimizer_schema, validate_optimizer_exists |
service | SyntheticDataService(backend, backend_config, generator_config, agents_config, llm_client=None, entity_extractor=None, routing_decider=None, query_enhancer=None, profile_labeler=None), where query_enhancer receives (query, tenant_id, source_text) and backend is live with a non-empty backend_config.profiles; exposes async generate(request) and synchronous get_optimizer_info(name) / list_all_optimizers() |
api | router, get_service, configure_service, and endpoint callables generate_synthetic_data, list_available_optimizers, get_optimizer_details, health_check, generate_batch_synthetic_data |
generators | BaseGenerator, QueryEnhancementGenerator, EntityExtractionGenerator, ProfileGenerator, RoutingGenerator, WorkflowGenerator; each exposes async generate, validate_inputs, and get_generator_info |
profile_selector | ProfileSelector(llm_client=None, generator_config=None) and async select_profiles(optimizer_name, optimizer_task, available_profiles, max_profiles=3) |
backend_querier | BackendQuerier(backend, backend_config, field_mappings), async query_profiles(...), and async query_by_modality(...) |
utils | AgentInferrer(agents_config, agent_mappings) with configuration validation and exact workflow-sequence inference; canonical topic extraction is provided by cogniverse_synthetic.generators.base.extract_topic(...) |
dspy_signatures / dspy_modules | GenerateModalityQuery, GenerateEntityQuery, RegenerateSyntheticExample, InferAgentFromModality, ValidatedEntityQueryGenerator(max_retries).forward(topics, entities, entity_types), and ValidatedSyntheticExampleRegenerator(max_retries) |
approval | SyntheticDataConfidenceExtractor() with extract / get_confidence_breakdown; SyntheticDataFeedbackHandler(generator, generation_timeout_seconds, max_regeneration_attempts=2) with async process_rejection |
QueryEnhancementExampleSchema and EntityExtractionExampleSchema are public from cogniverse_synthetic.schemas but are not re-exported by the package root.