@bluefly/openstandardagents 0.4.0 → 0.4.1
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.
- package/CHANGELOG.md +117 -0
- package/DEMO.md +212 -0
- package/README.md +75 -15
- package/dist/adapters/drupal/generator.d.ts +149 -0
- package/dist/adapters/drupal/generator.d.ts.map +1 -0
- package/dist/adapters/drupal/generator.js +1760 -0
- package/dist/adapters/drupal/generator.js.map +1 -0
- package/dist/adapters/drupal/index.d.ts +2 -0
- package/dist/adapters/drupal/index.d.ts.map +1 -1
- package/dist/adapters/drupal/index.js +3 -0
- package/dist/adapters/drupal/index.js.map +1 -1
- package/dist/adapters/npm/adapter.js +2 -2
- package/dist/adapters/npm/converter.js +3 -3
- package/dist/cli/banner.d.ts +21 -0
- package/dist/cli/banner.d.ts.map +1 -0
- package/dist/cli/banner.js +128 -0
- package/dist/cli/banner.js.map +1 -0
- package/dist/cli/commands/dev.command.d.ts +20 -0
- package/dist/cli/commands/dev.command.d.ts.map +1 -0
- package/dist/cli/commands/dev.command.js +78 -0
- package/dist/cli/commands/dev.command.js.map +1 -0
- package/dist/cli/commands/estimate.command.d.ts +12 -0
- package/dist/cli/commands/estimate.command.d.ts.map +1 -0
- package/dist/cli/commands/estimate.command.js +226 -0
- package/dist/cli/commands/estimate.command.js.map +1 -0
- package/dist/cli/commands/export-enhanced.command.d.ts +7 -0
- package/dist/cli/commands/export-enhanced.command.d.ts.map +1 -0
- package/dist/cli/commands/{export-v2.command.js → export-enhanced.command.js} +3 -3
- package/dist/cli/commands/export-enhanced.command.js.map +1 -0
- package/dist/cli/commands/export.command.d.ts.map +1 -1
- package/dist/cli/commands/export.command.js +82 -4
- package/dist/cli/commands/export.command.js.map +1 -1
- package/dist/cli/commands/init.command.d.ts.map +1 -1
- package/dist/cli/commands/init.command.js +2 -0
- package/dist/cli/commands/init.command.js.map +1 -1
- package/dist/cli/commands/test.command.d.ts +1 -0
- package/dist/cli/commands/test.command.d.ts.map +1 -1
- package/dist/cli/commands/test.command.js +172 -105
- package/dist/cli/commands/test.command.js.map +1 -1
- package/dist/cli/commands/types/wizard-config.types.d.ts +59 -0
- package/dist/cli/commands/types/wizard-config.types.d.ts.map +1 -0
- package/dist/cli/commands/types/wizard-config.types.js +34 -0
- package/dist/cli/commands/types/wizard-config.types.js.map +1 -0
- package/dist/cli/commands/upgrade.command.d.ts +9 -0
- package/dist/cli/commands/upgrade.command.d.ts.map +1 -0
- package/dist/cli/commands/upgrade.command.js +167 -0
- package/dist/cli/commands/upgrade.command.js.map +1 -0
- package/dist/cli/commands/wizard-api-first.command.d.ts +18 -0
- package/dist/cli/commands/wizard-api-first.command.d.ts.map +1 -0
- package/dist/cli/commands/wizard-api-first.command.js +854 -0
- package/dist/cli/commands/wizard-api-first.command.js.map +1 -0
- package/dist/cli/commands/wizard-interactive.command.d.ts +25 -0
- package/dist/cli/commands/wizard-interactive.command.d.ts.map +1 -0
- package/dist/cli/commands/wizard-interactive.command.js +1875 -0
- package/dist/cli/commands/wizard-interactive.command.js.map +1 -0
- package/dist/cli/commands/workspace.command.js +1 -1
- package/dist/cli/commands/workspace.command.js.map +1 -1
- package/dist/cli/index.js +9 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/schema-driven/index.d.ts +27 -0
- package/dist/cli/schema-driven/index.d.ts.map +1 -0
- package/dist/cli/schema-driven/index.js +34 -0
- package/dist/cli/schema-driven/index.js.map +1 -0
- package/dist/cli/schema-driven/schema-loader.d.ts +115 -0
- package/dist/cli/schema-driven/schema-loader.d.ts.map +1 -0
- package/dist/cli/schema-driven/schema-loader.js +270 -0
- package/dist/cli/schema-driven/schema-loader.js.map +1 -0
- package/dist/cli/schema-driven/ui-generator.d.ts +88 -0
- package/dist/cli/schema-driven/ui-generator.d.ts.map +1 -0
- package/dist/cli/schema-driven/ui-generator.js +326 -0
- package/dist/cli/schema-driven/ui-generator.js.map +1 -0
- package/dist/cli/wizard/interactive-wizard.d.ts +26 -0
- package/dist/cli/wizard/interactive-wizard.d.ts.map +1 -0
- package/dist/cli/wizard/interactive-wizard.js +296 -0
- package/dist/cli/wizard/interactive-wizard.js.map +1 -0
- package/dist/cli/wizard/template-catalog.d.ts +32 -0
- package/dist/cli/wizard/template-catalog.d.ts.map +1 -0
- package/dist/cli/wizard/template-catalog.js +99 -0
- package/dist/cli/wizard/template-catalog.js.map +1 -0
- package/dist/cli/wizard/use-cases.d.ts +37 -0
- package/dist/cli/wizard/use-cases.d.ts.map +1 -0
- package/dist/cli/wizard/use-cases.js +157 -0
- package/dist/cli/wizard/use-cases.js.map +1 -0
- package/dist/di-container.d.ts.map +1 -1
- package/dist/di-container.js +2 -0
- package/dist/di-container.js.map +1 -1
- package/dist/package.json +19 -9
- package/dist/runtime/agent-runner.d.ts +46 -0
- package/dist/runtime/agent-runner.d.ts.map +1 -0
- package/dist/runtime/agent-runner.js +346 -0
- package/dist/runtime/agent-runner.js.map +1 -0
- package/dist/sdks/kagent/crd-generator.d.ts +4 -0
- package/dist/sdks/kagent/crd-generator.d.ts.map +1 -1
- package/dist/sdks/kagent/crd-generator.js +83 -2
- package/dist/sdks/kagent/crd-generator.js.map +1 -1
- package/dist/sdks/kagent/k8s-resources-generator.d.ts +73 -0
- package/dist/sdks/kagent/k8s-resources-generator.d.ts.map +1 -0
- package/dist/sdks/kagent/k8s-resources-generator.js +286 -0
- package/dist/sdks/kagent/k8s-resources-generator.js.map +1 -0
- package/dist/sdks/kagent/types.d.ts +79 -0
- package/dist/sdks/kagent/types.d.ts.map +1 -1
- package/dist/sdks/shared/validation.d.ts +2 -2
- package/dist/services/cost-estimation/optimization-patterns.d.ts +23 -0
- package/dist/services/cost-estimation/optimization-patterns.d.ts.map +1 -0
- package/dist/services/cost-estimation/optimization-patterns.js +147 -0
- package/dist/services/cost-estimation/optimization-patterns.js.map +1 -0
- package/dist/services/cost-estimation/pricing.d.ts +29 -0
- package/dist/services/cost-estimation/pricing.d.ts.map +1 -0
- package/dist/services/cost-estimation/pricing.js +225 -0
- package/dist/services/cost-estimation/pricing.js.map +1 -0
- package/dist/services/cost-estimation/scenario-estimator.d.ts +59 -0
- package/dist/services/cost-estimation/scenario-estimator.d.ts.map +1 -0
- package/dist/services/cost-estimation/scenario-estimator.js +145 -0
- package/dist/services/cost-estimation/scenario-estimator.js.map +1 -0
- package/dist/services/cost-estimation/token-counter.service.d.ts +51 -0
- package/dist/services/cost-estimation/token-counter.service.d.ts.map +1 -0
- package/dist/services/cost-estimation/token-counter.service.js +125 -0
- package/dist/services/cost-estimation/token-counter.service.js.map +1 -0
- package/dist/services/dev-server/dev-server.service.d.ts +121 -0
- package/dist/services/dev-server/dev-server.service.d.ts.map +1 -0
- package/dist/services/dev-server/dev-server.service.js +290 -0
- package/dist/services/dev-server/dev-server.service.js.map +1 -0
- package/dist/services/dev-server/file-watcher.d.ts +101 -0
- package/dist/services/dev-server/file-watcher.d.ts.map +1 -0
- package/dist/services/dev-server/file-watcher.js +190 -0
- package/dist/services/dev-server/file-watcher.js.map +1 -0
- package/dist/services/dev-server/live-validator.d.ts +157 -0
- package/dist/services/dev-server/live-validator.d.ts.map +1 -0
- package/dist/services/dev-server/live-validator.js +301 -0
- package/dist/services/dev-server/live-validator.js.map +1 -0
- package/dist/services/dev-server/websocket-server.d.ts +137 -0
- package/dist/services/dev-server/websocket-server.d.ts.map +1 -0
- package/dist/services/dev-server/websocket-server.js +229 -0
- package/dist/services/dev-server/websocket-server.js.map +1 -0
- package/dist/services/export/anthropic/anthropic-exporter.d.ts +70 -0
- package/dist/services/export/anthropic/anthropic-exporter.d.ts.map +1 -0
- package/dist/services/export/anthropic/anthropic-exporter.js +576 -0
- package/dist/services/export/anthropic/anthropic-exporter.js.map +1 -0
- package/dist/services/export/anthropic/api-generator.d.ts +39 -0
- package/dist/services/export/anthropic/api-generator.d.ts.map +1 -0
- package/dist/services/export/anthropic/api-generator.js +395 -0
- package/dist/services/export/anthropic/api-generator.js.map +1 -0
- package/dist/services/export/anthropic/index.d.ts +18 -0
- package/dist/services/export/anthropic/index.d.ts.map +1 -0
- package/dist/services/export/anthropic/index.js +16 -0
- package/dist/services/export/anthropic/index.js.map +1 -0
- package/dist/services/export/anthropic/tools-generator.d.ts +35 -0
- package/dist/services/export/anthropic/tools-generator.d.ts.map +1 -0
- package/dist/services/export/anthropic/tools-generator.js +260 -0
- package/dist/services/export/anthropic/tools-generator.js.map +1 -0
- package/dist/services/export/langchain/api-generator.d.ts +17 -0
- package/dist/services/export/langchain/api-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/api-generator.js +375 -0
- package/dist/services/export/langchain/api-generator.js.map +1 -0
- package/dist/services/export/langchain/callbacks-generator.d.ts +63 -0
- package/dist/services/export/langchain/callbacks-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/callbacks-generator.js +408 -0
- package/dist/services/export/langchain/callbacks-generator.js.map +1 -0
- package/dist/services/export/langchain/error-handling-generator.d.ts +76 -0
- package/dist/services/export/langchain/error-handling-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/error-handling-generator.js +522 -0
- package/dist/services/export/langchain/error-handling-generator.js.map +1 -0
- package/dist/services/export/langchain/index.d.ts +17 -0
- package/dist/services/export/langchain/index.d.ts.map +1 -0
- package/dist/services/export/langchain/index.js +13 -0
- package/dist/services/export/langchain/index.js.map +1 -0
- package/dist/services/export/langchain/langchain-exporter.d.ts +174 -0
- package/dist/services/export/langchain/langchain-exporter.d.ts.map +1 -0
- package/dist/services/export/langchain/langchain-exporter.js +953 -0
- package/dist/services/export/langchain/langchain-exporter.js.map +1 -0
- package/dist/services/export/langchain/langgraph-generator.d.ts +86 -0
- package/dist/services/export/langchain/langgraph-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/langgraph-generator.js +473 -0
- package/dist/services/export/langchain/langgraph-generator.js.map +1 -0
- package/dist/services/export/langchain/langserve-generator.d.ts +95 -0
- package/dist/services/export/langchain/langserve-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/langserve-generator.js +807 -0
- package/dist/services/export/langchain/langserve-generator.js.map +1 -0
- package/dist/services/export/langchain/memory-generator.d.ts +71 -0
- package/dist/services/export/langchain/memory-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/memory-generator.js +1182 -0
- package/dist/services/export/langchain/memory-generator.js.map +1 -0
- package/dist/services/export/langchain/openapi-generator.d.ts +20 -0
- package/dist/services/export/langchain/openapi-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/openapi-generator.js +364 -0
- package/dist/services/export/langchain/openapi-generator.js.map +1 -0
- package/dist/services/export/langchain/plan-execute-generator.d.ts +60 -0
- package/dist/services/export/langchain/plan-execute-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/plan-execute-generator.js +679 -0
- package/dist/services/export/langchain/plan-execute-generator.js.map +1 -0
- package/dist/services/export/langchain/streaming-generator.d.ts +66 -0
- package/dist/services/export/langchain/streaming-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/streaming-generator.js +749 -0
- package/dist/services/export/langchain/streaming-generator.js.map +1 -0
- package/dist/services/export/langchain/tools-generator.d.ts +67 -0
- package/dist/services/export/langchain/tools-generator.d.ts.map +1 -0
- package/dist/services/export/langchain/tools-generator.js +543 -0
- package/dist/services/export/langchain/tools-generator.js.map +1 -0
- package/dist/services/export/npm/express-generator.d.ts +23 -0
- package/dist/services/export/npm/express-generator.d.ts.map +1 -0
- package/dist/services/export/npm/express-generator.js +296 -0
- package/dist/services/export/npm/express-generator.js.map +1 -0
- package/dist/services/export/npm/index.d.ts +13 -0
- package/dist/services/export/npm/index.d.ts.map +1 -0
- package/dist/services/export/npm/index.js +11 -0
- package/dist/services/export/npm/index.js.map +1 -0
- package/dist/services/export/npm/npm-exporter.d.ts +142 -0
- package/dist/services/export/npm/npm-exporter.d.ts.map +1 -0
- package/dist/services/export/npm/npm-exporter.js +480 -0
- package/dist/services/export/npm/npm-exporter.js.map +1 -0
- package/dist/services/export/npm/openapi-generator.d.ts +19 -0
- package/dist/services/export/npm/openapi-generator.d.ts.map +1 -0
- package/dist/services/export/npm/openapi-generator.js +428 -0
- package/dist/services/export/npm/openapi-generator.js.map +1 -0
- package/dist/services/export/npm/package-json-generator.d.ts +31 -0
- package/dist/services/export/npm/package-json-generator.d.ts.map +1 -0
- package/dist/services/export/npm/package-json-generator.js +153 -0
- package/dist/services/export/npm/package-json-generator.js.map +1 -0
- package/dist/services/export/npm/typescript-generator.d.ts +69 -0
- package/dist/services/export/npm/typescript-generator.d.ts.map +1 -0
- package/dist/services/export/npm/typescript-generator.js +437 -0
- package/dist/services/export/npm/typescript-generator.js.map +1 -0
- package/dist/services/export/testing/index.d.ts +8 -0
- package/dist/services/export/testing/index.d.ts.map +1 -0
- package/dist/services/export/testing/index.js +7 -0
- package/dist/services/export/testing/index.js.map +1 -0
- package/dist/services/export/testing/test-generator.d.ts +178 -0
- package/dist/services/export/testing/test-generator.d.ts.map +1 -0
- package/dist/services/export/testing/test-generator.js +2542 -0
- package/dist/services/export/testing/test-generator.js.map +1 -0
- package/dist/services/test-runner/mock-llm.service.d.ts +77 -0
- package/dist/services/test-runner/mock-llm.service.d.ts.map +1 -0
- package/dist/services/test-runner/mock-llm.service.js +173 -0
- package/dist/services/test-runner/mock-llm.service.js.map +1 -0
- package/dist/services/test-runner/scenarios.d.ts +36 -0
- package/dist/services/test-runner/scenarios.d.ts.map +1 -0
- package/dist/services/test-runner/scenarios.js +196 -0
- package/dist/services/test-runner/scenarios.js.map +1 -0
- package/dist/services/test-runner/test-runner.service.d.ts +19 -1
- package/dist/services/test-runner/test-runner.service.d.ts.map +1 -1
- package/dist/services/test-runner/test-runner.service.js +72 -6
- package/dist/services/test-runner/test-runner.service.js.map +1 -1
- package/dist/services/validation/best-practices-validator.d.ts +84 -0
- package/dist/services/validation/best-practices-validator.d.ts.map +1 -0
- package/dist/services/validation/best-practices-validator.js +499 -0
- package/dist/services/validation/best-practices-validator.js.map +1 -0
- package/dist/services/validation/cost-estimator.d.ts +69 -0
- package/dist/services/validation/cost-estimator.d.ts.map +1 -0
- package/dist/services/validation/cost-estimator.js +221 -0
- package/dist/services/validation/cost-estimator.js.map +1 -0
- package/dist/services/validation/enhanced-validator.d.ts +78 -0
- package/dist/services/validation/enhanced-validator.d.ts.map +1 -0
- package/dist/services/validation/enhanced-validator.js +212 -0
- package/dist/services/validation/enhanced-validator.js.map +1 -0
- package/dist/services/validation/index.d.ts +13 -0
- package/dist/services/validation/index.d.ts.map +1 -0
- package/dist/services/validation/index.js +9 -0
- package/dist/services/validation/index.js.map +1 -0
- package/dist/services/validation/security-validator.d.ts +81 -0
- package/dist/services/validation/security-validator.d.ts.map +1 -0
- package/dist/services/validation/security-validator.js +328 -0
- package/dist/services/validation/security-validator.js.map +1 -0
- package/dist/services/wizard/prompts.d.ts +71 -0
- package/dist/services/wizard/prompts.d.ts.map +1 -0
- package/dist/services/wizard/prompts.js +322 -0
- package/dist/services/wizard/prompts.js.map +1 -0
- package/dist/services/wizard/wizard.service.d.ts +60 -0
- package/dist/services/wizard/wizard.service.d.ts.map +1 -0
- package/dist/services/wizard/wizard.service.js +261 -0
- package/dist/services/wizard/wizard.service.js.map +1 -0
- package/dist/types/personality.zod.d.ts +23 -23
- package/dist/utils/version.d.ts +1 -1
- package/dist/utils/version.js +1 -1
- package/dist/version-management/core/version-manager.test.js.map +1 -1
- package/dist/version.d.ts +62 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +73 -0
- package/dist/version.js.map +1 -0
- package/examples/a2a/agent-handoff.ossa.yaml +1 -1
- package/examples/a2a/service-discovery.ossa.yaml +1 -1
- package/examples/adapters/drupal-eca-mapping.yaml +1 -1
- package/examples/adapters/drupal-eca-task.yaml +1 -1
- package/examples/adapters/drupal-flowdrop-mapping.yaml +1 -1
- package/examples/adapters/drupal-maestro-mapping.yaml +1 -1
- package/examples/adapters/mistral-agent.yaml +1 -1
- package/examples/adapters/symfony-messenger-task.yaml +1 -1
- package/examples/adapters/symfony-messenger-workflow.yaml +1 -1
- package/examples/adk-integration/code-review-workflow.yml +1 -1
- package/examples/adk-integration/customer-support.yml +1 -1
- package/examples/adk-integration/data-pipeline.yml +1 -1
- package/examples/advanced/reasoning-agent.yaml +1 -1
- package/examples/advanced/workflows/hybrid-model-strategy.yaml +1 -1
- package/examples/agent-manifests/critics/critic-agent.yaml +1 -1
- package/examples/agent-manifests/governors/governor-agent.yaml +1 -1
- package/examples/agent-manifests/integrators/integrator-agent.yaml +1 -1
- package/examples/agent-manifests/judges/judge-agent.yaml +1 -1
- package/examples/agent-manifests/monitors/monitor-agent.yaml +1 -1
- package/examples/agent-manifests/orchestrators/orchestrator-agent.yaml +1 -1
- package/examples/agent-manifests/sample-compliant-agent.yaml +1 -1
- package/examples/agent-manifests/workers/worker-agent.yaml +1 -1
- package/examples/agents/01-customer-support-bot/.env.example +32 -0
- package/examples/agents/01-customer-support-bot/Dockerfile +30 -0
- package/examples/agents/01-customer-support-bot/README.md +295 -0
- package/examples/agents/01-customer-support-bot/agent.ossa.yaml +172 -0
- package/examples/agents/01-customer-support-bot/docker-compose.yml +55 -0
- package/examples/agents/01-customer-support-bot/openapi.yaml +238 -0
- package/examples/agents/01-customer-support-bot/package.json +48 -0
- package/examples/agents/02-code-review-agent/README.md +72 -0
- package/examples/agents/02-code-review-agent/agent.ossa.yaml +239 -0
- package/examples/agents/02-code-review-agent/docker-compose.yml +22 -0
- package/examples/agents/02-code-review-agent/openapi.yaml +150 -0
- package/examples/agents/03-data-analysis-agent/README.md +51 -0
- package/examples/agents/03-data-analysis-agent/agent.ossa.yaml +97 -0
- package/examples/agents/03-data-analysis-agent/openapi.yaml +74 -0
- package/examples/agents/04-content-moderator/README.md +55 -0
- package/examples/agents/04-content-moderator/agent.ossa.yaml +131 -0
- package/examples/agents/04-content-moderator/openapi.yaml +50 -0
- package/examples/agents/05-sales-assistant/README.md +37 -0
- package/examples/agents/05-sales-assistant/agent.ossa.yaml +146 -0
- package/examples/agents/05-sales-assistant/openapi.yaml +59 -0
- package/examples/agents/06-devops-agent/README.md +39 -0
- package/examples/agents/06-devops-agent/agent.ossa.yaml +141 -0
- package/examples/agents/06-devops-agent/openapi.yaml +51 -0
- package/examples/agents/07-research-assistant/README.md +31 -0
- package/examples/agents/07-research-assistant/agent.ossa.yaml +119 -0
- package/examples/agents/07-research-assistant/openapi.yaml +56 -0
- package/examples/agents/08-email-triage-agent/README.md +33 -0
- package/examples/agents/08-email-triage-agent/agent.ossa.yaml +133 -0
- package/examples/agents/08-email-triage-agent/openapi.yaml +41 -0
- package/examples/agents/09-security-scanner/README.md +49 -0
- package/examples/agents/09-security-scanner/agent.ossa.yaml +174 -0
- package/examples/agents/09-security-scanner/openapi.yaml +46 -0
- package/examples/agents/10-meeting-assistant/README.md +53 -0
- package/examples/agents/10-meeting-assistant/agent.ossa.yaml +211 -0
- package/examples/agents/10-meeting-assistant/docker-compose.yml +27 -0
- package/examples/agents/10-meeting-assistant/openapi.yaml +131 -0
- package/examples/agents/COMPLETION_REPORT.txt +272 -0
- package/examples/agents/INDEX.md +296 -0
- package/examples/agents/README.md +452 -0
- package/examples/agents/SUMMARY.md +362 -0
- package/examples/agents/TEST_RESULTS.md +458 -0
- package/examples/agents/architecture-healer-enterprise.yaml +1 -1
- package/examples/agents/dependency-healer-npm.yaml +1 -1
- package/examples/agents/spec-healer-openapi.yaml +1 -1
- package/examples/agents/wiki-healer-production.yaml +1 -1
- package/examples/agents-md/code-agent.ossa.json +1 -1
- package/examples/agents-md/monorepo-agent.ossa.yaml +1 -1
- package/examples/anthropic/claude-assistant.ossa.json +1 -1
- package/examples/autogen/multi-agent.ossa.json +1 -1
- package/examples/autonomous-evolution/self-evolving-agent.ossa.yaml +1 -1
- package/examples/build-once-use-everywhere/agent.ossa.yaml +1 -1
- package/examples/claude-code/code-reviewer.ossa.yaml +1 -1
- package/examples/claude-code/ossa-validator.ossa.yaml +1 -1
- package/examples/common_npm/agent-router.ossa.yaml +2 -2
- package/examples/contracts/data-consumer.ossa.yaml +1 -1
- package/examples/contracts/data-producer-v2.ossa.yaml +1 -1
- package/examples/contracts/data-producer.ossa.yaml +1 -1
- package/examples/crewai/research-team.ossa.json +1 -1
- package/examples/cursor/code-review-agent.ossa.json +1 -1
- package/examples/drupal/QUICKSTART.md +439 -0
- package/examples/drupal/ai_agents_ossa-module/.agents/example-agent/agent.ossa.yaml +1 -1
- package/examples/drupal/content-moderator.ossa.yaml +107 -0
- package/examples/drupal/gitlab-ml-recommender.ossa.yaml +2 -2
- package/examples/economics/marketplace-agent.ossa.json +1 -1
- package/examples/export/langchain/production-agent-with-memory/README.md +373 -0
- package/examples/export/langchain/production-agent-with-memory/agent.ossa.yaml +97 -0
- package/examples/export/langchain/production-agent-with-streaming/README.md +617 -0
- package/examples/export/langchain/production-agent-with-streaming/agent.ossa.yaml +100 -0
- package/examples/export/langchain/production-agent-with-streaming/client-example.py +263 -0
- package/examples/export/langchain/production-agent-with-tools/README.md +296 -0
- package/examples/export/langchain/production-agent-with-tools/agent.ossa.yaml +216 -0
- package/examples/export/langchain-export-example.ts +246 -0
- package/examples/export/langserve-export-example.ts +246 -0
- package/examples/export/test-generation-example.ts +457 -0
- package/examples/extensions/agents-md-advanced.yml +1 -1
- package/examples/extensions/agents-md-basic.yml +1 -1
- package/examples/extensions/agents-md-sync.yml +1 -1
- package/examples/extensions/agents-md-v1.yml +1 -1
- package/examples/extensions/drupal-v1.yml +1 -1
- package/examples/extensions/encryption-multi-provider.yaml +4 -4
- package/examples/extensions/kagent-v1.yml +1 -1
- package/examples/extensions/knowledge-sources.yaml +1 -1
- package/examples/extensions/mcp-full-featured.yaml +1 -1
- package/examples/genetics/breeding-agent.ossa.json +1 -1
- package/examples/getting-started/01-minimal-agent.ossa.yaml +1 -1
- package/examples/getting-started/02-agent-with-tools.ossa.yaml +1 -1
- package/examples/getting-started/03-agent-with-safety.ossa.yaml +1 -1
- package/examples/getting-started/04-agent-with-messaging.ossa.yaml +1 -1
- package/examples/getting-started/05-workflow-composition.ossa.yaml +1 -1
- package/examples/getting-started/hello-world-complete.ossa.yaml +1 -1
- package/examples/integration-patterns/agent-to-agent-orchestration.ossa.yaml +1 -1
- package/examples/kagent/compliance-validator.ossa.yaml +1 -1
- package/examples/kagent/cost-optimizer.ossa.yaml +1 -1
- package/examples/kagent/documentation-agent.ossa.yaml +1 -1
- package/examples/kagent/k8s-troubleshooter-v1.ossa.yaml +2 -2
- package/examples/kagent/k8s-troubleshooter.ossa.yaml +1 -1
- package/examples/kagent/security-scanner.ossa.yaml +1 -1
- package/examples/langchain/chain-agent.ossa.json +1 -1
- package/examples/langflow/workflow-agent.ossa.json +1 -1
- package/examples/langgraph/state-machine-agent.ossa.json +1 -1
- package/examples/lifecycle/mentoring-agent.ossa.json +1 -1
- package/examples/llamaindex/rag-agent.ossa.json +1 -1
- package/examples/mcp/database-mcp.ossa.yaml +1 -1
- package/examples/mcp/filesystem-mcp.ossa.yaml +1 -1
- package/examples/messaging/dependency-healer.ossa.yaml +1 -1
- package/examples/messaging/incident-responder.ossa.yaml +1 -1
- package/examples/messaging/routing-rules.ossa.yaml +1 -1
- package/examples/messaging/security-scanner.ossa.yaml +1 -1
- package/examples/migration-guides/from-langchain-to-ossa.yaml +4 -4
- package/examples/migrations/langchain/01-python-react-agent-after.ossa.yaml +1 -1
- package/examples/migrations/langchain/02-typescript-conversational-after.ossa.yaml +1 -1
- package/examples/migrations/langchain/03-sequential-chain-after.ossa.yaml +1 -1
- package/examples/migrations/langchain/04-config-based-after.ossa.yaml +1 -1
- package/examples/migrations/swarm-to-ossa/after-handoffs.ossa.yaml +6 -6
- package/examples/migrations/swarm-to-ossa/after-triage-agent.ossa.yaml +3 -3
- package/examples/multi-agent/conditional-router.ossa.yaml +1 -1
- package/examples/multi-agent/parallel-execution.ossa.yaml +1 -1
- package/examples/multi-agent/sequential-pipeline.ossa.yaml +1 -1
- package/examples/multi-agent-research-workflow.ossa.yaml +133 -0
- package/examples/multi-platform/single-manifest/agent.ossa.yaml +1 -1
- package/examples/npm-export-example.ts +150 -0
- package/examples/observability/activity-stream-full.yaml +1 -1
- package/examples/openai/basic-agent.ossa.yaml +1 -1
- package/examples/openai/multi-tool-agent.ossa.json +1 -1
- package/examples/openai/swarm-agent.ossa.json +1 -1
- package/examples/ossa-templates/01-code-assistant.ossa.yaml +1 -1
- package/examples/ossa-templates/02-security-scanner.ossa.yaml +1 -1
- package/examples/ossa-templates/03-ci-pipeline.ossa.yaml +1 -1
- package/examples/ossa-templates/04-code-reviewer.ossa.yaml +1 -1
- package/examples/ossa-templates/05-doc-generator.ossa.yaml +1 -1
- package/examples/ossa-templates/06-compliance-validator.ossa.yaml +1 -1
- package/examples/ossa-templates/07-workflow-orchestrator.ossa.yaml +1 -1
- package/examples/ossa-templates/08-content-writer.ossa.yaml +1 -1
- package/examples/ossa-templates/09-test-generator.ossa.yaml +1 -1
- package/examples/ossa-templates/10-data-transformer.ossa.yaml +1 -1
- package/examples/ossa-templates/11-react-performance-expert.ossa.yaml +1 -1
- package/examples/ossa-templates/12-typescript-type-safety-expert.ossa.yaml +1 -1
- package/examples/ossa-templates/13-accessibility-champion.ossa.yaml +1 -1
- package/examples/ossa-templates/14-security-hardening-agent.ossa.yaml +1 -1
- package/examples/production/document-analyzer-openai.yml +1 -1
- package/examples/production-ready/01-customer-support-bot/.env.example +32 -0
- package/examples/production-ready/01-customer-support-bot/Dockerfile +30 -0
- package/examples/production-ready/01-customer-support-bot/README.md +295 -0
- package/examples/production-ready/01-customer-support-bot/agent.ossa.yaml +172 -0
- package/examples/production-ready/01-customer-support-bot/docker-compose.yml +55 -0
- package/examples/production-ready/01-customer-support-bot/openapi.yaml +238 -0
- package/examples/production-ready/01-customer-support-bot/package.json +48 -0
- package/examples/production-ready/02-code-review-agent/README.md +72 -0
- package/examples/production-ready/02-code-review-agent/agent.ossa.yaml +239 -0
- package/examples/production-ready/02-code-review-agent/docker-compose.yml +22 -0
- package/examples/production-ready/02-code-review-agent/openapi.yaml +150 -0
- package/examples/production-ready/03-data-analysis-agent/README.md +51 -0
- package/examples/production-ready/03-data-analysis-agent/agent.ossa.yaml +97 -0
- package/examples/production-ready/03-data-analysis-agent/openapi.yaml +74 -0
- package/examples/production-ready/04-content-moderator/README.md +55 -0
- package/examples/production-ready/04-content-moderator/agent.ossa.yaml +131 -0
- package/examples/production-ready/04-content-moderator/openapi.yaml +50 -0
- package/examples/production-ready/05-sales-assistant/README.md +37 -0
- package/examples/production-ready/05-sales-assistant/agent.ossa.yaml +146 -0
- package/examples/production-ready/05-sales-assistant/openapi.yaml +59 -0
- package/examples/production-ready/06-devops-agent/README.md +39 -0
- package/examples/production-ready/06-devops-agent/agent.ossa.yaml +141 -0
- package/examples/production-ready/06-devops-agent/openapi.yaml +51 -0
- package/examples/production-ready/07-research-assistant/README.md +31 -0
- package/examples/production-ready/07-research-assistant/agent.ossa.yaml +119 -0
- package/examples/production-ready/07-research-assistant/openapi.yaml +56 -0
- package/examples/production-ready/08-email-triage-agent/README.md +33 -0
- package/examples/production-ready/08-email-triage-agent/agent.ossa.yaml +133 -0
- package/examples/production-ready/08-email-triage-agent/openapi.yaml +41 -0
- package/examples/production-ready/09-security-scanner/README.md +49 -0
- package/examples/production-ready/09-security-scanner/agent.ossa.yaml +174 -0
- package/examples/production-ready/09-security-scanner/openapi.yaml +46 -0
- package/examples/production-ready/10-meeting-assistant/README.md +53 -0
- package/examples/production-ready/10-meeting-assistant/agent.ossa.yaml +211 -0
- package/examples/production-ready/10-meeting-assistant/docker-compose.yml +27 -0
- package/examples/production-ready/10-meeting-assistant/openapi.yaml +131 -0
- package/examples/production-ready/COMPLETION_REPORT.txt +272 -0
- package/examples/production-ready/INDEX.md +296 -0
- package/examples/production-ready/README.md +452 -0
- package/examples/production-ready/SUMMARY.md +362 -0
- package/examples/production-ready/TEST_RESULTS.md +458 -0
- package/examples/quickstart/support-agent.ossa.yaml +1 -1
- package/examples/real-world/gitlab-cicd-optimizer.ossa.yaml +1 -1
- package/examples/real-world/rag-documentation-assistant.ossa.yaml +1 -1
- package/examples/registry/agents/code-reviewer/agent.yaml +1 -1
- package/examples/registry/agents/security-scanner/agent.yaml +1 -1
- package/examples/runtime-adapters/bedrock-claude-example.ossa.yaml +1 -1
- package/examples/schema/reusable-components.yaml +1 -1
- package/examples/showcase/ci-pipeline.ossa.yaml +1 -1
- package/examples/showcase/code-assistant.ossa.yaml +1 -1
- package/examples/showcase/code-reviewer.ossa.yaml +1 -1
- package/examples/showcase/compliance-validator.ossa.yaml +1 -1
- package/examples/showcase/content-writer.ossa.yaml +1 -1
- package/examples/showcase/data-transformer.ossa.yaml +1 -1
- package/examples/showcase/doc-generator.ossa.yaml +1 -1
- package/examples/showcase/security-scanner.ossa.yaml +1 -1
- package/examples/showcase/test-generator.ossa.yaml +1 -1
- package/examples/showcase/workflow-orchestrator.ossa.yaml +1 -1
- package/examples/skills-example.ossa.yaml +140 -0
- package/examples/swarm/pso-optimizer.ossa.json +1 -1
- package/examples/tasks/batch-email-sender.yaml +1 -1
- package/examples/tasks/data-transform.yaml +1 -1
- package/examples/tasks/publish-content.yaml +1 -1
- package/examples/templates/ossa-compliance.yaml +1 -1
- package/examples/unified/security-scanner.ossa.yaml +1 -1
- package/examples/v0.3.6-features/genetics-breeding-advanced.ossa.yaml +1 -1
- package/examples/v0.3.6-features/genetics-breeding-simple.ossa.yaml +1 -1
- package/examples/v0.3.6-features/genetics-fitness-scoring.ossa.yaml +1 -1
- package/examples/vercel/edge-agent.ossa.json +1 -1
- package/examples/workflows/batch-email-campaign.yaml +1 -1
- package/examples/workflows/content-review-publish.yaml +1 -1
- package/examples/workflows/simple-etl.yaml +1 -1
- package/openapi/cli/openapi.yaml +221 -5
- package/package.json +17 -7
- package/dist/cli/commands/export-v2.command.d.ts +0 -7
- package/dist/cli/commands/export-v2.command.d.ts.map +0 -1
- package/dist/cli/commands/export-v2.command.js.map +0 -1
|
@@ -0,0 +1,1182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LangChain Memory Generator (Production Quality - v0.4.1)
|
|
3
|
+
*
|
|
4
|
+
* Generates production-ready memory configuration for LangChain agents
|
|
5
|
+
* Supports: ConversationBuffer, ConversationSummary, EntityMemory, Redis, PostgreSQL
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Full OSSA spec.memory parsing
|
|
9
|
+
* - Connection validation and health checks
|
|
10
|
+
* - Retry logic with exponential backoff
|
|
11
|
+
* - Comprehensive error handling
|
|
12
|
+
* - Structured logging
|
|
13
|
+
* - Type-safe configuration
|
|
14
|
+
*
|
|
15
|
+
* SOLID: Single Responsibility - Memory configuration only
|
|
16
|
+
* DRY: Reusable memory templates
|
|
17
|
+
*/
|
|
18
|
+
export class MemoryGenerator {
|
|
19
|
+
/**
|
|
20
|
+
* Generate memory.py module with production-quality features
|
|
21
|
+
*/
|
|
22
|
+
generate(manifest, backend = 'buffer') {
|
|
23
|
+
const memoryConfig = this.getMemoryConfig(manifest);
|
|
24
|
+
// If memory is disabled, return minimal implementation
|
|
25
|
+
if (!memoryConfig.enabled) {
|
|
26
|
+
return this.generateNoMemory();
|
|
27
|
+
}
|
|
28
|
+
switch (backend) {
|
|
29
|
+
case 'redis':
|
|
30
|
+
return this.generateRedisMemory(memoryConfig);
|
|
31
|
+
case 'postgres':
|
|
32
|
+
return this.generatePostgresMemory(memoryConfig);
|
|
33
|
+
case 'entity':
|
|
34
|
+
return this.generateEntityMemory(memoryConfig);
|
|
35
|
+
case 'summary':
|
|
36
|
+
return this.generateSummaryMemory(memoryConfig);
|
|
37
|
+
case 'buffer':
|
|
38
|
+
default:
|
|
39
|
+
return this.generateBufferMemory(memoryConfig);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extract memory configuration from OSSA manifest
|
|
44
|
+
*/
|
|
45
|
+
getMemoryConfig(manifest) {
|
|
46
|
+
const spec = manifest.spec;
|
|
47
|
+
const memory = spec?.memory || {};
|
|
48
|
+
return {
|
|
49
|
+
enabled: memory?.enabled !== false,
|
|
50
|
+
type: memory?.type || 'conversation_buffer',
|
|
51
|
+
windowSize: memory?.window_size || memory?.windowSize || 10,
|
|
52
|
+
maxTokenLimit: memory?.max_token_limit || memory?.maxTokenLimit || 2000,
|
|
53
|
+
returnMessages: memory?.return_messages !== false && memory?.returnMessages !== false,
|
|
54
|
+
persistence: memory?.persistence
|
|
55
|
+
? {
|
|
56
|
+
enabled: memory.persistence.enabled !== false,
|
|
57
|
+
backend: memory.persistence.backend || 'redis',
|
|
58
|
+
ttl: memory.persistence.ttl || 86400, // 24 hours
|
|
59
|
+
connection: {
|
|
60
|
+
url: memory.persistence.connection?.url,
|
|
61
|
+
poolSize: memory.persistence.connection?.pool_size || 10,
|
|
62
|
+
timeout: memory.persistence.connection?.timeout || 30,
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
: undefined,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generate no-memory implementation
|
|
70
|
+
*/
|
|
71
|
+
generateNoMemory() {
|
|
72
|
+
return `"""
|
|
73
|
+
LangChain Memory - Disabled
|
|
74
|
+
|
|
75
|
+
Memory is disabled for this agent.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
from typing import Optional
|
|
79
|
+
import logging
|
|
80
|
+
|
|
81
|
+
logger = logging.getLogger(__name__)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_memory() -> None:
|
|
85
|
+
"""
|
|
86
|
+
Memory is disabled for this agent
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
None
|
|
90
|
+
"""
|
|
91
|
+
logger.info("Memory is disabled")
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def clear_memory(memory: Optional[any] = None) -> None:
|
|
96
|
+
"""
|
|
97
|
+
No-op: Memory is disabled
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
memory: Ignored
|
|
101
|
+
"""
|
|
102
|
+
pass
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Generate ConversationBufferMemory (in-memory) with production features
|
|
107
|
+
*/
|
|
108
|
+
generateBufferMemory(config) {
|
|
109
|
+
const returnMessages = config.returnMessages ? 'True' : 'False';
|
|
110
|
+
const windowSize = config.windowSize || 10;
|
|
111
|
+
return `"""
|
|
112
|
+
LangChain Memory - Buffer (In-Memory) [Production Quality]
|
|
113
|
+
|
|
114
|
+
Simple conversation buffer memory with window management.
|
|
115
|
+
|
|
116
|
+
Features:
|
|
117
|
+
- Configurable message window (last ${windowSize} messages)
|
|
118
|
+
- Memory statistics and monitoring
|
|
119
|
+
- Structured logging
|
|
120
|
+
|
|
121
|
+
Good for: Development, short conversations, stateless deployments
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
from langchain.memory import ConversationBufferMemory, ConversationBufferWindowMemory
|
|
125
|
+
from typing import Optional, Dict, Any
|
|
126
|
+
import logging
|
|
127
|
+
|
|
128
|
+
logger = logging.getLogger(__name__)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_memory(use_window: bool = True) -> ConversationBufferWindowMemory:
|
|
132
|
+
"""
|
|
133
|
+
Create conversation buffer memory with window
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
use_window: Use windowed memory (last ${windowSize} messages)
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
ConversationBufferWindowMemory instance
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
logger.info("Creating conversation buffer memory (window_size=${windowSize})")
|
|
143
|
+
|
|
144
|
+
memory = ConversationBufferWindowMemory(
|
|
145
|
+
k=${windowSize},
|
|
146
|
+
memory_key="chat_history",
|
|
147
|
+
return_messages=${returnMessages},
|
|
148
|
+
output_key="output",
|
|
149
|
+
input_key="input",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
logger.info("Conversation buffer memory created successfully")
|
|
153
|
+
return memory
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error(f"Error creating buffer memory: {str(e)}", exc_info=True)
|
|
156
|
+
raise
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def clear_memory(memory: ConversationBufferWindowMemory) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Clear all conversation history
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
memory: Memory instance to clear
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
logger.info("Clearing conversation buffer memory")
|
|
168
|
+
memory.clear()
|
|
169
|
+
logger.info("Memory cleared successfully")
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.error(f"Error clearing memory: {str(e)}", exc_info=True)
|
|
172
|
+
raise
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_memory_stats(memory: ConversationBufferWindowMemory) -> Dict[str, Any]:
|
|
176
|
+
"""
|
|
177
|
+
Get memory usage statistics
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
memory: Memory instance
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Dictionary with memory statistics
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
messages = memory.load_memory_variables({}).get("chat_history", [])
|
|
187
|
+
|
|
188
|
+
if isinstance(messages, list):
|
|
189
|
+
message_count = len(messages)
|
|
190
|
+
else:
|
|
191
|
+
message_count = 0
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
"type": "buffer_window",
|
|
195
|
+
"window_size": ${windowSize},
|
|
196
|
+
"message_count": message_count,
|
|
197
|
+
"messages": messages if isinstance(messages, list) else [],
|
|
198
|
+
}
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.error(f"Error getting memory stats: {str(e)}", exc_info=True)
|
|
201
|
+
return {
|
|
202
|
+
"type": "buffer_window",
|
|
203
|
+
"window_size": ${windowSize},
|
|
204
|
+
"message_count": 0,
|
|
205
|
+
"error": str(e),
|
|
206
|
+
}
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Generate ConversationSummaryMemory with production features
|
|
211
|
+
*/
|
|
212
|
+
generateSummaryMemory(config) {
|
|
213
|
+
const returnMessages = config.returnMessages ? 'True' : 'False';
|
|
214
|
+
const maxTokenLimit = config.maxTokenLimit || 2000;
|
|
215
|
+
return `"""
|
|
216
|
+
LangChain Memory - Summary [Production Quality]
|
|
217
|
+
|
|
218
|
+
Conversation summary memory with token limits.
|
|
219
|
+
|
|
220
|
+
Features:
|
|
221
|
+
- Automatic summarization when token limit reached
|
|
222
|
+
- Configurable token limit (${maxTokenLimit} tokens)
|
|
223
|
+
- Summary regeneration
|
|
224
|
+
- Memory statistics
|
|
225
|
+
|
|
226
|
+
Good for: Long conversations, token efficiency
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
from langchain.memory import ConversationSummaryMemory, ConversationSummaryBufferMemory
|
|
230
|
+
from langchain_openai import ChatOpenAI
|
|
231
|
+
from typing import Optional, Dict, Any
|
|
232
|
+
import os
|
|
233
|
+
import logging
|
|
234
|
+
|
|
235
|
+
logger = logging.getLogger(__name__)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_memory() -> ConversationSummaryBufferMemory:
|
|
239
|
+
"""
|
|
240
|
+
Create conversation summary memory with buffer
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
ConversationSummaryBufferMemory instance
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
logger.info("Creating conversation summary memory (max_tokens=${maxTokenLimit})")
|
|
247
|
+
|
|
248
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
|
249
|
+
if not api_key:
|
|
250
|
+
logger.error("OPENAI_API_KEY not set in environment")
|
|
251
|
+
raise ValueError("OPENAI_API_KEY environment variable is required")
|
|
252
|
+
|
|
253
|
+
llm = ChatOpenAI(
|
|
254
|
+
model="gpt-4o-mini", # Cost-effective for summarization
|
|
255
|
+
temperature=0,
|
|
256
|
+
api_key=api_key,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
memory = ConversationSummaryBufferMemory(
|
|
260
|
+
llm=llm,
|
|
261
|
+
memory_key="chat_history",
|
|
262
|
+
return_messages=${returnMessages},
|
|
263
|
+
max_token_limit=${maxTokenLimit},
|
|
264
|
+
output_key="output",
|
|
265
|
+
input_key="input",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
logger.info("Conversation summary memory created successfully")
|
|
269
|
+
return memory
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.error(f"Error creating summary memory: {str(e)}", exc_info=True)
|
|
272
|
+
raise
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def clear_memory(memory: ConversationSummaryBufferMemory) -> None:
|
|
276
|
+
"""
|
|
277
|
+
Clear conversation summary
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
memory: Memory instance to clear
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
logger.info("Clearing conversation summary memory")
|
|
284
|
+
memory.clear()
|
|
285
|
+
logger.info("Memory cleared successfully")
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.error(f"Error clearing memory: {str(e)}", exc_info=True)
|
|
288
|
+
raise
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def get_memory_stats(memory: ConversationSummaryBufferMemory) -> Dict[str, Any]:
|
|
292
|
+
"""
|
|
293
|
+
Get memory usage statistics
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
memory: Memory instance
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Dictionary with memory statistics
|
|
300
|
+
"""
|
|
301
|
+
try:
|
|
302
|
+
memory_vars = memory.load_memory_variables({})
|
|
303
|
+
messages = memory_vars.get("chat_history", [])
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
"type": "summary_buffer",
|
|
307
|
+
"max_token_limit": ${maxTokenLimit},
|
|
308
|
+
"message_count": len(messages) if isinstance(messages, list) else 0,
|
|
309
|
+
"has_summary": hasattr(memory, "moving_summary_buffer") and len(memory.moving_summary_buffer) > 0,
|
|
310
|
+
}
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.error(f"Error getting memory stats: {str(e)}", exc_info=True)
|
|
313
|
+
return {
|
|
314
|
+
"type": "summary_buffer",
|
|
315
|
+
"max_token_limit": ${maxTokenLimit},
|
|
316
|
+
"error": str(e),
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def regenerate_summary(memory: ConversationSummaryBufferMemory) -> str:
|
|
321
|
+
"""
|
|
322
|
+
Force regeneration of conversation summary
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
memory: Memory instance
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Generated summary
|
|
329
|
+
"""
|
|
330
|
+
try:
|
|
331
|
+
logger.info("Regenerating conversation summary")
|
|
332
|
+
|
|
333
|
+
# Force summary regeneration by accessing moving_summary_buffer
|
|
334
|
+
if hasattr(memory, "moving_summary_buffer"):
|
|
335
|
+
summary = memory.moving_summary_buffer
|
|
336
|
+
logger.info(f"Summary regenerated: {len(summary)} characters")
|
|
337
|
+
return summary
|
|
338
|
+
|
|
339
|
+
return ""
|
|
340
|
+
except Exception as e:
|
|
341
|
+
logger.error(f"Error regenerating summary: {str(e)}", exc_info=True)
|
|
342
|
+
return f"Error: {str(e)}"
|
|
343
|
+
`;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Generate ConversationEntityMemory with production features
|
|
347
|
+
*/
|
|
348
|
+
generateEntityMemory(config) {
|
|
349
|
+
const returnMessages = config.returnMessages ? 'True' : 'False';
|
|
350
|
+
return `"""
|
|
351
|
+
LangChain Memory - Entity [Production Quality]
|
|
352
|
+
|
|
353
|
+
Entity-based conversation memory that tracks entities and their context.
|
|
354
|
+
|
|
355
|
+
Features:
|
|
356
|
+
- Automatic entity extraction
|
|
357
|
+
- Entity context tracking
|
|
358
|
+
- Relationship mapping
|
|
359
|
+
- Memory statistics
|
|
360
|
+
|
|
361
|
+
Good for: Complex conversations, entity-focused interactions, knowledge graphs
|
|
362
|
+
"""
|
|
363
|
+
|
|
364
|
+
from langchain.memory import ConversationEntityMemory
|
|
365
|
+
from langchain_openai import ChatOpenAI
|
|
366
|
+
from typing import Optional, Dict, Any, List
|
|
367
|
+
import os
|
|
368
|
+
import logging
|
|
369
|
+
|
|
370
|
+
logger = logging.getLogger(__name__)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def get_memory() -> ConversationEntityMemory:
|
|
374
|
+
"""
|
|
375
|
+
Create conversation entity memory
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
ConversationEntityMemory instance
|
|
379
|
+
"""
|
|
380
|
+
try:
|
|
381
|
+
logger.info("Creating conversation entity memory")
|
|
382
|
+
|
|
383
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
|
384
|
+
if not api_key:
|
|
385
|
+
logger.error("OPENAI_API_KEY not set in environment")
|
|
386
|
+
raise ValueError("OPENAI_API_KEY environment variable is required")
|
|
387
|
+
|
|
388
|
+
llm = ChatOpenAI(
|
|
389
|
+
model="gpt-4o-mini", # Cost-effective for entity extraction
|
|
390
|
+
temperature=0,
|
|
391
|
+
api_key=api_key,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
memory = ConversationEntityMemory(
|
|
395
|
+
llm=llm,
|
|
396
|
+
memory_key="chat_history",
|
|
397
|
+
return_messages=${returnMessages},
|
|
398
|
+
output_key="output",
|
|
399
|
+
input_key="input",
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
logger.info("Conversation entity memory created successfully")
|
|
403
|
+
return memory
|
|
404
|
+
except Exception as e:
|
|
405
|
+
logger.error(f"Error creating entity memory: {str(e)}", exc_info=True)
|
|
406
|
+
raise
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def clear_memory(memory: ConversationEntityMemory) -> None:
|
|
410
|
+
"""
|
|
411
|
+
Clear entity memory
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
memory: Memory instance to clear
|
|
415
|
+
"""
|
|
416
|
+
try:
|
|
417
|
+
logger.info("Clearing conversation entity memory")
|
|
418
|
+
memory.clear()
|
|
419
|
+
logger.info("Memory cleared successfully")
|
|
420
|
+
except Exception as e:
|
|
421
|
+
logger.error(f"Error clearing memory: {str(e)}", exc_info=True)
|
|
422
|
+
raise
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def get_memory_stats(memory: ConversationEntityMemory) -> Dict[str, Any]:
|
|
426
|
+
"""
|
|
427
|
+
Get memory usage statistics
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
memory: Memory instance
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Dictionary with memory statistics including entities
|
|
434
|
+
"""
|
|
435
|
+
try:
|
|
436
|
+
# Get entity store
|
|
437
|
+
entities = {}
|
|
438
|
+
if hasattr(memory, "entity_store") and hasattr(memory.entity_store, "store"):
|
|
439
|
+
entities = memory.entity_store.store
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
"type": "entity",
|
|
443
|
+
"entity_count": len(entities),
|
|
444
|
+
"entities": list(entities.keys()),
|
|
445
|
+
"entity_details": {k: v[:100] + "..." if len(v) > 100 else v for k, v in entities.items()},
|
|
446
|
+
}
|
|
447
|
+
except Exception as e:
|
|
448
|
+
logger.error(f"Error getting memory stats: {str(e)}", exc_info=True)
|
|
449
|
+
return {
|
|
450
|
+
"type": "entity",
|
|
451
|
+
"entity_count": 0,
|
|
452
|
+
"error": str(e),
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def get_entity_context(memory: ConversationEntityMemory, entity: str) -> Optional[str]:
|
|
457
|
+
"""
|
|
458
|
+
Get context for a specific entity
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
memory: Memory instance
|
|
462
|
+
entity: Entity name
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Entity context or None
|
|
466
|
+
"""
|
|
467
|
+
try:
|
|
468
|
+
if hasattr(memory, "entity_store") and hasattr(memory.entity_store, "get"):
|
|
469
|
+
context = memory.entity_store.get(entity)
|
|
470
|
+
logger.info(f"Retrieved context for entity '{entity}': {len(context) if context else 0} characters")
|
|
471
|
+
return context
|
|
472
|
+
|
|
473
|
+
return None
|
|
474
|
+
except Exception as e:
|
|
475
|
+
logger.error(f"Error getting entity context: {str(e)}", exc_info=True)
|
|
476
|
+
return None
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def list_entities(memory: ConversationEntityMemory) -> List[str]:
|
|
480
|
+
"""
|
|
481
|
+
List all tracked entities
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
memory: Memory instance
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
List of entity names
|
|
488
|
+
"""
|
|
489
|
+
try:
|
|
490
|
+
if hasattr(memory, "entity_store") and hasattr(memory.entity_store, "store"):
|
|
491
|
+
entities = list(memory.entity_store.store.keys())
|
|
492
|
+
logger.info(f"Found {len(entities)} tracked entities")
|
|
493
|
+
return entities
|
|
494
|
+
|
|
495
|
+
return []
|
|
496
|
+
except Exception as e:
|
|
497
|
+
logger.error(f"Error listing entities: {str(e)}", exc_info=True)
|
|
498
|
+
return []
|
|
499
|
+
`;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Generate Redis-backed memory with production features
|
|
503
|
+
*/
|
|
504
|
+
generateRedisMemory(config) {
|
|
505
|
+
const returnMessages = config.returnMessages ? 'True' : 'False';
|
|
506
|
+
const ttl = config.persistence?.ttl || 86400;
|
|
507
|
+
const timeout = config.persistence?.connection?.timeout || 30;
|
|
508
|
+
return `"""
|
|
509
|
+
LangChain Memory - Redis [Production Quality]
|
|
510
|
+
|
|
511
|
+
Redis-backed persistent conversation memory.
|
|
512
|
+
|
|
513
|
+
Features:
|
|
514
|
+
- Connection pooling and validation
|
|
515
|
+
- Automatic retry with exponential backoff
|
|
516
|
+
- Health checks
|
|
517
|
+
- Session management
|
|
518
|
+
- TTL configuration (${ttl}s)
|
|
519
|
+
|
|
520
|
+
Good for: Production, multi-instance deployments, session persistence
|
|
521
|
+
"""
|
|
522
|
+
|
|
523
|
+
from langchain.memory import ConversationBufferMemory
|
|
524
|
+
from langchain.memory.chat_message_histories import RedisChatMessageHistory
|
|
525
|
+
from typing import Optional, Dict, Any, List
|
|
526
|
+
import os
|
|
527
|
+
import time
|
|
528
|
+
import logging
|
|
529
|
+
from redis.exceptions import ConnectionError, TimeoutError
|
|
530
|
+
|
|
531
|
+
logger = logging.getLogger(__name__)
|
|
532
|
+
|
|
533
|
+
# Redis connection pool (shared across sessions)
|
|
534
|
+
_redis_pool = None
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _get_redis_client():
|
|
538
|
+
"""
|
|
539
|
+
Get Redis client with connection pooling
|
|
540
|
+
|
|
541
|
+
Returns:
|
|
542
|
+
Redis client instance
|
|
543
|
+
"""
|
|
544
|
+
import redis
|
|
545
|
+
from redis.connection import ConnectionPool
|
|
546
|
+
|
|
547
|
+
global _redis_pool
|
|
548
|
+
|
|
549
|
+
redis_url = os.getenv("REDIS_URL", "redis://localhost:6379")
|
|
550
|
+
|
|
551
|
+
if _redis_pool is None:
|
|
552
|
+
logger.info(f"Creating Redis connection pool (url={redis_url})")
|
|
553
|
+
_redis_pool = ConnectionPool.from_url(
|
|
554
|
+
redis_url,
|
|
555
|
+
max_connections=10,
|
|
556
|
+
socket_timeout=${timeout},
|
|
557
|
+
socket_connect_timeout=${timeout},
|
|
558
|
+
retry_on_timeout=True,
|
|
559
|
+
health_check_interval=30,
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
return redis.Redis(connection_pool=_redis_pool, decode_responses=True)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def validate_redis_connection(max_retries: int = 3, retry_delay: float = 1.0) -> bool:
|
|
566
|
+
"""
|
|
567
|
+
Validate Redis connection with retry logic
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
max_retries: Maximum number of connection attempts
|
|
571
|
+
retry_delay: Initial delay between retries (exponential backoff)
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
True if connection successful, False otherwise
|
|
575
|
+
"""
|
|
576
|
+
for attempt in range(max_retries):
|
|
577
|
+
try:
|
|
578
|
+
logger.info(f"Validating Redis connection (attempt {attempt + 1}/{max_retries})")
|
|
579
|
+
client = _get_redis_client()
|
|
580
|
+
client.ping()
|
|
581
|
+
logger.info("Redis connection validated successfully")
|
|
582
|
+
return True
|
|
583
|
+
except (ConnectionError, TimeoutError) as e:
|
|
584
|
+
if attempt < max_retries - 1:
|
|
585
|
+
wait_time = retry_delay * (2 ** attempt)
|
|
586
|
+
logger.warning(f"Redis connection failed: {str(e)}. Retrying in {wait_time}s...")
|
|
587
|
+
time.sleep(wait_time)
|
|
588
|
+
else:
|
|
589
|
+
logger.error(f"Redis connection failed after {max_retries} attempts: {str(e)}")
|
|
590
|
+
return False
|
|
591
|
+
except Exception as e:
|
|
592
|
+
logger.error(f"Unexpected error validating Redis connection: {str(e)}", exc_info=True)
|
|
593
|
+
return False
|
|
594
|
+
|
|
595
|
+
return False
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def get_memory(session_id: str = "default") -> ConversationBufferMemory:
|
|
599
|
+
"""
|
|
600
|
+
Create Redis-backed conversation memory with validation
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
session_id: Unique session identifier for this conversation
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
ConversationBufferMemory with Redis backend
|
|
607
|
+
|
|
608
|
+
Raises:
|
|
609
|
+
ConnectionError: If Redis connection cannot be established
|
|
610
|
+
"""
|
|
611
|
+
try:
|
|
612
|
+
logger.info(f"Creating Redis-backed memory for session: {session_id}")
|
|
613
|
+
|
|
614
|
+
# Validate connection first
|
|
615
|
+
if not validate_redis_connection():
|
|
616
|
+
raise ConnectionError("Failed to connect to Redis after multiple attempts")
|
|
617
|
+
|
|
618
|
+
redis_url = os.getenv("REDIS_URL", "redis://localhost:6379")
|
|
619
|
+
|
|
620
|
+
message_history = RedisChatMessageHistory(
|
|
621
|
+
session_id=session_id,
|
|
622
|
+
url=redis_url,
|
|
623
|
+
key_prefix="langchain:chat:",
|
|
624
|
+
ttl=${ttl},
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
memory = ConversationBufferMemory(
|
|
628
|
+
chat_memory=message_history,
|
|
629
|
+
memory_key="chat_history",
|
|
630
|
+
return_messages=${returnMessages},
|
|
631
|
+
output_key="output",
|
|
632
|
+
input_key="input",
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
logger.info(f"Redis memory created successfully for session: {session_id}")
|
|
636
|
+
return memory
|
|
637
|
+
except Exception as e:
|
|
638
|
+
logger.error(f"Error creating Redis memory: {str(e)}", exc_info=True)
|
|
639
|
+
raise
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def clear_memory(memory: ConversationBufferMemory, session_id: str = "default") -> None:
|
|
643
|
+
"""
|
|
644
|
+
Clear conversation history for session
|
|
645
|
+
|
|
646
|
+
Args:
|
|
647
|
+
memory: Memory instance
|
|
648
|
+
session_id: Session to clear
|
|
649
|
+
"""
|
|
650
|
+
try:
|
|
651
|
+
logger.info(f"Clearing Redis memory for session: {session_id}")
|
|
652
|
+
if hasattr(memory, "chat_memory"):
|
|
653
|
+
memory.chat_memory.clear()
|
|
654
|
+
logger.info("Memory cleared successfully")
|
|
655
|
+
except Exception as e:
|
|
656
|
+
logger.error(f"Error clearing memory: {str(e)}", exc_info=True)
|
|
657
|
+
raise
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def get_memory_stats(session_id: str = "default") -> Dict[str, Any]:
|
|
661
|
+
"""
|
|
662
|
+
Get memory usage statistics for session
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
session_id: Session to get stats for
|
|
666
|
+
|
|
667
|
+
Returns:
|
|
668
|
+
Dictionary with memory statistics
|
|
669
|
+
"""
|
|
670
|
+
try:
|
|
671
|
+
client = _get_redis_client()
|
|
672
|
+
key = f"langchain:chat:{session_id}"
|
|
673
|
+
|
|
674
|
+
# Get message count
|
|
675
|
+
message_count = client.llen(key) if client.exists(key) else 0
|
|
676
|
+
|
|
677
|
+
# Get TTL
|
|
678
|
+
ttl_remaining = client.ttl(key) if client.exists(key) else -1
|
|
679
|
+
|
|
680
|
+
return {
|
|
681
|
+
"type": "redis",
|
|
682
|
+
"session_id": session_id,
|
|
683
|
+
"message_count": message_count,
|
|
684
|
+
"ttl_remaining": ttl_remaining,
|
|
685
|
+
"key": key,
|
|
686
|
+
}
|
|
687
|
+
except Exception as e:
|
|
688
|
+
logger.error(f"Error getting memory stats: {str(e)}", exc_info=True)
|
|
689
|
+
return {
|
|
690
|
+
"type": "redis",
|
|
691
|
+
"session_id": session_id,
|
|
692
|
+
"error": str(e),
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def health_check() -> Dict[str, Any]:
|
|
697
|
+
"""
|
|
698
|
+
Check Redis health
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
Dictionary with health check results
|
|
702
|
+
"""
|
|
703
|
+
try:
|
|
704
|
+
start_time = time.time()
|
|
705
|
+
client = _get_redis_client()
|
|
706
|
+
|
|
707
|
+
# Ping Redis
|
|
708
|
+
client.ping()
|
|
709
|
+
|
|
710
|
+
# Get Redis info
|
|
711
|
+
info = client.info("server")
|
|
712
|
+
|
|
713
|
+
latency = (time.time() - start_time) * 1000 # ms
|
|
714
|
+
|
|
715
|
+
return {
|
|
716
|
+
"healthy": True,
|
|
717
|
+
"latency_ms": round(latency, 2),
|
|
718
|
+
"redis_version": info.get("redis_version", "unknown"),
|
|
719
|
+
"uptime_seconds": info.get("uptime_in_seconds", 0),
|
|
720
|
+
}
|
|
721
|
+
except Exception as e:
|
|
722
|
+
logger.error(f"Redis health check failed: {str(e)}", exc_info=True)
|
|
723
|
+
return {
|
|
724
|
+
"healthy": False,
|
|
725
|
+
"error": str(e),
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def get_all_sessions() -> List[str]:
|
|
730
|
+
"""
|
|
731
|
+
Get all active session IDs from Redis
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
List of session IDs
|
|
735
|
+
"""
|
|
736
|
+
try:
|
|
737
|
+
client = _get_redis_client()
|
|
738
|
+
keys = client.keys("langchain:chat:*")
|
|
739
|
+
sessions = [key.replace("langchain:chat:", "") for key in keys]
|
|
740
|
+
logger.info(f"Found {len(sessions)} active sessions")
|
|
741
|
+
return sessions
|
|
742
|
+
except Exception as e:
|
|
743
|
+
logger.error(f"Error getting sessions: {str(e)}", exc_info=True)
|
|
744
|
+
return []
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def delete_session(session_id: str) -> bool:
|
|
748
|
+
"""
|
|
749
|
+
Delete a specific session from Redis
|
|
750
|
+
|
|
751
|
+
Args:
|
|
752
|
+
session_id: Session to delete
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
755
|
+
True if session was deleted, False otherwise
|
|
756
|
+
"""
|
|
757
|
+
try:
|
|
758
|
+
logger.info(f"Deleting session: {session_id}")
|
|
759
|
+
client = _get_redis_client()
|
|
760
|
+
result = client.delete(f"langchain:chat:{session_id}")
|
|
761
|
+
logger.info(f"Session deleted: {result > 0}")
|
|
762
|
+
return result > 0
|
|
763
|
+
except Exception as e:
|
|
764
|
+
logger.error(f"Error deleting session: {str(e)}", exc_info=True)
|
|
765
|
+
return False
|
|
766
|
+
`;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Generate PostgreSQL-backed memory with production features
|
|
770
|
+
*/
|
|
771
|
+
generatePostgresMemory(config) {
|
|
772
|
+
const returnMessages = config.returnMessages ? 'True' : 'False';
|
|
773
|
+
const poolSize = config.persistence?.connection?.poolSize || 10;
|
|
774
|
+
const timeout = config.persistence?.connection?.timeout || 30;
|
|
775
|
+
return `"""
|
|
776
|
+
LangChain Memory - PostgreSQL [Production Quality]
|
|
777
|
+
|
|
778
|
+
PostgreSQL-backed persistent conversation memory.
|
|
779
|
+
|
|
780
|
+
Features:
|
|
781
|
+
- Connection pooling with psycopg2
|
|
782
|
+
- Automatic retry with exponential backoff
|
|
783
|
+
- Health checks and monitoring
|
|
784
|
+
- Session analytics
|
|
785
|
+
- Export capabilities
|
|
786
|
+
|
|
787
|
+
Good for: Production, analytics, long-term storage, compliance
|
|
788
|
+
"""
|
|
789
|
+
|
|
790
|
+
from langchain.memory import ConversationBufferMemory
|
|
791
|
+
from langchain.memory.chat_message_histories import PostgresChatMessageHistory
|
|
792
|
+
from typing import Optional, Dict, Any, List
|
|
793
|
+
from contextlib import contextmanager
|
|
794
|
+
import os
|
|
795
|
+
import time
|
|
796
|
+
import logging
|
|
797
|
+
import psycopg2
|
|
798
|
+
from psycopg2 import pool, OperationalError
|
|
799
|
+
import json
|
|
800
|
+
|
|
801
|
+
logger = logging.getLogger(__name__)
|
|
802
|
+
|
|
803
|
+
# PostgreSQL connection pool (shared across sessions)
|
|
804
|
+
_pg_pool = None
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _get_pg_pool():
|
|
808
|
+
"""
|
|
809
|
+
Get PostgreSQL connection pool
|
|
810
|
+
|
|
811
|
+
Returns:
|
|
812
|
+
Connection pool instance
|
|
813
|
+
"""
|
|
814
|
+
global _pg_pool
|
|
815
|
+
|
|
816
|
+
postgres_url = os.getenv(
|
|
817
|
+
"POSTGRES_URL",
|
|
818
|
+
"postgresql://postgres:postgres@localhost:5432/agent_memory"
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
if _pg_pool is None:
|
|
822
|
+
logger.info(f"Creating PostgreSQL connection pool (minconn=1, maxconn=${poolSize})")
|
|
823
|
+
_pg_pool = psycopg2.pool.ThreadedConnectionPool(
|
|
824
|
+
minconn=1,
|
|
825
|
+
maxconn=${poolSize},
|
|
826
|
+
dsn=postgres_url,
|
|
827
|
+
connect_timeout=${timeout},
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
return _pg_pool
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
@contextmanager
|
|
834
|
+
def _get_pg_connection():
|
|
835
|
+
"""
|
|
836
|
+
Context manager for PostgreSQL connections
|
|
837
|
+
|
|
838
|
+
Yields:
|
|
839
|
+
Database connection from pool
|
|
840
|
+
"""
|
|
841
|
+
pool = _get_pg_pool()
|
|
842
|
+
conn = pool.getconn()
|
|
843
|
+
try:
|
|
844
|
+
yield conn
|
|
845
|
+
finally:
|
|
846
|
+
pool.putconn(conn)
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def validate_postgres_connection(max_retries: int = 3, retry_delay: float = 1.0) -> bool:
|
|
850
|
+
"""
|
|
851
|
+
Validate PostgreSQL connection with retry logic
|
|
852
|
+
|
|
853
|
+
Args:
|
|
854
|
+
max_retries: Maximum number of connection attempts
|
|
855
|
+
retry_delay: Initial delay between retries (exponential backoff)
|
|
856
|
+
|
|
857
|
+
Returns:
|
|
858
|
+
True if connection successful, False otherwise
|
|
859
|
+
"""
|
|
860
|
+
for attempt in range(max_retries):
|
|
861
|
+
try:
|
|
862
|
+
logger.info(f"Validating PostgreSQL connection (attempt {attempt + 1}/{max_retries})")
|
|
863
|
+
|
|
864
|
+
with _get_pg_connection() as conn:
|
|
865
|
+
cursor = conn.cursor()
|
|
866
|
+
cursor.execute("SELECT 1")
|
|
867
|
+
cursor.close()
|
|
868
|
+
|
|
869
|
+
logger.info("PostgreSQL connection validated successfully")
|
|
870
|
+
return True
|
|
871
|
+
except OperationalError as e:
|
|
872
|
+
if attempt < max_retries - 1:
|
|
873
|
+
wait_time = retry_delay * (2 ** attempt)
|
|
874
|
+
logger.warning(f"PostgreSQL connection failed: {str(e)}. Retrying in {wait_time}s...")
|
|
875
|
+
time.sleep(wait_time)
|
|
876
|
+
else:
|
|
877
|
+
logger.error(f"PostgreSQL connection failed after {max_retries} attempts: {str(e)}")
|
|
878
|
+
return False
|
|
879
|
+
except Exception as e:
|
|
880
|
+
logger.error(f"Unexpected error validating PostgreSQL connection: {str(e)}", exc_info=True)
|
|
881
|
+
return False
|
|
882
|
+
|
|
883
|
+
return False
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def initialize_schema() -> bool:
|
|
887
|
+
"""
|
|
888
|
+
Initialize database schema if not exists
|
|
889
|
+
|
|
890
|
+
Returns:
|
|
891
|
+
True if schema initialized successfully
|
|
892
|
+
"""
|
|
893
|
+
try:
|
|
894
|
+
logger.info("Initializing PostgreSQL schema")
|
|
895
|
+
|
|
896
|
+
with _get_pg_connection() as conn:
|
|
897
|
+
cursor = conn.cursor()
|
|
898
|
+
|
|
899
|
+
# Create message_store table
|
|
900
|
+
cursor.execute("""
|
|
901
|
+
CREATE TABLE IF NOT EXISTS message_store (
|
|
902
|
+
id SERIAL PRIMARY KEY,
|
|
903
|
+
session_id TEXT NOT NULL,
|
|
904
|
+
message JSONB NOT NULL,
|
|
905
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
906
|
+
INDEX idx_session_id (session_id),
|
|
907
|
+
INDEX idx_created_at (created_at)
|
|
908
|
+
)
|
|
909
|
+
""")
|
|
910
|
+
|
|
911
|
+
conn.commit()
|
|
912
|
+
cursor.close()
|
|
913
|
+
|
|
914
|
+
logger.info("Schema initialized successfully")
|
|
915
|
+
return True
|
|
916
|
+
except Exception as e:
|
|
917
|
+
logger.error(f"Error initializing schema: {str(e)}", exc_info=True)
|
|
918
|
+
return False
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def get_memory(session_id: str = "default") -> ConversationBufferMemory:
|
|
922
|
+
"""
|
|
923
|
+
Create PostgreSQL-backed conversation memory with validation
|
|
924
|
+
|
|
925
|
+
Args:
|
|
926
|
+
session_id: Unique session identifier for this conversation
|
|
927
|
+
|
|
928
|
+
Returns:
|
|
929
|
+
ConversationBufferMemory with PostgreSQL backend
|
|
930
|
+
|
|
931
|
+
Raises:
|
|
932
|
+
ConnectionError: If PostgreSQL connection cannot be established
|
|
933
|
+
"""
|
|
934
|
+
try:
|
|
935
|
+
logger.info(f"Creating PostgreSQL-backed memory for session: {session_id}")
|
|
936
|
+
|
|
937
|
+
# Validate connection first
|
|
938
|
+
if not validate_postgres_connection():
|
|
939
|
+
raise ConnectionError("Failed to connect to PostgreSQL after multiple attempts")
|
|
940
|
+
|
|
941
|
+
# Initialize schema
|
|
942
|
+
initialize_schema()
|
|
943
|
+
|
|
944
|
+
postgres_url = os.getenv(
|
|
945
|
+
"POSTGRES_URL",
|
|
946
|
+
"postgresql://postgres:postgres@localhost:5432/agent_memory"
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
message_history = PostgresChatMessageHistory(
|
|
950
|
+
connection_string=postgres_url,
|
|
951
|
+
session_id=session_id,
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
memory = ConversationBufferMemory(
|
|
955
|
+
chat_memory=message_history,
|
|
956
|
+
memory_key="chat_history",
|
|
957
|
+
return_messages=${returnMessages},
|
|
958
|
+
output_key="output",
|
|
959
|
+
input_key="input",
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
logger.info(f"PostgreSQL memory created successfully for session: {session_id}")
|
|
963
|
+
return memory
|
|
964
|
+
except Exception as e:
|
|
965
|
+
logger.error(f"Error creating PostgreSQL memory: {str(e)}", exc_info=True)
|
|
966
|
+
raise
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
def clear_memory(memory: ConversationBufferMemory, session_id: str = "default") -> None:
|
|
970
|
+
"""
|
|
971
|
+
Clear conversation history for session
|
|
972
|
+
|
|
973
|
+
Args:
|
|
974
|
+
memory: Memory instance
|
|
975
|
+
session_id: Session to clear
|
|
976
|
+
"""
|
|
977
|
+
try:
|
|
978
|
+
logger.info(f"Clearing PostgreSQL memory for session: {session_id}")
|
|
979
|
+
if hasattr(memory, "chat_memory"):
|
|
980
|
+
memory.chat_memory.clear()
|
|
981
|
+
logger.info("Memory cleared successfully")
|
|
982
|
+
except Exception as e:
|
|
983
|
+
logger.error(f"Error clearing memory: {str(e)}", exc_info=True)
|
|
984
|
+
raise
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def get_memory_stats(session_id: str = "default") -> Dict[str, Any]:
|
|
988
|
+
"""
|
|
989
|
+
Get memory usage statistics for session
|
|
990
|
+
|
|
991
|
+
Args:
|
|
992
|
+
session_id: Session to get stats for
|
|
993
|
+
|
|
994
|
+
Returns:
|
|
995
|
+
Dictionary with memory statistics
|
|
996
|
+
"""
|
|
997
|
+
try:
|
|
998
|
+
with _get_pg_connection() as conn:
|
|
999
|
+
cursor = conn.cursor()
|
|
1000
|
+
|
|
1001
|
+
# Get message count
|
|
1002
|
+
cursor.execute("SELECT COUNT(*) FROM message_store WHERE session_id = %s", (session_id,))
|
|
1003
|
+
message_count = cursor.fetchone()[0]
|
|
1004
|
+
|
|
1005
|
+
# Get first and last message timestamps
|
|
1006
|
+
cursor.execute(
|
|
1007
|
+
"SELECT MIN(created_at), MAX(created_at) FROM message_store WHERE session_id = %s",
|
|
1008
|
+
(session_id,)
|
|
1009
|
+
)
|
|
1010
|
+
first_msg, last_msg = cursor.fetchone()
|
|
1011
|
+
|
|
1012
|
+
cursor.close()
|
|
1013
|
+
|
|
1014
|
+
return {
|
|
1015
|
+
"type": "postgres",
|
|
1016
|
+
"session_id": session_id,
|
|
1017
|
+
"message_count": message_count,
|
|
1018
|
+
"first_message_at": first_msg.isoformat() if first_msg else None,
|
|
1019
|
+
"last_message_at": last_msg.isoformat() if last_msg else None,
|
|
1020
|
+
}
|
|
1021
|
+
except Exception as e:
|
|
1022
|
+
logger.error(f"Error getting memory stats: {str(e)}", exc_info=True)
|
|
1023
|
+
return {
|
|
1024
|
+
"type": "postgres",
|
|
1025
|
+
"session_id": session_id,
|
|
1026
|
+
"error": str(e),
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def health_check() -> Dict[str, Any]:
|
|
1031
|
+
"""
|
|
1032
|
+
Check PostgreSQL health
|
|
1033
|
+
|
|
1034
|
+
Returns:
|
|
1035
|
+
Dictionary with health check results
|
|
1036
|
+
"""
|
|
1037
|
+
try:
|
|
1038
|
+
start_time = time.time()
|
|
1039
|
+
|
|
1040
|
+
with _get_pg_connection() as conn:
|
|
1041
|
+
cursor = conn.cursor()
|
|
1042
|
+
|
|
1043
|
+
# Check connection
|
|
1044
|
+
cursor.execute("SELECT version()")
|
|
1045
|
+
version = cursor.fetchone()[0]
|
|
1046
|
+
|
|
1047
|
+
# Check table exists
|
|
1048
|
+
cursor.execute("""
|
|
1049
|
+
SELECT EXISTS (
|
|
1050
|
+
SELECT FROM information_schema.tables
|
|
1051
|
+
WHERE table_name = 'message_store'
|
|
1052
|
+
)
|
|
1053
|
+
""")
|
|
1054
|
+
table_exists = cursor.fetchone()[0]
|
|
1055
|
+
|
|
1056
|
+
cursor.close()
|
|
1057
|
+
|
|
1058
|
+
latency = (time.time() - start_time) * 1000 # ms
|
|
1059
|
+
|
|
1060
|
+
return {
|
|
1061
|
+
"healthy": True,
|
|
1062
|
+
"latency_ms": round(latency, 2),
|
|
1063
|
+
"postgres_version": version.split()[0] if version else "unknown",
|
|
1064
|
+
"schema_initialized": table_exists,
|
|
1065
|
+
}
|
|
1066
|
+
except Exception as e:
|
|
1067
|
+
logger.error(f"PostgreSQL health check failed: {str(e)}", exc_info=True)
|
|
1068
|
+
return {
|
|
1069
|
+
"healthy": False,
|
|
1070
|
+
"error": str(e),
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
def get_all_sessions() -> List[str]:
|
|
1075
|
+
"""
|
|
1076
|
+
Get all active session IDs from PostgreSQL
|
|
1077
|
+
|
|
1078
|
+
Returns:
|
|
1079
|
+
List of session IDs
|
|
1080
|
+
"""
|
|
1081
|
+
try:
|
|
1082
|
+
with _get_pg_connection() as conn:
|
|
1083
|
+
cursor = conn.cursor()
|
|
1084
|
+
cursor.execute("SELECT DISTINCT session_id FROM message_store")
|
|
1085
|
+
sessions = [row[0] for row in cursor.fetchall()]
|
|
1086
|
+
cursor.close()
|
|
1087
|
+
|
|
1088
|
+
logger.info(f"Found {len(sessions)} sessions")
|
|
1089
|
+
return sessions
|
|
1090
|
+
except Exception as e:
|
|
1091
|
+
logger.error(f"Error getting sessions: {str(e)}", exc_info=True)
|
|
1092
|
+
return []
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
def delete_session(session_id: str) -> bool:
|
|
1096
|
+
"""
|
|
1097
|
+
Delete a specific session from PostgreSQL
|
|
1098
|
+
|
|
1099
|
+
Args:
|
|
1100
|
+
session_id: Session to delete
|
|
1101
|
+
|
|
1102
|
+
Returns:
|
|
1103
|
+
True if session was deleted, False otherwise
|
|
1104
|
+
"""
|
|
1105
|
+
try:
|
|
1106
|
+
logger.info(f"Deleting session: {session_id}")
|
|
1107
|
+
|
|
1108
|
+
with _get_pg_connection() as conn:
|
|
1109
|
+
cursor = conn.cursor()
|
|
1110
|
+
cursor.execute("DELETE FROM message_store WHERE session_id = %s", (session_id,))
|
|
1111
|
+
rows_deleted = cursor.rowcount
|
|
1112
|
+
conn.commit()
|
|
1113
|
+
cursor.close()
|
|
1114
|
+
|
|
1115
|
+
logger.info(f"Deleted {rows_deleted} messages for session {session_id}")
|
|
1116
|
+
return rows_deleted > 0
|
|
1117
|
+
except Exception as e:
|
|
1118
|
+
logger.error(f"Error deleting session: {str(e)}", exc_info=True)
|
|
1119
|
+
return False
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def export_session_history(session_id: str, format: str = "json") -> Any:
|
|
1123
|
+
"""
|
|
1124
|
+
Export full conversation history for a session
|
|
1125
|
+
|
|
1126
|
+
Args:
|
|
1127
|
+
session_id: Session to export
|
|
1128
|
+
format: Export format ("json" or "csv")
|
|
1129
|
+
|
|
1130
|
+
Returns:
|
|
1131
|
+
Exported data in requested format
|
|
1132
|
+
"""
|
|
1133
|
+
try:
|
|
1134
|
+
logger.info(f"Exporting session {session_id} as {format}")
|
|
1135
|
+
|
|
1136
|
+
with _get_pg_connection() as conn:
|
|
1137
|
+
cursor = conn.cursor()
|
|
1138
|
+
cursor.execute(
|
|
1139
|
+
"SELECT id, message, created_at FROM message_store WHERE session_id = %s ORDER BY id",
|
|
1140
|
+
(session_id,)
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
rows = cursor.fetchall()
|
|
1144
|
+
cursor.close()
|
|
1145
|
+
|
|
1146
|
+
if format == "json":
|
|
1147
|
+
messages = [
|
|
1148
|
+
{
|
|
1149
|
+
"id": row[0],
|
|
1150
|
+
"message": json.loads(row[1]) if isinstance(row[1], str) else row[1],
|
|
1151
|
+
"created_at": row[2].isoformat() if row[2] else None,
|
|
1152
|
+
}
|
|
1153
|
+
for row in rows
|
|
1154
|
+
]
|
|
1155
|
+
return messages
|
|
1156
|
+
elif format == "csv":
|
|
1157
|
+
# CSV export
|
|
1158
|
+
import csv
|
|
1159
|
+
import io
|
|
1160
|
+
|
|
1161
|
+
output = io.StringIO()
|
|
1162
|
+
writer = csv.writer(output)
|
|
1163
|
+
writer.writerow(["id", "message", "created_at"])
|
|
1164
|
+
|
|
1165
|
+
for row in rows:
|
|
1166
|
+
writer.writerow([
|
|
1167
|
+
row[0],
|
|
1168
|
+
json.dumps(json.loads(row[1]) if isinstance(row[1], str) else row[1]),
|
|
1169
|
+
row[2].isoformat() if row[2] else "",
|
|
1170
|
+
])
|
|
1171
|
+
|
|
1172
|
+
return output.getvalue()
|
|
1173
|
+
else:
|
|
1174
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
1175
|
+
|
|
1176
|
+
except Exception as e:
|
|
1177
|
+
logger.error(f"Error exporting session: {str(e)}", exc_info=True)
|
|
1178
|
+
raise
|
|
1179
|
+
`;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
//# sourceMappingURL=memory-generator.js.map
|