icdev 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- args/agent_config.yaml +113 -0
- args/audit_regimes/cisa_sbd.json +381 -0
- args/audit_regimes/cmmc_l2.json +906 -0
- args/audit_regimes/dod_cssp.json +393 -0
- args/audit_regimes/dodi_5000_87.json +297 -0
- args/audit_regimes/fedramp_moderate.json +650 -0
- args/audit_regimes/ieee_1012.json +373 -0
- args/audit_regimes/nist_800_171.json +624 -0
- args/audit_regimes/nist_800_53.json +907 -0
- args/cloudforge_blueprints/aws_commercial.yaml +29 -0
- args/cloudforge_blueprints/aws_govcloud_il4.yaml +34 -0
- args/cloudforge_blueprints/aws_govcloud_il5.yaml +38 -0
- args/cloudforge_blueprints/azure_commercial.yaml +28 -0
- args/cloudforge_blueprints/azure_gov_il4.yaml +32 -0
- args/cloudforge_blueprints/azure_gov_il5.yaml +36 -0
- args/cloudforge_blueprints/gcp_commercial.yaml +28 -0
- args/cloudforge_blueprints/oci_commercial.yaml +28 -0
- args/cloudforge_config.yaml +231 -0
- args/cloudforge_runbook_templates/backup_verify.yaml +98 -0
- args/cloudforge_runbook_templates/dr_failover.yaml +107 -0
- args/cloudforge_runbook_templates/health_check.yaml +97 -0
- args/cloudforge_runbook_templates/incident_response.yaml +101 -0
- args/cloudforge_runbook_templates/migration_cutover.yaml +105 -0
- args/cloudforge_runbook_templates/patch_rollout.yaml +92 -0
- args/cloudforge_runbook_templates/zone_provision.yaml +93 -0
- args/code_pattern_config.yaml +151 -0
- args/code_quality_config.yaml +47 -0
- args/compliance_config.yaml +17 -0
- args/control_inheritance.yaml +177 -0
- args/csp_mcp_config.yaml +41 -0
- args/cui_markings.yaml +35 -0
- args/databridge_config.yaml +232 -0
- args/db_config.yaml +116 -0
- args/decision_tables/agent_trust_decision.yaml +143 -0
- args/decision_tables/ato_boundary_impact.yaml +132 -0
- args/decision_tables/deployment_approval.yaml +152 -0
- args/degradation_matrix.yaml +163 -0
- args/devsecops_config.yaml +286 -0
- args/endpoint_security_config.yaml +207 -0
- args/exit_criteria.yaml +102 -0
- args/feature_flags.yaml +235 -0
- args/file_access_tiers.yaml +88 -0
- args/forge_studio/blueprint_config.yaml +27 -0
- args/forge_studio/component_catalog.json +411 -0
- args/forge_studio/workflow_templates.yaml +103 -0
- args/govcon_config.yaml +41 -0
- args/harness_config.yaml +67 -0
- args/innovation_config.yaml +321 -0
- args/knowledge_graph_config.yaml +113 -0
- args/llm_config.yaml +222 -0
- args/marketplace_config.yaml +260 -0
- args/monitoring_config.yaml +127 -0
- args/mosa_config.yaml +190 -0
- args/observability_tracing_config.yaml +170 -0
- args/owasp_agentic_config.yaml +171 -0
- args/pipeline_gates.yaml +197 -0
- args/project_defaults.yaml +235 -0
- args/prompt_chains.yaml +163 -0
- args/rag_config.yaml +167 -0
- args/research_config.yaml +89 -0
- args/resilience_config.yaml +197 -0
- args/ricoas_config.yaml +191 -0
- args/security_gates.yaml +763 -0
- args/storage_config.yaml +63 -0
- args/writeguard_config.yaml +131 -0
- args/zta_config.yaml +247 -0
- context/__init__.py +6 -0
- context/agent/__init__.py +6 -0
- context/agent/response_schemas/__init__.py +6 -0
- context/agent/response_schemas/debate_position.json +46 -0
- context/agent/response_schemas/fitness_scorecard.json +74 -0
- context/agent/response_schemas/review_decision.json +39 -0
- context/agent/response_schemas/task_decomposition.json +82 -0
- context/agent/response_schemas/veto_decision.json +40 -0
- context/agentic/__init__.py +6 -0
- context/agentic/architecture_patterns.md +269 -0
- context/agentic/capability_registry.yaml +223 -0
- context/agentic/csp_integration.md +30 -0
- context/agentic/csp_mcp_registry.yaml +280 -0
- context/agentic/fitness_rubric.md +56 -0
- context/agentic/governance_baseline.md +205 -0
- context/ci/__init__.py +6 -0
- context/ci/worktree_templates.json +44 -0
- context/cloud/__init__.py +6 -0
- context/cloud/csp_service_registry.json +739 -0
- context/compliance/__init__.py +6 -0
- context/compliance/ai_rmf_crosswalk.yaml +226 -0
- context/compliance/atlas_mitigations.json +293 -0
- context/compliance/atlas_techniques.json +833 -0
- context/compliance/cisa_sbd_requirements.json +477 -0
- context/compliance/cjis_security_policy.json +522 -0
- context/compliance/cmmc_practices.json +2494 -0
- context/compliance/cmmc_report_template.md +142 -0
- context/compliance/cnssi_1253_overlay.json +109 -0
- context/compliance/control_crosswalk.json +1914 -0
- context/compliance/control_families/__init__.py +6 -0
- context/compliance/csp_certifications.json +251 -0
- context/compliance/cssp_report_template.md +193 -0
- context/compliance/cui_templates/__init__.py +6 -0
- context/compliance/cui_templates/banner_block.txt +4 -0
- context/compliance/cui_templates/code_header.txt +8 -0
- context/compliance/cui_templates/document_template.md +35 -0
- context/compliance/data_type_framework_map.json +321 -0
- context/compliance/data_type_registry.json +147 -0
- context/compliance/dod_cssp_8530.json +463 -0
- context/compliance/eu_ai_act_annex_iii.json +108 -0
- context/compliance/export_templates/__init__.py +6 -0
- context/compliance/export_templates/emass_controls.csv.j2 +4 -0
- context/compliance/export_templates/evidence_package.md.j2 +39 -0
- context/compliance/export_templates/executive_summary.md.j2 +55 -0
- context/compliance/export_templates/poam_tracking.csv.j2 +4 -0
- context/compliance/fedramp_20x_ksi_schemas.json +133 -0
- context/compliance/fedramp_high_baseline.json +4370 -0
- context/compliance/fedramp_moderate_baseline.json +2183 -0
- context/compliance/fedramp_report_template.md +181 -0
- context/compliance/fips_200_areas.json +362 -0
- context/compliance/gao_ai_accountability.json +262 -0
- context/compliance/hipaa_security_rule.json +720 -0
- context/compliance/hitrust_csf_v11.json +930 -0
- context/compliance/impact_level_profiles.json +251 -0
- context/compliance/incident_response_template.md +1110 -0
- context/compliance/iso27001_2022_controls.json +750 -0
- context/compliance/iso27001_nist_bridge.json +382 -0
- context/compliance/iso42001_controls.json +254 -0
- context/compliance/ivv_checklist_template.md +80 -0
- context/compliance/ivv_report_template.md +116 -0
- context/compliance/ivv_requirements.json +372 -0
- context/compliance/mosa_crosswalk.json +327 -0
- context/compliance/mosa_framework.json +250 -0
- context/compliance/narrative_templates/AC.md.j2 +101 -0
- context/compliance/narrative_templates/AU.md.j2 +106 -0
- context/compliance/narrative_templates/IA.md.j2 +104 -0
- context/compliance/narrative_templates/SC.md.j2 +102 -0
- context/compliance/narrative_templates/SI.md.j2 +111 -0
- context/compliance/narrative_templates/__init__.py +6 -0
- context/compliance/narrative_templates/default.md.j2 +50 -0
- context/compliance/narrative_templates/executive_summary.j2 +27 -0
- context/compliance/narrative_templates/poam_milestone.j2 +19 -0
- context/compliance/narrative_templates/ssp_section.j2 +11 -0
- context/compliance/nist_800_171_controls.json +1552 -0
- context/compliance/nist_800_207_crosswalk.json +399 -0
- context/compliance/nist_800_207_zta.json +258 -0
- context/compliance/nist_800_53.json +324 -0
- context/compliance/nist_ai_600_1_genai.json +326 -0
- context/compliance/nist_ai_rmf.json +206 -0
- context/compliance/nist_sp_800_60_types.json +1667 -0
- context/compliance/omb_m25_21_high_impact_ai.json +248 -0
- context/compliance/omb_m26_04_unbiased_ai.json +262 -0
- context/compliance/owasp_agentic_asi.json +133 -0
- context/compliance/owasp_agentic_threats.json +285 -0
- context/compliance/owasp_llm_top10.json +274 -0
- context/compliance/pci_dss_v4.json +510 -0
- context/compliance/poam_template.md +117 -0
- context/compliance/safeai_controls.json +512 -0
- context/compliance/sbd_report_template.md +77 -0
- context/compliance/siem_config_templates/__init__.py +6 -0
- context/compliance/siem_config_templates/filebeat.yml +213 -0
- context/compliance/siem_config_templates/log_sources.json +208 -0
- context/compliance/soc2_trust_criteria.json +661 -0
- context/compliance/ssp_template.md +432 -0
- context/compliance/stig_templates/__init__.py +6 -0
- context/compliance/stig_templates/webapp_stig.json +139 -0
- context/compliance/xai_requirements.json +108 -0
- context/dashboard/__init__.py +6 -0
- context/dashboard/nlq_examples.json +50 -0
- context/dashboard/schema_descriptions.json +23 -0
- context/icdev_methodology.md +100 -0
- context/integration/__init__.py +6 -0
- context/integration/approval_workflows.json +32 -0
- context/integration/gitlab_field_mappings.json +33 -0
- context/integration/jira_field_mappings.json +32 -0
- context/integration/reqif_export_schema.json +23 -0
- context/integration/servicenow_field_mappings.json +22 -0
- context/languages/__init__.py +6 -0
- context/languages/framework_patterns.json +205 -0
- context/languages/language_registry.json +279 -0
- context/llm/__init__.py +6 -0
- context/llm/example_provider.py +89 -0
- context/marketplace/assets/writeguard-core.yaml +100 -0
- context/marketplace/assets/writeguard-govcon.yaml +45 -0
- context/marketplace/assets/writeguard-style-guides.yaml +44 -0
- context/mbse/__init__.py +6 -0
- context/mbse/des_report_template.md +162 -0
- context/mbse/des_requirements.json +411 -0
- context/mbse/digital_thread_patterns.json +403 -0
- context/mbse/reqif_schema.json +280 -0
- context/mbse/sysml_element_types.json +432 -0
- context/oscal/NIST_SP-800-53_rev5_catalog.json +254987 -0
- context/oscal/README.md +43 -0
- context/patterns/__init__.py +6 -0
- context/profiles/__init__.py +6 -0
- context/profiles/dod_baseline_v1.yaml +145 -0
- context/profiles/fedramp_baseline_v1.yaml +143 -0
- context/profiles/financial_baseline_v1.yaml +142 -0
- context/profiles/healthcare_baseline_v1.yaml +135 -0
- context/profiles/law_enforcement_v1.yaml +129 -0
- context/profiles/startup_v1.yaml +134 -0
- context/rag/source_mappings.json +42 -0
- context/requirements/__init__.py +6 -0
- context/requirements/ambiguity_patterns.json +97 -0
- context/requirements/boundary_impact_rules.json +123 -0
- context/requirements/default_constitutions.json +67 -0
- context/requirements/document_extraction_rules.json +58 -0
- context/requirements/gap_patterns.json +108 -0
- context/requirements/readiness_rubric.json +78 -0
- context/requirements/red_alternative_patterns.json +210 -0
- context/requirements/safe_templates.json +72 -0
- context/requirements/spec_quality_checklist.json +122 -0
- context/research/regulatory_registry.json +114 -0
- context/research/verticals/cybersecurity.json +127 -0
- context/research/verticals/defense.json +104 -0
- context/research/verticals/fintech.json +125 -0
- context/research/verticals/healthcare.json +118 -0
- context/research/verticals/logistics.json +117 -0
- context/research/verticals/trading.json +145 -0
- context/simulation/__init__.py +6 -0
- context/simulation/architecture_patterns.json +36 -0
- context/simulation/coa_templates.json +38 -0
- context/simulation/cost_models.json +23 -0
- context/simulation/risk_categories.json +46 -0
- context/supply_chain/__init__.py +6 -0
- context/supply_chain/isa_templates.json +129 -0
- context/supply_chain/nist_800_161_controls.json +247 -0
- context/supply_chain/scrm_risk_matrix.json +147 -0
- context/templates/__init__.py +6 -0
- context/templates/ansible/__init__.py +6 -0
- context/templates/ansible/playbooks/__init__.py +6 -0
- context/templates/ansible/roles/__init__.py +6 -0
- context/templates/gitlab_ci/__init__.py +6 -0
- context/templates/grafana/__init__.py +6 -0
- context/templates/kubernetes/__init__.py +6 -0
- context/templates/project/__init__.py +6 -0
- context/templates/project/api/__init__.py +6 -0
- context/templates/project/cli/__init__.py +6 -0
- context/templates/project/data_pipeline/__init__.py +6 -0
- context/templates/project/iac/__init__.py +6 -0
- context/templates/project/javascript_frontend/__init__.py +6 -0
- context/templates/project/javascript_frontend/src/__init__.py +6 -0
- context/templates/project/javascript_frontend/tests/__init__.py +6 -0
- context/templates/project/microservice/__init__.py +6 -0
- context/templates/project/python_backend/__init__.py +6 -0
- context/templates/project/python_backend/src/__init__.py +6 -0
- context/templates/project/python_backend/tests/__init__.py +6 -0
- context/templates/project/python_backend/tests/features/__init__.py +6 -0
- context/templates/project/python_backend/tests/steps/__init__.py +6 -0
- context/templates/terraform/__init__.py +6 -0
- context/templates/terraform/govcloud_base/__init__.py +6 -0
- context/templates/terraform/modules/__init__.py +6 -0
- context/tone/__init__.py +6 -0
- context/writing/grammar_rules/common_errors.json +306 -0
- context/writing/grammar_rules/govcon_vocabulary.json +113 -0
- context/writing/style_guides/academic.yaml +43 -0
- context/writing/style_guides/business.yaml +42 -0
- context/writing/style_guides/government.yaml +59 -0
- context/writing/style_guides/proposal.yaml +58 -0
- context/writing/style_guides/technical.yaml +43 -0
- docs/adr/README.md +66 -0
- docs/adr/connector-forge-decisions.md +318 -0
- docs/adr/core-decisions.md +289 -0
- docs/adr/db-decisions.md +94 -0
- docs/adr/harness-decisions.md +122 -0
- docs/adr/innovation-decisions.md +262 -0
- docs/adr/marketplace-decisions.md +109 -0
- docs/adr/sbd-decisions.md +109 -0
- docs/adr/scale-engine-decisions.md +108 -0
- docs/adr/writeguard-decisions.md +136 -0
- docs/architecture/bounded-contexts.md +1032 -0
- docs/features/phase-65-writeguard.md +139 -0
- docs/features/phase-66-marketplace-commerce.md +79 -0
- docs/features/phase-67-knowledge-ingestion-rag-autodraft.md +97 -0
- docs/features/phase-68-enhanced-autodraft-pipeline.md +109 -0
- docs/features/phase-69-proposalai-marketplace-module.md +131 -0
- docs/features/phase-70-databridge.md +214 -0
- docs/features/phase-71-databridge-messaging.md +102 -0
- docs/implementation-plan-architecture-evolution.md +614 -0
- docs/marketplace/CONTRIBUTING.md +124 -0
- docs/marketplace/module_manifest_schema.yaml +83 -0
- docs/research/ai-architecture-patterns-2024-2026.md +1236 -0
- docs/research/app-builder-platform-analysis.md +582 -0
- docs/research/architecture-patterns-c4-ddd-agentic.md +871 -0
- docs/research/flowable-boat-competitive-analysis.md +426 -0
- docs/research/modern-dev-practices-2024-2026.md +1615 -0
- docs/research/secure-by-design-cloudyrion-adaptation.md +270 -0
- goals/agent_management.md +144 -0
- goals/ai_accountability.md +90 -0
- goals/ai_narratives.md +79 -0
- goals/ai_transparency.md +76 -0
- goals/ato_simulator.md +78 -0
- goals/audit_engine.md +177 -0
- goals/bite_sized_plans.md +225 -0
- goals/boundary_supply_chain.md +206 -0
- goals/brainstorming_gate.md +186 -0
- goals/build_app.md +604 -0
- goals/cato_live_evidence.md +77 -0
- goals/cloudforge.md +106 -0
- goals/code_intelligence.md +197 -0
- goals/compliance_workflow.md +858 -0
- goals/connector_forge.md +133 -0
- goals/databridge.md +128 -0
- goals/deploy_workflow.md +390 -0
- goals/developer_scorecard.md +78 -0
- goals/devsecops_workflow.md +408 -0
- goals/firmware_sbom.md +79 -0
- goals/forge_hub.md +78 -0
- goals/golden_path.md +77 -0
- goals/harness_engineering.md +91 -0
- goals/integration_testing.md +189 -0
- goals/knowledge_graph.md +128 -0
- goals/maintenance_audit.md +196 -0
- goals/manifest.md +50 -0
- goals/monitoring.md +126 -0
- goals/mosa_workflow.md +463 -0
- goals/multi_agent_orchestration.md +68 -0
- goals/observability_traceability_xai.md +154 -0
- goals/owasp_agentic_security.md +395 -0
- goals/pr_intelligence.md +78 -0
- goals/requirements_intake.md +213 -0
- goals/secure_by_design.md +135 -0
- goals/security_scan.md +381 -0
- goals/self_healing.md +120 -0
- goals/simulation_engine.md +111 -0
- goals/subagent_review.md +205 -0
- goals/systematic_debugging.md +257 -0
- goals/tdd_workflow.md +403 -0
- goals/template_exchange.md +77 -0
- goals/thread_heatmap.md +77 -0
- goals/threat_modeler.md +77 -0
- goals/verification_iron_law.md +192 -0
- goals/vsm_dashboard.md +76 -0
- goals/writeguard.md +89 -0
- goals/zero_trust_architecture.md +403 -0
- hardprompts/__init__.py +6 -0
- hardprompts/agent/__init__.py +6 -0
- hardprompts/agent/agentic_architect.md +100 -0
- hardprompts/agent/debate_prompt.md +32 -0
- hardprompts/agent/fitness_evaluation.md +48 -0
- hardprompts/agent/governance_review.md +214 -0
- hardprompts/agent/reviewer_prompt.md +34 -0
- hardprompts/agent/skill_design.md +172 -0
- hardprompts/agent/task_decomposition.md +275 -0
- hardprompts/agent/veto_check_prompt.md +33 -0
- hardprompts/architect/__init__.py +6 -0
- hardprompts/architect/api_design.md +283 -0
- hardprompts/architect/data_model.md +277 -0
- hardprompts/architect/system_design.md +180 -0
- hardprompts/builder/__init__.py +6 -0
- hardprompts/builder/code_generation.md +59 -0
- hardprompts/builder/refactor.md +58 -0
- hardprompts/builder/scaffold_project.md +69 -0
- hardprompts/builder/test_generation.md +87 -0
- hardprompts/ci/__init__.py +6 -0
- hardprompts/ci/worktree_setup.md +35 -0
- hardprompts/compliance/__init__.py +6 -0
- hardprompts/compliance/cmmc_assessment.md +63 -0
- hardprompts/compliance/cssp_assessment.md +75 -0
- hardprompts/compliance/cui_marking.md +86 -0
- hardprompts/compliance/fedramp_assessment.md +55 -0
- hardprompts/compliance/ivv_assessment.md +96 -0
- hardprompts/compliance/poam_generation.md +57 -0
- hardprompts/compliance/sbd_assessment.md +101 -0
- hardprompts/compliance/security_categorization.md +74 -0
- hardprompts/compliance/ssp_generation.md +56 -0
- hardprompts/compliance/stig_evaluation.md +63 -0
- hardprompts/dashboard/__init__.py +6 -0
- hardprompts/dashboard/nlq_system_prompt.md +26 -0
- hardprompts/infra/__init__.py +6 -0
- hardprompts/infra/k8s_manifests.md +118 -0
- hardprompts/infra/pipeline_generation.md +160 -0
- hardprompts/infra/terraform_generation.md +92 -0
- hardprompts/integration/__init__.py +6 -0
- hardprompts/integration/approval_review.md +17 -0
- hardprompts/integration/jira_mapping.md +25 -0
- hardprompts/integration/servicenow_mapping.md +14 -0
- hardprompts/knowledge/__init__.py +6 -0
- hardprompts/knowledge/pattern_detection.md +73 -0
- hardprompts/knowledge/recommendation_engine.md +90 -0
- hardprompts/knowledge/root_cause_analysis.md +91 -0
- hardprompts/maintenance/__init__.py +6 -0
- hardprompts/maintenance/maintenance_assessment.md +82 -0
- hardprompts/mbse/__init__.py +6 -0
- hardprompts/mbse/digital_thread.md +67 -0
- hardprompts/mbse/model_import.md +62 -0
- hardprompts/mbse/model_to_code.md +65 -0
- hardprompts/modernization/__init__.py +6 -0
- hardprompts/modernization/legacy_analysis.md +93 -0
- hardprompts/modernization/migration_planning.md +150 -0
- hardprompts/modernization/seven_r_assessment.md +107 -0
- hardprompts/proposal_draft.md +53 -0
- hardprompts/rag_citation.md +12 -0
- hardprompts/rag_rerank.md +31 -0
- hardprompts/requirements/__init__.py +6 -0
- hardprompts/requirements/bdd_generation.md +35 -0
- hardprompts/requirements/clarification_prioritization.md +29 -0
- hardprompts/requirements/decomposition.md +60 -0
- hardprompts/requirements/document_extraction.md +45 -0
- hardprompts/requirements/gap_detection.md +70 -0
- hardprompts/requirements/intake_conversation.md +101 -0
- hardprompts/requirements/readiness_assessment.md +39 -0
- hardprompts/requirements/spec_quality.md +33 -0
- hardprompts/requirements/traceability_analysis.md +23 -0
- hardprompts/security/__init__.py +6 -0
- hardprompts/security/endpoint_security.md +78 -0
- hardprompts/security/threat_model.md +70 -0
- hardprompts/security/vulnerability_assessment.md +81 -0
- hardprompts/simulation/__init__.py +6 -0
- hardprompts/simulation/architecture_impact.md +27 -0
- hardprompts/simulation/coa_alternative.md +27 -0
- hardprompts/simulation/coa_generation.md +25 -0
- hardprompts/simulation/compliance_impact.md +28 -0
- hardprompts/simulation/cost_estimation.md +33 -0
- hardprompts/simulation/risk_assessment.md +28 -0
- hardprompts/translation/code_translation.md +68 -0
- hardprompts/translation/dependency_suggestion.md +44 -0
- hardprompts/translation/test_translation.md +64 -0
- hardprompts/translation/translation_repair.md +59 -0
- icdev-0.0.3.dist-info/METADATA +909 -0
- icdev-0.0.3.dist-info/RECORD +1214 -0
- icdev-0.0.3.dist-info/WHEEL +5 -0
- icdev-0.0.3.dist-info/entry_points.txt +9 -0
- icdev-0.0.3.dist-info/licenses/LICENSE +201 -0
- icdev-0.0.3.dist-info/licenses/NOTICE +11 -0
- icdev-0.0.3.dist-info/top_level.txt +7 -0
- memory/MEMORY.md +52 -0
- memory/logs/2026-02-14.md +17 -0
- memory/logs/2026-03-03.md +2 -0
- memory/logs/__init__.py +1 -0
- tools/a2a/icdev_callback_client.py +210 -0
- tools/agent/cards/architect_card.json +29 -0
- tools/agent/cards/builder_card.json +34 -0
- tools/agent/cards/compliance_card.json +29 -0
- tools/agent/cards/connector_forge_card.json +49 -0
- tools/agent/cards/devsecops_zta_card.json +24 -0
- tools/agent/cards/knowledge_card.json +29 -0
- tools/agent/cards/monitor_card.json +29 -0
- tools/agent/cards/orchestrator_card.json +29 -0
- tools/agent/cards/requirements_analyst_card.json +24 -0
- tools/agent/cards/security_card.json +29 -0
- tools/agent/cards/simulation_card.json +24 -0
- tools/agent/cards/supply_chain_card.json +24 -0
- tools/analysis/__init__.py +1 -0
- tools/analysis/code_analyzer.py +770 -0
- tools/analysis/runtime_feedback.py +379 -0
- tools/analytics/__init__.py +2 -0
- tools/analytics/scorecard.py +538 -0
- tools/analytics/vsm_engine.py +612 -0
- tools/architecture/__init__.py +2 -0
- tools/architecture/adr_extractor.py +393 -0
- tools/audit/__init__.py +1 -0
- tools/audit/audit_logger.py +199 -0
- tools/audit/audit_query.py +153 -0
- tools/audit/decision_recorder.py +73 -0
- tools/audit_engine/__init__.py +12 -0
- tools/audit_engine/ai_advisor.py +906 -0
- tools/audit_engine/cli.py +286 -0
- tools/audit_engine/comparator.py +305 -0
- tools/audit_engine/eject_scaffolder.py +399 -0
- tools/audit_engine/engine.py +614 -0
- tools/audit_engine/git_fetcher.py +341 -0
- tools/audit_engine/regime_loader.py +200 -0
- tools/audit_engine/regime_updater.py +325 -0
- tools/audit_engine/report_card.py +289 -0
- tools/audit_engine/scanner.py +684 -0
- tools/audit_engine/self_heal.py +1042 -0
- tools/ci/__init__.py +2 -0
- tools/ci/connectors/__init__.py +2 -0
- tools/ci/connectors/base_connector.py +80 -0
- tools/ci/connectors/connector_registry.py +188 -0
- tools/ci/connectors/mattermost_connector.py +159 -0
- tools/ci/connectors/slack_connector.py +197 -0
- tools/ci/core/__init__.py +2 -0
- tools/ci/core/air_gap_detector.py +115 -0
- tools/ci/core/comment_handler.py +192 -0
- tools/ci/core/conversation_manager.py +480 -0
- tools/ci/core/event_envelope.py +500 -0
- tools/ci/core/event_router.py +444 -0
- tools/ci/core/failure_parser.py +397 -0
- tools/ci/core/recovery_engine.py +527 -0
- tools/ci/gate_enforcer.py +361 -0
- tools/ci/modules/__init__.py +2 -0
- tools/ci/modules/agent.py +271 -0
- tools/ci/modules/git_ops.py +175 -0
- tools/ci/modules/state.py +117 -0
- tools/ci/modules/vcs.py +303 -0
- tools/ci/modules/workflow_ops.py +295 -0
- tools/ci/modules/worktree.py +337 -0
- tools/ci/pipeline_config_generator.py +558 -0
- tools/ci/pr_intelligence.py +485 -0
- tools/ci/triggers/__init__.py +2 -0
- tools/ci/triggers/gitlab_task_monitor.py +327 -0
- tools/ci/triggers/poll_trigger.py +237 -0
- tools/ci/triggers/webhook_server.py +356 -0
- tools/ci/workflows/__init__.py +2 -0
- tools/ci/workflows/icdev_build.py +140 -0
- tools/ci/workflows/icdev_comply.py +284 -0
- tools/ci/workflows/icdev_document.py +152 -0
- tools/ci/workflows/icdev_e2e.py +188 -0
- tools/ci/workflows/icdev_patch.py +186 -0
- tools/ci/workflows/icdev_plan.py +202 -0
- tools/ci/workflows/icdev_plan_build.py +41 -0
- tools/ci/workflows/icdev_plan_build_test.py +46 -0
- tools/ci/workflows/icdev_plan_build_test_review.py +47 -0
- tools/ci/workflows/icdev_review.py +126 -0
- tools/ci/workflows/icdev_sdlc.py +261 -0
- tools/ci/workflows/icdev_test.py +240 -0
- tools/cli/__init__.py +1 -0
- tools/cli/output_formatter.py +756 -0
- tools/cloudforge/__init__.py +12 -0
- tools/cloudforge/airgap/__init__.py +2 -0
- tools/cloudforge/airgap/il_classifier.py +70 -0
- tools/cloudforge/airgap/offline_validator.py +42 -0
- tools/cloudforge/airgap/shift_emulator.py +155 -0
- tools/cloudforge/airgap/sneakernet.py +91 -0
- tools/cloudforge/cd_hub/__init__.py +2 -0
- tools/cloudforge/cd_hub/canary_deployer.py +88 -0
- tools/cloudforge/cd_hub/gitops_renderer.py +123 -0
- tools/cloudforge/cd_hub/hub_controller.py +143 -0
- tools/cloudforge/cd_hub/pipeline_bridge.py +30 -0
- tools/cloudforge/cd_hub/rollback_engine.py +29 -0
- tools/cloudforge/cd_hub/spoke_agent.py +51 -0
- tools/cloudforge/compliance/__init__.py +2 -0
- tools/cloudforge/compliance/ato_accelerator.py +272 -0
- tools/cloudforge/compliance/control_inheritor.py +127 -0
- tools/cloudforge/compliance/evidence_generator.py +129 -0
- tools/cloudforge/compliance/poam_bridge.py +41 -0
- tools/cloudforge/compliance/ssp_bridge.py +52 -0
- tools/cloudforge/compliance/stig_bridge.py +41 -0
- tools/cloudforge/container_forge/__init__.py +2 -0
- tools/cloudforge/container_forge/bigbang_renderer.py +85 -0
- tools/cloudforge/container_forge/hardener.py +169 -0
- tools/cloudforge/container_forge/image_scanner_bridge.py +33 -0
- tools/cloudforge/container_forge/runtime_policy.py +87 -0
- tools/cloudforge/container_forge/sbom_bridge.py +42 -0
- tools/cloudforge/finops/__init__.py +2 -0
- tools/cloudforge/finops/anomaly_detector.py +78 -0
- tools/cloudforge/finops/budget_tracker.py +96 -0
- tools/cloudforge/finops/chargeback.py +69 -0
- tools/cloudforge/finops/cost_collector.py +141 -0
- tools/cloudforge/finops/optimizer.py +55 -0
- tools/cloudforge/hybrid/__init__.py +2 -0
- tools/cloudforge/hybrid/connection_manager.py +141 -0
- tools/cloudforge/hybrid/dns_federator.py +56 -0
- tools/cloudforge/hybrid/health_monitor.py +108 -0
- tools/cloudforge/hybrid/identity_federator.py +53 -0
- tools/cloudforge/hybrid/network_bridge.py +68 -0
- tools/cloudforge/hybrid/topology_manager.py +147 -0
- tools/cloudforge/hybrid/workload_abstractor.py +92 -0
- tools/cloudforge/iac/__init__.py +2 -0
- tools/cloudforge/iac/drift_detector.py +154 -0
- tools/cloudforge/iac/module_library.py +265 -0
- tools/cloudforge/iac/opentofu_adapter.py +89 -0
- tools/cloudforge/iac/pulumi_renderer.py +292 -0
- tools/cloudforge/iac/state_backend.py +146 -0
- tools/cloudforge/iac/terraform_renderer.py +626 -0
- tools/cloudforge/landing_zone/__init__.py +2 -0
- tools/cloudforge/landing_zone/blueprint_loader.py +98 -0
- tools/cloudforge/landing_zone/blueprint_validator.py +113 -0
- tools/cloudforge/landing_zone/zone_provisioner.py +306 -0
- tools/cloudforge/landing_zone/zone_state.py +143 -0
- tools/cloudforge/mbse_thread/__init__.py +2 -0
- tools/cloudforge/mbse_thread/ato_thread_weaver.py +111 -0
- tools/cloudforge/mbse_thread/control_tracer.py +68 -0
- tools/cloudforge/mbse_thread/system_boundary.py +83 -0
- tools/cloudforge/metastore/__init__.py +2 -0
- tools/cloudforge/metastore/dependency_graph.py +202 -0
- tools/cloudforge/metastore/discovery.py +192 -0
- tools/cloudforge/metastore/registry.py +185 -0
- tools/cloudforge/metastore/rto_tracker.py +92 -0
- tools/cloudforge/metastore/runbook_linker.py +82 -0
- tools/cloudforge/migration/__init__.py +2 -0
- tools/cloudforge/migration/assessor.py +187 -0
- tools/cloudforge/migration/cutover_orchestrator.py +117 -0
- tools/cloudforge/migration/databridge_bridge.py +92 -0
- tools/cloudforge/migration/planner.py +98 -0
- tools/cloudforge/migration/risk_scorer.py +97 -0
- tools/cloudforge/migration/validation_runner.py +45 -0
- tools/cloudforge/migration/workload_inventory.py +107 -0
- tools/cloudforge/provider.py +319 -0
- tools/cloudforge/providers/__init__.py +2 -0
- tools/cloudforge/providers/aws_commercial.py +92 -0
- tools/cloudforge/providers/aws_govcloud.py +229 -0
- tools/cloudforge/providers/aws_secret.py +83 -0
- tools/cloudforge/providers/azure_commercial.py +80 -0
- tools/cloudforge/providers/azure_gov.py +91 -0
- tools/cloudforge/providers/azure_secret.py +71 -0
- tools/cloudforge/providers/gcp.py +102 -0
- tools/cloudforge/providers/oci.py +102 -0
- tools/cloudforge/registry.py +140 -0
- tools/cloudforge/runbooks/__init__.py +2 -0
- tools/cloudforge/runbooks/ai_generator.py +119 -0
- tools/cloudforge/runbooks/dag_validator.py +219 -0
- tools/cloudforge/runbooks/engine.py +470 -0
- tools/cloudforge/runbooks/models.py +99 -0
- tools/cloudforge/runbooks/snippet_library.py +158 -0
- tools/cloudforge/runbooks/template_loader.py +122 -0
- tools/cloudforge/runbooks/visualization.py +108 -0
- tools/cloudforge/siem/__init__.py +2 -0
- tools/cloudforge/siem/alert_rules.py +86 -0
- tools/cloudforge/siem/correlation_engine.py +61 -0
- tools/cloudforge/siem/log_aggregator.py +113 -0
- tools/cloudforge/siem/siem_dashboard_data.py +28 -0
- tools/cloudforge/supply_chain/__init__.py +2 -0
- tools/cloudforge/supply_chain/bridge.py +33 -0
- tools/cloudforge/supply_chain/iac_dependency_scanner.py +36 -0
- tools/cloudforge/supply_chain/provider_trust_scorer.py +54 -0
- tools/compat/__init__.py +21 -0
- tools/compat/cli_harmonizer.py +251 -0
- tools/compat/datetime_utils.py +18 -0
- tools/compat/db_utils.py +190 -0
- tools/compat/platform_utils.py +123 -0
- tools/compliance/__init__.py +1 -0
- tools/compliance/accountability_manager.py +391 -0
- tools/compliance/ai_accountability_audit.py +287 -0
- tools/compliance/ai_impact_assessor.py +267 -0
- tools/compliance/ai_incident_response.py +295 -0
- tools/compliance/ai_inventory_manager.py +233 -0
- tools/compliance/ai_reassessment_scheduler.py +250 -0
- tools/compliance/ai_transparency_audit.py +247 -0
- tools/compliance/atlas_assessor.py +276 -0
- tools/compliance/atlas_report_generator.py +1199 -0
- tools/compliance/base_assessor.py +591 -0
- tools/compliance/cato_live_engine.py +607 -0
- tools/compliance/cato_monitor.py +1371 -0
- tools/compliance/cato_scheduler.py +698 -0
- tools/compliance/cjis_assessor.py +76 -0
- tools/compliance/classification_manager.py +1340 -0
- tools/compliance/cmmc_assessor.py +1478 -0
- tools/compliance/cmmc_report_generator.py +1087 -0
- tools/compliance/compliance_detector.py +452 -0
- tools/compliance/compliance_exporter.py +418 -0
- tools/compliance/compliance_status.py +810 -0
- tools/compliance/control_mapper.py +488 -0
- tools/compliance/crosswalk_engine.py +1208 -0
- tools/compliance/cssp_assessor.py +1032 -0
- tools/compliance/cssp_evidence_collector.py +716 -0
- tools/compliance/cssp_report_generator.py +1103 -0
- tools/compliance/cui_marker.py +387 -0
- tools/compliance/diagram_validator.py +599 -0
- tools/compliance/emass/__init__.py +2 -0
- tools/compliance/emass/emass_client.py +822 -0
- tools/compliance/emass/emass_export.py +758 -0
- tools/compliance/emass/emass_sync.py +807 -0
- tools/compliance/eu_ai_act_classifier.py +193 -0
- tools/compliance/evidence_collector.py +459 -0
- tools/compliance/fairness_assessor.py +310 -0
- tools/compliance/fedramp_20x_ksi_emitter.py +692 -0
- tools/compliance/fedramp_assessor.py +1795 -0
- tools/compliance/fedramp_authorization_packager.py +137 -0
- tools/compliance/fedramp_ksi_generator.py +349 -0
- tools/compliance/fedramp_report_generator.py +1115 -0
- tools/compliance/fips199_categorizer.py +869 -0
- tools/compliance/fips200_validator.py +304 -0
- tools/compliance/firmware_sbom.py +646 -0
- tools/compliance/gao_ai_assessor.py +228 -0
- tools/compliance/gao_evidence_builder.py +302 -0
- tools/compliance/hipaa_assessor.py +78 -0
- tools/compliance/hitrust_assessor.py +49 -0
- tools/compliance/incident_response_plan.py +705 -0
- tools/compliance/inheritance_engine.py +693 -0
- tools/compliance/iso27001_assessor.py +92 -0
- tools/compliance/iso42001_assessor.py +114 -0
- tools/compliance/ivv_assessor.py +2314 -0
- tools/compliance/ivv_report_generator.py +1649 -0
- tools/compliance/model_card_generator.py +291 -0
- tools/compliance/mosa_assessor.py +117 -0
- tools/compliance/multi_regime_assessor.py +441 -0
- tools/compliance/narrative_generator.py +1012 -0
- tools/compliance/narrative_quality_gate.py +701 -0
- tools/compliance/narrative_workflow.py +814 -0
- tools/compliance/nist_800_207_assessor.py +191 -0
- tools/compliance/nist_ai_600_1_assessor.py +185 -0
- tools/compliance/nist_ai_rmf_assessor.py +110 -0
- tools/compliance/nist_lookup.py +244 -0
- tools/compliance/omb_m25_21_assessor.py +225 -0
- tools/compliance/omb_m26_04_assessor.py +185 -0
- tools/compliance/oscal_catalog_adapter.py +395 -0
- tools/compliance/oscal_generator.py +2157 -0
- tools/compliance/oscal_tools.py +1182 -0
- tools/compliance/oscal_validator.py +692 -0
- tools/compliance/owasp_agentic_assessor.py +227 -0
- tools/compliance/owasp_asi_assessor.py +197 -0
- tools/compliance/owasp_llm_assessor.py +245 -0
- tools/compliance/pci_dss_assessor.py +80 -0
- tools/compliance/pi_compliance_tracker.py +1447 -0
- tools/compliance/poam_generator.py +388 -0
- tools/compliance/resolve_marking.py +272 -0
- tools/compliance/sbd_assessor.py +2070 -0
- tools/compliance/sbd_report_generator.py +1223 -0
- tools/compliance/sbom_generator.py +993 -0
- tools/compliance/siem_config_generator.py +661 -0
- tools/compliance/slsa_attestation_generator.py +479 -0
- tools/compliance/soc2_assessor.py +77 -0
- tools/compliance/ssp_generator.py +556 -0
- tools/compliance/stig_checker.py +712 -0
- tools/compliance/swft_evidence_bundler.py +326 -0
- tools/compliance/system_card_generator.py +303 -0
- tools/compliance/template_exchange.py +513 -0
- tools/compliance/traceability_matrix.py +1268 -0
- tools/compliance/universal_classification_manager.py +1159 -0
- tools/compliance/xacta/__init__.py +2 -0
- tools/compliance/xacta/xacta_client.py +438 -0
- tools/compliance/xacta/xacta_export.py +546 -0
- tools/compliance/xacta/xacta_sync.py +322 -0
- tools/compliance/xai_assessor.py +231 -0
- tools/core/__init__.py +2 -0
- tools/core/circuit_breaker.py +353 -0
- tools/core/compliance_sidecar.py +344 -0
- tools/core/container.py +110 -0
- tools/core/errors.py +256 -0
- tools/core/feature_flags.py +311 -0
- tools/core/task_dlq.py +350 -0
- tools/dashboard/__init__.py +2 -0
- tools/dashboard/app.py +6288 -0
- tools/dashboard/templates/agent_evolution.html +287 -0
- tools/dashboard/templates/agents/list.html +71 -0
- tools/dashboard/templates/agents.html +132 -0
- tools/dashboard/templates/architecture.html +289 -0
- tools/dashboard/templates/ato_simulator.html +170 -0
- tools/dashboard/templates/audit_engine.html +844 -0
- tools/dashboard/templates/base.html +236 -0
- tools/dashboard/templates/cato_live.html +116 -0
- tools/dashboard/templates/cloudforge.html +195 -0
- tools/dashboard/templates/cloudforge_finops.html +111 -0
- tools/dashboard/templates/cloudforge_hybrid.html +122 -0
- tools/dashboard/templates/cloudforge_metastore.html +234 -0
- tools/dashboard/templates/cloudforge_migration.html +87 -0
- tools/dashboard/templates/cloudforge_runbooks.html +201 -0
- tools/dashboard/templates/cloudforge_siem.html +94 -0
- tools/dashboard/templates/compliance_accel.html +292 -0
- tools/dashboard/templates/crashes.html +122 -0
- tools/dashboard/templates/databridge.html +305 -0
- tools/dashboard/templates/databridge_analytics.html +195 -0
- tools/dashboard/templates/databridge_mapping.html +345 -0
- tools/dashboard/templates/databridge_messaging.html +321 -0
- tools/dashboard/templates/decisions.html +258 -0
- tools/dashboard/templates/devices.html +151 -0
- tools/dashboard/templates/devsecops_maturity.html +278 -0
- tools/dashboard/templates/edge_ai.html +128 -0
- tools/dashboard/templates/firmware.html +120 -0
- tools/dashboard/templates/firmware_sbom.html +193 -0
- tools/dashboard/templates/forge_hub.html +196 -0
- tools/dashboard/templates/forge_studio.html +379 -0
- tools/dashboard/templates/forge_studio_analytics.html +360 -0
- tools/dashboard/templates/forge_studio_builder.html +1637 -0
- tools/dashboard/templates/forge_studio_compliance.html +310 -0
- tools/dashboard/templates/forge_studio_deploy.html +573 -0
- tools/dashboard/templates/forge_studio_enterprise.html +888 -0
- tools/dashboard/templates/forge_studio_marketplace.html +502 -0
- tools/dashboard/templates/forge_studio_workflow.html +696 -0
- tools/dashboard/templates/golden_path.html +175 -0
- tools/dashboard/templates/govcon.html +280 -0
- tools/dashboard/templates/harness.html +148 -0
- tools/dashboard/templates/index.html +207 -0
- tools/dashboard/templates/intelligence.html +336 -0
- tools/dashboard/templates/knowledge/index.html +190 -0
- tools/dashboard/templates/knowledge_graph.html +739 -0
- tools/dashboard/templates/login.html +51 -0
- tools/dashboard/templates/marketplace.html +336 -0
- tools/dashboard/templates/marketplace_admin.html +247 -0
- tools/dashboard/templates/missions.html +403 -0
- tools/dashboard/templates/narratives.html +154 -0
- tools/dashboard/templates/pr_intelligence.html +151 -0
- tools/dashboard/templates/proposals/detail.html +300 -0
- tools/dashboard/templates/proposals/list.html +52 -0
- tools/dashboard/templates/proposals/sam_detail.html +132 -0
- tools/dashboard/templates/proposals/section_detail.html +375 -0
- tools/dashboard/templates/research.html +222 -0
- tools/dashboard/templates/resilience.html +300 -0
- tools/dashboard/templates/scorecard.html +162 -0
- tools/dashboard/templates/simulator.html +131 -0
- tools/dashboard/templates/template_exchange.html +147 -0
- tools/dashboard/templates/thread_heatmap.html +151 -0
- tools/dashboard/templates/threat_model.html +195 -0
- tools/dashboard/templates/vsm.html +141 -0
- tools/dashboard/templates/writeguard.html +277 -0
- tools/databridge/__init__.py +5 -0
- tools/databridge/agent/__init__.py +2 -0
- tools/databridge/agent/daemon.py +227 -0
- tools/databridge/agent/tunnel.py +101 -0
- tools/databridge/agent/ws_relay.py +91 -0
- tools/databridge/analytics.py +167 -0
- tools/databridge/arrow_pipeline.py +327 -0
- tools/databridge/connection_manager.py +424 -0
- tools/databridge/connector.py +331 -0
- tools/databridge/connectors/__init__.py +2 -0
- tools/databridge/connectors/argocd_connector.py +160 -0
- tools/databridge/connectors/avro_connector.py +203 -0
- tools/databridge/connectors/azure_blob.py +63 -0
- tools/databridge/connectors/cdc_connector.py +205 -0
- tools/databridge/connectors/csv_connector.py +172 -0
- tools/databridge/connectors/datadog_connector.py +153 -0
- tools/databridge/connectors/discord_messaging.py +215 -0
- tools/databridge/connectors/dynamics365.py +151 -0
- tools/databridge/connectors/elasticsearch_connector.py +145 -0
- tools/databridge/connectors/email_base.py +114 -0
- tools/databridge/connectors/excel_connector.py +175 -0
- tools/databridge/connectors/fsspec_base.py +300 -0
- tools/databridge/connectors/gcs.py +53 -0
- tools/databridge/connectors/github_connector.py +138 -0
- tools/databridge/connectors/gitlab_connector.py +132 -0
- tools/databridge/connectors/gmail_connector.py +182 -0
- tools/databridge/connectors/hdfs.py +57 -0
- tools/databridge/connectors/health_base.py +401 -0
- tools/databridge/connectors/hubspot.py +124 -0
- tools/databridge/connectors/imap_connector.py +171 -0
- tools/databridge/connectors/jenkins_connector.py +138 -0
- tools/databridge/connectors/jira_connector.py +86 -0
- tools/databridge/connectors/json_connector.py +184 -0
- tools/databridge/connectors/kafka_connector.py +246 -0
- tools/databridge/connectors/kinesis_connector.py +238 -0
- tools/databridge/connectors/local_fs.py +30 -0
- tools/databridge/connectors/matrix.py +197 -0
- tools/databridge/connectors/mattermost_messaging.py +184 -0
- tools/databridge/connectors/messaging_base.py +172 -0
- tools/databridge/connectors/mssql.py +63 -0
- tools/databridge/connectors/mysql.py +57 -0
- tools/databridge/connectors/netsuite.py +170 -0
- tools/databridge/connectors/o365_mail.py +196 -0
- tools/databridge/connectors/oracle.py +65 -0
- tools/databridge/connectors/pagerduty_connector.py +162 -0
- tools/databridge/connectors/parquet_connector.py +131 -0
- tools/databridge/connectors/postgresql.py +58 -0
- tools/databridge/connectors/s3.py +65 -0
- tools/databridge/connectors/saas_base.py +198 -0
- tools/databridge/connectors/salesforce.py +126 -0
- tools/databridge/connectors/sap.py +89 -0
- tools/databridge/connectors/servicenow.py +60 -0
- tools/databridge/connectors/signal_messaging.py +150 -0
- tools/databridge/connectors/slack_messaging.py +203 -0
- tools/databridge/connectors/smtp_connector.py +126 -0
- tools/databridge/connectors/soap_base.py +258 -0
- tools/databridge/connectors/splunk_connector.py +171 -0
- tools/databridge/connectors/sql_base.py +310 -0
- tools/databridge/connectors/sqlite_connector.py +76 -0
- tools/databridge/connectors/teams.py +148 -0
- tools/databridge/connectors/telegram.py +192 -0
- tools/databridge/connectors/whatsapp.py +137 -0
- tools/databridge/data_profiler.py +99 -0
- tools/databridge/forge/__init__.py +6 -0
- tools/databridge/forge/base_selector.py +150 -0
- tools/databridge/forge/code_generator.py +206 -0
- tools/databridge/forge/community_hub.py +539 -0
- tools/databridge/forge/forge_agent.py +306 -0
- tools/databridge/forge/import_handler.py +133 -0
- tools/databridge/forge/integration_tester.py +127 -0
- tools/databridge/forge/marketplace_publisher.py +164 -0
- tools/databridge/forge/promoter.py +159 -0
- tools/databridge/forge/sandbox_manager.py +257 -0
- tools/databridge/forge/spec_parser.py +358 -0
- tools/databridge/forge/static_validator.py +363 -0
- tools/databridge/forge/templates/__init__.py +591 -0
- tools/databridge/format_converter.py +188 -0
- tools/databridge/mapping_engine.py +348 -0
- tools/databridge/messaging/__init__.py +5 -0
- tools/databridge/messaging/agent_bridge.py +254 -0
- tools/databridge/messaging/message_envelope.py +111 -0
- tools/databridge/messaging/message_logger.py +204 -0
- tools/databridge/messaging/messaging_daemon.py +326 -0
- tools/databridge/messaging/oauth2_manager.py +411 -0
- tools/databridge/pii_detector.py +221 -0
- tools/databridge/registry.py +352 -0
- tools/databridge/relay_server.py +105 -0
- tools/databridge/scale/__init__.py +16 -0
- tools/databridge/scale/backpressure.py +134 -0
- tools/databridge/scale/chunked_pipeline.py +169 -0
- tools/databridge/scale/connection_pool.py +293 -0
- tools/databridge/scale/engine.py +492 -0
- tools/databridge/scale/worker_pool.py +140 -0
- tools/databridge/scale/write_batcher.py +250 -0
- tools/databridge/schema_engine.py +324 -0
- tools/databridge/stream_manager.py +225 -0
- tools/databridge/sync_engine.py +411 -0
- tools/databridge/transforms.py +302 -0
- tools/db/__init__.py +1 -0
- tools/db/backup.py +312 -0
- tools/db/backup_manager.py +832 -0
- tools/db/init_icdev_db.py +7753 -0
- tools/db/init_sparkpilot_db.py +431 -0
- tools/db/migrate.py +177 -0
- tools/db/migrate_innovation_audit.py +165 -0
- tools/db/migration_runner.py +548 -0
- tools/db/migrations/001_baseline/meta.json +9 -0
- tools/db/migrations/001_baseline/up.py +67 -0
- tools/db/migrations/002_memory_enhancements/down.sql +8 -0
- tools/db/migrations/002_memory_enhancements/meta.json +9 -0
- tools/db/migrations/002_memory_enhancements/up.py +119 -0
- tools/db/migrations/003_dev_profiles/meta.json +8 -0
- tools/db/migrations/003_dev_profiles/up.py +93 -0
- tools/db/migrations/004_innovation_engine/down.py +19 -0
- tools/db/migrations/004_innovation_engine/up.py +227 -0
- tools/db/migrations/005_phase_37_ai_security/down.py +19 -0
- tools/db/migrations/005_phase_37_ai_security/up.py +257 -0
- tools/db/migrations/006_phase_36_evolution/down.py +21 -0
- tools/db/migrations/006_phase_36_evolution/up.py +323 -0
- tools/db/migrations/007_phase_38_cloud/down.py +14 -0
- tools/db/migrations/007_phase_38_cloud/up.py +110 -0
- tools/db/migrations/008_phase36_37_integration/up.py +55 -0
- tools/db/migrations/__init__.py +2 -0
- tools/db/pg_migrate.py +642 -0
- tools/db/storage.py +1080 -0
- tools/decisions/__init__.py +2 -0
- tools/decisions/dmn_engine.py +695 -0
- tools/devsecops/__init__.py +2 -0
- tools/devsecops/attestation_manager.py +449 -0
- tools/devsecops/network_segmentation_generator.py +604 -0
- tools/devsecops/pdp_config_generator.py +1246 -0
- tools/devsecops/pipeline_security_generator.py +475 -0
- tools/devsecops/policy_generator.py +644 -0
- tools/devsecops/profile_manager.py +374 -0
- tools/devsecops/service_mesh_generator.py +1063 -0
- tools/devsecops/zta_maturity_scorer.py +355 -0
- tools/devsecops/zta_terraform_generator.py +1301 -0
- tools/edge_ai/__init__.py +2 -0
- tools/edge_ai/model_manager.py +200 -0
- tools/embedded/__init__.py +2 -0
- tools/embedded/cmake_generator.py +318 -0
- tools/embedded/crash_analyzer.py +191 -0
- tools/embedded/nl_to_firmware.py +277 -0
- tools/events/__init__.py +1 -0
- tools/events/event_bus.py +199 -0
- tools/finetune/pair_generator.py +832 -0
- tools/fleet/__init__.py +2 -0
- tools/fleet/device_registry.py +148 -0
- tools/fleet/ota_manager.py +153 -0
- tools/forge_studio/__init__.py +13 -0
- tools/forge_studio/analytics/__init__.py +0 -0
- tools/forge_studio/analytics/process_miner.py +383 -0
- tools/forge_studio/audit.py +183 -0
- tools/forge_studio/blueprint/__init__.py +2 -0
- tools/forge_studio/blueprint/build_tracker.py +317 -0
- tools/forge_studio/blueprint/export_engine.py +441 -0
- tools/forge_studio/blueprint/parent_client.py +335 -0
- tools/forge_studio/catalog/__init__.py +2 -0
- tools/forge_studio/catalog/component_registry.py +176 -0
- tools/forge_studio/catalog/schema_validator.py +193 -0
- tools/forge_studio/compliance/__init__.py +1 -0
- tools/forge_studio/compliance/compliance_wiring.py +554 -0
- tools/forge_studio/deploy/__init__.py +1 -0
- tools/forge_studio/deploy/airgap_packager.py +466 -0
- tools/forge_studio/deploy/deploy_engine.py +1792 -0
- tools/forge_studio/deploy/env_manager.py +431 -0
- tools/forge_studio/eject/__init__.py +2 -0
- tools/forge_studio/eject/docker_compose_generator.py +237 -0
- tools/forge_studio/eject/eject_engine.py +230 -0
- tools/forge_studio/eject/expo_scaffolder.py +303 -0
- tools/forge_studio/eject/nextjs_scaffolder.py +338 -0
- tools/forge_studio/enterprise/__init__.py +0 -0
- tools/forge_studio/enterprise/custom_frameworks.py +826 -0
- tools/forge_studio/enterprise/hardening_engine.py +1530 -0
- tools/forge_studio/enterprise/sso_manager.py +718 -0
- tools/forge_studio/enterprise/whitelabel_engine.py +887 -0
- tools/forge_studio/formula/__init__.py +0 -0
- tools/forge_studio/formula/expression_engine.py +562 -0
- tools/forge_studio/formula/formula_registry.py +265 -0
- tools/forge_studio/generator/__init__.py +2 -0
- tools/forge_studio/generator/app_generator.py +584 -0
- tools/forge_studio/generator/complexity_detector.py +368 -0
- tools/forge_studio/generator/prompt_templates.py +104 -0
- tools/forge_studio/generator/spec_builder.py +192 -0
- tools/forge_studio/intake_bridge.py +898 -0
- tools/forge_studio/marketplace/__init__.py +0 -0
- tools/forge_studio/marketplace/component_hub.py +428 -0
- tools/forge_studio/models.py +369 -0
- tools/forge_studio/renderer/__init__.py +2 -0
- tools/forge_studio/renderer/json_render_engine.py +623 -0
- tools/forge_studio/renderer/layout_engine.py +214 -0
- tools/forge_studio/renderer/rn_component_map.py +182 -0
- tools/forge_studio/supabase/__init__.py +2 -0
- tools/forge_studio/supabase/auth_generator.py +283 -0
- tools/forge_studio/supabase/migration_generator.py +93 -0
- tools/forge_studio/supabase/schema_generator.py +281 -0
- tools/forge_studio/tenant_manager.py +387 -0
- tools/forge_studio/workflow/__init__.py +2 -0
- tools/forge_studio/workflow/bpmn_adapter.py +489 -0
- tools/govcon/draft_orchestrator.py +1151 -0
- tools/govcon/engine_enrichment.py +373 -0
- tools/govcon/knowledge_base.py +487 -0
- tools/govcon/knowledge_ingestion.py +510 -0
- tools/govcon/sam_scanner.py +754 -0
- tools/harness/__init__.py +6 -0
- tools/harness/exit_criteria_evaluator.py +231 -0
- tools/harness/maturity_assessor.py +347 -0
- tools/harness/scaffold_harness.py +416 -0
- tools/harness/trace_analyzer.py +281 -0
- tools/infra/__init__.py +1 -0
- tools/infra/ansible_generator.py +867 -0
- tools/infra/dockerfile_generator.py +359 -0
- tools/infra/infra_status.py +384 -0
- tools/infra/ironbank_metadata_generator.py +403 -0
- tools/infra/k8s_generator.py +1000 -0
- tools/infra/pipeline_generator.py +830 -0
- tools/infra/rollback.py +389 -0
- tools/infra/terraform_generator.py +1140 -0
- tools/infra/terraform_generator_azure.py +1252 -0
- tools/infra/terraform_generator_gcp.py +951 -0
- tools/infra/terraform_generator_ibm.py +359 -0
- tools/infra/terraform_generator_oci.py +918 -0
- tools/infra/terraform_generator_onprem.py +318 -0
- tools/knowledge/__init__.py +1 -0
- tools/knowledge/knowledge_ingest.py +281 -0
- tools/knowledge/pattern_detector.py +681 -0
- tools/knowledge/recommendation_engine.py +449 -0
- tools/knowledge/self_heal_analyzer.py +492 -0
- tools/knowledge_graph/__init__.py +2 -0
- tools/knowledge_graph/graph_rag.py +498 -0
- tools/knowledge_graph/ingester.py +406 -0
- tools/knowledge_graph/insight_generator.py +369 -0
- tools/knowledge_graph/text_network.py +832 -0
- tools/llm/__init__.py +72 -0
- tools/llm/anthropic_provider.py +170 -0
- tools/llm/azure_openai_provider.py +338 -0
- tools/llm/bedrock_provider.py +315 -0
- tools/llm/embedding_provider.py +438 -0
- tools/llm/gemini_provider.py +381 -0
- tools/llm/ibm_watsonx_provider.py +231 -0
- tools/llm/oci_genai_provider.py +462 -0
- tools/llm/ollama_provider.py +350 -0
- tools/llm/openai_provider.py +225 -0
- tools/llm/prompt_registry.py +447 -0
- tools/llm/provider.py +355 -0
- tools/llm/provider_sdk.py +175 -0
- tools/llm/router.py +1124 -0
- tools/llm/semantic_cache.py +394 -0
- tools/llm/vertex_ai_provider.py +374 -0
- tools/maintenance/__init__.py +2 -0
- tools/maintenance/dependency_scanner.py +1016 -0
- tools/maintenance/maintenance_auditor.py +804 -0
- tools/maintenance/remediation_engine.py +957 -0
- tools/maintenance/vulnerability_checker.py +978 -0
- tools/manifest.md +1066 -0
- tools/marketplace/asset_installer.py +639 -0
- tools/marketplace/feedback_validator.py +359 -0
- tools/marketplace/license_client.py +458 -0
- tools/marketplace/module_crypto.py +544 -0
- tools/marketplace/module_runtime.py +236 -0
- tools/marketplace/token_store.py +264 -0
- tools/mbse/__init__.py +3 -0
- tools/mbse/des_assessor.py +1173 -0
- tools/mbse/des_report_generator.py +787 -0
- tools/mbse/diagram_extractor.py +792 -0
- tools/mbse/digital_thread.py +1650 -0
- tools/mbse/model_code_generator.py +1115 -0
- tools/mbse/model_control_mapper.py +410 -0
- tools/mbse/pi_model_tracker.py +1079 -0
- tools/mbse/reqif_parser.py +1468 -0
- tools/mbse/sync_engine.py +1789 -0
- tools/mbse/thread_heatmap.py +445 -0
- tools/mbse/xmi_parser.py +1558 -0
- tools/mcp/builder_server.py +64 -0
- tools/mcp/compliance_server.py +64 -0
- tools/mcp/connector_forge_server.py +155 -0
- tools/mcp/core_server.py +64 -0
- tools/mcp/devsecops_server.py +11 -0
- tools/mcp/devsecops_zta_server.py +64 -0
- tools/mcp/knowledge_server.py +64 -0
- tools/mcp/monitor_server.py +64 -0
- tools/mcp/ops_server.py +300 -0
- tools/mcp/requirements_analyst_server.py +64 -0
- tools/mcp/requirements_server.py +11 -0
- tools/mcp/security_server.py +64 -0
- tools/mcp/simulation_server.py +64 -0
- tools/mcp/supply_chain_server.py +64 -0
- tools/mcp/tool_registry.py +299 -0
- tools/memory/__init__.py +2 -0
- tools/memory/auto_capture.py +346 -0
- tools/memory/embed_memory.py +157 -0
- tools/memory/history_compressor.py +334 -0
- tools/memory/hybrid_search.py +235 -0
- tools/memory/maintenance_cron.py +288 -0
- tools/memory/memory_consolidation.py +439 -0
- tools/memory/memory_db.py +132 -0
- tools/memory/memory_read.py +101 -0
- tools/memory/memory_write.py +221 -0
- tools/memory/semantic_search.py +138 -0
- tools/memory/time_decay.py +434 -0
- tools/missions/__init__.py +2 -0
- tools/missions/mission_engine.py +459 -0
- tools/monitor/__init__.py +1 -0
- tools/monitor/alert_correlator.py +486 -0
- tools/monitor/auto_resolver.py +603 -0
- tools/monitor/health_checker.py +507 -0
- tools/monitor/heartbeat_daemon.py +779 -0
- tools/monitor/log_analyzer.py +507 -0
- tools/monitor/metric_collector.py +484 -0
- tools/mosa/__init__.py +10 -0
- tools/mosa/icd_generator.py +358 -0
- tools/mosa/modular_design_analyzer.py +682 -0
- tools/mosa/mosa_code_enforcer.py +348 -0
- tools/mosa/tsp_generator.py +265 -0
- tools/observability/__init__.py +100 -0
- tools/observability/genai_attributes.py +88 -0
- tools/observability/instrumentation.py +140 -0
- tools/observability/mlflow_exporter.py +193 -0
- tools/observability/otel_tracer.py +168 -0
- tools/observability/provenance/__init__.py +3 -0
- tools/observability/provenance/prov_recorder.py +322 -0
- tools/observability/shap/__init__.py +3 -0
- tools/observability/shap/agent_shap.py +274 -0
- tools/observability/sqlite_tracer.py +360 -0
- tools/observability/trace_context.py +205 -0
- tools/observability/tracer.py +230 -0
- tools/orchestration/__init__.py +1 -0
- tools/orchestration/peer_channels.py +254 -0
- tools/orchestration/saga_coordinator.py +390 -0
- tools/project/__init__.py +1 -0
- tools/project/manifest_loader.py +418 -0
- tools/project/project_create.py +350 -0
- tools/project/project_list.py +171 -0
- tools/project/project_scaffold.py +1715 -0
- tools/project/project_status.py +478 -0
- tools/project/session_context_builder.py +752 -0
- tools/project/validate_manifest.py +54 -0
- tools/rag/corrective_rag.py +582 -0
- tools/rag/source_registry.py +482 -0
- tools/requirements/__init__.py +1 -0
- tools/requirements/ai_governance_scorer.py +207 -0
- tools/requirements/boundary_analyzer.py +1281 -0
- tools/requirements/clarification_engine.py +605 -0
- tools/requirements/complexity_scorer.py +369 -0
- tools/requirements/consistency_analyzer.py +789 -0
- tools/requirements/constitution_manager.py +592 -0
- tools/requirements/decomposition_engine.py +764 -0
- tools/requirements/document_extractor.py +1002 -0
- tools/requirements/elicitation_techniques.py +508 -0
- tools/requirements/gap_detector.py +260 -0
- tools/requirements/intake_engine.py +2175 -0
- tools/requirements/prd_generator.py +839 -0
- tools/requirements/prd_validator.py +584 -0
- tools/requirements/readiness_scorer.py +302 -0
- tools/requirements/spec_organizer.py +1015 -0
- tools/requirements/spec_quality_checker.py +1083 -0
- tools/requirements/traceability_builder.py +566 -0
- tools/research/__init__.py +3 -0
- tools/research/academic_scanner.py +130 -0
- tools/research/build_buy_analyzer.py +229 -0
- tools/research/challenge_scorer.py +280 -0
- tools/research/community_scanner.py +174 -0
- tools/research/cross_engine_bridge.py +124 -0
- tools/research/dossier_generator.py +305 -0
- tools/research/landscape_scanner.py +315 -0
- tools/research/regulatory_scanner.py +248 -0
- tools/research/research_manager.py +469 -0
- tools/research/source_scanner.py +150 -0
- tools/research/vertical_loader.py +118 -0
- tools/saas/__init__.py +0 -0
- tools/saas/licensing/__init__.py +0 -0
- tools/saas/licensing/license_validator.py +345 -0
- tools/scaffold/__init__.py +2 -0
- tools/scaffold/golden_path.py +504 -0
- tools/security/__init__.py +1 -0
- tools/security/agent_output_validator.py +330 -0
- tools/security/agent_trust_scorer.py +652 -0
- tools/security/ai_bom_generator.py +718 -0
- tools/security/ai_telemetry_logger.py +469 -0
- tools/security/atlas_red_team.py +541 -0
- tools/security/code_pattern_scanner.py +382 -0
- tools/security/confabulation_detector.py +265 -0
- tools/security/container_scanner.py +489 -0
- tools/security/dependency_auditor.py +942 -0
- tools/security/endpoint_security_scanner.py +626 -0
- tools/security/mcp_tool_authorizer.py +242 -0
- tools/security/output_verifier.py +427 -0
- tools/security/prompt_injection_detector.py +737 -0
- tools/security/sast_runner.py +946 -0
- tools/security/secret_detector.py +376 -0
- tools/security/threat_modeler.py +678 -0
- tools/security/tool_chain_validator.py +357 -0
- tools/security/vuln_scanner.py +536 -0
- tools/simulation/__init__.py +2 -0
- tools/simulation/ato_simulator.py +517 -0
- tools/simulation/coa_generator.py +1539 -0
- tools/simulation/monte_carlo.py +745 -0
- tools/simulation/scenario_manager.py +1060 -0
- tools/simulation/simulation_engine.py +1091 -0
- tools/simulator/__init__.py +2 -0
- tools/simulator/sim_runner.py +272 -0
- tools/supply_chain/__init__.py +2 -0
- tools/supply_chain/cve_triager.py +690 -0
- tools/supply_chain/dependency_graph.py +630 -0
- tools/supply_chain/isa_manager.py +526 -0
- tools/supply_chain/scrm_assessor.py +531 -0
- tools/supply_chain/slsa_verifier.py +473 -0
- tools/testing/__init__.py +2 -0
- tools/testing/acceptance_validator.py +411 -0
- tools/testing/api_surface_extractor.py +749 -0
- tools/testing/claude_dir_validator.py +831 -0
- tools/testing/data_types.py +199 -0
- tools/testing/e2e_runner.py +715 -0
- tools/testing/fuzz_cli.py +306 -0
- tools/testing/health_check.py +483 -0
- tools/testing/platform_check.py +143 -0
- tools/testing/production_audit.py +1836 -0
- tools/testing/production_remediate.py +803 -0
- tools/testing/screenshot_validator.py +538 -0
- tools/testing/smoke_test.py +283 -0
- tools/testing/test_agent_models.py +117 -0
- tools/testing/test_orchestrator.py +957 -0
- tools/testing/utils.py +229 -0
- tools/writeguard/__init__.py +1 -0
- tools/writeguard/main.py +1 -0
- tools/writing/__init__.py +7 -0
- tools/writing/ai_content_detector.py +316 -0
- tools/writing/analysis_engine.py +454 -0
- tools/writing/batch_analyzer.py +276 -0
- tools/writing/coherence_analyzer.py +221 -0
- tools/writing/govcon_bridge.py +509 -0
- tools/writing/grammar_checker.py +270 -0
- tools/writing/plagiarism_detector.py +106 -0
- tools/writing/readability_scorer.py +201 -0
- tools/writing/rewriter.py +96 -0
- tools/writing/signal_registrar.py +167 -0
- tools/writing/snippet_manager.py +276 -0
- tools/writing/style_enforcer.py +220 -0
- tools/writing/style_guide_manager.py +438 -0
- tools/writing/tone_profiler.py +168 -0
tools/llm/router.py
ADDED
|
@@ -0,0 +1,1124 @@
|
|
|
1
|
+
# [TEMPLATE: CUI // SP-CTI]
|
|
2
|
+
"""Config-driven LLM router.
|
|
3
|
+
|
|
4
|
+
Reads args/llm_config.yaml and resolves each SPARKPILOT function to a
|
|
5
|
+
provider + model via fallback chain. Probes provider availability
|
|
6
|
+
and caches results.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sqlite3
|
|
14
|
+
import time
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Dict, Optional, Tuple
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
import yaml
|
|
20
|
+
except ImportError:
|
|
21
|
+
yaml = None
|
|
22
|
+
|
|
23
|
+
from tools.llm.provider import LLMProvider, LLMRequest, LLMResponse, EmbeddingProvider
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from tools.core.circuit_breaker import get_breaker, CircuitOpenError
|
|
27
|
+
except ImportError:
|
|
28
|
+
get_breaker = None # type: ignore[assignment]
|
|
29
|
+
CircuitOpenError = None # type: ignore[assignment,misc]
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger("icdev.llm.router")
|
|
32
|
+
|
|
33
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
34
|
+
DEFAULT_CONFIG_PATH = BASE_DIR / "args" / "llm_config.yaml"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _expand_env(value):
|
|
38
|
+
"""Expand ${VAR:-default} patterns in string values."""
|
|
39
|
+
if not isinstance(value, str):
|
|
40
|
+
return value
|
|
41
|
+
pattern = r'\$\{([^}]+)\}'
|
|
42
|
+
def replacer(match):
|
|
43
|
+
expr = match.group(1)
|
|
44
|
+
if ":-" in expr:
|
|
45
|
+
var, default = expr.split(":-", 1)
|
|
46
|
+
return os.environ.get(var, default)
|
|
47
|
+
return os.environ.get(expr, match.group(0))
|
|
48
|
+
return re.sub(pattern, replacer, value)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class LLMRouter:
|
|
52
|
+
"""Config-driven router that maps SPARKPILOT functions to LLM providers.
|
|
53
|
+
|
|
54
|
+
Walks fallback chains, probes availability, and returns the first
|
|
55
|
+
responsive provider + model pair.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, config_path=None):
|
|
59
|
+
self._config_path = Path(config_path) if config_path else DEFAULT_CONFIG_PATH
|
|
60
|
+
self._config: Dict = {}
|
|
61
|
+
self._providers: Dict[str, LLMProvider] = {}
|
|
62
|
+
self._embedding_providers: Dict[str, EmbeddingProvider] = {}
|
|
63
|
+
self._availability_cache: Dict[str, bool] = {}
|
|
64
|
+
self._availability_cache_time: float = 0.0
|
|
65
|
+
self._cache_ttl: float = 1800.0
|
|
66
|
+
|
|
67
|
+
self._load_config()
|
|
68
|
+
|
|
69
|
+
# -------------------------------------------------------------------
|
|
70
|
+
# Config loading
|
|
71
|
+
# -------------------------------------------------------------------
|
|
72
|
+
def _load_config(self):
|
|
73
|
+
"""Load and parse llm_config.yaml."""
|
|
74
|
+
if yaml is None:
|
|
75
|
+
logger.warning("PyYAML not available — using empty LLM config")
|
|
76
|
+
self._config = {}
|
|
77
|
+
return
|
|
78
|
+
if not self._config_path.exists():
|
|
79
|
+
logger.warning("LLM config not found at %s — using empty config", self._config_path)
|
|
80
|
+
self._config = {}
|
|
81
|
+
return
|
|
82
|
+
try:
|
|
83
|
+
with open(self._config_path, "r", encoding="utf-8") as f:
|
|
84
|
+
self._config = yaml.safe_load(f) or {}
|
|
85
|
+
self._cache_ttl = float(
|
|
86
|
+
self._config.get("settings", {}).get(
|
|
87
|
+
"availability_cache_ttl_seconds", 1800
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
logger.info(
|
|
91
|
+
"LLM config loaded: %d providers, %d models, %d routes",
|
|
92
|
+
len(self._config.get("providers", {})),
|
|
93
|
+
len(self._config.get("models", {})),
|
|
94
|
+
len(self._config.get("routing", {})),
|
|
95
|
+
)
|
|
96
|
+
except Exception as exc:
|
|
97
|
+
logger.error("Failed to load LLM config: %s", exc)
|
|
98
|
+
self._config = {}
|
|
99
|
+
|
|
100
|
+
# -------------------------------------------------------------------
|
|
101
|
+
# Provider instantiation (lazy)
|
|
102
|
+
# -------------------------------------------------------------------
|
|
103
|
+
def _get_provider(self, provider_name: str) -> Optional[LLMProvider]:
|
|
104
|
+
"""Get or create a provider instance by name."""
|
|
105
|
+
if provider_name in self._providers:
|
|
106
|
+
return self._providers[provider_name]
|
|
107
|
+
|
|
108
|
+
provider_cfg = self._config.get("providers", {}).get(provider_name, {})
|
|
109
|
+
if not provider_cfg:
|
|
110
|
+
logger.warning("Provider '%s' not found in config", provider_name)
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
ptype = provider_cfg.get("type", "")
|
|
114
|
+
instance = None
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
if ptype == "bedrock":
|
|
118
|
+
from tools.llm.bedrock_provider import BedrockLLMProvider
|
|
119
|
+
region = _expand_env(provider_cfg.get("region", "us-gov-west-1"))
|
|
120
|
+
instance = BedrockLLMProvider(region=region)
|
|
121
|
+
|
|
122
|
+
elif ptype == "anthropic":
|
|
123
|
+
from tools.llm.anthropic_provider import AnthropicLLMProvider
|
|
124
|
+
api_key_env = provider_cfg.get("api_key_env", "ANTHROPIC_API_KEY")
|
|
125
|
+
api_key = os.environ.get(api_key_env, "")
|
|
126
|
+
base_url = provider_cfg.get("base_url", "https://api.anthropic.com")
|
|
127
|
+
instance = AnthropicLLMProvider(api_key=api_key, base_url=base_url)
|
|
128
|
+
|
|
129
|
+
elif ptype == "ollama":
|
|
130
|
+
from tools.llm.ollama_provider import OllamaProvider
|
|
131
|
+
base_url = _expand_env(
|
|
132
|
+
provider_cfg.get("base_url", "http://localhost:11434")
|
|
133
|
+
)
|
|
134
|
+
instance = OllamaProvider(base_url=base_url)
|
|
135
|
+
|
|
136
|
+
elif ptype == "gemini":
|
|
137
|
+
from tools.llm.gemini_provider import GeminiProvider
|
|
138
|
+
api_key = provider_cfg.get("api_key", "")
|
|
139
|
+
if not api_key:
|
|
140
|
+
api_key_env = provider_cfg.get("api_key_env", "GOOGLE_API_KEY")
|
|
141
|
+
if api_key_env:
|
|
142
|
+
api_key = os.environ.get(api_key_env, "")
|
|
143
|
+
instance = GeminiProvider(api_key=api_key)
|
|
144
|
+
|
|
145
|
+
elif ptype in ("openai", "openai_compatible"):
|
|
146
|
+
from tools.llm.openai_provider import OpenAICompatibleProvider
|
|
147
|
+
api_key = provider_cfg.get("api_key", "")
|
|
148
|
+
if not api_key:
|
|
149
|
+
api_key_env = provider_cfg.get("api_key_env", "")
|
|
150
|
+
if api_key_env:
|
|
151
|
+
api_key = os.environ.get(api_key_env, "")
|
|
152
|
+
base_url = _expand_env(provider_cfg.get("base_url", "https://api.openai.com/v1"))
|
|
153
|
+
instance = OpenAICompatibleProvider(
|
|
154
|
+
api_key=api_key,
|
|
155
|
+
base_url=base_url,
|
|
156
|
+
provider_label=provider_name,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
elif ptype == "azure_openai":
|
|
160
|
+
from tools.llm.azure_openai_provider import AzureOpenAIProvider
|
|
161
|
+
endpoint = _expand_env(provider_cfg.get("endpoint", ""))
|
|
162
|
+
api_key = _expand_env(provider_cfg.get("api_key", ""))
|
|
163
|
+
if not api_key:
|
|
164
|
+
api_key_env = provider_cfg.get("api_key_env", "AZURE_OPENAI_API_KEY")
|
|
165
|
+
api_key = os.environ.get(api_key_env, "")
|
|
166
|
+
api_version = provider_cfg.get("api_version", "2024-06-01")
|
|
167
|
+
instance = AzureOpenAIProvider(
|
|
168
|
+
endpoint=endpoint, api_key=api_key,
|
|
169
|
+
api_version=api_version,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
elif ptype == "vertex_ai":
|
|
173
|
+
from tools.llm.vertex_ai_provider import VertexAIProvider
|
|
174
|
+
project_id = _expand_env(provider_cfg.get("project_id", ""))
|
|
175
|
+
location = provider_cfg.get("location", "us-east4")
|
|
176
|
+
instance = VertexAIProvider(
|
|
177
|
+
project_id=project_id, location=location,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
elif ptype == "oci_genai":
|
|
181
|
+
from tools.llm.oci_genai_provider import OCIGenAIProvider
|
|
182
|
+
compartment_id = _expand_env(provider_cfg.get("compartment_id", ""))
|
|
183
|
+
serving_mode = provider_cfg.get("serving_mode", "ON_DEMAND")
|
|
184
|
+
instance = OCIGenAIProvider(
|
|
185
|
+
compartment_id=compartment_id,
|
|
186
|
+
serving_mode=serving_mode,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
elif ptype == "ibm_watsonx":
|
|
190
|
+
from tools.llm.ibm_watsonx_provider import IBMWatsonxProvider
|
|
191
|
+
api_key = _expand_env(provider_cfg.get("api_key", ""))
|
|
192
|
+
if not api_key:
|
|
193
|
+
api_key_env = provider_cfg.get("api_key_env", "IBM_CLOUD_API_KEY")
|
|
194
|
+
api_key = os.environ.get(api_key_env, "")
|
|
195
|
+
project_id = _expand_env(provider_cfg.get("project_id", ""))
|
|
196
|
+
if not project_id:
|
|
197
|
+
project_id = os.environ.get("IBM_WATSONX_PROJECT_ID", "")
|
|
198
|
+
url = _expand_env(provider_cfg.get("url", ""))
|
|
199
|
+
if not url:
|
|
200
|
+
url = os.environ.get("IBM_WATSONX_URL",
|
|
201
|
+
"https://us-south.ml.cloud.ibm.com")
|
|
202
|
+
instance = IBMWatsonxProvider(
|
|
203
|
+
api_key=api_key, project_id=project_id, url=url,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
else:
|
|
207
|
+
logger.warning("Unknown provider type: %s", ptype)
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
except ImportError as exc:
|
|
211
|
+
logger.warning("Could not import provider '%s': %s", provider_name, exc)
|
|
212
|
+
return None
|
|
213
|
+
except Exception as exc:
|
|
214
|
+
logger.warning("Failed to create provider '%s': %s", provider_name, exc)
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
if instance:
|
|
218
|
+
self._providers[provider_name] = instance
|
|
219
|
+
logger.debug("Created provider instance: %s (%s)", provider_name, ptype)
|
|
220
|
+
|
|
221
|
+
return instance
|
|
222
|
+
|
|
223
|
+
# -------------------------------------------------------------------
|
|
224
|
+
# Model resolution
|
|
225
|
+
# -------------------------------------------------------------------
|
|
226
|
+
def _get_model_config(self, model_name: str) -> dict:
|
|
227
|
+
"""Get model configuration by logical name."""
|
|
228
|
+
return self._config.get("models", {}).get(model_name, {})
|
|
229
|
+
|
|
230
|
+
def _get_model_breaker(self, model_name: str):
|
|
231
|
+
"""Get or create a circuit breaker for the named model.
|
|
232
|
+
|
|
233
|
+
Returns None if the circuit breaker module is not available.
|
|
234
|
+
"""
|
|
235
|
+
if get_breaker is None:
|
|
236
|
+
return None
|
|
237
|
+
return get_breaker(f"llm.{model_name}")
|
|
238
|
+
|
|
239
|
+
def _check_model_available(self, model_name: str) -> bool:
|
|
240
|
+
"""Check if a model is available, using cache and circuit breaker."""
|
|
241
|
+
# Circuit breaker fast-path: if breaker is OPEN, skip immediately
|
|
242
|
+
breaker = self._get_model_breaker(model_name)
|
|
243
|
+
if breaker is not None and not breaker.allow_request():
|
|
244
|
+
logger.debug("Circuit breaker OPEN for %s — skipping", model_name)
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
now = time.time()
|
|
248
|
+
if (now - self._availability_cache_time) > self._cache_ttl:
|
|
249
|
+
self._availability_cache = {}
|
|
250
|
+
self._availability_cache_time = now
|
|
251
|
+
|
|
252
|
+
if model_name in self._availability_cache:
|
|
253
|
+
return self._availability_cache[model_name]
|
|
254
|
+
|
|
255
|
+
model_cfg = self._get_model_config(model_name)
|
|
256
|
+
if not model_cfg:
|
|
257
|
+
self._availability_cache[model_name] = False
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
provider_name = model_cfg.get("provider", "")
|
|
261
|
+
provider = self._get_provider(provider_name)
|
|
262
|
+
if provider is None:
|
|
263
|
+
self._availability_cache[model_name] = False
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
prefer_local = self._config.get("settings", {}).get("prefer_local", False)
|
|
267
|
+
if prefer_local:
|
|
268
|
+
ptype = self._config.get("providers", {}).get(provider_name, {}).get("type", "")
|
|
269
|
+
if ptype not in ("openai_compatible",) and provider_name not in ("ollama", "vllm"):
|
|
270
|
+
# In prefer_local mode, skip cloud providers
|
|
271
|
+
self._availability_cache[model_name] = False
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
available = provider.check_availability(model_cfg.get("model_id", ""))
|
|
276
|
+
self._availability_cache[model_name] = available
|
|
277
|
+
return available
|
|
278
|
+
except Exception:
|
|
279
|
+
self._availability_cache[model_name] = False
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
# -------------------------------------------------------------------
|
|
283
|
+
# Routing
|
|
284
|
+
# -------------------------------------------------------------------
|
|
285
|
+
def get_provider_for_function(self, function: str) -> Tuple[Optional[LLMProvider], str, dict]:
|
|
286
|
+
"""Resolve function to (provider, model_id, model_config).
|
|
287
|
+
|
|
288
|
+
Walks the fallback chain for the given function.
|
|
289
|
+
Returns (None, "", {}) if no model is available.
|
|
290
|
+
"""
|
|
291
|
+
routing = self._config.get("routing", {})
|
|
292
|
+
route = routing.get(function, routing.get("default", {}))
|
|
293
|
+
chain = route.get("chain", [])
|
|
294
|
+
|
|
295
|
+
if not chain:
|
|
296
|
+
logger.warning("No routing chain for function '%s'", function)
|
|
297
|
+
return None, "", {}
|
|
298
|
+
|
|
299
|
+
for model_name in chain:
|
|
300
|
+
if self._check_model_available(model_name):
|
|
301
|
+
model_cfg = self._get_model_config(model_name)
|
|
302
|
+
provider_name = model_cfg.get("provider", "")
|
|
303
|
+
provider = self._get_provider(provider_name)
|
|
304
|
+
if provider:
|
|
305
|
+
logger.debug(
|
|
306
|
+
"Resolved %s -> %s (%s via %s)",
|
|
307
|
+
function, model_name, model_cfg.get("model_id"), provider_name,
|
|
308
|
+
)
|
|
309
|
+
return provider, model_cfg.get("model_id", ""), model_cfg
|
|
310
|
+
|
|
311
|
+
# Fallback: try first model in chain without availability check
|
|
312
|
+
if chain:
|
|
313
|
+
model_name = chain[0]
|
|
314
|
+
model_cfg = self._get_model_config(model_name)
|
|
315
|
+
provider_name = model_cfg.get("provider", "")
|
|
316
|
+
provider = self._get_provider(provider_name)
|
|
317
|
+
if provider:
|
|
318
|
+
logger.warning(
|
|
319
|
+
"No confirmed available model for '%s'; attempting %s anyway",
|
|
320
|
+
function, model_name,
|
|
321
|
+
)
|
|
322
|
+
return provider, model_cfg.get("model_id", ""), model_cfg
|
|
323
|
+
|
|
324
|
+
return None, "", {}
|
|
325
|
+
|
|
326
|
+
def get_effort(self, function: str) -> str:
|
|
327
|
+
"""Get configured effort level for a function."""
|
|
328
|
+
routing = self._config.get("routing", {})
|
|
329
|
+
route = routing.get(function, routing.get("default", {}))
|
|
330
|
+
return route.get("effort", "medium")
|
|
331
|
+
|
|
332
|
+
def _get_chain_for_function(self, function: str) -> list:
|
|
333
|
+
"""Get the model chain for a function."""
|
|
334
|
+
routing = self._config.get("routing", {})
|
|
335
|
+
route = routing.get(function, routing.get("default", {}))
|
|
336
|
+
return route.get("chain", [])
|
|
337
|
+
|
|
338
|
+
def _scan_for_injection(self, request: LLMRequest) -> Optional[str]:
|
|
339
|
+
"""Scan request messages for prompt injection patterns.
|
|
340
|
+
|
|
341
|
+
Returns action string ('block', 'flag', 'warn', 'allow') or None
|
|
342
|
+
if scanner is unavailable. Graceful import — does not fail if
|
|
343
|
+
prompt_injection_detector is not importable.
|
|
344
|
+
"""
|
|
345
|
+
try:
|
|
346
|
+
from tools.security.prompt_injection_detector import PromptInjectionDetector
|
|
347
|
+
except ImportError:
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
detector = PromptInjectionDetector()
|
|
351
|
+
# Scan all user messages in the request
|
|
352
|
+
texts = []
|
|
353
|
+
for msg in (request.messages or []):
|
|
354
|
+
if isinstance(msg, dict):
|
|
355
|
+
content = msg.get("content", "")
|
|
356
|
+
if isinstance(content, str):
|
|
357
|
+
texts.append(content)
|
|
358
|
+
|
|
359
|
+
if not texts:
|
|
360
|
+
return "allow"
|
|
361
|
+
|
|
362
|
+
combined = "\n".join(texts)
|
|
363
|
+
result = detector.scan_text(combined, source="llm_router")
|
|
364
|
+
|
|
365
|
+
if result["detected"]:
|
|
366
|
+
logger.warning(
|
|
367
|
+
"Prompt injection detected in LLM request: confidence=%.2f action=%s findings=%d",
|
|
368
|
+
result["confidence"], result["action"], result["finding_count"],
|
|
369
|
+
)
|
|
370
|
+
# Log to DB (best-effort)
|
|
371
|
+
detector.log_detection(
|
|
372
|
+
result,
|
|
373
|
+
project_id=request.project_id,
|
|
374
|
+
user_id=None,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
return result["action"]
|
|
378
|
+
|
|
379
|
+
# -------------------------------------------------------------------
|
|
380
|
+
# Two-tier routing helpers (D-TT1: qwen3 worker → Claude planner)
|
|
381
|
+
# -------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def _estimate_tokens(request: LLMRequest) -> int:
|
|
385
|
+
"""Rough token estimate for a request (~4 chars per token).
|
|
386
|
+
|
|
387
|
+
Used to detect prompts that will exceed a local model's context
|
|
388
|
+
window (e.g. qwen3.5 at 32K) before sending to Ollama.
|
|
389
|
+
"""
|
|
390
|
+
total_chars = len(request.system_prompt or "")
|
|
391
|
+
for msg in (request.messages or []):
|
|
392
|
+
content = msg.get("content", "") if isinstance(msg, dict) else str(msg)
|
|
393
|
+
if isinstance(content, str):
|
|
394
|
+
total_chars += len(content)
|
|
395
|
+
elif isinstance(content, list):
|
|
396
|
+
for block in content:
|
|
397
|
+
if isinstance(block, dict):
|
|
398
|
+
total_chars += len(block.get("text", ""))
|
|
399
|
+
return total_chars // 4 # conservative ~4 chars/token estimate
|
|
400
|
+
|
|
401
|
+
def _fit_to_context(self, model_name: str, request: LLMRequest) -> LLMRequest:
|
|
402
|
+
"""Truncate request messages if estimated tokens exceed model num_ctx.
|
|
403
|
+
|
|
404
|
+
Trims user message content from the middle, preserving the first and
|
|
405
|
+
last portions so the model sees the task instruction and most recent
|
|
406
|
+
context. Only applies to Ollama models with num_ctx configured.
|
|
407
|
+
"""
|
|
408
|
+
model_cfg = self._get_model_config(model_name)
|
|
409
|
+
num_ctx = model_cfg.get("num_ctx", 0)
|
|
410
|
+
if num_ctx <= 0:
|
|
411
|
+
return request
|
|
412
|
+
|
|
413
|
+
estimated = self._estimate_tokens(request)
|
|
414
|
+
# Reserve 20% of context for output tokens
|
|
415
|
+
input_budget = int(num_ctx * 0.80)
|
|
416
|
+
|
|
417
|
+
if estimated <= input_budget:
|
|
418
|
+
return request
|
|
419
|
+
|
|
420
|
+
overshoot = estimated - input_budget
|
|
421
|
+
overshoot_chars = overshoot * 4 # convert back to chars
|
|
422
|
+
logger.warning(
|
|
423
|
+
"Prompt exceeds model context (%s): ~%d tokens vs %d budget. "
|
|
424
|
+
"Truncating %d chars from user messages.",
|
|
425
|
+
model_name, estimated, input_budget, overshoot_chars,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
req = copy.copy(request)
|
|
429
|
+
req.messages = list(request.messages or [])
|
|
430
|
+
|
|
431
|
+
# Truncate the longest user message from the middle
|
|
432
|
+
for i, msg in enumerate(req.messages):
|
|
433
|
+
if not isinstance(msg, dict) or msg.get("role") != "user":
|
|
434
|
+
continue
|
|
435
|
+
content = msg.get("content", "")
|
|
436
|
+
if not isinstance(content, str) or len(content) < overshoot_chars:
|
|
437
|
+
continue
|
|
438
|
+
|
|
439
|
+
# Keep first and last portions, cut from middle
|
|
440
|
+
keep = len(content) - overshoot_chars
|
|
441
|
+
half = keep // 2
|
|
442
|
+
truncated = (
|
|
443
|
+
content[:half]
|
|
444
|
+
+ "\n\n[... content truncated to fit model context window ...]\n\n"
|
|
445
|
+
+ content[-half:]
|
|
446
|
+
)
|
|
447
|
+
req.messages[i] = {**msg, "content": truncated}
|
|
448
|
+
logger.info(
|
|
449
|
+
"Truncated user message from %d to %d chars for %s",
|
|
450
|
+
len(content), len(truncated), model_name,
|
|
451
|
+
)
|
|
452
|
+
break
|
|
453
|
+
|
|
454
|
+
return req
|
|
455
|
+
|
|
456
|
+
def _invoke_model_direct(self, model_name: str, request: LLMRequest) -> Optional[LLMResponse]:
|
|
457
|
+
"""Invoke a specific named model without chain fallback.
|
|
458
|
+
|
|
459
|
+
Returns None on any error so callers can fall through to chain.
|
|
460
|
+
"""
|
|
461
|
+
# Circuit breaker fast-path
|
|
462
|
+
breaker = self._get_model_breaker(model_name)
|
|
463
|
+
if breaker is not None and not breaker.allow_request():
|
|
464
|
+
logger.debug("Two-tier: circuit breaker OPEN for %s — skipping", model_name)
|
|
465
|
+
return None
|
|
466
|
+
|
|
467
|
+
model_cfg = self._get_model_config(model_name)
|
|
468
|
+
if not model_cfg:
|
|
469
|
+
logger.warning("Two-tier: model '%s' not found in config", model_name)
|
|
470
|
+
return None
|
|
471
|
+
provider_name = model_cfg.get("provider", "")
|
|
472
|
+
provider = self._get_provider(provider_name)
|
|
473
|
+
if provider is None:
|
|
474
|
+
logger.warning("Two-tier: provider '%s' unavailable for model '%s'", provider_name, model_name)
|
|
475
|
+
return None
|
|
476
|
+
model_id = model_cfg.get("model_id", "")
|
|
477
|
+
|
|
478
|
+
# Guard: truncate prompt if it exceeds model context window
|
|
479
|
+
fitted_request = self._fit_to_context(model_name, request)
|
|
480
|
+
|
|
481
|
+
try:
|
|
482
|
+
result = provider.invoke(fitted_request, model_id, model_cfg)
|
|
483
|
+
if breaker is not None:
|
|
484
|
+
breaker.record_success()
|
|
485
|
+
return result
|
|
486
|
+
except Exception as exc:
|
|
487
|
+
if breaker is not None:
|
|
488
|
+
breaker.record_failure()
|
|
489
|
+
logger.warning("Two-tier: direct invoke failed for %s/%s: %s", model_name, model_id, exc)
|
|
490
|
+
return None
|
|
491
|
+
|
|
492
|
+
@staticmethod
|
|
493
|
+
def _sanitize_rag_chunk(text: str) -> str:
|
|
494
|
+
"""Remove known prompt injection patterns from RAG chunk content.
|
|
495
|
+
|
|
496
|
+
SEC: Mitigates indirect prompt injection — attackers could embed
|
|
497
|
+
instructions in documents that get retrieved and injected into
|
|
498
|
+
the LLM system prompt. This strips common injection patterns.
|
|
499
|
+
"""
|
|
500
|
+
import re as _re
|
|
501
|
+
# Patterns that attempt to override system behavior
|
|
502
|
+
_injection_patterns = [
|
|
503
|
+
_re.compile(r"(?i)ignore\s+(all\s+)?previous\s+instructions?"),
|
|
504
|
+
_re.compile(r"(?i)you\s+are\s+now\s+(?:a|an|in)\s+"),
|
|
505
|
+
_re.compile(r"(?i)system\s*:\s*"),
|
|
506
|
+
_re.compile(r"(?i)<<\s*SYS\s*>>"),
|
|
507
|
+
_re.compile(r"(?i)\[INST\]"),
|
|
508
|
+
_re.compile(r"(?i)forget\s+(?:all|everything|your)\s+"),
|
|
509
|
+
_re.compile(r"(?i)new\s+instructions?\s*:"),
|
|
510
|
+
_re.compile(r"(?i)override\s+(?:previous|all|system)"),
|
|
511
|
+
]
|
|
512
|
+
sanitized = text
|
|
513
|
+
for pattern in _injection_patterns:
|
|
514
|
+
sanitized = pattern.sub("[FILTERED]", sanitized)
|
|
515
|
+
return sanitized
|
|
516
|
+
|
|
517
|
+
def _rag_augment(self, request: LLMRequest, function: str) -> LLMRequest:
|
|
518
|
+
"""Prepend RAG context to request system prompt (D-RAG-2, D-RAG-21).
|
|
519
|
+
|
|
520
|
+
Graceful import: does nothing if RAG subsystem unavailable.
|
|
521
|
+
RAG context goes into the system prompt of _draft_request() so
|
|
522
|
+
qwen3 produces a better draft; Claude reviews the draft without
|
|
523
|
+
seeing raw chunks. Maximum token savings.
|
|
524
|
+
|
|
525
|
+
D-RAG-21: When citation_enabled=true, chunks are tagged as [SOURCE-N]
|
|
526
|
+
and a citation instruction is appended from hardprompts/rag_citation.md.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
request: Original LLM request.
|
|
530
|
+
function: SPARKPILOT function name (checked against denylist).
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
Augmented LLMRequest (or original if RAG unavailable/disabled).
|
|
534
|
+
"""
|
|
535
|
+
try:
|
|
536
|
+
from tools.rag.retriever import RAGRetriever
|
|
537
|
+
except ImportError:
|
|
538
|
+
return request # RAG subsystem not installed
|
|
539
|
+
|
|
540
|
+
# Check if RAG injection is enabled
|
|
541
|
+
rag_cfg = self._config.get("rag", {})
|
|
542
|
+
injection_cfg = rag_cfg.get("injection", {})
|
|
543
|
+
if not rag_cfg.get("enabled", False) or not injection_cfg.get("enabled", True):
|
|
544
|
+
return request
|
|
545
|
+
|
|
546
|
+
# Check function denylist
|
|
547
|
+
denylist = injection_cfg.get("function_denylist", [])
|
|
548
|
+
if function in denylist:
|
|
549
|
+
return request
|
|
550
|
+
|
|
551
|
+
# Extract user query from messages
|
|
552
|
+
query = ""
|
|
553
|
+
for msg in (request.messages or []):
|
|
554
|
+
if isinstance(msg, dict) and msg.get("role") == "user":
|
|
555
|
+
c = msg.get("content", "")
|
|
556
|
+
query = c if isinstance(c, str) else str(c)
|
|
557
|
+
break
|
|
558
|
+
if not query:
|
|
559
|
+
return request
|
|
560
|
+
|
|
561
|
+
try:
|
|
562
|
+
retriever = RAGRetriever()
|
|
563
|
+
top_k = injection_cfg.get("injection_top_k", 5)
|
|
564
|
+
max_chars = injection_cfg.get("max_injection_chars", 4000)
|
|
565
|
+
citation_enabled = injection_cfg.get("citation_enabled", True)
|
|
566
|
+
citation_instruction = injection_cfg.get("citation_instruction", True)
|
|
567
|
+
|
|
568
|
+
results = retriever.search(query=query, top_k=top_k)
|
|
569
|
+
if not results:
|
|
570
|
+
return request
|
|
571
|
+
|
|
572
|
+
# Build context block with optional [SOURCE-N] tags (D-RAG-21)
|
|
573
|
+
# SEC: Sanitize chunk content to mitigate indirect prompt injection
|
|
574
|
+
context_parts = []
|
|
575
|
+
total_chars = 0
|
|
576
|
+
for i, r in enumerate(results):
|
|
577
|
+
snippet = r.content[:max_chars - total_chars] if total_chars + len(r.content) > max_chars else r.content
|
|
578
|
+
# SEC: Strip known prompt injection patterns from retrieved chunks
|
|
579
|
+
snippet = self._sanitize_rag_chunk(snippet)
|
|
580
|
+
source_label = f"{r.source_type}"
|
|
581
|
+
if r.source_id:
|
|
582
|
+
source_label += f":{r.source_id}"
|
|
583
|
+
if citation_enabled:
|
|
584
|
+
tag = f"[SOURCE-{i + 1}]"
|
|
585
|
+
context_parts.append(f"{tag} ({source_label} | score={r.final_score:.2f})\n{snippet}")
|
|
586
|
+
else:
|
|
587
|
+
context_parts.append(f"[{source_label} | score={r.final_score:.2f}]\n{snippet}")
|
|
588
|
+
total_chars += len(snippet)
|
|
589
|
+
if total_chars >= max_chars:
|
|
590
|
+
break
|
|
591
|
+
|
|
592
|
+
if not context_parts:
|
|
593
|
+
return request
|
|
594
|
+
|
|
595
|
+
context_block = (
|
|
596
|
+
"\n[RELEVANT CONTEXT — retrieved from SPARKPILOT knowledge base]\n"
|
|
597
|
+
+ "\n---\n".join(context_parts)
|
|
598
|
+
+ "\n[END CONTEXT]\n"
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
# Append citation instruction if enabled (D-RAG-21)
|
|
602
|
+
citation_block = ""
|
|
603
|
+
if citation_enabled and citation_instruction:
|
|
604
|
+
citation_path = Path(__file__).resolve().parent.parent.parent / "hardprompts" / "rag_citation.md"
|
|
605
|
+
if citation_path.exists():
|
|
606
|
+
try:
|
|
607
|
+
citation_block = "\n" + citation_path.read_text(encoding="utf-8") + "\n"
|
|
608
|
+
except Exception:
|
|
609
|
+
pass
|
|
610
|
+
|
|
611
|
+
# Prepend to system prompt
|
|
612
|
+
req = copy.copy(request)
|
|
613
|
+
req.system_prompt = context_block + citation_block + (request.system_prompt or "")
|
|
614
|
+
logger.debug("RAG augment: injected %d chunks (%d chars, citations=%s) for %s",
|
|
615
|
+
len(context_parts), total_chars, citation_enabled, function)
|
|
616
|
+
return req
|
|
617
|
+
|
|
618
|
+
except Exception as exc:
|
|
619
|
+
logger.debug("RAG augment skipped for %s: %s", function, exc)
|
|
620
|
+
return request # Never fail the main pipeline
|
|
621
|
+
|
|
622
|
+
def _draft_request(self, request: LLMRequest) -> LLMRequest:
|
|
623
|
+
"""Return a copy of request with a compact-output instruction appended.
|
|
624
|
+
|
|
625
|
+
Instructs qwen3 to produce a short, structured draft — the key to
|
|
626
|
+
keeping Claude's review input token count LOW vs Claude doing the
|
|
627
|
+
full task alone.
|
|
628
|
+
"""
|
|
629
|
+
req = copy.copy(request)
|
|
630
|
+
compact = (
|
|
631
|
+
"\n\n[DRAFT MODE] Produce a COMPACT, structured response: bullet points, "
|
|
632
|
+
"short sentences, no step-by-step reasoning chains. Max ~400 words. "
|
|
633
|
+
"This draft will be reviewed and finalized by another model."
|
|
634
|
+
)
|
|
635
|
+
req.system_prompt = (request.system_prompt or "") + compact
|
|
636
|
+
return req
|
|
637
|
+
|
|
638
|
+
def _review_request(self, original: LLMRequest, draft: LLMResponse, function: str) -> LLMRequest:
|
|
639
|
+
"""Build a Claude review request from the original task + qwen3 draft.
|
|
640
|
+
|
|
641
|
+
Claude receives: compact review system prompt + original task + draft.
|
|
642
|
+
This is intentionally smaller than Claude handling the full task alone.
|
|
643
|
+
"""
|
|
644
|
+
req = copy.copy(original)
|
|
645
|
+
req.system_prompt = (
|
|
646
|
+
f"You are reviewing a draft from a local AI assistant (function: {function}). "
|
|
647
|
+
"Verify correctness, fix errors, fill gaps, and return the final polished response. "
|
|
648
|
+
"Be direct — do not explain what you changed."
|
|
649
|
+
)
|
|
650
|
+
# Extract original user message for context
|
|
651
|
+
original_task = ""
|
|
652
|
+
for msg in (original.messages or []):
|
|
653
|
+
if isinstance(msg, dict) and msg.get("role") == "user":
|
|
654
|
+
c = msg.get("content", "")
|
|
655
|
+
original_task = c if isinstance(c, str) else str(c)
|
|
656
|
+
break
|
|
657
|
+
req.messages = [{"role": "user", "content": (
|
|
658
|
+
f"ORIGINAL TASK:\n{original_task}\n\n"
|
|
659
|
+
f"DRAFT TO REVIEW:\n{draft.content}\n\n"
|
|
660
|
+
"Return the corrected, final response only."
|
|
661
|
+
)}]
|
|
662
|
+
return req
|
|
663
|
+
|
|
664
|
+
# -------------------------------------------------------------------
|
|
665
|
+
# Fine-tuned model override (D-FT-6)
|
|
666
|
+
# -------------------------------------------------------------------
|
|
667
|
+
def _check_finetuned_override(
|
|
668
|
+
self, function: str, tenant_id: str = "", project_id: str = "",
|
|
669
|
+
) -> Optional[str]:
|
|
670
|
+
"""Check if a fine-tuned model is active for this function (D-FT-6).
|
|
671
|
+
|
|
672
|
+
Queries ft_active_models for a promoted model version. Returns
|
|
673
|
+
the Ollama model name if found, else None.
|
|
674
|
+
|
|
675
|
+
This is an additive lookup — if no fine-tuned model is active,
|
|
676
|
+
returns None and caller falls through to default routing.
|
|
677
|
+
"""
|
|
678
|
+
db_path = BASE_DIR / "data" / "icdev.db"
|
|
679
|
+
if not db_path.exists():
|
|
680
|
+
return None
|
|
681
|
+
|
|
682
|
+
try:
|
|
683
|
+
conn = sqlite3.connect(str(db_path))
|
|
684
|
+
row = conn.execute(
|
|
685
|
+
"""SELECT ollama_model_name FROM ft_active_models
|
|
686
|
+
WHERE function_name = ? AND deactivated_at IS NULL
|
|
687
|
+
AND (tenant_id = ? OR tenant_id = '')
|
|
688
|
+
AND (project_id = ? OR project_id = '')
|
|
689
|
+
ORDER BY id DESC LIMIT 1""",
|
|
690
|
+
(function, tenant_id, project_id),
|
|
691
|
+
).fetchone()
|
|
692
|
+
conn.close()
|
|
693
|
+
if row and row["ollama_model_name"]:
|
|
694
|
+
logger.info(
|
|
695
|
+
"Fine-tuned override: %s → %s",
|
|
696
|
+
function, row["ollama_model_name"],
|
|
697
|
+
)
|
|
698
|
+
return row["ollama_model_name"]
|
|
699
|
+
except Exception as exc:
|
|
700
|
+
logger.debug("Fine-tuned override check failed: %s", exc)
|
|
701
|
+
|
|
702
|
+
return None
|
|
703
|
+
|
|
704
|
+
def _invoke_finetuned_model(
|
|
705
|
+
self, ollama_model_name: str, request: LLMRequest,
|
|
706
|
+
) -> Optional[LLMResponse]:
|
|
707
|
+
"""Invoke a fine-tuned model via the Ollama provider.
|
|
708
|
+
|
|
709
|
+
Returns None on failure so caller can fall through to default.
|
|
710
|
+
"""
|
|
711
|
+
# Find the Ollama provider instance
|
|
712
|
+
provider = self._get_provider("ollama")
|
|
713
|
+
if provider is None:
|
|
714
|
+
# Try to find any ollama-type provider
|
|
715
|
+
for pname, pcfg in self._config.get("providers", {}).items():
|
|
716
|
+
if pcfg.get("type") == "ollama":
|
|
717
|
+
provider = self._get_provider(pname)
|
|
718
|
+
if provider:
|
|
719
|
+
break
|
|
720
|
+
if provider is None:
|
|
721
|
+
logger.warning("Fine-tuned invoke: no Ollama provider available")
|
|
722
|
+
return None
|
|
723
|
+
|
|
724
|
+
try:
|
|
725
|
+
# Use the fine-tuned model name directly as model_id
|
|
726
|
+
model_cfg = {"model_id": ollama_model_name, "provider": "ollama"}
|
|
727
|
+
return provider.invoke(request, ollama_model_name, model_cfg)
|
|
728
|
+
except Exception as exc:
|
|
729
|
+
logger.warning(
|
|
730
|
+
"Fine-tuned invoke failed for %s: %s",
|
|
731
|
+
ollama_model_name, exc,
|
|
732
|
+
)
|
|
733
|
+
return None
|
|
734
|
+
|
|
735
|
+
def _maybe_invoke_two_tier(
|
|
736
|
+
self, function: str, request: LLMRequest
|
|
737
|
+
) -> Optional[LLMResponse]:
|
|
738
|
+
"""Apply two-tier routing if function is configured for it.
|
|
739
|
+
|
|
740
|
+
Returns LLMResponse if two-tier handled the call, else None
|
|
741
|
+
(caller falls through to normal chain-based routing).
|
|
742
|
+
|
|
743
|
+
Three paths:
|
|
744
|
+
planner_functions → Claude directly (no qwen3 pre-step)
|
|
745
|
+
worker_functions → qwen3 compact draft → Claude review
|
|
746
|
+
scanner_functions → qwen3 only (no review)
|
|
747
|
+
"""
|
|
748
|
+
cfg = self._config.get("two_tier", {})
|
|
749
|
+
if not cfg.get("enabled", False):
|
|
750
|
+
return None
|
|
751
|
+
|
|
752
|
+
tier1 = cfg.get("tier1_model", "qwen3-local")
|
|
753
|
+
tier2 = cfg.get("tier2_model", "claude-sonnet")
|
|
754
|
+
planners = cfg.get("planner_functions", [])
|
|
755
|
+
workers = cfg.get("worker_functions", [])
|
|
756
|
+
scanners = cfg.get("scanner_functions", [])
|
|
757
|
+
|
|
758
|
+
if function in planners:
|
|
759
|
+
# Claude plans directly
|
|
760
|
+
logger.debug("Two-tier: %s → planner (Claude direct)", function)
|
|
761
|
+
result = self._invoke_model_direct(tier2, request)
|
|
762
|
+
if result is not None:
|
|
763
|
+
return result
|
|
764
|
+
# Fall through to chain on failure
|
|
765
|
+
|
|
766
|
+
elif function in workers:
|
|
767
|
+
# D-FT-6: Check if a fine-tuned model overrides tier1 for this function
|
|
768
|
+
ft_override = self._check_finetuned_override(
|
|
769
|
+
function,
|
|
770
|
+
tenant_id=getattr(request, "tenant_id", "") or "",
|
|
771
|
+
project_id=getattr(request, "project_id", "") or "",
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
# RAG augment: inject relevant context before drafting (D-RAG-2)
|
|
775
|
+
augmented = self._rag_augment(request, function)
|
|
776
|
+
|
|
777
|
+
if ft_override:
|
|
778
|
+
# Fine-tuned model replaces qwen3 as drafter
|
|
779
|
+
logger.debug(
|
|
780
|
+
"Two-tier: %s → worker (fine-tuned %s draft → Claude review)",
|
|
781
|
+
function, ft_override,
|
|
782
|
+
)
|
|
783
|
+
draft = self._invoke_finetuned_model(
|
|
784
|
+
ft_override, self._draft_request(augmented),
|
|
785
|
+
)
|
|
786
|
+
else:
|
|
787
|
+
# Default: qwen3 drafts
|
|
788
|
+
logger.debug("Two-tier: %s → worker (qwen3 draft → Claude review)", function)
|
|
789
|
+
draft = self._invoke_model_direct(tier1, self._draft_request(augmented))
|
|
790
|
+
|
|
791
|
+
if draft is not None:
|
|
792
|
+
review_req = self._review_request(request, draft, function)
|
|
793
|
+
reviewed = self._invoke_model_direct(tier2, review_req)
|
|
794
|
+
if reviewed is not None:
|
|
795
|
+
# Store draft on response for audit/observability
|
|
796
|
+
reviewed.draft_content = draft.content # type: ignore[attr-defined]
|
|
797
|
+
if ft_override:
|
|
798
|
+
reviewed.ft_model_used = ft_override # type: ignore[attr-defined]
|
|
799
|
+
return reviewed
|
|
800
|
+
# Claude unavailable — return draft as fallback
|
|
801
|
+
logger.warning("Two-tier: Claude review unavailable for %s, returning draft", function)
|
|
802
|
+
return draft
|
|
803
|
+
# Drafter unavailable — fall through to chain
|
|
804
|
+
|
|
805
|
+
elif function in scanners:
|
|
806
|
+
# qwen3 only, no review
|
|
807
|
+
logger.debug("Two-tier: %s → scanner (qwen3 only)", function)
|
|
808
|
+
result = self._invoke_model_direct(tier1, request)
|
|
809
|
+
if result is not None:
|
|
810
|
+
return result
|
|
811
|
+
# Fall through to chain on failure
|
|
812
|
+
|
|
813
|
+
return None # Not in two_tier config or model unavailable → use chain
|
|
814
|
+
|
|
815
|
+
def invoke(self, function: str, request: LLMRequest) -> LLMResponse:
|
|
816
|
+
"""Resolve provider for function and invoke with fallback.
|
|
817
|
+
|
|
818
|
+
Walks the full fallback chain: if the first provider fails at
|
|
819
|
+
invocation time (e.g. missing credentials, network error), tries
|
|
820
|
+
the next model in the chain rather than raising immediately.
|
|
821
|
+
|
|
822
|
+
Args:
|
|
823
|
+
function: SPARKPILOT function name (e.g. 'code_generation', 'nlq_sql').
|
|
824
|
+
request: Vendor-agnostic LLM request.
|
|
825
|
+
|
|
826
|
+
Returns:
|
|
827
|
+
LLMResponse.
|
|
828
|
+
|
|
829
|
+
Raises:
|
|
830
|
+
RuntimeError: If no provider in the chain can serve the request.
|
|
831
|
+
"""
|
|
832
|
+
# Scan for prompt injection before invoking (D217)
|
|
833
|
+
injection_action = self._scan_for_injection(request)
|
|
834
|
+
if injection_action == "block":
|
|
835
|
+
raise RuntimeError(
|
|
836
|
+
"Prompt injection detected with high confidence — request blocked. "
|
|
837
|
+
"Review the input content for injection patterns."
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
# Apply configured effort if not set on request
|
|
841
|
+
if not request.effort or request.effort == "medium":
|
|
842
|
+
request.effort = self.get_effort(function)
|
|
843
|
+
|
|
844
|
+
# Two-tier routing: qwen3 worker → Claude planner/reviewer
|
|
845
|
+
two_tier_result = self._maybe_invoke_two_tier(function, request)
|
|
846
|
+
if two_tier_result is not None:
|
|
847
|
+
return two_tier_result
|
|
848
|
+
|
|
849
|
+
chain = self._get_chain_for_function(function)
|
|
850
|
+
last_error = None
|
|
851
|
+
|
|
852
|
+
# D286: Create trace span for LLM invocation
|
|
853
|
+
try:
|
|
854
|
+
from tools.observability import get_tracer
|
|
855
|
+
tracer = get_tracer()
|
|
856
|
+
except ImportError:
|
|
857
|
+
tracer = None
|
|
858
|
+
|
|
859
|
+
for model_name in chain:
|
|
860
|
+
model_cfg = self._get_model_config(model_name)
|
|
861
|
+
if not model_cfg:
|
|
862
|
+
continue
|
|
863
|
+
provider_name = model_cfg.get("provider", "")
|
|
864
|
+
provider = self._get_provider(provider_name)
|
|
865
|
+
if provider is None:
|
|
866
|
+
continue
|
|
867
|
+
model_id = model_cfg.get("model_id", "")
|
|
868
|
+
|
|
869
|
+
# D286: Span with GenAI semantic conventions
|
|
870
|
+
span = None
|
|
871
|
+
if tracer:
|
|
872
|
+
span = tracer.start_span("gen_ai.invoke", kind="CLIENT", attributes={
|
|
873
|
+
"gen_ai.operation.name": "chat",
|
|
874
|
+
"gen_ai.system": provider_name,
|
|
875
|
+
"gen_ai.request.model": model_id,
|
|
876
|
+
"gen_ai.effort": request.effort or "medium",
|
|
877
|
+
"sparkpilot.llm_function": function,
|
|
878
|
+
})
|
|
879
|
+
|
|
880
|
+
breaker = self._get_model_breaker(model_name)
|
|
881
|
+
try:
|
|
882
|
+
import time as _time
|
|
883
|
+
_start = _time.time()
|
|
884
|
+
# Guard: truncate prompt if it exceeds model context window
|
|
885
|
+
fitted_request = self._fit_to_context(model_name, request)
|
|
886
|
+
response = provider.invoke(fitted_request, model_id, model_cfg)
|
|
887
|
+
_latency = int((_time.time() - _start) * 1000)
|
|
888
|
+
|
|
889
|
+
# Record success in circuit breaker
|
|
890
|
+
if breaker is not None:
|
|
891
|
+
breaker.record_success()
|
|
892
|
+
|
|
893
|
+
if span:
|
|
894
|
+
span.set_attribute("gen_ai.response.model", getattr(response, "model_id", model_id))
|
|
895
|
+
span.set_attribute("gen_ai.usage.input_tokens", getattr(response, "input_tokens", 0))
|
|
896
|
+
span.set_attribute("gen_ai.usage.output_tokens", getattr(response, "output_tokens", 0))
|
|
897
|
+
span.set_attribute("gen_ai.latency_ms", _latency)
|
|
898
|
+
if hasattr(response, "cost_usd"):
|
|
899
|
+
span.set_attribute("gen_ai.usage.cost_usd", response.cost_usd)
|
|
900
|
+
span.set_status("OK")
|
|
901
|
+
span.end()
|
|
902
|
+
|
|
903
|
+
return response
|
|
904
|
+
except Exception as exc:
|
|
905
|
+
# Record failure in circuit breaker
|
|
906
|
+
if breaker is not None:
|
|
907
|
+
breaker.record_failure()
|
|
908
|
+
|
|
909
|
+
logger.warning(
|
|
910
|
+
"Provider %s (%s) failed for %s: %s — trying next in chain",
|
|
911
|
+
provider_name, model_id, function, exc,
|
|
912
|
+
)
|
|
913
|
+
if span:
|
|
914
|
+
span.set_status("ERROR", str(exc))
|
|
915
|
+
span.add_event("provider_fallback", {
|
|
916
|
+
"failed_provider": provider_name,
|
|
917
|
+
"failed_model": model_id,
|
|
918
|
+
"error": str(exc),
|
|
919
|
+
})
|
|
920
|
+
span.end()
|
|
921
|
+
last_error = exc
|
|
922
|
+
# Mark model as unavailable in cache so next call skips it
|
|
923
|
+
self._availability_cache[model_name] = False
|
|
924
|
+
continue
|
|
925
|
+
|
|
926
|
+
raise RuntimeError(
|
|
927
|
+
"All providers in chain {} failed for function '{}'. "
|
|
928
|
+
"Last error: {}".format(chain, function, last_error)
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
def invoke_streaming(self, function: str, request: LLMRequest):
|
|
932
|
+
"""Resolve provider and invoke with streaming + fallback."""
|
|
933
|
+
if not request.effort or request.effort == "medium":
|
|
934
|
+
request.effort = self.get_effort(function)
|
|
935
|
+
|
|
936
|
+
chain = self._get_chain_for_function(function)
|
|
937
|
+
last_error = None
|
|
938
|
+
|
|
939
|
+
for model_name in chain:
|
|
940
|
+
model_cfg = self._get_model_config(model_name)
|
|
941
|
+
if not model_cfg:
|
|
942
|
+
continue
|
|
943
|
+
provider_name = model_cfg.get("provider", "")
|
|
944
|
+
provider = self._get_provider(provider_name)
|
|
945
|
+
if provider is None:
|
|
946
|
+
continue
|
|
947
|
+
model_id = model_cfg.get("model_id", "")
|
|
948
|
+
try:
|
|
949
|
+
return provider.invoke_streaming(request, model_id, model_cfg)
|
|
950
|
+
except Exception as exc:
|
|
951
|
+
logger.warning(
|
|
952
|
+
"Streaming provider %s (%s) failed for %s: %s — trying next",
|
|
953
|
+
provider_name, model_id, function, exc,
|
|
954
|
+
)
|
|
955
|
+
last_error = exc
|
|
956
|
+
self._availability_cache[model_name] = False
|
|
957
|
+
continue
|
|
958
|
+
|
|
959
|
+
raise RuntimeError(
|
|
960
|
+
"All streaming providers in chain {} failed for function '{}'. "
|
|
961
|
+
"Last error: {}".format(chain, function, last_error)
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
# -------------------------------------------------------------------
|
|
965
|
+
# Embedding providers
|
|
966
|
+
# -------------------------------------------------------------------
|
|
967
|
+
def get_embedding_provider(self) -> EmbeddingProvider:
|
|
968
|
+
"""Get the first available embedding provider.
|
|
969
|
+
|
|
970
|
+
Walks the embeddings.default_chain from config.
|
|
971
|
+
|
|
972
|
+
Raises:
|
|
973
|
+
RuntimeError if no embedding provider is available.
|
|
974
|
+
"""
|
|
975
|
+
emb_cfg = self._config.get("embeddings", {})
|
|
976
|
+
chain = emb_cfg.get("default_chain", [])
|
|
977
|
+
models = emb_cfg.get("models", {})
|
|
978
|
+
|
|
979
|
+
for model_name in chain:
|
|
980
|
+
if model_name in self._embedding_providers:
|
|
981
|
+
return self._embedding_providers[model_name]
|
|
982
|
+
|
|
983
|
+
mcfg = models.get(model_name, {})
|
|
984
|
+
if not mcfg:
|
|
985
|
+
continue
|
|
986
|
+
|
|
987
|
+
provider_name = mcfg.get("provider", "")
|
|
988
|
+
ptype = self._config.get("providers", {}).get(provider_name, {}).get("type", "")
|
|
989
|
+
|
|
990
|
+
try:
|
|
991
|
+
emb = None
|
|
992
|
+
if ptype in ("openai", "openai_compatible"):
|
|
993
|
+
from tools.llm.embedding_provider import OpenAIEmbeddingProvider
|
|
994
|
+
pcfg = self._config.get("providers", {}).get(provider_name, {})
|
|
995
|
+
api_key = pcfg.get("api_key", "")
|
|
996
|
+
if not api_key:
|
|
997
|
+
api_key_env = pcfg.get("api_key_env", "")
|
|
998
|
+
if api_key_env:
|
|
999
|
+
api_key = os.environ.get(api_key_env, "")
|
|
1000
|
+
base_url = _expand_env(pcfg.get("base_url", "https://api.openai.com/v1"))
|
|
1001
|
+
emb = OpenAIEmbeddingProvider(
|
|
1002
|
+
api_key=api_key,
|
|
1003
|
+
base_url=base_url,
|
|
1004
|
+
model_id=mcfg.get("model_id", "text-embedding-3-small"),
|
|
1005
|
+
dims=mcfg.get("dimensions", 1536),
|
|
1006
|
+
)
|
|
1007
|
+
elif ptype == "bedrock":
|
|
1008
|
+
from tools.llm.embedding_provider import BedrockEmbeddingProvider
|
|
1009
|
+
pcfg = self._config.get("providers", {}).get(provider_name, {})
|
|
1010
|
+
region = _expand_env(pcfg.get("region", "us-gov-west-1"))
|
|
1011
|
+
emb = BedrockEmbeddingProvider(
|
|
1012
|
+
region=region,
|
|
1013
|
+
model_id=mcfg.get("model_id", "amazon.titan-embed-text-v2:0"),
|
|
1014
|
+
dims=mcfg.get("dimensions", 1024),
|
|
1015
|
+
)
|
|
1016
|
+
elif ptype == "gemini":
|
|
1017
|
+
from tools.llm.embedding_provider import GeminiEmbeddingProvider
|
|
1018
|
+
pcfg = self._config.get("providers", {}).get(provider_name, {})
|
|
1019
|
+
api_key = pcfg.get("api_key", "")
|
|
1020
|
+
if not api_key:
|
|
1021
|
+
api_key_env = pcfg.get("api_key_env", "GOOGLE_API_KEY")
|
|
1022
|
+
api_key = os.environ.get(api_key_env, "")
|
|
1023
|
+
emb = GeminiEmbeddingProvider(
|
|
1024
|
+
api_key=api_key,
|
|
1025
|
+
model_id=mcfg.get("model_id", "text-embedding-004"),
|
|
1026
|
+
dims=mcfg.get("dimensions", 768),
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
elif ptype == "azure_openai":
|
|
1030
|
+
from tools.llm.embedding_provider import AzureEmbeddingProvider
|
|
1031
|
+
pcfg = self._config.get("providers", {}).get(provider_name, {})
|
|
1032
|
+
api_key = _expand_env(pcfg.get("api_key", ""))
|
|
1033
|
+
if not api_key:
|
|
1034
|
+
api_key_env = pcfg.get("api_key_env", "AZURE_OPENAI_API_KEY")
|
|
1035
|
+
api_key = os.environ.get(api_key_env, "")
|
|
1036
|
+
endpoint = _expand_env(pcfg.get("endpoint", ""))
|
|
1037
|
+
if not endpoint:
|
|
1038
|
+
endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT", "")
|
|
1039
|
+
api_version = pcfg.get("api_version", "2024-02-01")
|
|
1040
|
+
emb = AzureEmbeddingProvider(
|
|
1041
|
+
api_key=api_key,
|
|
1042
|
+
endpoint=endpoint,
|
|
1043
|
+
api_version=api_version,
|
|
1044
|
+
deployment=mcfg.get("model_id", "text-embedding-ada-002"),
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
elif ptype == "oci_genai":
|
|
1048
|
+
from tools.llm.embedding_provider import OCIEmbeddingProvider
|
|
1049
|
+
pcfg = self._config.get("providers", {}).get(provider_name, {})
|
|
1050
|
+
compartment_id = _expand_env(pcfg.get("compartment_id", ""))
|
|
1051
|
+
if not compartment_id:
|
|
1052
|
+
compartment_id = os.environ.get("OCI_COMPARTMENT_OCID", "")
|
|
1053
|
+
service_endpoint = _expand_env(pcfg.get("service_endpoint", ""))
|
|
1054
|
+
if not service_endpoint:
|
|
1055
|
+
service_endpoint = os.environ.get(
|
|
1056
|
+
"OCI_GENAI_ENDPOINT",
|
|
1057
|
+
"https://inference.generativeai.us-chicago-1.oci.oraclecloud.com",
|
|
1058
|
+
)
|
|
1059
|
+
emb = OCIEmbeddingProvider(
|
|
1060
|
+
compartment_id=compartment_id,
|
|
1061
|
+
model_id=mcfg.get("model_id", "cohere.embed-english-v3.0"),
|
|
1062
|
+
service_endpoint=service_endpoint,
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1065
|
+
elif ptype == "ibm_watsonx":
|
|
1066
|
+
from tools.llm.embedding_provider import IBMWatsonxEmbeddingProvider
|
|
1067
|
+
pcfg = self._config.get("providers", {}).get(provider_name, {})
|
|
1068
|
+
api_key = _expand_env(pcfg.get("api_key", ""))
|
|
1069
|
+
if not api_key:
|
|
1070
|
+
api_key = os.environ.get(
|
|
1071
|
+
pcfg.get("api_key_env", "IBM_CLOUD_API_KEY"), ""
|
|
1072
|
+
)
|
|
1073
|
+
project_id = _expand_env(pcfg.get("project_id", ""))
|
|
1074
|
+
if not project_id:
|
|
1075
|
+
project_id = os.environ.get("IBM_WATSONX_PROJECT_ID", "")
|
|
1076
|
+
url = _expand_env(pcfg.get("url", ""))
|
|
1077
|
+
if not url:
|
|
1078
|
+
url = os.environ.get("IBM_WATSONX_URL",
|
|
1079
|
+
"https://us-south.ml.cloud.ibm.com")
|
|
1080
|
+
emb = IBMWatsonxEmbeddingProvider(
|
|
1081
|
+
api_key=api_key, project_id=project_id, url=url,
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
if emb and emb.check_availability():
|
|
1085
|
+
self._embedding_providers[model_name] = emb
|
|
1086
|
+
logger.info("Embedding provider ready: %s", model_name)
|
|
1087
|
+
return emb
|
|
1088
|
+
except ImportError as exc:
|
|
1089
|
+
logger.debug("Embedding provider '%s' not importable: %s", model_name, exc)
|
|
1090
|
+
except Exception as exc:
|
|
1091
|
+
logger.debug("Embedding provider '%s' failed: %s", model_name, exc)
|
|
1092
|
+
|
|
1093
|
+
raise RuntimeError(
|
|
1094
|
+
"No embedding provider available. Check llm_config.yaml embeddings section."
|
|
1095
|
+
)
|
|
1096
|
+
|
|
1097
|
+
# -------------------------------------------------------------------
|
|
1098
|
+
# Model pricing lookup
|
|
1099
|
+
# -------------------------------------------------------------------
|
|
1100
|
+
def get_model_pricing(self, model_id: str) -> dict:
|
|
1101
|
+
"""Look up pricing for a model_id (searches all models)."""
|
|
1102
|
+
for _name, cfg in self._config.get("models", {}).items():
|
|
1103
|
+
if cfg.get("model_id") == model_id:
|
|
1104
|
+
return cfg.get("pricing", {})
|
|
1105
|
+
# Also check embedding models
|
|
1106
|
+
emb_models = self._config.get("embeddings", {}).get("models", {})
|
|
1107
|
+
for _name, cfg in emb_models.items():
|
|
1108
|
+
if cfg.get("model_id") == model_id:
|
|
1109
|
+
return cfg.get("pricing", {})
|
|
1110
|
+
return {}
|
|
1111
|
+
|
|
1112
|
+
def get_all_model_pricing(self) -> Dict[str, dict]:
|
|
1113
|
+
"""Get pricing for all configured models. Returns {model_id: pricing}."""
|
|
1114
|
+
result = {}
|
|
1115
|
+
for _name, cfg in self._config.get("models", {}).items():
|
|
1116
|
+
mid = cfg.get("model_id", "")
|
|
1117
|
+
if mid:
|
|
1118
|
+
result[mid] = cfg.get("pricing", {})
|
|
1119
|
+
emb_models = self._config.get("embeddings", {}).get("models", {})
|
|
1120
|
+
for _name, cfg in emb_models.items():
|
|
1121
|
+
mid = cfg.get("model_id", "")
|
|
1122
|
+
if mid:
|
|
1123
|
+
result[mid] = cfg.get("pricing", {})
|
|
1124
|
+
return result
|