├── .github └── workflows │ ├── ci.yaml │ ├── helm-release.yaml │ └── python-publish.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── demo ├── .coveragerc ├── .dockerignore ├── .env.example ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── agents │ ├── __init__.py │ ├── audit │ │ ├── Makefile │ │ ├── README.md │ │ ├── __init__.py │ │ ├── agent.py │ │ ├── config.py │ │ ├── main.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ └── test_agent.py │ │ ├── types.py │ │ └── utils.py │ ├── billing │ │ ├── Makefile │ │ ├── README.md │ │ ├── __init__.py │ │ ├── agent.py │ │ ├── api_main.py │ │ ├── config.py │ │ ├── dspy_modules │ │ │ ├── __init__.py │ │ │ ├── billing.py │ │ │ ├── billing_data.py │ │ │ ├── billing_dataset.py │ │ │ ├── billing_optimizer.py │ │ │ ├── billing_optimizer_simba.py │ │ │ ├── evaluation │ │ │ │ ├── __init__.py │ │ │ │ ├── metrics.py │ │ │ │ └── report.py │ │ │ └── optimized_billing_simba.json │ │ ├── main.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ ├── test_agent.py │ │ │ ├── test_agent_additional.py │ │ │ ├── test_api_main.py │ │ │ ├── test_billing_data.py │ │ │ ├── test_billing_dataset.py │ │ │ ├── test_billing_module.py │ │ │ ├── test_evaluation.py │ │ │ ├── test_evaluation_metrics.py │ │ │ ├── test_lazy_initialization.py │ │ │ ├── test_main_integration.py │ │ │ ├── test_module.py │ │ │ ├── test_module_fixed.py │ │ │ ├── test_module_simple.py │ │ │ ├── test_utils_coverage.py │ │ │ └── utils.py │ │ ├── types.py │ │ └── utils.py │ ├── claims │ │ ├── Makefile │ │ ├── README.md │ │ ├── __init__.py │ │ ├── agent.py │ │ ├── api_main.py │ │ ├── config.py │ │ ├── dspy_modules │ │ │ ├── __init__.py │ │ │ ├── claims.py │ │ │ ├── claims_data.py │ │ │ ├── claims_dataset.py │ │ │ ├── claims_errors.py │ │ │ ├── claims_optimizer.py │ │ │ ├── claims_optimizer_simba.py │ │ │ ├── claims_validators.py │ │ │ ├── optimized_claims.json │ │ │ └── optimized_claims_simba.json │ │ ├── main.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ ├── test_agent.py │ │ │ ├── test_agent_additional.py │ │ │ ├── test_api_main.py │ │ │ ├── test_claims_data.py │ │ │ ├── test_claims_dataset.py │ │ │ ├── test_claims_module.py │ │ │ └── test_main_integration.py │ │ └── types.py │ ├── escalation │ │ ├── Makefile │ │ ├── README.md │ │ ├── __init__.py │ │ ├── agent.py │ │ ├── config.py │ │ ├── dspy_modules │ │ │ ├── __init__.py │ │ │ └── escalation.py │ │ ├── main.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ ├── test_agent.py │ │ │ ├── test_agent_additional.py │ │ │ ├── test_escalation_data.py │ │ │ └── test_escalation_module.py │ │ └── types.py │ ├── frontend │ │ ├── Makefile │ │ ├── README.md │ │ ├── __init__.py │ │ ├── agent.py │ │ ├── config.py │ │ ├── guardrails.py │ │ ├── main.py │ │ ├── public │ │ │ ├── admin.html │ │ │ └── index.html │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ ├── test_agent.py │ │ │ ├── test_config_guardrails.py │ │ │ ├── test_main.py │ │ │ └── test_main_endpoint.py │ │ ├── types.py │ │ └── websocket_manager.py │ ├── policies │ │ ├── Makefile │ │ ├── README.md │ │ ├── __init__.py │ │ ├── agent │ │ │ ├── __init__.py │ │ │ ├── agent.py │ │ │ ├── api │ │ │ │ ├── __init__.py │ │ │ │ ├── dependencies.py │ │ │ │ ├── middleware.py │ │ │ │ ├── models.py │ │ │ │ ├── routes.py │ │ │ │ └── validators.py │ │ │ ├── config.py │ │ │ ├── main.py │ │ │ ├── optimization │ │ │ │ ├── __init__.py │ │ │ │ ├── optimized_policies_simba.json │ │ │ │ ├── policies_dataset.py │ │ │ │ └── policies_optimizer_simba.py │ │ │ ├── reasoning.py │ │ │ ├── services │ │ │ │ ├── __init__.py │ │ │ │ ├── document_service.py │ │ │ │ ├── embeddings.py │ │ │ │ ├── reindex_service.py │ │ │ │ └── search_service.py │ │ │ ├── tests │ │ │ │ ├── test_policy_data.py │ │ │ │ └── test_routes.py │ │ │ ├── tools │ │ │ │ ├── __init__.py │ │ │ │ ├── database │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── example_data.py │ │ │ │ │ └── policy_data.py │ │ │ │ └── retrieval │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── full_document_retrieval.py │ │ │ │ │ └── policy_search.py │ │ │ ├── types.py │ │ │ └── utils │ │ │ │ ├── __init__.py │ │ │ │ └── async_helpers.py │ │ ├── ingestion │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ ├── documents │ │ │ │ ├── auto.md │ │ │ │ ├── health.md │ │ │ │ ├── home.md │ │ │ │ └── life.md │ │ │ ├── minio_client.py │ │ │ ├── start_worker.py │ │ │ ├── temporal_client.py │ │ │ └── workflows │ │ │ │ ├── __init__.py │ │ │ │ ├── activities │ │ │ │ ├── __init__.py │ │ │ │ ├── document_chunking_activity.py │ │ │ │ ├── document_indexing_activity.py │ │ │ │ ├── document_loading_activity.py │ │ │ │ ├── document_verification_activity.py │ │ │ │ └── minio_activities.py │ │ │ │ ├── ingestion_workflow.py │ │ │ │ ├── minio_watcher_workflow.py │ │ │ │ └── worker.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ ├── retrieval_performance │ │ │ │ ├── __init__.py │ │ │ │ ├── api_client.py │ │ │ │ ├── context_metrics.py │ │ │ │ ├── data_utilities.py │ │ │ │ ├── evaluator.py │ │ │ │ ├── filtered_qa_pairs.json │ │ │ │ ├── llm_judge.py │ │ │ │ ├── metrics_config.py │ │ │ │ ├── mlflow_reporter.py │ │ │ │ ├── models.py │ │ │ │ └── performance_calculator.py │ │ │ ├── test_agent.py │ │ │ ├── test_api_integration.py │ │ │ ├── test_config.py │ │ │ ├── test_embeddings.py │ │ │ ├── test_reasoning.py │ │ │ ├── test_retrieval_performance.py │ │ │ ├── test_services.py │ │ │ ├── test_start_worker_integration.py │ │ │ ├── test_tools.py │ │ │ ├── test_validators.py │ │ │ └── test_vespa_deployment.py │ │ └── vespa │ │ │ ├── __init__.py │ │ │ ├── deploy_package.py │ │ │ ├── drop_index.py │ │ │ └── generate_package.py │ └── triage │ │ ├── Makefile │ │ ├── README.md │ │ ├── __init__.py │ │ ├── agent.py │ │ ├── attention_net │ │ ├── __init__.py │ │ ├── attention_based_classifier.py │ │ ├── attention_net_trainer.py │ │ ├── classifier_v5.py │ │ └── config.py │ │ ├── baseline_model │ │ ├── __init__.py │ │ ├── classifier_v3.py │ │ ├── config.py │ │ ├── few_shots_classifier.py │ │ ├── fewshot_trainer.py │ │ ├── metrics.py │ │ └── utils.py │ │ ├── classifier_v6 │ │ ├── README.md │ │ ├── __init__.py │ │ ├── classifier_v6.py │ │ ├── data_utils.py │ │ ├── finetune_trainer.py │ │ ├── model_utils.py │ │ └── training_utils.py │ │ ├── classifier_v7 │ │ ├── README.md │ │ ├── __init__.py │ │ ├── classifier_v7.py │ │ ├── config.py │ │ ├── device_utils.py │ │ ├── finetune_trainer.py │ │ ├── gemma3_seq_cls.py │ │ ├── serve_classifier.py │ │ └── training_utils.py │ │ ├── classifier_v8 │ │ ├── README.md │ │ ├── __init__.py │ │ ├── classifier_v8.py │ │ ├── config.py │ │ ├── finetune_trainer.py │ │ └── training_utils.py │ │ ├── config.py │ │ ├── data_sets │ │ ├── bitext │ │ │ ├── triage-test-dataset.csv │ │ │ └── triage-train-dataset.csv │ │ ├── loader.py │ │ ├── synthetic │ │ │ ├── .env.example │ │ │ ├── Dockerfile │ │ │ ├── Makefile │ │ │ ├── WINDOWS_DEV.md │ │ │ ├── artifacts │ │ │ │ ├── triage-testing.jsonl │ │ │ │ └── triage-training.jsonl │ │ │ ├── dev_generator.py │ │ │ ├── dev_webserver.py │ │ │ ├── docker-compose.yml │ │ │ ├── src │ │ │ │ ├── triage_agent_dataset │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── __main__.py │ │ │ │ │ ├── config.py │ │ │ │ │ ├── constants.py │ │ │ │ │ ├── dataset_generator.py │ │ │ │ │ └── models.py │ │ │ │ └── triage_webserver │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── __main__.py │ │ │ │ │ ├── app.py │ │ │ │ │ ├── config.py │ │ │ │ │ ├── database │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── base.py │ │ │ │ │ └── connection.py │ │ │ │ │ ├── models │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── data_models.py │ │ │ │ │ ├── routes │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── datasets.py │ │ │ │ │ └── examples.py │ │ │ │ │ ├── schemas │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── dataset_schemas.py │ │ │ │ │ └── example_schemas.py │ │ │ │ │ ├── services │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── dataset_service.py │ │ │ │ │ ├── static │ │ │ │ │ ├── img │ │ │ │ │ │ └── eggai-logo.svg │ │ │ │ │ └── js │ │ │ │ │ │ ├── datasets.js │ │ │ │ │ │ └── main.js │ │ │ │ │ └── templates │ │ │ │ │ ├── create_dataset.html │ │ │ │ │ ├── dataset_detail.html │ │ │ │ │ ├── datasets.html │ │ │ │ │ ├── edit_example.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── layout.html │ │ │ └── triage_viewer.html │ │ ├── triage-testing-proofread.jsonl │ │ ├── triage-testing.jsonl │ │ ├── triage-training-proofread.jsonl │ │ └── triage-training.jsonl │ │ ├── dspy_modules │ │ ├── __init__.py │ │ ├── classifier_v0.py │ │ ├── classifier_v1.py │ │ ├── classifier_v2 │ │ │ ├── __init__.py │ │ │ ├── classifier_v2.py │ │ │ ├── classifier_v2_optimizer.py │ │ │ └── optimizations_v2.json │ │ ├── classifier_v4 │ │ │ ├── __init__.py │ │ │ ├── classifier_v4.py │ │ │ ├── classifier_v4_optimizer.py │ │ │ └── optimizations_v4.json │ │ ├── evaluation │ │ │ ├── __init__.py │ │ │ ├── evaluate.py │ │ │ └── report.py │ │ └── small_talk.py │ │ ├── main.py │ │ ├── model_checkpoints │ │ ├── attention_net.pth │ │ └── fewshot_baseline_n_all.pkl │ │ ├── models.py │ │ ├── notebooks │ │ ├── exploratory_data_analysis.ipynb │ │ └── posthoc_model_analysis.ipynb │ │ ├── shared │ │ ├── __init__.py │ │ └── data_utils.py │ │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── run_classifier_tests.py │ │ ├── test_agent.py │ │ ├── test_agent_additional.py │ │ ├── test_agent_utils.py │ │ ├── test_classifier_comparison.py │ │ ├── test_classifier_v0.py │ │ ├── test_classifier_v1.py │ │ ├── test_classifier_v2.py │ │ ├── test_classifier_v3.py │ │ ├── test_classifier_v4.py │ │ ├── test_classifier_v5.py │ │ ├── test_classifier_v6.py │ │ ├── test_classifier_v6_evaluation.py │ │ ├── test_classifier_v7.py │ │ ├── test_classifier_v7_evaluation.py │ │ ├── test_classifier_v8.py │ │ ├── test_serve_classifier.py │ │ ├── test_shared_data_utils.py │ │ ├── test_small_talk.py │ │ └── test_utils.py ├── conftest.py ├── dev-requirements.txt ├── docker-compose-lmstudio.yaml ├── docker-compose-mlflow.yml ├── docker-compose.yml ├── dockerConfig │ ├── development-sql.yaml │ ├── grafana-dashboard.json │ ├── grafana-datasources.yaml │ ├── grafana-provisioning.yaml │ ├── loki.yaml │ ├── otel-collector.yaml │ ├── prometheus.yaml │ └── tempo.yaml ├── docs │ ├── advanced-topics │ │ ├── agent-optimization.md │ │ └── multi-environment-deployment.md │ ├── agentic-rag.md │ ├── agents-overview.md │ ├── building-agents-eggai.md │ ├── ingestion-pipeline.md │ ├── multi-agent-communication.md │ ├── retrieval-performance-testing.md │ ├── system-architecture.md │ └── vespa-search-guide.md ├── helm │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ ├── deployments.yaml │ │ ├── monitoring.yaml │ │ ├── service.yaml │ │ └── tests │ │ │ └── test-connection.yaml │ └── values.yaml ├── libraries │ ├── README.md │ ├── __init__.py │ ├── communication │ │ ├── __init__.py │ │ ├── channels.py │ │ ├── messaging.py │ │ ├── protocol │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── enums.py │ │ │ └── messages.py │ │ └── transport.py │ ├── core │ │ ├── __init__.py │ │ ├── config.py │ │ ├── models.py │ │ └── patches.py │ ├── integrations │ │ ├── __init__.py │ │ └── vespa │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ ├── schemas.py │ │ │ └── vespa_client.py │ ├── ml │ │ ├── __init__.py │ │ ├── device.py │ │ ├── dspy │ │ │ ├── __init__.py │ │ │ ├── language_model.py │ │ │ └── optimizer.py │ │ └── mlflow.py │ ├── observability │ │ ├── __init__.py │ │ ├── logger │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ └── logger.py │ │ └── tracing │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ ├── dspy.py │ │ │ ├── init_metrics.py │ │ │ ├── otel.py │ │ │ ├── pricing.py │ │ │ └── schemas.py │ └── testing │ │ ├── __init__.py │ │ ├── tests │ │ ├── __init__.py │ │ ├── test_channels.py │ │ ├── test_dspy_set_language_model.py │ │ ├── test_init_metrics.py │ │ ├── test_logger.py │ │ ├── test_otel.py │ │ └── test_pricing.py │ │ └── utils │ │ ├── __init__.py │ │ ├── agent_helpers.py │ │ ├── dspy_helpers.py │ │ ├── fixtures.py │ │ └── helpers.py ├── pyproject.toml ├── pytest.ini ├── requirements.txt └── sonar-project.properties ├── docs ├── docs │ ├── CNAME │ ├── assets │ │ ├── agent-1.jpeg │ │ ├── agent-2.jpeg │ │ ├── agent-3.jpeg │ │ ├── agent-4.jpeg │ │ ├── agent-5.jpeg │ │ ├── agent-evaluation-dspy.png │ │ ├── architecture-coordinator.svg │ │ ├── architecture-example-06-multi-agent-conversation.svg │ │ ├── architecture-gateway.svg │ │ ├── architecture-getting-started.svg │ │ ├── architecture-multi-agent-insurance-support-system.svg │ │ ├── avatar │ │ │ ├── audit-agent.png │ │ │ ├── billing-agent.png │ │ │ ├── claims-agent.png │ │ │ ├── escalation-agent.png │ │ │ ├── frontend-agent.png │ │ │ ├── policies-agent.png │ │ │ └── triage-agent.png │ │ ├── eggai-meta-framework-arch.png │ │ ├── eggai-word-and-figuremark.svg │ │ ├── example-00.png │ │ ├── example-01.png │ │ ├── example-02.png │ │ ├── example-03.png │ │ ├── example-04.png │ │ ├── example-05.png │ │ ├── example-06.png │ │ ├── example-07-chat.png │ │ ├── example-07.png │ │ ├── example-mcp.png │ │ ├── favicon.png │ │ ├── interoperability.png │ │ ├── logo.png │ │ ├── multi-agent-human-chat.png │ │ ├── react-agent-dspy.png │ │ ├── redpanda-console.png │ │ ├── safe-agents-guardrails.png │ │ ├── support-chat.png │ │ ├── system-architecture.svg │ │ ├── test_tsne.png │ │ ├── train_tsne.png │ │ ├── triage-agent.png │ │ ├── triage-custom-classifier-training.png │ │ └── triage-evaluation-report.png │ ├── concepts │ │ ├── agent.md │ │ ├── enterprise-ai-platform.md │ │ └── multi-agent-system.md │ ├── examples │ │ ├── agent_evaluation_dspy.md │ │ ├── agent_evaluation_dspy │ │ │ └── tests │ │ │ │ └── reports │ │ │ │ ├── classifier_v1.html │ │ │ │ ├── classifier_v2.html │ │ │ │ └── classifier_v3.html │ │ ├── coordinator.md │ │ ├── dspy_react.md │ │ ├── getting_started.md │ │ ├── langchain_tool_calling.md │ │ ├── litellm_agent.md │ │ ├── mcp.md │ │ ├── multi_agent_conversation.md │ │ ├── safe_agents_guardrails.md │ │ ├── shared_context.md │ │ ├── triage_agent.md │ │ └── websocket_gateway.md │ ├── index.md │ └── sdk │ │ ├── agent.md │ │ ├── channel.md │ │ ├── inmemory-transport.md │ │ ├── kafka-transport.md │ │ ├── message.md │ │ └── transport.md ├── mkdocs.yml ├── poetry.lock └── pyproject.toml ├── examples ├── a2a │ ├── README.md │ ├── __init__.py │ ├── client.py │ ├── requirements.txt │ └── simple_demo.py ├── agent_evaluation_dspy │ ├── .gitignore │ ├── README.md │ ├── __init__.py │ ├── datasets │ │ ├── __init__.py │ │ ├── loader.py │ │ ├── triage-testing.csv │ │ └── triage-training.csv │ ├── docker-compose.yml │ ├── requirements.txt │ ├── src │ │ ├── __init__.py │ │ ├── agents │ │ │ ├── __init__.py │ │ │ └── triage.py │ │ └── dspy_modules │ │ │ ├── __init__.py │ │ │ ├── classifier_v1.py │ │ │ ├── classifier_v2.py │ │ │ ├── classifier_v3.py │ │ │ ├── lm.py │ │ │ ├── optimizations_v3.json │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── reports │ │ ├── 20250110-165944-classifier_v1.html │ │ ├── 20250110-165948-classifier_v2.html │ │ ├── 20250110-165953-classifier_v3.html │ │ ├── classifier_v1.html │ │ ├── classifier_v2.html │ │ └── classifier_v3.html │ │ ├── test_classifier_v1.py │ │ ├── test_classifier_v2.py │ │ ├── test_classifier_v3.py │ │ ├── test_triage.py │ │ └── utilities.py ├── coordinator │ ├── README.md │ ├── coordinator.py │ ├── docker-compose.yml │ ├── email_agent.py │ ├── main.py │ ├── order_agent.py │ └── requirements.txt ├── dspy_react │ ├── .gitignore │ ├── README.md │ ├── docker-compose.yml │ ├── requirements.txt │ ├── src │ │ ├── __init__.py │ │ ├── agents │ │ │ ├── __init__.py │ │ │ └── react_agent.py │ │ ├── dspy_modules │ │ │ └── react_module.py │ │ └── main.py │ └── tests │ │ ├── __init__.py │ │ └── test_react_module.py ├── getting_started │ ├── README.md │ ├── email_agent.py │ ├── main.py │ ├── order_agent.py │ └── requirements.txt ├── langchain_tool_calling │ ├── README.md │ ├── docker-compose.yml │ ├── email_agent.py │ ├── main.py │ └── requirements.txt ├── litellm_agent │ ├── README.md │ ├── docker-compose.yml │ ├── escalate_agent.py │ ├── lite_llm_agent.py │ ├── main.py │ ├── requirements.txt │ └── support_agent.py ├── mcp │ ├── .env.example │ ├── Makefile │ ├── README.md │ ├── docker-compose.yml │ ├── docs │ │ └── mcp-example-architecture.png │ ├── eggai_adapter │ │ ├── __init__.py │ │ ├── client.py │ │ ├── dspy.py │ │ ├── mcp.py │ │ └── types.py │ ├── requirements.txt │ ├── schemas.py │ ├── start_console.py │ ├── start_ticketing_adapter.py │ ├── start_ticketing_agent.py │ └── start_ticketing_backend.py ├── multi_agent_conversation │ ├── README.md │ ├── chat_display.py │ ├── claims.py │ ├── docker-compose.yml │ ├── escalation.py │ ├── lite_llm_agent.py │ ├── main.py │ ├── memory.py │ ├── policy.py │ ├── requirements.txt │ ├── shared.py │ └── triage.py ├── safe_agents_guardrails │ ├── .gitignore │ ├── README.md │ ├── docker-compose.yml │ ├── requirements.txt │ ├── src │ │ ├── __init__.py │ │ ├── agents │ │ │ ├── __init__.py │ │ │ ├── answers_agent.py │ │ │ └── guardrail.py │ │ ├── dspy_modules │ │ │ ├── lm.py │ │ │ └── wiki_qa.py │ │ └── main.py │ └── tests │ │ ├── __init__.py │ │ └── test_answer_agent.py ├── shared_context │ ├── README.md │ ├── channels.py │ ├── docker-compose.yml │ ├── human_agent.py │ ├── main.py │ ├── memory_db.py │ ├── openai_client.py │ ├── products_agent.py │ ├── recommendation_agent.py │ └── requirements.txt ├── tool_calling │ ├── .env.example │ ├── Makefile │ ├── README.md │ ├── docker-compose.yml │ ├── requirements.txt │ ├── src │ │ ├── __init__.py │ │ ├── agents │ │ │ ├── __init__.py │ │ │ └── react_agent.py │ │ ├── config.py │ │ ├── dspy_modules │ │ │ └── react_module.py │ │ └── main.py │ └── tests │ │ ├── __init__.py │ │ └── test_react_module.py ├── triage_agent │ ├── .gitignore │ ├── README.md │ ├── __init__.py │ ├── docker-compose.yml │ ├── requirements.txt │ ├── src │ │ ├── __init__.py │ │ ├── agents │ │ │ ├── __init__.py │ │ │ └── triage.py │ │ ├── dspy_modules │ │ │ ├── __init__.py │ │ │ ├── lm.py │ │ │ ├── triage_module.py │ │ │ └── utils.py │ │ ├── loader.py │ │ └── triage-testing.csv │ └── tests │ │ ├── __init__.py │ │ ├── test_intent_classifier.py │ │ ├── test_triage.py │ │ └── utilities.py └── websocket_gateway │ ├── README.md │ ├── coordinator.py │ ├── docker-compose.yml │ ├── email_agent.py │ ├── gateway │ ├── server.py │ ├── websocket_agent.py │ └── websocket_manager.py │ ├── main.py │ ├── order_agent.py │ └── requirements.txt ├── scripts ├── __init__.py ├── run_all_tests.py └── run_tests.py └── sdk ├── cli ├── __init__.py ├── main.py ├── templates.py ├── templates │ ├── README.md.j2 │ ├── agent.py.j2 │ ├── agents_init.py.j2 │ ├── common_models.py.j2 │ ├── console.py.j2 │ ├── env.j2 │ ├── main.py.j2 │ └── requirements.txt.j2 └── wizard.py ├── eggai ├── __init__.py ├── adapters │ ├── __init__.py │ ├── a2a │ │ ├── __init__.py │ │ ├── config.py │ │ ├── executor.py │ │ └── plugin.py │ └── mcp │ │ ├── __init__.py │ │ ├── mcp.py │ │ └── models.py ├── agent.py ├── channel.py ├── hooks.py ├── schemas.py └── transport │ ├── __init__.py │ ├── base.py │ ├── defaults.py │ ├── inmemory.py │ └── kafka.py ├── poetry.lock ├── poetry.toml ├── pyproject.toml ├── requirements.txt └── tests ├── __init__.py ├── docker-compose.yml ├── test_catch_all.py ├── test_group_ids.py ├── test_inmemory_transport.py ├── test_kafka.py ├── test_namespace.py ├── test_simple_scenario.py └── test_typed_subscribe.py /.github/workflows/helm-release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Helm Chart 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "demo/helm/**" 9 | 10 | permissions: 11 | contents: write 12 | packages: write 13 | 14 | jobs: 15 | release: 16 | name: Helm Chart Release 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout source 21 | uses: actions/checkout@v3 22 | 23 | - name: Install Helm 24 | run: | 25 | curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash 26 | 27 | - name: Log in to GitHub Container Registry 28 | run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin 29 | 30 | - name: Push chart to GHCR 31 | run: | 32 | version=$(yq '.version' demo/helm/Chart.yaml) 33 | helm registry login ghcr.io/eggai-tech/multi_agent_human_chat/helm --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} 34 | helm package demo/helm --version ${version} --destination packaged 35 | helm push packaged/eggai-multi-agent-chat-${version}.tgz oci://ghcr.io/${{ github.repository_owner }}/helm 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 EggAI Technologies GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /demo/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = agents, libraries 3 | omit = 4 | */tests/* 5 | */test_*.py 6 | */*_test.py 7 | */*_test_*.py 8 | */shared_test_utils.py 9 | */conftest.py 10 | */__pycache__/* 11 | */venv/* 12 | */.venv/* 13 | 14 | [report] 15 | exclude_lines = 16 | pragma: no cover 17 | def __repr__ 18 | raise AssertionError 19 | raise NotImplementedError 20 | if __name__ == .__main__.: 21 | if TYPE_CHECKING: 22 | @abstract -------------------------------------------------------------------------------- /demo/.dockerignore: -------------------------------------------------------------------------------- 1 | # Python cache 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.pyc 6 | *.pyo 7 | *.pyd 8 | .Python 9 | pip-log.txt 10 | pip-delete-this-directory.txt 11 | 12 | # Virtual environments 13 | .venv/ 14 | venv/ 15 | ENV/ 16 | env/ 17 | 18 | # IDE 19 | .vscode/ 20 | .idea/ 21 | *.swp 22 | *.swo 23 | *~ 24 | 25 | # Testing and coverage 26 | .pytest_cache/ 27 | .coverage 28 | .coverage.* 29 | htmlcov/ 30 | .tox/ 31 | .nox/ 32 | coverage.xml 33 | *.cover 34 | *.log 35 | .hypothesis/ 36 | 37 | # Jupyter 38 | .ipynb_checkpoints/ 39 | *.ipynb 40 | 41 | # Documentation 42 | docs/ 43 | *.md 44 | README.* 45 | 46 | # Git 47 | .git/ 48 | .gitignore 49 | .gitattributes 50 | 51 | # Docker 52 | Dockerfile 53 | docker-compose*.yml 54 | .dockerignore 55 | 56 | # Build artifacts 57 | build/ 58 | dist/ 59 | *.egg-info/ 60 | .eggs/ 61 | 62 | # ML/Data artifacts 63 | *.pkl 64 | *.pth 65 | *.pt 66 | *.h5 67 | *.bin 68 | *.onnx 69 | *.safetensors 70 | checkpoints/ 71 | model_checkpoints/ 72 | mlflow_experiments/ 73 | .cache/ 74 | temp/ 75 | logs/ 76 | reports/ 77 | 78 | # Development files 79 | dev-requirements.txt 80 | Makefile 81 | pytest.ini 82 | pyproject.toml 83 | sonar-project.properties 84 | .ruff_cache/ 85 | 86 | # Helm charts 87 | helm/ 88 | 89 | # Test data and notebooks 90 | notebooks/ 91 | tests/ 92 | *test* 93 | conftest.py 94 | 95 | # Large data files 96 | data_sets/ 97 | artifacts/ 98 | 99 | # Temporary files 100 | *.tmp 101 | *.temp 102 | *.bak -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | agents/triage/dspy_modules/evaluation/reports/*.html 2 | reports/ 3 | tmp 4 | /agents/policies/vespa/artifacts/* 5 | 6 | # Model files and caches 7 | models/ 8 | dspy_cache/ 9 | -------------------------------------------------------------------------------- /demo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim AS builder 2 | 3 | # Install build dependencies 4 | RUN apt-get update && apt-get install -y --no-install-recommends \ 5 | gcc \ 6 | g++ \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | # Set working directory 10 | WORKDIR /app 11 | 12 | # Copy and install requirements 13 | COPY requirements.txt . 14 | RUN pip install --upgrade pip && \ 15 | pip install --no-cache-dir --user -r requirements.txt 16 | 17 | # Final stage 18 | FROM python:3.12-slim 19 | 20 | # Install runtime dependencies only 21 | RUN apt-get update && apt-get install -y --no-install-recommends \ 22 | libgomp1 \ 23 | && rm -rf /var/lib/apt/lists/* 24 | 25 | WORKDIR /app 26 | 27 | # Copy installed packages from builder 28 | COPY --from=builder /root/.local /root/.local 29 | 30 | # Copy application code 31 | COPY agents /app/agents 32 | COPY libraries /app/libraries 33 | 34 | # Make sure scripts in .local are usable 35 | ENV PATH=/root/.local/bin:$PATH 36 | 37 | USER 1000 38 | 39 | ENTRYPOINT ["python", "-m"] 40 | -------------------------------------------------------------------------------- /demo/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/__init__.py -------------------------------------------------------------------------------- /demo/agents/audit/Makefile: -------------------------------------------------------------------------------- 1 | ../../Makefile -------------------------------------------------------------------------------- /demo/agents/audit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/audit/__init__.py -------------------------------------------------------------------------------- /demo/agents/audit/config.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | from dotenv import load_dotenv 4 | from pydantic import Field 5 | from pydantic_settings import BaseSettings, SettingsConfigDict 6 | 7 | from .types import AuditCategory, AuditConfig 8 | 9 | load_dotenv() 10 | 11 | MESSAGE_CATEGORIES: Dict[str, AuditCategory] = { 12 | "agent_message": "User Communication", 13 | "billing_request": "Billing", 14 | "policy_request": "Policies", 15 | "escalation_request": "Escalation", 16 | "triage_request": "Triage", 17 | } 18 | 19 | class Settings(BaseSettings): 20 | app_name: str = Field(default="audit_agent") 21 | kafka_bootstrap_servers: str = Field(default="localhost:19092") 22 | kafka_ca_content: str = Field(default="") 23 | otel_endpoint: str = Field(default="http://localhost:4318") 24 | prometheus_metrics_port: int = Field(default=9096) 25 | 26 | debug_logging_enabled: bool = Field(default=False) 27 | audit_channel_name: str = Field(default="audit_logs") 28 | default_category: AuditCategory = Field(default="Other") 29 | 30 | model_config = SettingsConfigDict( 31 | env_prefix="AUDIT_", env_file=".env", env_ignore_empty=True, extra="ignore" 32 | ) 33 | 34 | settings = Settings() 35 | 36 | audit_config = AuditConfig( 37 | message_categories=MESSAGE_CATEGORIES, 38 | default_category=settings.default_category, 39 | enable_debug_logging=settings.debug_logging_enabled, 40 | audit_channel_name=settings.audit_channel_name, 41 | ) 42 | -------------------------------------------------------------------------------- /demo/agents/audit/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | 4 | from eggai import eggai_main 5 | from eggai.transport import eggai_set_default_transport 6 | 7 | from libraries.communication.transport import create_kafka_transport 8 | from libraries.observability.logger import get_console_logger 9 | from libraries.observability.tracing import init_telemetry 10 | 11 | from .config import settings 12 | 13 | eggai_set_default_transport( 14 | lambda: create_kafka_transport( 15 | bootstrap_servers=settings.kafka_bootstrap_servers, 16 | ssl_cert=settings.kafka_ca_content, 17 | ) 18 | ) 19 | 20 | from .agent import audit_agent 21 | 22 | logger = get_console_logger("audit_agent") 23 | logger.setLevel(logging.INFO) 24 | 25 | 26 | @eggai_main 27 | async def main(): 28 | logger.info(f"Starting {settings.app_name}") 29 | 30 | init_telemetry(app_name=settings.app_name, endpoint=settings.otel_endpoint) 31 | logger.info(f"Telemetry initialized for {settings.app_name}") 32 | 33 | logger.info( 34 | f"Using Kafka transport with servers: {settings.kafka_bootstrap_servers}" 35 | ) 36 | 37 | await audit_agent.start() 38 | logger.info(f"{settings.app_name} started successfully") 39 | 40 | try: 41 | await asyncio.Future() 42 | except asyncio.CancelledError: 43 | logger.info("Audit agent task cancelled") 44 | 45 | 46 | if __name__ == "__main__": 47 | try: 48 | asyncio.run(main()) 49 | except KeyboardInterrupt: 50 | logger.info("Shutting down audit agent") 51 | except Exception as e: 52 | logger.error(f"Error in main: {e}", exc_info=True) 53 | -------------------------------------------------------------------------------- /demo/agents/audit/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/audit/tests/__init__.py -------------------------------------------------------------------------------- /demo/agents/audit/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="session") 7 | def event_loop(): 8 | """Create a fresh event loop for the test session.""" 9 | loop = asyncio.new_event_loop() 10 | asyncio.set_event_loop(loop) 11 | yield loop 12 | loop.close() 13 | -------------------------------------------------------------------------------- /demo/agents/audit/types.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Any, Dict, Literal, Optional 3 | 4 | from pydantic import BaseModel, Field 5 | 6 | AuditCategory = Literal[ 7 | "User Communication", 8 | "Billing", 9 | "Policies", 10 | "Escalation", 11 | "Triage", 12 | "Other", 13 | "Error", 14 | ] 15 | 16 | class AuditConfig(BaseModel): 17 | message_categories: Dict[str, AuditCategory] = Field(default_factory=dict) 18 | default_category: AuditCategory = Field(default="Other") 19 | enable_debug_logging: bool = Field(default=False) 20 | audit_channel_name: str = Field(default="audit_logs") 21 | 22 | model_config = {"validate_assignment": True, "extra": "forbid"} 23 | 24 | class AuditEvent(BaseModel): 25 | message_id: str = Field(...) 26 | message_type: str = Field(...) 27 | message_source: str = Field(...) 28 | channel: str = Field(...) 29 | category: AuditCategory = Field(...) 30 | audit_timestamp: datetime = Field(default_factory=datetime.now) 31 | content: Optional[str] = Field(default=None) 32 | error: Optional[Dict[str, str]] = Field(default=None) 33 | 34 | def to_dict(self) -> Dict[str, Any]: 35 | result = self.model_dump() 36 | result["audit_timestamp"] = self.audit_timestamp.isoformat() 37 | return result 38 | 39 | model_config = {"validate_assignment": True} -------------------------------------------------------------------------------- /demo/agents/billing/Makefile: -------------------------------------------------------------------------------- 1 | ../../Makefile -------------------------------------------------------------------------------- /demo/agents/billing/README.md: -------------------------------------------------------------------------------- 1 | # Billing Agent 2 | 3 | Handles payment and financial inquiries for insurance policies. 4 | 5 | ## What it does 6 | - Retrieves billing info (premium amounts, due dates, payment status) 7 | - Updates payment information 8 | - Requires policy number for privacy 9 | 10 | ## Quick Start 11 | ```bash 12 | make start-billing 13 | ``` 14 | 15 | ## Configuration 16 | ```bash 17 | BILLING_LANGUAGE_MODEL=lm_studio/gemma-3-12b-it # Or openai/gpt-4o-mini 18 | ``` 19 | 20 | ## Tools 21 | - `get_billing_info(policy_number)` - Returns premium, due date, status 22 | - `update_billing_info(policy_number, field, value)` - Updates payment info 23 | 24 | ## Testing 25 | ```bash 26 | make test-billing-agent 27 | ``` -------------------------------------------------------------------------------- /demo/agents/billing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/billing/__init__.py -------------------------------------------------------------------------------- /demo/agents/billing/config.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from pydantic import Field 3 | from pydantic_settings import SettingsConfigDict 4 | 5 | from libraries.core import BaseAgentConfig 6 | 7 | from .types import ModelConfig 8 | 9 | load_dotenv() 10 | 11 | MESSAGE_TYPE_BILLING_REQUEST = "billing_request" 12 | 13 | class Settings(BaseAgentConfig): 14 | app_name: str = Field(default="billing_agent") 15 | prometheus_metrics_port: int = Field( 16 | default=9095, description="Port for Prometheus metrics server" 17 | ) 18 | 19 | billing_database_path: str = Field(default="") 20 | 21 | model_name: str = Field(default="billing_react", description="Name of the model") 22 | max_iterations: int = Field(default=5, ge=1, le=10) 23 | use_tracing: bool = Field(default=True) 24 | cache_enabled: bool = Field(default=False) 25 | timeout_seconds: float = Field(default=30.0, ge=1.0) 26 | truncation_length: int = Field(default=15000, ge=1000) 27 | 28 | model_config = SettingsConfigDict( 29 | env_prefix="BILLING_", env_file=".env", env_ignore_empty=True, extra="ignore" 30 | ) 31 | 32 | settings = Settings() 33 | 34 | model_config = ModelConfig( 35 | name=settings.model_name, 36 | max_iterations=settings.max_iterations, 37 | use_tracing=settings.use_tracing, 38 | cache_enabled=settings.cache_enabled, 39 | timeout_seconds=settings.timeout_seconds, 40 | truncation_length=settings.truncation_length, 41 | ) 42 | -------------------------------------------------------------------------------- /demo/agents/billing/dspy_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/billing/dspy_modules/__init__.py -------------------------------------------------------------------------------- /demo/agents/billing/dspy_modules/evaluation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/billing/dspy_modules/evaluation/__init__.py -------------------------------------------------------------------------------- /demo/agents/billing/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/billing/tests/__init__.py -------------------------------------------------------------------------------- /demo/agents/billing/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="session") 7 | def event_loop(): 8 | """Ensure a fresh event loop for the test session to avoid 'RuntimeError: Event loop is closed'.""" 9 | loop = asyncio.new_event_loop() 10 | asyncio.set_event_loop(loop) 11 | yield loop 12 | loop.close() 13 | -------------------------------------------------------------------------------- /demo/agents/billing/tests/test_module.py: -------------------------------------------------------------------------------- 1 | from agents.billing.config import settings 2 | from libraries.ml.dspy.language_model import dspy_set_language_model 3 | from libraries.observability.logger import get_console_logger 4 | 5 | logger = get_console_logger("billing_agent.tests.module") 6 | 7 | # Configure language model based on settings with caching disabled for accuracy 8 | dspy_lm = dspy_set_language_model(settings, overwrite_cache_enabled=False) -------------------------------------------------------------------------------- /demo/agents/billing/types.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | from libraries.communication.protocol import ChatMessage as ChatMessage 6 | from libraries.core import ModelConfig as BaseModelConfig 7 | from libraries.core import ModelResult as ModelResult 8 | 9 | 10 | class ModelConfig(BaseModelConfig): 11 | name: str = Field(default="billing_react", description="Name of the model") 12 | 13 | class BillingRecord(BaseModel): 14 | 15 | policy_number: str = Field(..., description="Unique identifier for the policy") 16 | customer_name: str = Field(..., description="Name of the customer") 17 | amount_due: float = Field(..., description="Amount due", ge=0) 18 | due_date: str = Field(..., description="Due date for payment (YYYY-MM-DD)") 19 | billing_status: str = Field(..., description="Current billing status") 20 | billing_cycle: str = Field( 21 | ..., description="Billing cycle (monthly, quarterly, etc.)" 22 | ) 23 | last_payment_date: Optional[str] = Field( 24 | None, description="Date of last payment (YYYY-MM-DD)" 25 | ) 26 | last_payment_amount: Optional[float] = Field( 27 | None, description="Amount of last payment", ge=0 28 | ) 29 | next_payment_amount: Optional[float] = Field( 30 | None, description="Amount of next payment", ge=0 31 | ) 32 | contact_email: Optional[str] = Field(None, description="Contact email address") 33 | contact_phone: Optional[str] = Field(None, description="Contact phone number") 34 | 35 | model_config = {"extra": "forbid"} 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /demo/agents/claims/Makefile: -------------------------------------------------------------------------------- 1 | ../../Makefile -------------------------------------------------------------------------------- /demo/agents/claims/README.md: -------------------------------------------------------------------------------- 1 | # Claims Agent 2 | 3 | Manages insurance claim inquiries, status checks, and new claim filing. 4 | 5 | ## What it does 6 | - Files new insurance claims 7 | - Checks claim status by claim number 8 | - Provides claim history for policies 9 | 10 | ## Quick Start 11 | ```bash 12 | make start-claims 13 | ``` 14 | 15 | ## Configuration 16 | ```bash 17 | CLAIMS_LANGUAGE_MODEL=lm_studio/gemma-3-12b-it # Or openai/gpt-4o-mini 18 | ``` 19 | 20 | ## Tools 21 | - `get_claim_status(claim_number)` - Returns status and next steps 22 | - `file_new_claim(policy_number, incident_details)` - Creates new claim 23 | - `get_claim_history(policy_number)` - Lists all claims for a policy 24 | 25 | ## Testing 26 | ```bash 27 | make test-claims-agent 28 | ``` -------------------------------------------------------------------------------- /demo/agents/claims/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/claims/__init__.py -------------------------------------------------------------------------------- /demo/agents/claims/config.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from pydantic import Field 3 | from pydantic_settings import SettingsConfigDict 4 | 5 | from libraries.core import BaseAgentConfig 6 | 7 | from .types import ModelConfig 8 | 9 | load_dotenv() 10 | 11 | MESSAGE_TYPE_CLAIM_REQUEST = "claim_request" 12 | 13 | 14 | class Settings(BaseAgentConfig): 15 | app_name: str = Field(default="claims_agent") 16 | prometheus_metrics_port: int = Field(default=9092) 17 | 18 | model_name: str = Field(default="claims_react", description="Name of the model") 19 | max_iterations: int = Field(default=5, ge=1, le=10) 20 | use_tracing: bool = Field(default=True) 21 | cache_enabled: bool = Field(default=False) 22 | timeout_seconds: float = Field(default=30.0, ge=1.0) 23 | truncation_length: int = Field(default=15000, ge=1000) 24 | 25 | model_config = SettingsConfigDict( 26 | env_prefix="CLAIMS_", env_file=".env", env_ignore_empty=True, extra="ignore" 27 | ) 28 | 29 | 30 | settings = Settings() 31 | 32 | model_config = ModelConfig( 33 | name=settings.model_name, 34 | max_iterations=settings.max_iterations, 35 | use_tracing=settings.use_tracing, 36 | cache_enabled=settings.cache_enabled, 37 | timeout_seconds=settings.timeout_seconds, 38 | truncation_length=settings.truncation_length, 39 | ) 40 | -------------------------------------------------------------------------------- /demo/agents/claims/dspy_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/claims/dspy_modules/__init__.py -------------------------------------------------------------------------------- /demo/agents/claims/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/claims/tests/__init__.py -------------------------------------------------------------------------------- /demo/agents/claims/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Test configuration for claims agent tests.""" 2 | 3 | import asyncio 4 | from unittest.mock import AsyncMock, MagicMock 5 | 6 | import pytest 7 | 8 | 9 | @pytest.fixture(scope="function") 10 | def event_loop(): 11 | """Create an instance of the default event loop for the test session.""" 12 | loop = asyncio.new_event_loop() 13 | yield loop 14 | loop.close() 15 | 16 | 17 | @pytest.fixture 18 | def mock_language_model(monkeypatch): 19 | """Mock the language model to avoid initialization issues in tests.""" 20 | # Mock the language model initialization 21 | mock_lm = MagicMock() 22 | monkeypatch.setattr("dspy.LM", lambda *args, **kwargs: mock_lm) 23 | monkeypatch.setattr( 24 | "libraries.dspy_set_language_model.dspy_set_language_model", 25 | lambda *args, **kwargs: mock_lm, 26 | ) 27 | return mock_lm 28 | 29 | 30 | @pytest.fixture 31 | def mock_claims_response(): 32 | """Simple mock for claims responses.""" 33 | 34 | async def mock_response(*args, **kwargs): 35 | from dspy import Prediction 36 | 37 | yield Prediction(final_response="I'll help you with your claims request.") 38 | 39 | return mock_response 40 | 41 | 42 | @pytest.fixture 43 | def mock_stream_channel(monkeypatch): 44 | """Mock the human stream channel for testing.""" 45 | mock_publish = AsyncMock() 46 | monkeypatch.setattr( 47 | "agents.claims.agent.human_stream_channel.publish", mock_publish 48 | ) 49 | return mock_publish 50 | 51 | 52 | @pytest.fixture 53 | def mock_human_channel(monkeypatch): 54 | """Mock the human channel for testing.""" 55 | mock_publish = AsyncMock() 56 | monkeypatch.setattr("agents.claims.agent.human_channel.publish", mock_publish) 57 | return mock_publish 58 | -------------------------------------------------------------------------------- /demo/agents/escalation/Makefile: -------------------------------------------------------------------------------- 1 | ../../Makefile -------------------------------------------------------------------------------- /demo/agents/escalation/README.md: -------------------------------------------------------------------------------- 1 | # Escalation Agent 2 | 3 | Handles complex issues, complaints, and requests requiring special attention. 4 | 5 | ## What it does 6 | - Creates support tickets for complex issues 7 | - Routes to appropriate departments 8 | - Tracks ticket status and updates 9 | - Handles complaints professionally 10 | 11 | ## Quick Start 12 | ```bash 13 | make start-escalation 14 | ``` 15 | 16 | ## Configuration 17 | ```bash 18 | ESCALATION_LANGUAGE_MODEL=lm_studio/gemma-3-12b-it # Or openai/gpt-4o-mini 19 | ``` 20 | 21 | ## Tools 22 | - `create_ticket(description, department, priority)` - Creates support ticket 23 | - `get_ticket_status(ticket_id)` - Returns current status 24 | - `update_ticket(ticket_id, update)` - Adds notes to ticket 25 | 26 | Departments: Technical Support, Billing, Sales 27 | 28 | ## Testing 29 | ```bash 30 | make test-escalation-agent 31 | ``` -------------------------------------------------------------------------------- /demo/agents/escalation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/escalation/__init__.py -------------------------------------------------------------------------------- /demo/agents/escalation/dspy_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/escalation/dspy_modules/__init__.py -------------------------------------------------------------------------------- /demo/agents/escalation/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/escalation/tests/__init__.py -------------------------------------------------------------------------------- /demo/agents/escalation/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from unittest.mock import AsyncMock, MagicMock 3 | 4 | import pytest 5 | 6 | 7 | @pytest.fixture(scope="session") 8 | def event_loop(): 9 | """Ensure a fresh event loop for the test session to avoid 'RuntimeError: Event loop is closed'.""" 10 | loop = asyncio.new_event_loop() 11 | asyncio.set_event_loop(loop) 12 | yield loop 13 | loop.close() 14 | 15 | 16 | @pytest.fixture 17 | def mock_language_model(monkeypatch): 18 | """Mock the language model to avoid initialization issues in tests.""" 19 | # Mock the language model initialization 20 | mock_lm = MagicMock() 21 | monkeypatch.setattr("dspy.LM", lambda *args, **kwargs: mock_lm) 22 | monkeypatch.setattr( 23 | "libraries.dspy_set_language_model.dspy_set_language_model", 24 | lambda *args, **kwargs: mock_lm, 25 | ) 26 | return mock_lm 27 | 28 | 29 | @pytest.fixture 30 | def mock_escalation_response(): 31 | """Simple mock for escalation responses.""" 32 | 33 | async def mock_response(*args, **kwargs): 34 | from dspy import Prediction 35 | 36 | yield Prediction(final_response="I'll help you with your escalation request.") 37 | 38 | return mock_response 39 | 40 | 41 | @pytest.fixture 42 | def mock_stream_channel(monkeypatch): 43 | """Mock the human stream channel for testing.""" 44 | mock_publish = AsyncMock() 45 | monkeypatch.setattr( 46 | "agents.escalation.agent.human_stream_channel.publish", mock_publish 47 | ) 48 | return mock_publish 49 | -------------------------------------------------------------------------------- /demo/agents/escalation/types.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional, TypedDict 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | from libraries.communication.protocol import ( 6 | ChatMessage as ChatMessage, 7 | ) 8 | from libraries.communication.protocol import ( 9 | MessageData as MessageData, 10 | ) 11 | from libraries.core import ( 12 | ModelConfig as BaseModelConfig, 13 | ) 14 | from libraries.core import ( 15 | ModelResult as ModelResult, 16 | ) 17 | 18 | TicketDepartment = Literal["Technical Support", "Billing", "Sales"] 19 | 20 | WorkflowStep = Literal["ask_additional_data", "ask_confirmation", "create_ticket"] 21 | 22 | ConfirmationResponse = Literal["yes", "no"] 23 | 24 | 25 | 26 | class TicketingRequestMessage(TypedDict): 27 | 28 | id: str 29 | type: Literal["ticketing_request"] 30 | source: str 31 | data: MessageData 32 | traceparent: Optional[str] 33 | tracestate: Optional[str] 34 | 35 | 36 | class ModelConfig(BaseModelConfig): 37 | 38 | name: str = Field("ticketing_agent", description="Name of the DSPy ticketing model") 39 | 40 | 41 | 42 | class TicketInfo(BaseModel): 43 | 44 | id: str = Field(..., description="Unique identifier for the ticket") 45 | policy_number: str = Field(description="Policy number associated with the ticket") 46 | department: TicketDepartment = Field(..., description="Department for the ticket") 47 | title: str = Field(..., description="Title of the ticket") 48 | contact_info: str = Field(..., description="Contact information of the user") 49 | created_at: str = Field(..., description="Creation timestamp") 50 | 51 | model_config = {"extra": "forbid"} 52 | 53 | 54 | DspyModelConfig = ModelConfig 55 | -------------------------------------------------------------------------------- /demo/agents/frontend/Makefile: -------------------------------------------------------------------------------- 1 | ../../Makefile -------------------------------------------------------------------------------- /demo/agents/frontend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/frontend/__init__.py -------------------------------------------------------------------------------- /demo/agents/frontend/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pydantic import Field 4 | from pydantic_settings import SettingsConfigDict 5 | 6 | from libraries.core import BaseAgentConfig 7 | 8 | 9 | class Settings(BaseAgentConfig): 10 | app_name: str = Field(default="frontend_agent") 11 | prometheus_metrics_port: int = Field(default=9097, description="Port for Prometheus metrics server") 12 | 13 | host: str = Field(default="127.0.0.1") 14 | port: int = Field(default=8000) 15 | log_level: str = Field(default="info") 16 | 17 | websocket_path: str = Field(default="/ws") 18 | websocket_ping_interval: float = Field(default=30.0) 19 | websocket_ping_timeout: float = Field(default=10.0) 20 | 21 | public_dir: str = Field(default="") 22 | 23 | @property 24 | def default_public_dir(self) -> str: 25 | if not self.public_dir: 26 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "public") 27 | return self.public_dir 28 | 29 | model_config = SettingsConfigDict( 30 | env_prefix="FRONTEND_", env_file=".env", env_ignore_empty=True, extra="ignore" 31 | ) 32 | 33 | 34 | settings = Settings() 35 | -------------------------------------------------------------------------------- /demo/agents/frontend/guardrails.py: -------------------------------------------------------------------------------- 1 | from guardrails import AsyncGuard 2 | 3 | from libraries.observability.logger import get_console_logger 4 | 5 | logger = get_console_logger("frontend_agent") 6 | 7 | try: 8 | from guardrails.hub import ToxicLanguage 9 | except ImportError: 10 | logger.warning("ToxicLanguage validator not found in guardrails.hub; disabling guardrails") 11 | ToxicLanguage = None 12 | 13 | _toxic_language_guard = None 14 | if ToxicLanguage: 15 | _toxic_language_guard = AsyncGuard().use( 16 | ToxicLanguage, 17 | threshold=0.5, 18 | validation_method="sentence", 19 | on_fail="noop", 20 | ) 21 | 22 | 23 | async def toxic_language_guard(text: str) -> str | None: 24 | """ 25 | Validate and filter text via ToxicLanguage guardrail. 26 | 27 | Returns the sanitized text if passed, otherwise None. 28 | If ToxicLanguage is unavailable, returns text unchanged. 29 | """ 30 | if _toxic_language_guard is None: 31 | return text 32 | result = await _toxic_language_guard.validate(text) 33 | if result.validation_passed is False: 34 | return None 35 | return result.validated_output 36 | -------------------------------------------------------------------------------- /demo/agents/frontend/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/frontend/tests/__init__.py -------------------------------------------------------------------------------- /demo/agents/frontend/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="session") 7 | def event_loop(): 8 | """Ensure a fresh event loop for the test session to avoid 'RuntimeError: Event loop is closed'.""" 9 | loop = asyncio.new_event_loop() 10 | asyncio.set_event_loop(loop) 11 | yield loop 12 | loop.close() 13 | -------------------------------------------------------------------------------- /demo/agents/frontend/tests/test_main_endpoint.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from fastapi.testclient import TestClient 3 | 4 | from ..main import api, settings 5 | 6 | 7 | @pytest.fixture(autouse=True) 8 | def temp_public_dir(tmp_path, monkeypatch): 9 | """Monkey-patch settings.public_dir to a temporary directory.""" 10 | monkeypatch.setattr(settings, "public_dir", str(tmp_path)) 11 | return tmp_path 12 | 13 | 14 | def test_read_root_success(temp_public_dir): 15 | """When index.html exists, GET / returns its contents.""" 16 | html_file = temp_public_dir / "index.html" 17 | content = "OK" 18 | html_file.write_text(content, encoding="utf-8") 19 | client = TestClient(api) 20 | response = client.get("/") 21 | assert response.status_code == 200 22 | assert response.text == content 23 | 24 | 25 | def test_read_root_not_found(temp_public_dir): 26 | """When index.html is missing, GET / returns 404.""" 27 | client = TestClient(api) 28 | response = client.get("/") 29 | assert response.status_code == 404 30 | 31 | 32 | def test_read_root_error(temp_public_dir, monkeypatch): 33 | """When reading index.html raises an error, GET / returns 500.""" 34 | html_file = temp_public_dir / "index.html" 35 | html_file.write_text("data", encoding="utf-8") 36 | # Simulate aiofiles.open() throwing an unexpected error 37 | import aiofiles 38 | monkeypatch.setattr( 39 | aiofiles, 40 | 'open', 41 | lambda *args, **kwargs: (_ for _ in ()).throw(Exception("fail")), 42 | ) 43 | client = TestClient(api) 44 | response = client.get("/") 45 | assert response.status_code == 500 46 | assert "An error occurred" in response.json().get("detail", "") -------------------------------------------------------------------------------- /demo/agents/frontend/types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Literal, Optional, TypedDict 3 | 4 | from libraries.communication.protocol import ( 5 | MessageData, 6 | ) 7 | from libraries.core import ( 8 | ModelConfig as BaseModelConfig, 9 | ) 10 | 11 | WebSocketStateType = Literal["connected", "disconnected", "connecting"] 12 | 13 | 14 | class UserMessage(TypedDict): 15 | 16 | id: str 17 | type: Literal["user_message"] 18 | source: Literal["Frontend"] 19 | data: MessageData 20 | traceparent: Optional[str] 21 | tracestate: Optional[str] 22 | 23 | 24 | class AgentResponseMessage(TypedDict): 25 | 26 | id: str 27 | type: Literal["agent_message"] 28 | source: str 29 | data: MessageData 30 | traceparent: Optional[str] 31 | tracestate: Optional[str] 32 | 33 | 34 | class ModelConfig(BaseModelConfig): 35 | pass 36 | 37 | 38 | class MessageType(str, Enum): 39 | USER_MESSAGE = "user_message" 40 | AGENT_MESSAGE = "agent_message" 41 | AGENT_MESSAGE_STREAM_START = "agent_message_stream_start" 42 | AGENT_MESSAGE_STREAM_WAITING_MESSAGE = "agent_message_stream_waiting_message" 43 | AGENT_MESSAGE_STREAM_CHUNK = "agent_message_stream_chunk" 44 | AGENT_MESSAGE_STREAM_END = "agent_message_stream_end" 45 | ASSISTANT_MESSAGE = "assistant_message" 46 | ASSISTANT_MESSAGE_STREAM_START = "assistant_message_stream_start" 47 | ASSISTANT_MESSAGE_STREAM_WAITING_MESSAGE = "assistant_message_stream_waiting_message" 48 | ASSISTANT_MESSAGE_STREAM_CHUNK = "assistant_message_stream_chunk" 49 | ASSISTANT_MESSAGE_STREAM_END = "assistant_message_stream_end" -------------------------------------------------------------------------------- /demo/agents/policies/Makefile: -------------------------------------------------------------------------------- 1 | ../../Makefile -------------------------------------------------------------------------------- /demo/agents/policies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/agent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/agent/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/agent/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/agent/api/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/agent/api/dependencies.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | from typing import Annotated 3 | 4 | from fastapi import Depends 5 | from sentence_transformers import SentenceTransformer 6 | 7 | from agents.policies.agent.services.document_service import DocumentService 8 | from agents.policies.agent.services.reindex_service import ReindexService 9 | from agents.policies.agent.services.search_service import SearchService 10 | from libraries.integrations.vespa import VespaClient 11 | from libraries.observability.logger import get_console_logger 12 | 13 | logger = get_console_logger("policies_api_dependencies") 14 | 15 | 16 | @lru_cache() 17 | def get_vespa_client() -> VespaClient: 18 | logger.info("Creating Vespa client instance") 19 | return VespaClient() 20 | 21 | 22 | @lru_cache() 23 | def get_embedding_model() -> SentenceTransformer: 24 | logger.info("Loading embedding model") 25 | return SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2") 26 | 27 | 28 | def get_document_service( 29 | vespa_client: Annotated[VespaClient, Depends(get_vespa_client)] 30 | ) -> DocumentService: 31 | return DocumentService(vespa_client) 32 | 33 | 34 | def get_search_service( 35 | vespa_client: Annotated[VespaClient, Depends(get_vespa_client)], 36 | embedding_model: Annotated[SentenceTransformer, Depends(get_embedding_model)] 37 | ) -> SearchService: 38 | return SearchService(vespa_client, embedding_model) 39 | 40 | 41 | def get_reindex_service( 42 | vespa_client: Annotated[VespaClient, Depends(get_vespa_client)] 43 | ) -> ReindexService: 44 | return ReindexService(vespa_client) -------------------------------------------------------------------------------- /demo/agents/policies/agent/api/middleware.py: -------------------------------------------------------------------------------- 1 | import time 2 | from typing import Callable 3 | 4 | from fastapi import Request, Response 5 | from fastapi.responses import JSONResponse 6 | 7 | from libraries.observability.logger import get_console_logger 8 | 9 | logger = get_console_logger("policies_api_middleware") 10 | 11 | 12 | async def add_process_time_header(request: Request, call_next: Callable) -> Response: 13 | start_time = time.time() 14 | response = await call_next(request) 15 | process_time = time.time() - start_time 16 | response.headers["X-Process-Time"] = str(process_time) 17 | return response 18 | 19 | 20 | async def catch_exceptions_middleware(request: Request, call_next: Callable) -> Response: 21 | try: 22 | return await call_next(request) 23 | except Exception as e: 24 | # Log the error with full traceback 25 | logger.error(f"Unhandled exception: {e}", exc_info=True) 26 | 27 | # Return generic error response to avoid exposing internals 28 | return JSONResponse( 29 | status_code=500, 30 | content={"detail": "An unexpected error occurred. Please try again later."} 31 | ) 32 | 33 | 34 | async def add_security_headers(request: Request, call_next: Callable) -> Response: 35 | response = await call_next(request) 36 | 37 | # Add security headers 38 | response.headers["X-Content-Type-Options"] = "nosniff" 39 | response.headers["X-Frame-Options"] = "DENY" 40 | response.headers["X-XSS-Protection"] = "1; mode=block" 41 | response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" 42 | 43 | return response -------------------------------------------------------------------------------- /demo/agents/policies/agent/optimization/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/agent/optimization/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/agent/optimization/optimized_policies_simba.json: -------------------------------------------------------------------------------- 1 | { 2 | "module_name": "PolicyAgentSignature", 3 | "metadata": { 4 | "optimization_method": "SIMBA", 5 | "tools": ["get_personal_policy_details", "search_policy_documentation"], 6 | "description": "Optimized policy agent with personal policy lookup and documentation search" 7 | }, 8 | "prompts": { 9 | "instructions": "You are a Policy Agent for an insurance company.\n\nROLE:\n- Help with both personal policy information and general policy questions\n- Your primary responsibility is data privacy - never reveal personal policy details without a valid policy number\n- Always call tools to get real data, never use hardcoded examples\n\nTOOLS AVAILABLE:\n1. get_personal_policy_details - Get specific policy data from database using policy number\n2. search_policy_documentation - Search policy documentation and coverage information\n\nDECISION LOGIC:\n- Personal policy queries (my premium, my policy details): Use get_personal_policy_details with policy number\n- General coverage questions (what's covered, how does it work): Use search_policy_documentation\n- If personal info requested without policy number: Ask for policy number\n\nWORKFLOW:\n1. Determine if user wants personal data or general information\n2. For personal queries: Check for policy number, call get_personal_policy_details\n3. For general queries: Call search_policy_documentation with relevant query\n4. Always use actual tool responses\n\nGUIDELINES:\n- Be professional and helpful\n- Include policy numbers when providing personal information\n- Format dates as YYYY-MM-DD, amounts with $ sign\n- Never skip tool calls when data is needed" 10 | } 11 | } -------------------------------------------------------------------------------- /demo/agents/policies/agent/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/agent/services/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/agent/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/agent/tools/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/agent/tools/database/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/agent/tools/database/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/agent/tools/database/example_data.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | # Sample policies for testing and demonstration 4 | EXAMPLE_POLICIES: List[Dict] = [ 5 | { 6 | "policy_number": "A12345", 7 | "name": "John Doe", 8 | "policy_category": "home", 9 | "premium_amount": 500, 10 | "due_date": "2026-03-01", 11 | "status": "active", 12 | }, 13 | { 14 | "policy_number": "B67890", 15 | "name": "Jane Smith", 16 | "policy_category": "life", 17 | "premium_amount": 300, 18 | "due_date": "2026-03-15", 19 | "status": "active", 20 | }, 21 | { 22 | "policy_number": "C24680", 23 | "name": "Alice Johnson", 24 | "policy_category": "auto", 25 | "premium_amount": 400, 26 | "due_date": "2026-03-01", 27 | "status": "active", 28 | }, 29 | ] 30 | 31 | # This would typically come from environment configuration 32 | USE_EXAMPLE_DATA = True # Set to False in production -------------------------------------------------------------------------------- /demo/agents/policies/agent/tools/retrieval/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/agent/tools/retrieval/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/agent/types.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import Field 4 | 5 | from libraries.communication.protocol import ( 6 | ChatMessage as ChatMessage, 7 | ) 8 | from libraries.core import ( 9 | ModelConfig as BaseModelConfig, 10 | ) 11 | 12 | PolicyCategory = Literal["auto", "life", "home", "health"] 13 | 14 | 15 | class ModelConfig(BaseModelConfig): 16 | name: str = Field("policies_react", description="Name of the model") 17 | date_format: str = Field("YYYY-MM-DD", description="Required date format for responses") 18 | -------------------------------------------------------------------------------- /demo/agents/policies/agent/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .async_helpers import run_async_safe as run_async_safe 2 | -------------------------------------------------------------------------------- /demo/agents/policies/ingestion/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/ingestion/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/ingestion/workflows/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/ingestion/workflows/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/ingestion/workflows/activities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/ingestion/workflows/activities/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/tests/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="function") 7 | def event_loop(): 8 | """Ensure a fresh event loop for each test to avoid 'RuntimeError: Event loop is closed'.""" 9 | loop = asyncio.new_event_loop() 10 | asyncio.set_event_loop(loop) 11 | yield loop 12 | loop.close() 13 | -------------------------------------------------------------------------------- /demo/agents/policies/tests/retrieval_performance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/tests/retrieval_performance/__init__.py -------------------------------------------------------------------------------- /demo/agents/policies/tests/retrieval_performance/data_utilities.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import List 4 | 5 | from .models import RetrievalTestCase 6 | 7 | 8 | def load_qa_pairs_from_json() -> List[RetrievalTestCase]: 9 | """Load test cases from filtered QA pairs JSON file.""" 10 | json_path = Path(__file__).parent / "filtered_qa_pairs.json" 11 | 12 | with open(json_path, "r") as f: 13 | qa_pairs = json.load(f) 14 | 15 | test_cases = [] 16 | for qa_pair in qa_pairs: 17 | test_cases.append( 18 | RetrievalTestCase( 19 | id=qa_pair["qa_pair_id"], 20 | question=qa_pair["question"], 21 | expected_answer=qa_pair["answer"], 22 | expected_context=qa_pair["context"], 23 | category=qa_pair.get("source_document", "").replace(".md", ""), 24 | description=f"Test case from {qa_pair.get('source_document', 'unknown')}", 25 | ) 26 | ) 27 | 28 | return test_cases 29 | 30 | 31 | def get_retrieval_test_cases() -> List[RetrievalTestCase]: 32 | """Get test dataset - now loads from JSON file with context-based evaluation.""" 33 | return load_qa_pairs_from_json() 34 | -------------------------------------------------------------------------------- /demo/agents/policies/vespa/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/policies/vespa/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/Makefile: -------------------------------------------------------------------------------- 1 | ../../Makefile -------------------------------------------------------------------------------- /demo/agents/triage/README.md: -------------------------------------------------------------------------------- 1 | # Triage Agent 2 | 3 | ML-powered message classification and routing to specialized agents. 4 | 5 | ## What it does 6 | - Classifies incoming messages using ML 7 | - Routes to: Billing, Claims, Policies, or Escalation agents 8 | - Handles small talk directly 9 | - Supports multiple classifier versions (v0-v5) 10 | 11 | ## Quick Start 12 | ```bash 13 | make start-triage 14 | ``` 15 | 16 | ## Configuration 17 | ```bash 18 | TRIAGE_LANGUAGE_MODEL=lm_studio/gemma-3-12b-it # Or openai/gpt-4o-mini 19 | TRIAGE_CLASSIFIER_VERSION=v4 # v0-v5 available 20 | ``` 21 | 22 | ## Classifier Versions 23 | - **v0**: Basic prompt-based 24 | - **v1**: Enhanced prompt with examples 25 | - **v2**: DSPy optimized few-shot 26 | - **v3**: Baseline few-shot with training 27 | - **v4**: Zero-shot COPRO optimized (default) 28 | - **v5**: Attention-based neural classifier 29 | 30 | ## Training 31 | ```bash 32 | make train-triage-classifier-v3 # Baseline 33 | make train-triage-classifier-v5 # Attention-based 34 | make compile-triage-classifier-v4 # Optimize DSPy 35 | make train-triage-classifier-v6 # run fine-tuning via OpenAI API 36 | make train-triage-classifier-v7 # run parameter efficient fine-tuning locally 37 | ``` 38 | 39 | ## Testing 40 | ```bash 41 | make test-triage-agent 42 | make test-triage-classifier-v4 # Test specific version 43 | ``` -------------------------------------------------------------------------------- /demo/agents/triage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/attention_net/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/attention_net/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/baseline_model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/baseline_model/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/baseline_model/metrics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sklearn.metrics import roc_auc_score 3 | 4 | 5 | def compute_f1_score(y_true: np.array, y_pred: np.array, n_classes: int) -> float: 6 | """ 7 | Compute the F1 score for a multi-class classification problem. 8 | 9 | Args: 10 | y_true: True labels. 11 | y_pred: Predicted labels. 12 | n_classes: Number of classes. 13 | 14 | Returns: 15 | f1_score: F1 score. 16 | """ 17 | # Convert to binary labels 18 | y_true_binary = np.eye(n_classes)[y_true] 19 | y_pred_binary = np.eye(n_classes)[y_pred.argmax(axis=1)] 20 | 21 | # Compute precision and recall 22 | tp = np.sum(y_true_binary * y_pred_binary, axis=0) 23 | fp = np.sum((1 - y_true_binary) * y_pred_binary, axis=0) 24 | fn = np.sum(y_true_binary * (1 - y_pred_binary), axis=0) 25 | 26 | precision = tp / (tp + fp + 1e-10) 27 | recall = tp / (tp + fn + 1e-10) 28 | 29 | # Compute F1 score 30 | f1_score = 2 * (precision * recall) / (precision + recall + 1e-10) 31 | return np.mean(f1_score) 32 | 33 | 34 | def compute_roc_auc(y_true: np.array, y_pred: np.array) -> float: 35 | """ 36 | Macro-averaged One-vs-Rest ROC AUC score for multi-class classification. 37 | 38 | Args: 39 | y_true: True labels. 40 | y_pred: Predicted labels. 41 | n_classes: Number of classes. 42 | 43 | Returns: 44 | auc: AUC score. 45 | """ 46 | # Compute AUC 47 | auc = roc_auc_score(y_true, y_pred, average="macro", multi_class="ovr") 48 | return auc 49 | -------------------------------------------------------------------------------- /demo/agents/triage/classifier_v6/README.md: -------------------------------------------------------------------------------- 1 | # Classifier V6 - OpenAI Fine-tuned 2 | 3 | Fine-tuned GPT-4o-mini for triage classification using OpenAI's fine-tuning API. 4 | 5 | ## Benefits 6 | 7 | **Performance** 8 | - Higher accuracy on domain-specific triage decisions 9 | - More consistent classification behavior 10 | - Specialized knowledge of agent roles and routing 11 | 12 | **Efficiency** 13 | - Shorter prompts (knowledge stored in model weights) 14 | - Reduced token usage per classification 15 | - Faster inference without complex instructions 16 | 17 | **Production** 18 | - No prompt engineering required 19 | - Reliable behavior at scale 20 | - Less maintenance overhead 21 | 22 | ## Setup 23 | 24 | ```bash 25 | make train-triage-classifier-v6 26 | export TRIAGE_CLASSIFIER_VERSION="v6" 27 | ``` 28 | 29 | ## Usage 30 | 31 | ```python 32 | from agents.triage.classifier_v6.classifier_v6 import classifier_v6 33 | 34 | result = classifier_v6(chat_history="User: I need help with my claim") 35 | ``` 36 | 37 | ## Training Options 38 | 39 | ```bash 40 | # Demo (20 examples) 41 | make train-triage-classifier-v6 42 | 43 | # Custom size 44 | FINETUNE_SAMPLE_SIZE=100 make train-triage-classifier-v6 45 | 46 | # Full dataset 47 | FINETUNE_SAMPLE_SIZE=-1 make train-triage-classifier-v6 48 | ``` 49 | 50 | ## Testing 51 | 52 | ```bash 53 | make test-triage-classifier-v6 54 | ``` -------------------------------------------------------------------------------- /demo/agents/triage/classifier_v6/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/classifier_v6/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/classifier_v6/data_utils.py: -------------------------------------------------------------------------------- 1 | """Data utilities for classifier v6.""" 2 | 3 | from typing import List 4 | 5 | import dspy 6 | 7 | from agents.triage.shared.data_utils import ( 8 | create_examples as _create_training_examples, 9 | ) 10 | 11 | 12 | def create_training_examples(sample_size: int = 20, seed: int = 42) -> List[dspy.Example]: 13 | """Create training examples for v6 classifier with default sample size of 20. 14 | 15 | Args: 16 | sample_size: Number of examples to sample (default: 20 for cost-effective OpenAI fine-tuning) 17 | seed: Random seed for reproducible sampling 18 | 19 | Returns: 20 | List of DSPy examples for training 21 | """ 22 | return _create_training_examples(sample_size=sample_size, seed=seed) -------------------------------------------------------------------------------- /demo/agents/triage/classifier_v7/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/classifier_v7/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/classifier_v8/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/classifier_v8/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/classifier_v8/config.py: -------------------------------------------------------------------------------- 1 | from pydantic import Field 2 | from pydantic_settings import BaseSettings, SettingsConfigDict 3 | 4 | 5 | class ClassifierV8Settings(BaseSettings): 6 | n_classes: int = Field(default=5) 7 | model_config = SettingsConfigDict( 8 | env_prefix="TRIAGE_V8_", 9 | env_file=".env", 10 | env_file_encoding='utf-8', 11 | case_sensitive=False, 12 | extra='ignore', 13 | protected_namespaces=('settings_',) 14 | ) 15 | 16 | # RoBERTa model configuration 17 | model_name: str = Field(default="roberta-base", description="RoBERTa base model from HuggingFace") 18 | 19 | # Specify device for model loading 20 | device: str = Field(default="mps", description="Device to load the model on (mps, cuda, cpu)") 21 | 22 | # Fine-tuning parameters 23 | train_sample_size: int = Field(default=-1, description="Number of training samples to use") 24 | eval_sample_size: int = Field(default=-1, description="Number of evaluation samples to use") 25 | learning_rate: float = Field(default=2e-4) 26 | num_epochs: int = Field(default=10) 27 | batch_size: int = Field(default=8) 28 | gradient_accumulation_steps: int = Field(default=2) 29 | 30 | # LoRA parameters 31 | lora_r: int = Field(default=16) 32 | lora_alpha: int = Field(default=32) 33 | lora_dropout: float = Field(default=0.1) 34 | lora_target_modules: list = Field(default_factory=lambda: ["query", "value"]) 35 | 36 | # Model paths 37 | output_dir: str = Field(default="./models/roberta-triage-v8") -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/.env.example: -------------------------------------------------------------------------------- 1 | # Example environment variables for Docker deployment 2 | # You need at least one of these API keys 3 | OPENAI_API_KEY=your_openai_key_here 4 | GEMINI_API_KEY=your_gemini_key_here 5 | ANTHROPIC_API_KEY=your_anthropic_key_here 6 | 7 | # Database connection for Docker 8 | DATABASE_URL=postgresql://postgres:postgres@db:5432/triage_db 9 | 10 | # Application settings 11 | SECRET_KEY=development_secret_key 12 | DEBUG=true 13 | MODEL=openai/gpt-4o-mini 14 | -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | WORKDIR /app 4 | 5 | # Install dependencies 6 | COPY pyproject.toml . 7 | COPY uv.lock . 8 | 9 | # Install system dependencies 10 | RUN apt-get update && \ 11 | apt-get install -y --no-install-recommends gcc libpq-dev && \ 12 | apt-get clean && \ 13 | rm -rf /var/lib/apt/lists/* 14 | 15 | # Copy source code 16 | COPY src/ /app/src/ 17 | 18 | # Install application in development mode 19 | RUN pip install --no-cache-dir --upgrade pip && \ 20 | pip install --no-cache-dir -e . 21 | 22 | USER 1000 23 | 24 | # Run the application 25 | CMD ["triage-server", "--reload"] -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/dev_webserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Development script for running the webserver directly. 4 | For production use, install the package and use the `triage-server` command. 5 | """ 6 | 7 | import argparse 8 | import os 9 | import sys 10 | 11 | import uvicorn 12 | from dotenv import load_dotenv 13 | 14 | # Add src directory to path 15 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "."))) 16 | 17 | if __name__ == "__main__": 18 | # Load environment variables 19 | load_dotenv() 20 | 21 | # Parse command line arguments 22 | parser = argparse.ArgumentParser( 23 | description="Start the Triage Dataset Manager web server" 24 | ) 25 | parser.add_argument( 26 | "--host", 27 | type=str, 28 | default="0.0.0.0", 29 | help="Host to bind the server to (default: 0.0.0.0)", 30 | ) 31 | parser.add_argument( 32 | "--port", 33 | type=int, 34 | default=int(os.getenv("PORT", "8000")), 35 | help="Port to bind the server to (default: 8000 or PORT env var)", 36 | ) 37 | 38 | args = parser.parse_args() 39 | 40 | print(f"Starting development server on {args.host}:{args.port}...") 41 | uvicorn.run( 42 | "src.triage_webserver.app:app", host=args.host, port=args.port, reload=True 43 | ) 44 | -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: postgres:16-alpine 4 | restart: always 5 | environment: 6 | POSTGRES_USER: postgres 7 | POSTGRES_PASSWORD: postgres 8 | POSTGRES_DB: triage_db 9 | ports: 10 | - "5432:5432" 11 | volumes: 12 | - postgres_data:/var/lib/postgresql/data 13 | healthcheck: 14 | test: ["CMD-SHELL", "pg_isready -U postgres"] 15 | interval: 5s 16 | timeout: 5s 17 | retries: 5 18 | start_period: 10s 19 | 20 | webapp: 21 | build: 22 | context: . 23 | dockerfile: Dockerfile 24 | restart: always 25 | depends_on: 26 | db: 27 | condition: service_healthy 28 | environment: 29 | DATABASE_URL: postgresql://postgres:postgres@db:5432/triage_db 30 | MODEL: openai/gpt-4o-mini 31 | SECRET_KEY: ${SECRET_KEY:-development_secret_key} 32 | DEBUG: ${DEBUG:-true} 33 | OPENAI_API_KEY: ${OPENAI_API_KEY:-} 34 | ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} 35 | GEMINI_API_KEY: ${GEMINI_API_KEY:-} 36 | ports: 37 | - "8000:8000" 38 | volumes: 39 | - ./src:/app/src 40 | command: ["triage-server", "--host", "0.0.0.0", "--port", "8000", "--reload"] 41 | 42 | volumes: 43 | postgres_data: -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_agent_dataset/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/data_sets/synthetic/src/triage_agent_dataset/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_agent_dataset/config.py: -------------------------------------------------------------------------------- 1 | from pydantic_settings import BaseSettings 2 | 3 | 4 | class AppConfig(BaseSettings): 5 | TOTAL_TARGET: int = 100 6 | AGENT_DISTRIBUTION: dict = { 7 | "BillingAgent": 0.3, 8 | "PolicyAgent": 0.2, 9 | "ClaimsAgent": 0.25, 10 | "EscalationAgent": 0.15, 11 | "ChattyAgent": 0.1, 12 | } 13 | SPECIAL_CASE_DISTRIBUTION: dict = { 14 | "none": 0.5, 15 | "edge_case": 0.1, 16 | "cross_domain": 0.1, 17 | "language_switch": 0.1, 18 | "short_query": 0.05, 19 | "complex_query": 0.05, 20 | "small_talk": 0.05, 21 | "angry_customer": 0.025, 22 | "technical_error": 0.025, 23 | } 24 | MODEL: str = "openai/gpt-4o-mini" 25 | 26 | class Config: 27 | env_prefix = "" 28 | -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_agent_dataset/models.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class Agents(str, Enum): 8 | BILLING = "BillingAgent" 9 | POLICY = "PolicyAgent" 10 | CLAIMS = "ClaimsAgent" 11 | ESCALATION = "EscalationAgent" 12 | CHATTY = "ChattyAgent" 13 | 14 | 15 | class SpecialCaseType(str, Enum): 16 | EDGE_CASE = "edge_case" 17 | CROSS_DOMAIN = "cross_domain" 18 | LANGUAGE_SWITCH = "language_switch" 19 | SHORT_QUERY = "short_query" 20 | COMPLEX_QUERY = "complex_query" 21 | SMALL_TALK = "small_talk" 22 | ANGRY_CUSTOMER = "angry_customer" 23 | TECHNICAL_ERROR = "technical_error" 24 | 25 | 26 | class ConversationExample(BaseModel): 27 | conversation: str 28 | target_agent: str 29 | turns: int 30 | temperature: float 31 | index_batch: int 32 | total_batch: int 33 | special_case: Optional[str] = None 34 | model: str 35 | -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/data_sets/synthetic/src/triage_webserver/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | import uvicorn 5 | from dotenv import load_dotenv 6 | 7 | 8 | def main(): 9 | """ 10 | Main entry point for the triage-server command 11 | """ 12 | # Load environment variables 13 | load_dotenv() 14 | 15 | # Parse command line arguments 16 | parser = argparse.ArgumentParser( 17 | description="Start the Triage Dataset Manager web server" 18 | ) 19 | parser.add_argument( 20 | "--host", 21 | type=str, 22 | default="0.0.0.0", 23 | help="Host to bind the server to (default: 0.0.0.0)", 24 | ) 25 | parser.add_argument( 26 | "--port", 27 | type=int, 28 | default=int(os.getenv("PORT", "8000")), 29 | help="Port to bind the server to (default: 8000 or PORT env var)", 30 | ) 31 | parser.add_argument( 32 | "--reload", action="store_true", help="Enable auto-reload for development" 33 | ) 34 | 35 | args = parser.parse_args() 36 | 37 | # Run the application 38 | uvicorn.run( 39 | "triage_webserver.app:app", host=args.host, port=args.port, reload=args.reload 40 | ) 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/config.py: -------------------------------------------------------------------------------- 1 | from pydantic_settings import BaseSettings 2 | 3 | 4 | class WebServerConfig(BaseSettings): 5 | DATABASE_URL: str = "postgresql://postgres:postgres@db:5432/triage_db" 6 | SECRET_KEY: str = "development_secret_key" 7 | MODEL: str = "openai/gpt-4o-mini" 8 | DEBUG: bool = True 9 | 10 | class Config: 11 | env_prefix = "" 12 | -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/database/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/data_sets/synthetic/src/triage_webserver/database/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/database/base.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.ext.declarative import declarative_base 2 | 3 | # Create a single base class for all models 4 | Base = declarative_base() 5 | -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/data_sets/synthetic/src/triage_webserver/models/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/routes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/data_sets/synthetic/src/triage_webserver/routes/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/schemas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/data_sets/synthetic/src/triage_webserver/schemas/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/schemas/dataset_schemas.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Dict, List, Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class DatasetBase(BaseModel): 8 | name: str 9 | description: Optional[str] = None 10 | model: Optional[str] = None 11 | 12 | 13 | class DatasetCreate(DatasetBase): 14 | total_target: int = 100 15 | agent_distribution: Optional[Dict[str, float]] = None 16 | special_case_distribution: Optional[Dict[str, float]] = None 17 | temperatures: List[float] = [0.7, 0.8, 0.9] 18 | turns: List[int] = [1, 3, 5] 19 | 20 | 21 | class DatasetResponse(DatasetBase): 22 | id: int 23 | total_examples: int 24 | created_at: datetime 25 | updated_at: datetime 26 | 27 | model_config = {"from_attributes": True} 28 | 29 | 30 | class DatasetList(BaseModel): 31 | datasets: List[DatasetResponse] 32 | total: int 33 | -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/schemas/example_schemas.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class ExampleBase(BaseModel): 8 | conversation: str 9 | target_agent: str 10 | turns: int 11 | temperature: float 12 | special_case: Optional[str] = None 13 | 14 | 15 | class ExampleCreate(ExampleBase): 16 | dataset_id: int 17 | index_batch: Optional[int] = None 18 | total_batch: Optional[int] = None 19 | 20 | 21 | class ExampleUpdate(BaseModel): 22 | conversation: Optional[str] = None 23 | target_agent: Optional[str] = None 24 | turns: Optional[int] = None 25 | temperature: Optional[float] = None 26 | special_case: Optional[str] = None 27 | 28 | 29 | class ExampleResponse(ExampleBase): 30 | id: int 31 | dataset_id: int 32 | index_batch: Optional[int] 33 | total_batch: Optional[int] 34 | created_at: datetime 35 | updated_at: datetime 36 | 37 | model_config = {"from_attributes": True} 38 | 39 | 40 | class ExampleList(BaseModel): 41 | examples: List[ExampleResponse] 42 | total: int 43 | -------------------------------------------------------------------------------- /demo/agents/triage/data_sets/synthetic/src/triage_webserver/services/__init__.py: -------------------------------------------------------------------------------- 1 | # Initialize the services package 2 | -------------------------------------------------------------------------------- /demo/agents/triage/dspy_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/dspy_modules/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/dspy_modules/classifier_v1.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | from dotenv import load_dotenv 3 | 4 | from agents.triage.config import Settings 5 | from agents.triage.models import ClassifierMetrics, TargetAgent 6 | from libraries.ml.dspy.language_model import dspy_set_language_model 7 | 8 | load_dotenv() 9 | 10 | settings = Settings() 11 | 12 | lm = dspy_set_language_model(settings) 13 | 14 | 15 | class AgentClassificationSignature(dspy.Signature): 16 | def __init__(self, chat_history: str): 17 | super().__init__(chat_history=chat_history) 18 | self.metrics: ClassifierMetrics 19 | 20 | chat_history: str = dspy.InputField() 21 | target_agent: TargetAgent = dspy.OutputField() 22 | 23 | 24 | classifier_v1_program = dspy.Predict(signature=AgentClassificationSignature) 25 | 26 | 27 | def classifier_v1(chat_history: str) -> AgentClassificationSignature: 28 | result = classifier_v1_program(chat_history=chat_history) 29 | result.metrics = ClassifierMetrics( 30 | total_tokens=lm.total_tokens, 31 | prompt_tokens=lm.prompt_tokens, 32 | completion_tokens=lm.completion_tokens, 33 | latency_ms=lm.latency_ms, 34 | ) 35 | return result 36 | 37 | 38 | if __name__ == "__main__": 39 | res = classifier_v1( 40 | chat_history="User: hello!", 41 | ) 42 | print(res.target_agent) 43 | print(res.metrics) 44 | -------------------------------------------------------------------------------- /demo/agents/triage/dspy_modules/classifier_v2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/dspy_modules/classifier_v2/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/dspy_modules/classifier_v4/__init__.py: -------------------------------------------------------------------------------- 1 | from .classifier_v4 import classifier_v4 as classifier_v4 -------------------------------------------------------------------------------- /demo/agents/triage/dspy_modules/evaluation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/dspy_modules/evaluation/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai import eggai_main 4 | from eggai.transport import eggai_set_default_transport 5 | 6 | from agents.triage.config import settings 7 | from libraries.communication.transport import create_kafka_transport 8 | from libraries.ml.dspy.language_model import dspy_set_language_model 9 | from libraries.observability.logger import get_console_logger 10 | from libraries.observability.tracing import init_telemetry 11 | from libraries.observability.tracing.init_metrics import init_token_metrics 12 | 13 | eggai_set_default_transport( 14 | lambda: create_kafka_transport( 15 | bootstrap_servers=settings.kafka_bootstrap_servers, 16 | ssl_cert=settings.kafka_ca_content, 17 | ) 18 | ) 19 | init_token_metrics(port=settings.prometheus_metrics_port, application_name=settings.app_name) 20 | 21 | from agents.triage.agent import triage_agent 22 | 23 | logger = get_console_logger("triage_agent") 24 | 25 | 26 | @eggai_main 27 | async def main(): 28 | 29 | logger.info(f"Starting {settings.app_name}") 30 | 31 | init_telemetry(app_name=settings.app_name, endpoint=settings.otel_endpoint) 32 | logger.info(f"Telemetry initialized for {settings.app_name}") 33 | dspy_set_language_model(settings) 34 | 35 | await triage_agent.start() 36 | logger.info(f"{settings.app_name} started successfully") 37 | 38 | await asyncio.Future() 39 | 40 | 41 | if __name__ == "__main__": 42 | try: 43 | asyncio.run(main()) 44 | except KeyboardInterrupt: 45 | logger.info("Shutting down triage agent") 46 | except Exception as e: 47 | logger.error(f"Error in main: {e}", exc_info=True) 48 | -------------------------------------------------------------------------------- /demo/agents/triage/model_checkpoints/attention_net.pth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/model_checkpoints/attention_net.pth -------------------------------------------------------------------------------- /demo/agents/triage/model_checkpoints/fewshot_baseline_n_all.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/model_checkpoints/fewshot_baseline_n_all.pkl -------------------------------------------------------------------------------- /demo/agents/triage/shared/__init__.py: -------------------------------------------------------------------------------- 1 | """Shared utilities for triage classifiers.""" -------------------------------------------------------------------------------- /demo/agents/triage/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/demo/agents/triage/tests/__init__.py -------------------------------------------------------------------------------- /demo/agents/triage/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="session") 7 | def event_loop(): 8 | """Ensure a fresh event loop for the test session to avoid 'RuntimeError: Event loop is closed'.""" 9 | loop = asyncio.new_event_loop() 10 | asyncio.set_event_loop(loop) 11 | yield loop 12 | loop.close() 13 | -------------------------------------------------------------------------------- /demo/agents/triage/tests/test_agent_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from agents.triage.agent import ( 4 | _CLASSIFIER_PATHS, 5 | build_conversation_string, 6 | get_current_classifier, 7 | ) 8 | from agents.triage.config import settings 9 | 10 | 11 | def test_build_conversation_string_empty(): 12 | # No messages yields empty string 13 | assert build_conversation_string([]) == "" 14 | 15 | 16 | def test_build_conversation_string_basic(): 17 | msgs = [ 18 | {"agent": "User", "content": "Hello"}, 19 | {"agent": "Bot", "content": "Hi there!"}, 20 | ] 21 | expected = "User: Hello\nBot: Hi there!\n" 22 | assert build_conversation_string(msgs) == expected 23 | 24 | 25 | def test_build_conversation_string_filters_empty_content(): 26 | msgs = [ 27 | {"agent": "User", "content": ""}, 28 | {"agent": "Bot", "content": "Response"}, 29 | ] 30 | expected = "Bot: Response\n" 31 | assert build_conversation_string(msgs) == expected 32 | 33 | 34 | @pytest.mark.parametrize("version,module_fn", list(_CLASSIFIER_PATHS.items())) 35 | def test_get_current_classifier_valid(monkeypatch, version, module_fn): 36 | # Ensure get_current_classifier returns a callable for each supported version 37 | monkeypatch.setattr(settings, "classifier_version", version) 38 | fn = get_current_classifier() 39 | assert callable(fn) 40 | 41 | 42 | def test_get_current_classifier_invalid(monkeypatch): 43 | # Unsupported version should raise ValueError 44 | monkeypatch.setattr(settings, "classifier_version", "nonexistent") 45 | with pytest.raises(ValueError): 46 | get_current_classifier() -------------------------------------------------------------------------------- /demo/dev-requirements.txt: -------------------------------------------------------------------------------- 1 | pytest-asyncio==0.24.0 2 | pytest==8.3.4 3 | ruff -------------------------------------------------------------------------------- /demo/docker-compose-lmstudio.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | lmstudio: 3 | image: ghcr.io/eggai-tech/lm-studio:gemma-3-12B-it-qat-GGUF-1.0 4 | environment: 5 | MODEL_NAME: gemma-3-12B-it-qat 6 | MODEL_PATH: /gemma-3-12B-it-QAT-Q4_0.gguf 7 | CONTEXT_LENGTH: 32768 8 | ports: 9 | - "1234:1234" 10 | networks: 11 | - eggai-example-network 12 | -------------------------------------------------------------------------------- /demo/dockerConfig/development-sql.yaml: -------------------------------------------------------------------------------- 1 | limit.maxIDLength: 2 | - value: 255 3 | constraints: {} 4 | system.forceSearchAttributesCacheRefreshOnRead: 5 | - value: true # Dev setup only. Please don't turn this on in production. 6 | constraints: {} -------------------------------------------------------------------------------- /demo/dockerConfig/grafana-provisioning.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'default' 5 | orgId: 1 6 | folder: '' 7 | type: file 8 | disableDeletion: false 9 | updateIntervalSeconds: 10 10 | options: 11 | path: /var/lib/grafana/dashboards 12 | -------------------------------------------------------------------------------- /demo/dockerConfig/loki.yaml: -------------------------------------------------------------------------------- 1 | auth_enabled: false 2 | 3 | server: 4 | http_listen_port: 3100 5 | grpc_listen_port: 9096 6 | 7 | common: 8 | instance_addr: 0.0.0.0 9 | path_prefix: /tmp/loki 10 | storage: 11 | filesystem: 12 | chunks_directory: /tmp/loki/chunks 13 | rules_directory: /tmp/loki/rules 14 | replication_factor: 1 15 | ring: 16 | kvstore: 17 | store: inmemory 18 | 19 | limits_config: 20 | allow_structured_metadata: false 21 | 22 | query_range: 23 | results_cache: 24 | cache: 25 | embedded_cache: 26 | enabled: true 27 | max_size_mb: 100 28 | 29 | schema_config: 30 | configs: 31 | - from: 2020-10-24 32 | store: tsdb 33 | object_store: filesystem 34 | schema: v12 35 | index: 36 | prefix: index_ 37 | period: 24h 38 | -------------------------------------------------------------------------------- /demo/dockerConfig/otel-collector.yaml: -------------------------------------------------------------------------------- 1 | receivers: 2 | otlp: 3 | protocols: 4 | grpc: 5 | endpoint: 0.0.0.0:4317 6 | http: 7 | endpoint: 0.0.0.0:4318 8 | exporters: 9 | otlp: 10 | endpoint: tempo:4317 11 | tls: 12 | insecure: true 13 | logging: 14 | verbosity: detailed 15 | prometheus: 16 | endpoint: 0.0.0.0:9464 17 | service: 18 | pipelines: 19 | traces: 20 | receivers: [otlp] 21 | exporters: [otlp] 22 | metrics: 23 | receivers: [ otlp ] 24 | exporters: [ prometheus,logging ] -------------------------------------------------------------------------------- /demo/dockerConfig/prometheus.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | evaluation_interval: 15s 4 | 5 | scrape_configs: 6 | - job_name: 'prometheus' 7 | static_configs: 8 | - targets: [ 'localhost:9090' ] 9 | 10 | - job_name: 'tempo' 11 | static_configs: 12 | - targets: [ 'tempo:3200' ] 13 | 14 | - job_name: 'otel-collector' 15 | scrape_interval: 5s 16 | static_configs: 17 | - targets: [ 'otel-collector:9464' ] 18 | 19 | - job_name: 'eggai-triage-agent' 20 | scrape_interval: 5s 21 | static_configs: 22 | - targets: [ 'host.docker.internal:9091' ] 23 | 24 | - job_name: 'eggai-claims-agent' 25 | scrape_interval: 5s 26 | static_configs: 27 | - targets: [ 'host.docker.internal:9092' ] 28 | 29 | - job_name: 'eggai-policies-agent' 30 | scrape_interval: 5s 31 | static_configs: 32 | - targets: [ 'host.docker.internal:9093' ] 33 | 34 | - job_name: 'eggai-escalation-agent' 35 | scrape_interval: 5s 36 | static_configs: 37 | - targets: [ 'host.docker.internal:9094' ] 38 | 39 | - job_name: 'eggai-billing-agent' 40 | scrape_interval: 5s 41 | static_configs: 42 | - targets: [ 'host.docker.internal:9095' ] 43 | 44 | - job_name: 'eggai-audit-agent' 45 | scrape_interval: 5s 46 | static_configs: 47 | - targets: [ 'host.docker.internal:9096' ] 48 | 49 | - job_name: 'eggai-frontend-agent' 50 | scrape_interval: 5s 51 | static_configs: 52 | - targets: [ 'host.docker.internal:9097' ] 53 | -------------------------------------------------------------------------------- /demo/docs/agents-overview.md: -------------------------------------------------------------------------------- 1 | # Agent Capabilities Overview 2 | 3 | ## Agent Roles 4 | 5 | | Agent | Purpose | Key Capabilities | 6 | |-------|---------|------------------| 7 | | **Frontend** | Web UI & WebSocket gateway | Serves chat interface, manages connections, handles reconnections | 8 | | **Triage** | Request routing | Classifies intent (ML), routes to specialists, handles greetings | 9 | | **Billing** | Payment & premiums | Retrieves/updates billing info, payment dates, amounts | 10 | | **Claims** | Claims processing | Files new claims, checks status, updates info, estimates | 11 | | **Policies** | Coverage & documents | Personal policy data, RAG document search, coverage Q&A | 12 | | **Escalation** | Complex issues | Multi-step workflows, support tickets, complaints | 13 | | **Audit** | Compliance tracking | Logs all messages, categorizes by domain, audit trail | 14 | 15 | ## Message Flow 16 | 17 | ``` 18 | User → Frontend → Triage → Specialized Agent → Response → User 19 | ↓ 20 | Audit (logs all interactions) 21 | ``` 22 | 23 | ## Agent Communication 24 | 25 | - **Channels**: `human` (user messages), `agents` (routed requests), `audit_logs` (compliance) 26 | - **Pattern**: Event-driven pub/sub via Redpanda 27 | - **Scaling**: Each agent runs independently, horizontally scalable 28 | 29 | ## Running Agents 30 | 31 | ```bash 32 | # Start all agents 33 | make start-all 34 | 35 | # Individual agents 36 | make start-triage 37 | make start-billing 38 | # etc. 39 | ``` 40 | 41 | --- 42 | 43 | **Previous:** [System Architecture](system-architecture.md) | **Next:** [Multi-Agent Communication](multi-agent-communication.md) -------------------------------------------------------------------------------- /demo/helm/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /demo/helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: eggai-multi-agent-chat 3 | description: A Helm chart for EggAI multi-agent human chat example application 4 | 5 | type: application 6 | 7 | version: "0.5.7" 8 | appVersion: "0.1.0" 9 | -------------------------------------------------------------------------------- /demo/helm/templates/monitoring.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.monitoring.enabled }} 2 | {{- range $name,$value := .Values.agents }} 3 | --- 4 | apiVersion: v1 5 | kind: Service 6 | metadata: 7 | name: {{ include "eggai-multi-agent-chat.fullname" $ }}-{{ lower $name }}-metrics 8 | labels: 9 | {{- include "eggai-multi-agent-chat.labels" $ | nindent 4 }} 10 | app.kubernetes.io/component: monitoring 11 | egg-ai.com/agent: {{ $name }} 12 | spec: 13 | type: ClusterIP 14 | ports: 15 | - port: {{ $.Values.monitoring.port }} 16 | targetPort: metrics 17 | protocol: TCP 18 | name: metrics 19 | selector: 20 | {{- include "eggai-multi-agent-chat.selectorLabels" $ | nindent 4 }} 21 | egg-ai.com/agent: {{ $name }} 22 | --- 23 | apiVersion: monitoring.coreos.com/v1 24 | kind: ServiceMonitor 25 | metadata: 26 | name: {{ include "eggai-multi-agent-chat.fullname" $ }}-{{ lower $name }} 27 | labels: 28 | {{- include "eggai-multi-agent-chat.labels" $ | nindent 4 }} 29 | spec: 30 | endpoints: 31 | - honorLabels: true 32 | interval: {{ $.Values.monitoring.serviceMonitor.interval | default "30s" }} 33 | path: /metrics 34 | port: metrics 35 | scheme: http 36 | scrapeTimeout: {{ $.Values.monitoring.serviceMonitor.scrapeTimeout | default "10s" }} 37 | selector: 38 | matchLabels: 39 | {{- include "eggai-multi-agent-chat.selectorLabels" $ | nindent 6 }} 40 | app.kubernetes.io/component: monitoring 41 | egg-ai.com/agent: {{ $name }} 42 | {{- end }} 43 | {{- end }} 44 | -------------------------------------------------------------------------------- /demo/helm/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "eggai-multi-agent-chat.fullname" . }} 5 | labels: 6 | {{- include "eggai-multi-agent-chat.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "eggai-multi-agent-chat.selectorLabels" . | nindent 4 }} 16 | egg-ai.com/agent: frontend 17 | -------------------------------------------------------------------------------- /demo/helm/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "eggai-multi-agent-chat.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "eggai-multi-agent-chat.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | automountServiceAccountToken: false 11 | containers: 12 | - name: wget 13 | image: busybox 14 | command: ['wget'] 15 | args: ['{{ include "eggai-multi-agent-chat.fullname" . }}:{{ .Values.service.port }}'] 16 | resources: 17 | limits: 18 | cpu: "250m" 19 | memory: "512Mi" 20 | requests: 21 | cpu: "100m" 22 | memory: "128Mi" 23 | ephemeralStorage: "2Gi" 24 | restartPolicy: Never 25 | -------------------------------------------------------------------------------- /demo/helm/values.yaml: -------------------------------------------------------------------------------- 1 | agents: 2 | audit: 3 | enabled: true 4 | replicaCount: 1 5 | environment: [] 6 | billing: 7 | enabled: true 8 | replicaCount: 1 9 | environment: [] 10 | claims: 11 | enabled: true 12 | replicaCount: 1 13 | environment: [] 14 | escalation: 15 | enabled: true 16 | replicaCount: 1 17 | environment: [] 18 | frontend: 19 | enabled: true 20 | replicaCount: 1 21 | environment: [] 22 | policiesDocumentIngestion: 23 | enabled: true 24 | replicaCount: 1 25 | environment: [] 26 | policies: 27 | enabled: true 28 | replicaCount: 1 29 | environment: [] 30 | triage: 31 | enabled: true 32 | replicaCount: 1 33 | environment: [] 34 | 35 | image: 36 | repository: ghcr.io/eggai-tech/multi-agent-human-chat 37 | pullPolicy: IfNotPresent 38 | tag: latest 39 | 40 | imagePullSecrets: [] 41 | nameOverride: "" 42 | fullnameOverride: "" 43 | 44 | resources: 45 | limits: 46 | cpu: "250m" 47 | memory: "512Mi" 48 | requests: 49 | cpu: "100m" 50 | memory: "128Mi" 51 | ephemeral-storage: "10Gi" 52 | 53 | cacheVolumeSize: "10Gi" 54 | 55 | podAnnotations: {} 56 | 57 | podSecurityContext: {} 58 | 59 | securityContext: {} 60 | 61 | service: 62 | type: ClusterIP 63 | port: 8000 64 | 65 | monitoring: 66 | enabled: true 67 | port: 9000 68 | serviceMonitor: 69 | interval: 30s 70 | scrapeTimeout: 10s 71 | 72 | nodeSelector: {} 73 | tolerations: [] 74 | 75 | gpuNodeSelector: {} 76 | gpuTolerations: [] 77 | -------------------------------------------------------------------------------- /demo/libraries/README.md: -------------------------------------------------------------------------------- 1 | # Libraries 2 | 3 | Shared utilities and infrastructure for EggAI agents. 4 | 5 | ## Structure 6 | 7 | ### Core (`core/`) 8 | - `BaseAgentConfig` - Base configuration class 9 | - `ModelConfig`, `ModelResult` - ML data models 10 | - System patches for DSPy tracking 11 | 12 | ### Communication (`communication/`) 13 | - `channels.py` - Channel configuration with namespacing 14 | - `messaging.py` - Type-safe message subscription 15 | - `transport.py` - Kafka transport with SSL 16 | - `protocol/` - Message types and enums 17 | 18 | ### ML (`ml/`) 19 | - `device.py` - PyTorch device utilities 20 | - `mlflow.py` - Model artifact management 21 | - `dspy/` - Language model wrapper and optimizer 22 | 23 | ### Observability (`observability/`) 24 | - `logger/` - Structured logging 25 | - `tracing/` - OpenTelemetry, metrics, pricing 26 | 27 | ### Integrations (`integrations/`) 28 | - `vespa/` - Document search client 29 | 30 | ### Testing (`testing/`) 31 | - `utils/` - Test helpers 32 | - `tests/` - Unit tests 33 | 34 | ## Usage 35 | 36 | ```python 37 | from libraries.communication import channels, subscribe 38 | from libraries.core import BaseAgentConfig 39 | from libraries.observability import get_console_logger 40 | from libraries.ml.dspy import dspy_set_language_model 41 | ``` -------------------------------------------------------------------------------- /demo/libraries/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | EggAI Libraries - Organized utilities for building AI agents. 3 | 4 | Structure: 5 | - core/: Core utilities and base classes 6 | - communication/: Messaging, channels, and transport 7 | - ml/: Machine learning utilities and DSPy integration 8 | - observability/: Logging, tracing, and monitoring 9 | - integrations/: External service integrations 10 | - testing/: Test utilities and unit tests 11 | 12 | Import from specific modules: 13 | from libraries.communication import channels, subscribe 14 | from libraries.core import BaseAgentConfig 15 | from libraries.observability import get_console_logger 16 | """ 17 | 18 | from libraries.core.patches import patch_usage_tracker 19 | 20 | # Apply patches 21 | patch_usage_tracker() 22 | -------------------------------------------------------------------------------- /demo/libraries/communication/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Communication infrastructure for EggAI agents. 3 | 4 | This module provides messaging, channels, and transport functionality. 5 | """ 6 | 7 | from .channels import channels 8 | from .messaging import subscribe 9 | from .transport import create_kafka_transport 10 | 11 | __all__ = [ 12 | "channels", 13 | "subscribe", 14 | "create_kafka_transport", 15 | ] -------------------------------------------------------------------------------- /demo/libraries/communication/transport.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | from typing import Optional 3 | 4 | from eggai.transport import KafkaTransport 5 | from faststream.kafka import KafkaBroker 6 | from faststream.security import BaseSecurity 7 | 8 | 9 | def create_kafka_transport( 10 | bootstrap_servers: str, ssl_cert: Optional[str] = None 11 | ) -> KafkaTransport: 12 | """ 13 | Create a KafkaTransport instance with SSL support when certificate is provided. 14 | 15 | Args: 16 | bootstrap_servers: Comma-separated list of Kafka bootstrap servers 17 | ssl_cert: SSL certificate content as a string (optional) 18 | 19 | Returns: 20 | KafkaTransport: Configured Kafka transport instance 21 | """ 22 | # Parse bootstrap servers 23 | servers_list = bootstrap_servers.split(",") 24 | 25 | # Use SSL if certificate is provided 26 | if ssl_cert and ssl_cert.strip(): 27 | ssl_context = ssl.create_default_context() 28 | ssl_context.check_hostname = False 29 | ssl_context.verify_mode = ssl.CERT_NONE 30 | 31 | # Load the certificate directly from the string 32 | ssl_context.load_verify_locations(cadata=ssl_cert) 33 | 34 | # Create broker with SSL 35 | broker = KafkaBroker( 36 | bootstrap_servers=servers_list, 37 | security=BaseSecurity(use_ssl=True, ssl_context=ssl_context), 38 | ) 39 | else: 40 | # Create broker without SSL 41 | broker = KafkaBroker(bootstrap_servers=servers_list) 42 | 43 | return KafkaTransport(broker=broker) 44 | -------------------------------------------------------------------------------- /demo/libraries/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core utilities and base classes for EggAI agents. 3 | 4 | This module provides fundamental building blocks used across all agents. 5 | """ 6 | 7 | from .config import BaseAgentConfig 8 | from .models import ModelConfig, ModelResult 9 | from .patches import patch_usage_tracker 10 | 11 | __all__ = [ 12 | "BaseAgentConfig", 13 | "ModelConfig", 14 | "ModelResult", 15 | "patch_usage_tracker", 16 | ] -------------------------------------------------------------------------------- /demo/libraries/core/patches.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from libraries.observability.logger import get_console_logger 4 | 5 | logger = get_console_logger("libraries.patches") 6 | 7 | 8 | def patch_usage_tracker(): 9 | # FIXME: Patch the UsageTracker to handle nested dicts in usage entries 10 | def _merge_usage_entries(self, entry1, entry2) -> dict[str, dict[str, Any]]: 11 | # If one side is empty or None, just clone the other 12 | if not entry1: 13 | return dict(entry2 or {}) 14 | if not entry2: 15 | return dict(entry1 or {}) 16 | 17 | # Start with a shallow copy of entry2 18 | result = dict(entry2) 19 | 20 | for key, val1 in entry1.items(): 21 | val2 = result.get(key) 22 | 23 | # If either side is a dict, merge them recursively (treat None as {}) 24 | if isinstance(val1, dict) or isinstance(val2, dict): 25 | sub1 = val1 if isinstance(val1, dict) else {} 26 | sub2 = val2 if isinstance(val2, dict) else {} 27 | result[key] = self._merge_usage_entries(sub1, sub2) 28 | 29 | else: 30 | # Treat None or missing as zero, then sum 31 | num1 = val1 or 0 32 | num2 = val2 or 0 33 | result[key] = num1 + num2 34 | 35 | return result 36 | 37 | # Apply the patch 38 | from dspy.utils.usage_tracker import UsageTracker 39 | 40 | UsageTracker._merge_usage_entries = _merge_usage_entries 41 | logger.info("DSPY UsageTracker patched successfully") 42 | -------------------------------------------------------------------------------- /demo/libraries/integrations/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | External service integrations for EggAI agents. 3 | 4 | This module provides integrations with external services like Vespa. 5 | """ 6 | 7 | # Currently empty - integrations are imported directly from submodules 8 | __all__ = [] -------------------------------------------------------------------------------- /demo/libraries/integrations/vespa/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import VespaConfig as VespaConfig 2 | from .schemas import DocumentMetadata as DocumentMetadata 3 | from .schemas import PolicyDocument as PolicyDocument 4 | from .vespa_client import VespaClient as VespaClient 5 | -------------------------------------------------------------------------------- /demo/libraries/ml/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Machine Learning utilities for EggAI agents. 3 | 4 | This module provides ML-related functionality including DSPy integration, 5 | device management, and MLflow utilities. 6 | """ 7 | 8 | from .device import get_device 9 | 10 | __all__ = [ 11 | "get_device", 12 | ] -------------------------------------------------------------------------------- /demo/libraries/ml/device.py: -------------------------------------------------------------------------------- 1 | import torch 2 | 3 | 4 | def get_device() -> str: 5 | """Get the optimal device for PyTorch operations.""" 6 | if torch.cuda.is_available(): 7 | return "cuda" 8 | if hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): 9 | return "mps" 10 | if hasattr(torch, "xpu") and torch.xpu.is_available(): 11 | return "xpu" 12 | return "cpu" 13 | -------------------------------------------------------------------------------- /demo/libraries/ml/dspy/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | DSPy-specific utilities. 3 | 4 | This module provides DSPy language model integration and optimizers. 5 | """ 6 | 7 | from .language_model import TrackingLM, dspy_set_language_model 8 | from .optimizer import SIMBAOptimizer 9 | 10 | __all__ = [ 11 | "SIMBAOptimizer", 12 | "TrackingLM", 13 | "dspy_set_language_model", 14 | ] -------------------------------------------------------------------------------- /demo/libraries/ml/mlflow.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import dotenv 4 | from mlflow import MlflowClient 5 | 6 | dotenv.load_dotenv() 7 | 8 | 9 | def find_model(model_name: str, version: str, artifact_path: str = "model") -> str: 10 | cache_path = Path(__file__).resolve().parents[1] / ".cache" 11 | cache_path.mkdir(parents=True, exist_ok=True) 12 | 13 | pickle_path = cache_path / model_name / version / artifact_path / "model.pkl" 14 | if pickle_path.exists(): 15 | return str(pickle_path) 16 | pytorch_path = ( 17 | cache_path / model_name / version / artifact_path / "data" / "model.pth" 18 | ) 19 | if pytorch_path.exists(): 20 | return str(pytorch_path) 21 | 22 | client = MlflowClient() 23 | mv = client.get_model_version(name=model_name, version=version) 24 | run_id = mv.run_id 25 | 26 | dest = cache_path / model_name / version 27 | dest.mkdir(parents=True, exist_ok=True) 28 | client.download_artifacts(run_id, artifact_path, dst_path=str(dest)) 29 | if pickle_path.exists(): 30 | return str(pickle_path) 31 | if pytorch_path.exists(): 32 | return str(pytorch_path) 33 | 34 | 35 | if __name__ == "__main__": 36 | print(find_model("attention_net_0.25_0.0002", version="1")) 37 | -------------------------------------------------------------------------------- /demo/libraries/observability/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Observability utilities for monitoring and debugging EggAI agents. 3 | 4 | This module provides logging, tracing, and metrics functionality. 5 | """ 6 | 7 | # Re-export commonly used functions for convenience 8 | from .logger import configure_logging, get_console_logger 9 | from .tracing import TracedMessage, create_tracer, init_telemetry, traced_handler 10 | 11 | __all__ = [ 12 | # Logging 13 | "configure_logging", 14 | "get_console_logger", 15 | # Tracing 16 | "TracedMessage", 17 | "create_tracer", 18 | "init_telemetry", 19 | "traced_handler", 20 | ] -------------------------------------------------------------------------------- /demo/libraries/observability/logger/__init__.py: -------------------------------------------------------------------------------- 1 | from .logger import configure_logging as configure_logging 2 | from .logger import get_console_logger as get_console_logger 3 | -------------------------------------------------------------------------------- /demo/libraries/observability/logger/config.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from pydantic import Field 3 | from pydantic_settings import BaseSettings, SettingsConfigDict 4 | 5 | # Load environment variables at module level 6 | load_dotenv() 7 | 8 | 9 | class Settings(BaseSettings): 10 | log_level: str = Field(default="INFO") 11 | log_format: str = Field( 12 | default="%(asctime)s %(levelname)s %(name)s %(filename)s %(lineno)d %(funcName)s %(message)s", 13 | env="LOG_FORMAT", 14 | ) 15 | 16 | # Logging formatter to use (json, standard, colored) 17 | log_formatter: str = Field(default="colored") 18 | 19 | # In which directories to automatically suppress certain loggers 20 | suppress_loggers: list[str] = Field( 21 | default=["httpx", "urllib3", "asyncio", "aiokafka"], env="SUPPRESS_LOGGERS" 22 | ) 23 | 24 | # Default suppression level (only apply warnings and above for suppressed loggers) 25 | suppress_level: str = Field(default="WARNING") 26 | 27 | model_config = SettingsConfigDict( 28 | env_prefix="LOGGER_", env_file=".env", env_ignore_empty=True, extra="ignore" 29 | ) 30 | 31 | 32 | settings = Settings() 33 | -------------------------------------------------------------------------------- /demo/libraries/observability/tracing/__init__.py: -------------------------------------------------------------------------------- 1 | from .dspy import TracedChainOfThought as TracedChainOfThought 2 | from .dspy import TracedReAct as TracedReAct 3 | from .dspy import traced_dspy_function as traced_dspy_function 4 | from .otel import create_tracer as create_tracer 5 | from .otel import format_span_as_traceparent as format_span_as_traceparent 6 | from .otel import init_telemetry as init_telemetry 7 | from .otel import safe_set_attribute as safe_set_attribute 8 | from .otel import traced_handler as traced_handler 9 | from .schemas import GenAIAttributes as GenAIAttributes 10 | from .schemas import TracedMessage as TracedMessage 11 | -------------------------------------------------------------------------------- /demo/libraries/observability/tracing/config.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from pydantic import Field 3 | from pydantic_settings import BaseSettings, SettingsConfigDict 4 | 5 | # Load environment variables at module level 6 | load_dotenv() 7 | 8 | 9 | class Settings(BaseSettings): 10 | # OpenTelemetry settings 11 | service_namespace: str = Field(default="eggai") 12 | otel_endpoint: str = Field(default="http://localhost:4318") 13 | otel_endpoint_insecure: bool = Field(default=True) 14 | otel_exporter_otlp_protocol: str = Field(default="http/protobuf") 15 | tracing_enabled: bool = Field(default=True) 16 | 17 | # Instrumentation configuration 18 | disabled_instrumentors: list[str] = Field(default=["langchain"]) 19 | 20 | # Sampling rate (1.0 = 100% of traces are sampled) 21 | sampling_rate: float = Field(default=1.0) 22 | 23 | model_config = SettingsConfigDict( 24 | env_prefix="TRACING_", env_file=".env", env_ignore_empty=True, extra="ignore" 25 | ) 26 | 27 | 28 | settings = Settings() 29 | -------------------------------------------------------------------------------- /demo/libraries/observability/tracing/schemas.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from typing import Dict, Optional 3 | 4 | from eggai.schemas import Message 5 | from pydantic import BaseModel, Field 6 | 7 | 8 | # TODO make traceparent required and remove default 9 | class TracedMessage(Message): 10 | 11 | traceparent: Optional[str] = Field( 12 | default=None, 13 | description="W3C trace context traceparent header (version-traceID-spanID-flags)", 14 | ) 15 | tracestate: Optional[str] = Field( 16 | default=None, 17 | description="W3C trace context tracestate header with vendor-specific trace info", 18 | ) 19 | service_tier: Optional[str] = Field( 20 | default="standard", 21 | description="Service tier for gen_ai spans (standard, premium, etc.)", 22 | ) 23 | 24 | 25 | class GenAIAttributes(BaseModel): 26 | 27 | model_provider: str = Field(default="unknown") 28 | model_name: str = Field(default="unknown") 29 | request_id: str = Field(default_factory=lambda: str(uuid.uuid4())) 30 | response_id: str = Field(default_factory=lambda: str(uuid.uuid4())) 31 | service_tier: str = Field(default="standard") 32 | token_count: Optional[int] = None 33 | 34 | def to_span_attributes(self) -> Dict[str, str]: 35 | result = {} 36 | for key, value in self.model_dump().items(): 37 | if value is not None: 38 | # Ensure all values are strings if they're not already 39 | if not isinstance(value, str) and not isinstance(value, (list, bytes)): 40 | value = str(value) 41 | result[f"gen_ai.{key}"] = value 42 | return result 43 | -------------------------------------------------------------------------------- /demo/libraries/testing/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Testing utilities and test suites for EggAI libraries. 3 | 4 | This module provides test utilities and houses unit tests for library components. 5 | """ 6 | 7 | # Re-export commonly used test utilities 8 | from .utils import ( 9 | MLflowTracker, 10 | assert_valid_agent_response, 11 | create_conversation_string, 12 | create_message_list, 13 | create_mock_agent_response, 14 | create_mock_request_message, 15 | setup_mlflow_tracking, 16 | wait_for_agent_response, 17 | ) 18 | 19 | __all__ = [ 20 | "create_message_list", 21 | "create_conversation_string", 22 | "wait_for_agent_response", 23 | "MLflowTracker", 24 | "setup_mlflow_tracking", 25 | "create_mock_agent_response", 26 | "create_mock_request_message", 27 | "assert_valid_agent_response", 28 | ] -------------------------------------------------------------------------------- /demo/libraries/testing/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test package for libraries module. 3 | """ 4 | -------------------------------------------------------------------------------- /demo/libraries/testing/utils/helpers.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Any, Dict, Optional 3 | 4 | import mlflow 5 | 6 | 7 | class MLflowTracker: 8 | 9 | def __init__( 10 | self, 11 | experiment_name: str, 12 | run_name: Optional[str] = None, 13 | params: Optional[Dict[str, Any]] = None 14 | ): 15 | self.experiment_name = experiment_name 16 | self.run_name = run_name 17 | self.params = params 18 | 19 | def __enter__(self): 20 | mlflow.set_experiment(self.experiment_name) 21 | if self.run_name is None: 22 | self.run_name = ( 23 | f"test_run_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}" 24 | ) 25 | 26 | self.run = mlflow.start_run(run_name=self.run_name) 27 | 28 | if self.params: 29 | for key, value in self.params.items(): 30 | mlflow.log_param(key, value) 31 | 32 | return self.run 33 | 34 | def __exit__(self, exc_type, exc_val, exc_tb): 35 | mlflow.end_run() 36 | return False # Don't suppress exceptions 37 | 38 | 39 | def setup_mlflow_tracking( 40 | experiment_name: str, 41 | run_name: Optional[str] = None, 42 | params: Optional[Dict[str, Any]] = None 43 | ) -> MLflowTracker: 44 | return MLflowTracker(experiment_name, run_name, params) -------------------------------------------------------------------------------- /demo/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | # Same as Black. 3 | line-length = 88 4 | # Assume Python 3.11 5 | target-version = "py311" 6 | 7 | # Exclude a variety of commonly ignored directories. 8 | exclude = [ 9 | ".bzr", 10 | ".direnv", 11 | ".eggs", 12 | ".git", 13 | ".github", 14 | ".hg", 15 | ".mypy_cache", 16 | ".nox", 17 | ".pants.d", 18 | ".pytype", 19 | ".ruff_cache", 20 | ".svn", 21 | ".tox", 22 | ".venv", 23 | "__pypackages__", 24 | "_build", 25 | "buck-out", 26 | "build", 27 | "dist", 28 | "node_modules", 29 | "venv", 30 | ] 31 | 32 | [tool.ruff.lint] 33 | # Enable flake8-bugbear (`B`) rules. 34 | select = ["E", "F", "B", "I"] 35 | 36 | # Ignore specific errors that are hard to fix in an existing codebase 37 | ignore = [ 38 | "E501", # Line too long (handled by formatter) 39 | "B904", # Within an `except` clause, raise exceptions with `raise ... from err` 40 | "F841", # Local variable is assigned to but never used 41 | "B008", # Do not perform function call in argument defaults (FastAPI pattern) 42 | "E402", # Module level import not at top of file (common in DSPy modules) 43 | ] 44 | 45 | # Allow unused variables when underscore-prefixed. 46 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 47 | 48 | [tool.ruff.lint.mccabe] 49 | # Unlike Flake8, default to a complexity level of 10. 50 | max-complexity = 10 51 | 52 | [tool.ruff.lint.isort] 53 | known-third-party = ["dspy", "vespa", "mlflow", "eggai", "pytest", "opentelemetry"] -------------------------------------------------------------------------------- /demo/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | filterwarnings = ignore::DeprecationWarning 3 | asyncio_default_fixture_loop_scope = function 4 | 5 | [pytest-asyncio] 6 | asyncio_mode = auto 7 | -------------------------------------------------------------------------------- /demo/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.46 2 | python-dotenv==1.0.1 3 | fastapi==0.115.6 4 | dspy==2.6.24 5 | psutil==7.0.0 6 | websockets==14.1 7 | guardrails-ai==0.6.2 8 | pyvespa==0.56.0 9 | tenacity==9.0.0 10 | httpx==0.28.1 11 | requests==2.32.2 12 | uvicorn==0.34.0 13 | openlit==1.33.21 14 | opentelemetry-sdk==1.33.1 15 | python-json-logger==2.0.7 16 | colorlog==6.8.2 17 | pydantic-settings==2.4.0 18 | transformers[torch]>=4.51.3 19 | peft>=0.14.0 20 | datasets>=3.3.2 21 | accelerate>=1.4.0 22 | bitsandbytes>=0.42.0 23 | trl>=0.15.2 24 | numpy==2.2.4 25 | scikit-learn==1.6.1 26 | aioboto3==13.3.0 27 | mlflow==2.21.3 28 | matplotlib==3.10.1 29 | seaborn==0.13.2 30 | pandas==2.2.3 31 | sentence-transformers==4.0.2 32 | pytest-html==4.1.1 33 | jupyter==1.1.1 34 | aiofiles>=24.1.0 35 | asyncio==3.4.3 36 | litellm==1.70.2 37 | pydantic 38 | tqdm 39 | jinja2>=3.1.3 40 | sqlalchemy>=2.0.30 41 | psycopg2-binary>=2.9.9 42 | python-multipart>=0.0.9 43 | temporalio>=1.8.0 44 | ruff==0.11.11 45 | pytest-cov==6.1.1 46 | tabulate==0.9.0 47 | docling==2.37.0 -------------------------------------------------------------------------------- /demo/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=eggai-tech_EggAI 2 | sonar.organization=eggai-tech 3 | 4 | # Sources - specify directories with Python code 5 | sonar.sources=. 6 | 7 | # Exclude non-code files and test files from analysis 8 | sonar.exclusions=**/*.md,**/*.txt,**/*.json,**/*.yml,**/*.yaml,**/*.html,**/*.css,**/*.js,**/*.sh,**/docs/**,**/tests/**,**/*_test.py,**/test_*.py,**/shared_test_utils.py,**/__pycache__/**,**/.venv/**,**/venv/**,**/node_modules/**,**/.git/**,**/coverage/**,**/reports/**,**/dist/**,**/build/**,**/*.log 9 | 10 | # Test files (for test execution reporting) 11 | sonar.tests=. 12 | sonar.test.inclusions=**/tests/**,**/test_*.py,**/*_test.py,**/shared_test_utils.py 13 | 14 | # Coverage exclusions (exclude test files and non-code files from coverage) 15 | sonar.coverage.exclusions=**/tests/**,**/test_*.py,**/*_test.py,**/shared_test_utils.py,**/conftest.py,**/__init__.py,**/*.md,**/*.txt,**/*.json,**/*.yml,**/*.yaml,**/*.html,**/*.css,**/*.js,**/*.sh,**/docs/**,**/__pycache__/**,**/.venv/**,**/venv/**,**/setup.py 16 | 17 | # Python specific 18 | sonar.python.version=3.11 19 | 20 | # Coverage reporting 21 | sonar.python.coverage.reportPaths=coverage.xml 22 | 23 | # Test reporting 24 | sonar.python.xunit.reportPath=reports/pytest-results.xml 25 | 26 | # Language specification 27 | sonar.language=py 28 | 29 | # Source encoding 30 | sonar.sourceEncoding=UTF-8 31 | 32 | # Duplication exclusions (exclude generated or similar files) 33 | sonar.cpd.exclusions=**/tests/**,**/*_test.py,**/test_*.py,**/migrations/**,**/__init__.py,**/setup.py,**/agents/*/api_main.py,**/agents/*/main.py 34 | 35 | # Debug - log more information 36 | # sonar.verbose=true 37 | -------------------------------------------------------------------------------- /docs/docs/CNAME: -------------------------------------------------------------------------------- 1 | docs.egg-ai.com 2 | -------------------------------------------------------------------------------- /docs/docs/assets/agent-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/agent-1.jpeg -------------------------------------------------------------------------------- /docs/docs/assets/agent-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/agent-2.jpeg -------------------------------------------------------------------------------- /docs/docs/assets/agent-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/agent-3.jpeg -------------------------------------------------------------------------------- /docs/docs/assets/agent-4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/agent-4.jpeg -------------------------------------------------------------------------------- /docs/docs/assets/agent-5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/agent-5.jpeg -------------------------------------------------------------------------------- /docs/docs/assets/agent-evaluation-dspy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/agent-evaluation-dspy.png -------------------------------------------------------------------------------- /docs/docs/assets/avatar/audit-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/avatar/audit-agent.png -------------------------------------------------------------------------------- /docs/docs/assets/avatar/billing-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/avatar/billing-agent.png -------------------------------------------------------------------------------- /docs/docs/assets/avatar/claims-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/avatar/claims-agent.png -------------------------------------------------------------------------------- /docs/docs/assets/avatar/escalation-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/avatar/escalation-agent.png -------------------------------------------------------------------------------- /docs/docs/assets/avatar/frontend-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/avatar/frontend-agent.png -------------------------------------------------------------------------------- /docs/docs/assets/avatar/policies-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/avatar/policies-agent.png -------------------------------------------------------------------------------- /docs/docs/assets/avatar/triage-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/avatar/triage-agent.png -------------------------------------------------------------------------------- /docs/docs/assets/eggai-meta-framework-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/eggai-meta-framework-arch.png -------------------------------------------------------------------------------- /docs/docs/assets/example-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-00.png -------------------------------------------------------------------------------- /docs/docs/assets/example-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-01.png -------------------------------------------------------------------------------- /docs/docs/assets/example-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-02.png -------------------------------------------------------------------------------- /docs/docs/assets/example-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-03.png -------------------------------------------------------------------------------- /docs/docs/assets/example-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-04.png -------------------------------------------------------------------------------- /docs/docs/assets/example-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-05.png -------------------------------------------------------------------------------- /docs/docs/assets/example-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-06.png -------------------------------------------------------------------------------- /docs/docs/assets/example-07-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-07-chat.png -------------------------------------------------------------------------------- /docs/docs/assets/example-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-07.png -------------------------------------------------------------------------------- /docs/docs/assets/example-mcp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/example-mcp.png -------------------------------------------------------------------------------- /docs/docs/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/favicon.png -------------------------------------------------------------------------------- /docs/docs/assets/interoperability.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/interoperability.png -------------------------------------------------------------------------------- /docs/docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/docs/assets/multi-agent-human-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/multi-agent-human-chat.png -------------------------------------------------------------------------------- /docs/docs/assets/react-agent-dspy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/react-agent-dspy.png -------------------------------------------------------------------------------- /docs/docs/assets/redpanda-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/redpanda-console.png -------------------------------------------------------------------------------- /docs/docs/assets/safe-agents-guardrails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/safe-agents-guardrails.png -------------------------------------------------------------------------------- /docs/docs/assets/support-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/support-chat.png -------------------------------------------------------------------------------- /docs/docs/assets/test_tsne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/test_tsne.png -------------------------------------------------------------------------------- /docs/docs/assets/train_tsne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/train_tsne.png -------------------------------------------------------------------------------- /docs/docs/assets/triage-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/triage-agent.png -------------------------------------------------------------------------------- /docs/docs/assets/triage-custom-classifier-training.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/triage-custom-classifier-training.png -------------------------------------------------------------------------------- /docs/docs/assets/triage-evaluation-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/docs/docs/assets/triage-evaluation-report.png -------------------------------------------------------------------------------- /docs/docs/concepts/agent.md: -------------------------------------------------------------------------------- 1 | # Agent 2 | 3 | An **agent** is an actor within a specific environment (e.g., software system, workflow, data pipeline, or service). Unlike static programs, agents follow clear goals, roles, or objectives. They: 4 | 5 | - **Perceive:** Gather and interpret data from their environment. 6 | - **Reason:** Use algorithms, rules, or models to choose the best action. 7 | - **Act:** Execute decisions to achieve desired outcomes and improve processes. 8 | 9 | ## Fundamental Characteristics 10 | 11 | 1. **Autonomy** 12 | Agents operate without constant human input. They use policies, models, and rules to adapt to changing conditions. 13 | 14 | 2. **Perception of the Environment** 15 | Agents integrate with data sources, APIs, and services to maintain awareness. They monitor key indicators (e.g., customer behavior, inventory, system performance) and respond in real time. 16 | 17 | 3. **Decision-Making and Reasoning** 18 | Agents apply AI methods—rules to machine learning—to evaluate actions. Their choices align with defined objectives, regulations, and business logic. 19 | 20 | 4. **Action and Influence** 21 | After deciding, agents take actions that can adjust resources, optimize operations, or make recommendations. These actions deliver tangible business value. 22 | 23 | 5. **Goal-Directed Behavior** 24 | Each agent works toward specific outcomes, such as boosting efficiency, raising customer satisfaction, cutting costs, or driving innovation. Clear goals guide their actions. 25 | 26 | 6. **(Often) Learning and Adaptation** 27 | Agents often improve over time. They learn from historical data, feedback, and performance metrics to refine their strategies and better meet evolving demands. -------------------------------------------------------------------------------- /docs/docs/concepts/multi-agent-system.md: -------------------------------------------------------------------------------- 1 | # Multi-Agent System 2 | 3 | A **multi-agent system** involves multiple agents working within the same environment. These agents interact, cooperate, or compete, collectively shaping the system’s behavior. Unlike single-agent systems, where a single entity makes all decisions and actions, multi-agent systems distribute decision-making across many autonomous agents. 4 | 5 | ## Fundamental Characteristics of Multi-Agent Systems 6 | 7 | 1. **Distributed Control** 8 | Decision-making and control are not centralized. Each agent operates autonomously, contributing to system-level goals. 9 | 10 | 2. **Interaction and Communication** 11 | Agents share information, negotiate tasks, and coordinate. This communication makes the system more responsive and adaptive. 12 | 13 | 3. **Cooperation and Competition** 14 | Agents may collaborate to achieve common objectives or compete over limited resources. Their relationships can shift as conditions evolve. 15 | 16 | 4. **Scalability and Flexibility** 17 | Multi-agent systems can grow or shrink by adding or removing agents. Their modular structure supports resilience and easier maintenance. 18 | 19 | 5. **Emergent Behavior** 20 | Complex global patterns can arise from local interactions, often resulting in innovative solutions and adaptive strategies. 21 | 22 | 6. **Heterogeneity** 23 | Agents may differ in capabilities, goals, and decision-making methods. Diversity among agents increases the system’s robustness and problem-solving potential. 24 | -------------------------------------------------------------------------------- /docs/docs/examples/agent_evaluation_dspy.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/agent_evaluation_dspy/README.md" %} -------------------------------------------------------------------------------- /docs/docs/examples/coordinator.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/coordinator/README.md" %} -------------------------------------------------------------------------------- /docs/docs/examples/dspy_react.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/dspy_react/README.md" %} -------------------------------------------------------------------------------- /docs/docs/examples/getting_started.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/getting_started/README.md" %} -------------------------------------------------------------------------------- /docs/docs/examples/langchain_tool_calling.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/langchain_tool_calling/README.md" %} -------------------------------------------------------------------------------- /docs/docs/examples/litellm_agent.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/litellm_agent/README.md" %} -------------------------------------------------------------------------------- /docs/docs/examples/mcp.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/mcp/README.md" %} 2 | -------------------------------------------------------------------------------- /docs/docs/examples/multi_agent_conversation.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/multi_agent_conversation/README.md" %} -------------------------------------------------------------------------------- /docs/docs/examples/safe_agents_guardrails.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/safe_agents_guardrails/README.md" %} 2 | -------------------------------------------------------------------------------- /docs/docs/examples/shared_context.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/shared_context/README.md" %} -------------------------------------------------------------------------------- /docs/docs/examples/triage_agent.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/triage_agent/README.md" %} -------------------------------------------------------------------------------- /docs/docs/examples/websocket_gateway.md: -------------------------------------------------------------------------------- 1 | {% include-markdown "../../../examples/websocket_gateway/README.md" %} 2 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | EggAI word and figuremark 2 | 3 | # Multi-Agent Meta Framework 4 | 5 | {% include-markdown "../../README.md" %} 6 | -------------------------------------------------------------------------------- /docs/docs/sdk/agent.md: -------------------------------------------------------------------------------- 1 | # Agent 2 | ::: eggai.agent.Agent -------------------------------------------------------------------------------- /docs/docs/sdk/channel.md: -------------------------------------------------------------------------------- 1 | # Channel 2 | ::: eggai.channel.Channel -------------------------------------------------------------------------------- /docs/docs/sdk/inmemory-transport.md: -------------------------------------------------------------------------------- 1 | # InMemoryTransport 2 | ::: eggai.transport.InMemoryTransport -------------------------------------------------------------------------------- /docs/docs/sdk/kafka-transport.md: -------------------------------------------------------------------------------- 1 | # KafkaTransport 2 | ::: eggai.transport.KafkaTransport -------------------------------------------------------------------------------- /docs/docs/sdk/message.md: -------------------------------------------------------------------------------- 1 | # Message 2 | ::: eggai.schemas.BaseMessage -------------------------------------------------------------------------------- /docs/docs/sdk/transport.md: -------------------------------------------------------------------------------- 1 | # Transport 2 | ::: eggai.transport.Transport -------------------------------------------------------------------------------- /docs/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "eggai-docs" 3 | version = "0.99" 4 | description = "EggAI Documentation" 5 | authors = ["Stefano Tucci "] 6 | license = "MIT" 7 | homepage = "https://eggai-tech.github.io/EggAI/" 8 | package-mode = false 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.10" 12 | 13 | [tool.poetry.group.dev.dependencies] 14 | mkdocs = "^1.6.1" 15 | mkdocstrings = {extras = ["python"], version = "^0.27.0"} 16 | mkdocs-material = "^9.5.48" 17 | mkdocs-include-markdown-plugin = "^7.1.2" 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | -------------------------------------------------------------------------------- /examples/a2a/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/a2a/__init__.py -------------------------------------------------------------------------------- /examples/a2a/requirements.txt: -------------------------------------------------------------------------------- 1 | -e ../../sdk 2 | a2a-sdk 3 | httpx 4 | pydantic 5 | uvicorn -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/agent_evaluation_dspy/__init__.py -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/datasets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/agent_evaluation_dspy/datasets/__init__.py -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/datasets/loader.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def load_dataset(file: str) -> list: 4 | csv_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), file + ".csv")) 5 | 6 | with open(csv_file_path, "r") as file: 7 | data = file.readlines() 8 | return [ 9 | { 10 | "conversation": line.strip().split(",")[0].replace('"', "").replace("\\n", "\n"), 11 | "target": line.strip().split(",")[1].replace('"', "").replace("\\n", "\n") 12 | } 13 | for line in data[1:] 14 | ] 15 | 16 | if __name__ == "__main__": 17 | print(load_dataset("triage-testing")) -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | dspy-ai==2.5.43 3 | python-dotenv==1.0.1 4 | pytest==8.3.4 5 | pytest-asyncio==0.25.2 -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/agent_evaluation_dspy/src/__init__.py -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/src/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/agent_evaluation_dspy/src/agents/__init__.py -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/src/agents/triage.py: -------------------------------------------------------------------------------- 1 | from eggai import Channel, Agent 2 | from ..dspy_modules.classifier_v3 import load 3 | 4 | human_channel = Channel("human") 5 | agents_channel = Channel("agents") 6 | 7 | triage_agent = Agent("TriageAgent") 8 | 9 | 10 | @triage_agent.subscribe( 11 | channel=human_channel, filter_func=lambda msg: msg["type"] == "user_message" 12 | ) 13 | async def handle_user_message(msg): 14 | """ 15 | Handles user messages and routes them to the appropriate target agent. 16 | """ 17 | try: 18 | payload = msg["payload"] 19 | chat_messages = payload.get("chat_messages", "") 20 | classifier = load() 21 | response = classifier(chat_history=chat_messages) 22 | target_agent = response.target_agent 23 | 24 | await agents_channel.publish({"target": target_agent, "payload": payload}) 25 | except Exception as e: 26 | print("Error in Triage Agent: ", e) 27 | -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/src/dspy_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/agent_evaluation_dspy/src/dspy_modules/__init__.py -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/src/dspy_modules/classifier_v1.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | import dspy 4 | from dotenv import load_dotenv 5 | 6 | from .lm import language_model 7 | from .utils import run_and_calculate_costs 8 | 9 | TargetAgent = Literal["PolicyAgent", "TicketingAgent", "TriageAgent"] 10 | 11 | dspy.configure(lm=language_model) 12 | 13 | 14 | class AgentClassificationSignature(dspy.Signature): 15 | chat_history: str = dspy.InputField() 16 | target_agent: TargetAgent = dspy.OutputField() 17 | confidence: float = dspy.OutputField() 18 | 19 | 20 | classifier = dspy.ChainOfThought(signature=AgentClassificationSignature) 21 | 22 | if __name__ == "__main__": 23 | load_dotenv() 24 | run_and_calculate_costs( 25 | classifier, 26 | chat_history="User: I need help with my policy." 27 | ) 28 | -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/src/dspy_modules/classifier_v3.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Literal 3 | 4 | import dspy 5 | from dotenv import load_dotenv 6 | 7 | from .classifier_v2 import AgentClassificationSignature, classifier as classifier_v2 8 | from .lm import language_model 9 | from .utils import run_and_calculate_costs 10 | 11 | TargetAgent = Literal["PolicyAgent", "TicketingAgent", "TriageAgent"] 12 | 13 | dspy.configure(lm=language_model) 14 | 15 | classifier_v3_json_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "optimizations_v3.json")) 16 | 17 | 18 | def load(): 19 | classifier = dspy.ChainOfThought(signature=AgentClassificationSignature) 20 | classifier.load(classifier_v3_json_path) 21 | return classifier 22 | 23 | 24 | def optimize(training_data_set, overwrite=False): 25 | if os.path.exists(classifier_v3_json_path) and overwrite is False: 26 | return 27 | teleprompter = dspy.BootstrapFewShot( 28 | metric=lambda example, pred, trace=None: example.target_agent.lower() == pred.target_agent.lower(), 29 | max_labeled_demos=22, 30 | max_bootstrapped_demos=22, 31 | max_rounds=10, 32 | max_errors=20 33 | ) 34 | optimized_program = teleprompter.compile(classifier_v2, trainset=training_data_set) 35 | optimized_program.save(classifier_v3_json_path) 36 | 37 | 38 | if __name__ == "__main__": 39 | load_dotenv() 40 | classifier = load() 41 | run_and_calculate_costs( 42 | classifier, 43 | chat_history="User: I need help with my policy." 44 | ) 45 | -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/src/dspy_modules/lm.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | 3 | language_model = dspy.LM("openai/gpt-4o-mini", cache=False) 4 | -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/src/dspy_modules/utils.py: -------------------------------------------------------------------------------- 1 | from .lm import language_model 2 | 3 | 4 | def run_and_calculate_costs(program, chat_history): 5 | program(chat_history=chat_history) 6 | last_history = language_model.history[-1] 7 | cost = last_history['cost'] 8 | if cost: 9 | print(f"Cost: {cost:.10f}$") 10 | print(f"Run it {1 / cost:.0f} times to reach one dollar.") 11 | else: 12 | print("No cost. (cached)") 13 | -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/agent_evaluation_dspy/tests/__init__.py -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/tests/test_classifier_v1.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | 7 | from ..src.dspy_modules.classifier_v1 import classifier as classifier_v1 8 | from .utilities import load_data, run_evaluation, generate_report 9 | 10 | 11 | @pytest.mark.asyncio 12 | def test_classifier_v1(): 13 | load_dotenv() 14 | test_dataset = load_data("triage-testing") 15 | score_v1, results_v1 = run_evaluation(classifier_v1, test_dataset) 16 | generate_report(results_v1, "classifier_v1") 17 | -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/tests/test_classifier_v2.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from ..src.dspy_modules.classifier_v2 import classifier as classifier_v2 7 | from .utilities import load_data, run_evaluation, generate_report 8 | 9 | 10 | @pytest.mark.asyncio 11 | def test_classifier_v2(): 12 | load_dotenv() 13 | test_dataset = load_data("triage-testing") 14 | score_v2, results_v2 = run_evaluation(classifier_v2, test_dataset) 15 | generate_report(results_v2, "classifier_v2") 16 | -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/tests/test_classifier_v3.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from ..src.dspy_modules.classifier_v3 import optimize, load 7 | from .utilities import load_data, run_evaluation, generate_report 8 | 9 | 10 | @pytest.mark.asyncio 11 | def test_classifier_v3(): 12 | load_dotenv() 13 | test_dataset = load_data("triage-testing") 14 | training_dataset = load_data("triage-training") 15 | REWRITE_OPTIMIZATION = False 16 | optimize(training_dataset, overwrite=REWRITE_OPTIMIZATION) 17 | classifier_v3 = load() 18 | score_v3, results_v3 = run_evaluation(classifier_v3, test_dataset) 19 | generate_report(results_v3, "classifier_v3") 20 | -------------------------------------------------------------------------------- /examples/agent_evaluation_dspy/tests/test_triage.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import AsyncMock 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from eggai import Channel 7 | 8 | from ..src.agents.triage import handle_user_message 9 | from ..datasets.loader import load_dataset 10 | 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_triage_agent(monkeypatch): 15 | load_dotenv() 16 | 17 | dataset = load_dataset("triage-testing") 18 | 19 | total = len(dataset) 20 | success = 0 21 | 22 | for entry in dataset: 23 | conversation = entry["conversation"] 24 | expected_target = entry["target"] 25 | test_event = { 26 | "type": "user_message", 27 | "payload": {"chat_messages": conversation}, 28 | } 29 | mock_publish = AsyncMock() 30 | monkeypatch.setattr(Channel, "publish", mock_publish) 31 | await handle_user_message(test_event) 32 | args, kwargs = mock_publish.call_args_list[0] 33 | actual_target = args[0].get("target") 34 | if actual_target == expected_target: 35 | success += 1 36 | 37 | success_percentage = (success / total) * 100 38 | assert (success_percentage > 90), f"Success rate {success_percentage:.2f}% is not greater than 90%." 39 | -------------------------------------------------------------------------------- /examples/coordinator/coordinator.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent, Channel 2 | 3 | agent = Agent("Coordinator") 4 | 5 | agents_channel = Channel() 6 | human_channel = Channel("human") 7 | 8 | 9 | @agent.subscribe(filter_func=lambda message: "human" in message and message["human"] == True) 10 | async def forward_agent_to_human(message): 11 | print("[COORDINATOR]: human=true message received. Forwarding to human channel.") 12 | await human_channel.publish(message) 13 | 14 | 15 | @agent.subscribe(channel=human_channel, filter_func=lambda message: "action" in message) 16 | async def forward_human_to_agents(message): 17 | print("[COORDINATOR]: action message received. Forwarding to agents channel.") 18 | if message["action"] == "create_order": 19 | await agents_channel.publish({"event_name": "order_requested", "payload": message.get("payload")}) 20 | 21 | 22 | @agent.subscribe(channel=human_channel, filter_func=lambda message: message["event_name"] == "notification") 23 | async def handle_notifications(message): 24 | print("[COORDINATOR]: Received notification for human:", message["payload"]["message"]) 25 | -------------------------------------------------------------------------------- /examples/coordinator/email_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent, Channel 2 | 3 | agent = Agent("EmailAgent") 4 | channel = Channel() 5 | 6 | 7 | @agent.subscribe(filter_func=lambda event: event["event_name"] == "order_created") 8 | async def send_email(message): 9 | print(f"[EMAIL AGENT]: order_created event received. Sending email to customer.") 10 | 11 | 12 | @agent.subscribe(filter_func=lambda event: event["event_name"] == "order_created") 13 | async def send_notification(message): 14 | print(f"[EMAIL AGENT]: order_created event received. Sending notification event.") 15 | await channel.publish({"human": True, "event_name": "notification", 16 | "payload": {"message": "Order created, you will receive an email soon."}}) 17 | -------------------------------------------------------------------------------- /examples/coordinator/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai import Channel 4 | 5 | from coordinator import agent as coordinator 6 | from email_agent import agent as email_agent 7 | from order_agent import agent as order_agent 8 | 9 | 10 | async def main(): 11 | human_channel = Channel("human") 12 | 13 | await coordinator.run() 14 | await order_agent.run() 15 | await email_agent.run() 16 | 17 | await human_channel.publish({ 18 | "action": "create_order", 19 | "payload": { 20 | "product": "Laptop", "quantity": 1 21 | } 22 | }) 23 | 24 | try: 25 | print("Agent is running. Press Ctrl+C to stop.") 26 | await asyncio.Event().wait() 27 | except asyncio.exceptions.CancelledError: 28 | print("Task was cancelled. Cleaning up...") 29 | finally: 30 | await order_agent.stop() 31 | await email_agent.stop() 32 | await coordinator.stop() 33 | await Channel.stop() 34 | 35 | print("Done.") 36 | 37 | 38 | if __name__ == "__main__": 39 | asyncio.run(main()) 40 | -------------------------------------------------------------------------------- /examples/coordinator/order_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent, Channel 2 | 3 | agent = Agent("OrderAgent") 4 | channel = Channel() 5 | 6 | 7 | @agent.subscribe(filter_func=lambda event: event["event_name"] == "order_requested") 8 | async def create_order(event): 9 | print(f"[ORDER AGENT]: order_requested event received. Emitting order_created event.") 10 | await channel.publish({"event_name": "order_created", "payload": event.get("payload")}) 11 | 12 | 13 | @agent.subscribe(filter_func=lambda event: event["event_name"] == "order_created") 14 | async def order_processing(message): 15 | print(f"[ORDER AGENT]: order_created event received.") 16 | -------------------------------------------------------------------------------- /examples/coordinator/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | -------------------------------------------------------------------------------- /examples/dspy_react/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /examples/dspy_react/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | dspy-ai==2.5.43 3 | python-dotenv==1.0.1 4 | pytest==8.3.4 5 | pytest-asyncio==0.25.2 6 | -------------------------------------------------------------------------------- /examples/dspy_react/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/dspy_react/src/__init__.py -------------------------------------------------------------------------------- /examples/dspy_react/src/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/dspy_react/src/agents/__init__.py -------------------------------------------------------------------------------- /examples/dspy_react/src/agents/react_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Channel, Agent 2 | 3 | from src.dspy_modules.react_module import react_module 4 | 5 | agents_channel = Channel("agents") 6 | 7 | react_agent = Agent("AnswersAgent") 8 | 9 | 10 | @react_agent.subscribe( 11 | channel=agents_channel, filter_func=lambda msg: msg["type"] == "question" 12 | ) 13 | async def handle_question(msg): 14 | question = msg["payload"] 15 | 16 | prediction = react_module(question=question) 17 | answer = prediction.answer 18 | 19 | print("Question:", question) 20 | print("Answer:", answer) 21 | await agents_channel.publish({ 22 | "type": "answer", 23 | "payload": { 24 | "question": question, 25 | "answer": answer 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /examples/dspy_react/src/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai import Channel 4 | from agents.react_agent import react_agent 5 | 6 | agents_channel = Channel("agents") 7 | 8 | 9 | async def main(): 10 | await react_agent.run() 11 | await agents_channel.publish({ 12 | "type": "question", 13 | "payload": "Give me the year of construction of the Eiffel Tower summed with the year of construction of the Empire State Building." 14 | }) 15 | # it should be 3818 16 | 17 | try: 18 | print("Agent is running. Press Ctrl+C to stop.") 19 | await asyncio.Event().wait() 20 | except asyncio.exceptions.CancelledError: 21 | print("Task was cancelled. Cleaning up...") 22 | finally: 23 | await react_agent.stop() 24 | await Channel.stop() 25 | 26 | 27 | if __name__ == "__main__": 28 | try: 29 | asyncio.run(main()) 30 | except KeyboardInterrupt: 31 | print("Exiting...") 32 | -------------------------------------------------------------------------------- /examples/dspy_react/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/dspy_react/tests/__init__.py -------------------------------------------------------------------------------- /examples/dspy_react/tests/test_react_module.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from src.dspy_modules.react_module import react_module, transactions_list 4 | 5 | EIFFEL_TOWER = 1889 6 | EMPIRE_STATE_BUILDING = 1931 7 | 8 | 9 | @pytest.mark.asyncio 10 | async def test_eiffel_plus_empire(): 11 | pred = react_module( 12 | question="Give me the year of construction of the Eiffel Tower summed with the year of construction of the Empire State Building.") 13 | assert pred.numeric_answer == EIFFEL_TOWER + EMPIRE_STATE_BUILDING 14 | 15 | 16 | @pytest.mark.asyncio 17 | async def test_handle_balance(): 18 | pred = react_module(question="What is my balance until 2022-01-03 included?", k=3) 19 | 20 | expected_balance = 0 21 | for transaction in transactions_list: 22 | if transaction["date"] <= "2022-01-03": 23 | expected_balance += transaction["amount"] if transaction["type"] == "credit" else -transaction["amount"] 24 | 25 | assert pred.numeric_answer == expected_balance 26 | 27 | 28 | @pytest.mark.asyncio 29 | async def test_handle_balance_plus_eiffel(): 30 | pred = react_module( 31 | question="What is my balance until 2022-01-03 included summed with the year of construction of the Eiffel Tower?") 32 | expected_balance = 0 33 | for transaction in transactions_list: 34 | if transaction["date"] <= "2022-01-03": 35 | expected_balance += transaction["amount"] if transaction["type"] == "credit" else -transaction["amount"] 36 | 37 | assert pred.numeric_answer == expected_balance + EIFFEL_TOWER 38 | -------------------------------------------------------------------------------- /examples/getting_started/email_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent 2 | 3 | agent = Agent("EmailAgent") 4 | 5 | @agent.subscribe(filter_by_message=lambda event: event.get("type") == "order_created") 6 | async def send_email(msg): 7 | print(f"[EMAIL AGENT]: Received order created event. {msg.get('type')} {msg.get('data')}") -------------------------------------------------------------------------------- /examples/getting_started/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai import Channel, eggai_cleanup 4 | from eggai.schemas import Message 5 | 6 | from email_agent import agent as email_agent 7 | from order_agent import agent as order_agent 8 | 9 | 10 | async def main(): 11 | channel = Channel() 12 | 13 | await order_agent.start() 14 | await email_agent.start() 15 | 16 | await channel.publish(Message( 17 | type="order_requested", 18 | source="main", 19 | data={"product": "Laptop", "quantity": 1} 20 | )) 21 | 22 | try: 23 | await asyncio.Event().wait() 24 | except (KeyboardInterrupt, asyncio.CancelledError): 25 | pass 26 | 27 | await eggai_cleanup() 28 | 29 | 30 | if __name__ == "__main__": 31 | asyncio.run(main()) 32 | -------------------------------------------------------------------------------- /examples/getting_started/order_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent, Channel 2 | from eggai.schemas import Message 3 | 4 | agent = Agent("OrderAgent") 5 | channel = Channel() 6 | 7 | 8 | @agent.subscribe(filter_by_message=lambda event: event.get("type") == "order_requested") 9 | async def create_order(msg): 10 | print(f"[ORDER AGENT]: Received request to create order. {msg.get('type')} {msg.get('data')}") 11 | await channel.publish(Message(type="order_created", source="main", data=msg.get("data"))) 12 | 13 | 14 | @agent.subscribe(filter_by_message=lambda event: event.get("type") == "order_created") 15 | async def order_processing(msg): 16 | print(f"[ORDER AGENT]: Received order created event. {msg.get('type')} {msg.get('data')}") 17 | -------------------------------------------------------------------------------- /examples/getting_started/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.43 -------------------------------------------------------------------------------- /examples/langchain_tool_calling/email_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent 2 | from typing import Dict, List 3 | from langchain_core.messages import AIMessage 4 | from langchain_core.tools import tool 5 | from langchain_openai import ChatOpenAI 6 | 7 | llm = ChatOpenAI(model="gpt-3.5-turbo-0125") 8 | 9 | agent = Agent("EmailAgent") 10 | 11 | @tool 12 | def count_emails(last_n_days: int) -> int: 13 | """Multiply two integers together.""" 14 | return last_n_days * 2 15 | 16 | tools = [count_emails] 17 | llm_with_tools = llm.bind_tools(tools) 18 | 19 | def call_tools(msg: AIMessage) -> List[Dict]: 20 | """Simple sequential tool calling helper.""" 21 | tool_map = {tool.name: tool for tool in tools} 22 | tool_calls = msg.tool_calls.copy() 23 | for tool_call in tool_calls: 24 | tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"]) 25 | return tool_calls 26 | 27 | 28 | chain = llm_with_tools | call_tools 29 | 30 | @agent.subscribe(filter_func=lambda event: event["event_name"] == "email_prompt_requested") 31 | async def send_email(message): 32 | prompt = message.get("payload", {}).get("prompt") 33 | result = chain.invoke(prompt) 34 | print(f"[EMAIL AGENT Result]: {result}") -------------------------------------------------------------------------------- /examples/langchain_tool_calling/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai import Channel 4 | from email_agent import agent as email_agent 5 | 6 | 7 | async def main(): 8 | channel = Channel() 9 | 10 | await email_agent.run() 11 | 12 | await channel.publish({ 13 | "event_name": "email_prompt_requested", 14 | "payload": { 15 | "prompt": "how many emails did i get in the last 5 days?" 16 | } 17 | }) 18 | 19 | try: 20 | print("Agent is running. Press Ctrl+C to stop.") 21 | await asyncio.Event().wait() 22 | except asyncio.exceptions.CancelledError: 23 | print("Task was cancelled. Cleaning up...") 24 | finally: 25 | await email_agent.stop() 26 | await channel.stop() 27 | 28 | print("Done.") 29 | 30 | 31 | if __name__ == "__main__": 32 | asyncio.run(main()) 33 | -------------------------------------------------------------------------------- /examples/langchain_tool_calling/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | langchain-core==0.3.29 3 | langchain-openai==0.2.14 -------------------------------------------------------------------------------- /examples/litellm_agent/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai import Channel 4 | from escalate_agent import escalation_agent 5 | from support_agent import support_agent 6 | 7 | agents_channel = Channel("agents") 8 | humans_channel = Channel("humans") 9 | 10 | 11 | async def main(): 12 | await support_agent.run() 13 | await escalation_agent.run() 14 | 15 | # Simulate a customer inquiry that SupportAgent can handle 16 | await humans_channel.publish({ 17 | "type": "customer_inquiry", 18 | "payload": "What is your return policy?" 19 | }) 20 | 21 | # # Simulate a customer inquiry that requires escalation 22 | await humans_channel.publish({ 23 | "type": "customer_inquiry", 24 | "payload": "I have a billing issue that isn't resolved yet." 25 | }) 26 | 27 | try: 28 | print("Agent is running. Press Ctrl+C to stop.") 29 | await asyncio.Event().wait() 30 | except asyncio.exceptions.CancelledError: 31 | print("Task was cancelled. Cleaning up...") 32 | finally: 33 | await support_agent.stop() 34 | await escalation_agent.stop() 35 | await Channel.stop() 36 | 37 | 38 | if __name__ == "__main__": 39 | try: 40 | asyncio.run(main()) 41 | except KeyboardInterrupt: 42 | print("Exiting...") 43 | -------------------------------------------------------------------------------- /examples/litellm_agent/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | litellm==1.57.5 -------------------------------------------------------------------------------- /examples/mcp/.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=your_openai_api_key_here 2 | -------------------------------------------------------------------------------- /examples/mcp/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: server console docker-up 2 | 3 | server: 4 | make -j3 agent adapter mcp_backend 5 | 6 | agent: 7 | python start_ticketing_agent.py 8 | 9 | adapter: 10 | python start_ticketing_adapter.py 11 | 12 | mcp_backend: 13 | python start_ticketing_backend.py 14 | 15 | console: 16 | python start_console.py 17 | 18 | docker-up: 19 | docker-compose up -d -------------------------------------------------------------------------------- /examples/mcp/docs/mcp-example-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/mcp/docs/mcp-example-architecture.png -------------------------------------------------------------------------------- /examples/mcp/eggai_adapter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/mcp/eggai_adapter/__init__.py -------------------------------------------------------------------------------- /examples/mcp/eggai_adapter/dspy.py: -------------------------------------------------------------------------------- 1 | from eggai_adapter.client import EggaiAdapterClient 2 | from eggai_adapter.types import ExternalTool 3 | 4 | try: 5 | import dspy 6 | 7 | def convert_tool_for_dspy(adapter_client: EggaiAdapterClient, tool: ExternalTool) -> dspy.Tool: 8 | """Return a callable for the tool.""" 9 | async def tool_func(**kwargs): 10 | result = await adapter_client.call_tool(tool.name, kwargs) 11 | if result.is_error: 12 | return f"Error calling tool {tool.name}: {result.data}" 13 | return result.data 14 | 15 | return dspy.Tool( 16 | name=tool.name, 17 | func=tool_func, 18 | desc=tool.description, 19 | args=tool.parameters, 20 | ) 21 | except ImportError: 22 | pass -------------------------------------------------------------------------------- /examples/mcp/eggai_adapter/types.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, List, Any 2 | from uuid import UUID 3 | 4 | from eggai.schemas import BaseMessage 5 | from openai import BaseModel 6 | 7 | 8 | class ExternalTool(BaseModel): 9 | name: str 10 | description: str 11 | parameters: dict = {} 12 | return_type: dict = {} 13 | 14 | 15 | class ToolListRequest(BaseModel): 16 | call_id: UUID 17 | adapter_name: str 18 | 19 | 20 | class ToolListRequestMessage(BaseMessage[ToolListRequest]): 21 | type: Literal["ToolListRequestMessage"] = "ToolListRequestMessage" 22 | 23 | 24 | class ToolListResponse(BaseModel): 25 | call_id: UUID 26 | tools: List[ExternalTool] 27 | 28 | 29 | class ToolListResponseMessage(BaseMessage[ToolListResponse]): 30 | type: Literal["ToolListResponseMessage"] = "ToolListResponseMessage" 31 | 32 | 33 | class ToolCallRequest(BaseModel): 34 | call_id: UUID 35 | tool_name: str 36 | parameters: dict = {} 37 | 38 | 39 | class ToolCallRequestMessage(BaseMessage[ToolCallRequest]): 40 | type: Literal["ToolCallRequestMessage"] = "ToolCallRequestMessage" 41 | 42 | 43 | class ToolCallResponse(BaseModel): 44 | call_id: UUID 45 | tool_name: str 46 | data: Any = None 47 | is_error: bool = False 48 | 49 | 50 | class ToolCallResponseMessage(BaseMessage[ToolCallResponse]): 51 | type: Literal["ToolCallResponseMessage"] = "ToolCallResponseMessage" 52 | -------------------------------------------------------------------------------- /examples/mcp/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.47 2 | python-dotenv==1.1.1 3 | mcp==1.12.1 4 | dspy-ai>=2.6.27 5 | aioconsole==0.8.1 6 | fastmcp==2.10.6 -------------------------------------------------------------------------------- /examples/mcp/schemas.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from openai import BaseModel 4 | 5 | 6 | class TicketStatus(Enum): 7 | OPEN = "open" 8 | IN_PROGRESS = "in_progress" 9 | CLOSED = "closed" 10 | 11 | 12 | class Ticket(BaseModel): 13 | id: int 14 | description: str 15 | status: TicketStatus 16 | 17 | 18 | TICKET_ADAPTER_NAME = "SupportTicketSystemAdapter" -------------------------------------------------------------------------------- /examples/mcp/start_console.py: -------------------------------------------------------------------------------- 1 | import aioconsole 2 | import dotenv 3 | from eggai import Channel, eggai_main 4 | from eggai.schemas import Message 5 | from eggai.transport import eggai_set_default_transport, KafkaTransport 6 | 7 | dotenv.load_dotenv() 8 | eggai_set_default_transport(lambda: KafkaTransport()) 9 | 10 | @eggai_main 11 | async def console_loop(): 12 | response_received = asyncio.Event() 13 | response_received.set() 14 | 15 | async def handle_assistant_response(message: Message): 16 | """Handle responses from the assistant.""" 17 | if message.type == "agent_response": 18 | await aioconsole.aprint(f"\n🤖 \033[92mAssistant:\033[0m ", end="") 19 | await aioconsole.aprint(f"{message.data.get('assistant_response')}") 20 | response_received.set() 21 | 22 | await Channel("human.out").subscribe(handle_assistant_response) 23 | 24 | while True: 25 | try: 26 | await response_received.wait() 27 | # print emoji and cyan color for user input 28 | command = await aioconsole.ainput( 29 | "\n💬 \033[96mUser:\033[0m " 30 | ) 31 | if command.lower() == "exit": 32 | break 33 | except (EOFError, KeyboardInterrupt): 34 | break 35 | 36 | await Channel("human.in").publish(Message( 37 | type="user_input", 38 | source="console", 39 | data={"user_message": command} 40 | )) 41 | 42 | response_received.clear() 43 | 44 | 45 | 46 | if __name__ == "__main__": 47 | import asyncio 48 | try: 49 | asyncio.run(console_loop()) 50 | except KeyboardInterrupt: 51 | print("\nExiting console...", flush=True) -------------------------------------------------------------------------------- /examples/mcp/start_ticketing_adapter.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai.transport import eggai_set_default_transport, KafkaTransport 4 | import dotenv 5 | 6 | from eggai_adapter.mcp import run_mcp_adapter 7 | from schemas import TICKET_ADAPTER_NAME 8 | 9 | dotenv.load_dotenv() 10 | 11 | if __name__ == "__main__": 12 | eggai_set_default_transport(lambda: KafkaTransport()) 13 | asyncio.run( 14 | run_mcp_adapter(TICKET_ADAPTER_NAME, "http://localhost:8000/sse") 15 | ) -------------------------------------------------------------------------------- /examples/multi_agent_conversation/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai import Channel 4 | from claims import claims_agent 5 | from escalation import escalation_agent 6 | from policy import policy_agent 7 | from triage import triage_agent 8 | from chat_display import chat_display_agent, console 9 | 10 | async def start_agents(): 11 | await asyncio.gather( 12 | triage_agent.run(), 13 | claims_agent.run(), 14 | policy_agent.run(), 15 | escalation_agent.run(), 16 | chat_display_agent.run() 17 | ) 18 | 19 | async def stop_agents(): 20 | await asyncio.gather( 21 | triage_agent.stop(), 22 | claims_agent.stop(), 23 | policy_agent.stop(), 24 | escalation_agent.stop(), 25 | chat_display_agent.stop() 26 | ) 27 | await Channel.stop() 28 | 29 | async def main(): 30 | from chat_display import ask_input, console 31 | stop_event = asyncio.Event() 32 | try: 33 | console.print("[bold cyan]Welcome to the Insurance Customer Service System![/bold cyan]") 34 | await start_agents() 35 | asyncio.create_task(ask_input(stop_event)) 36 | await stop_event.wait() 37 | except asyncio.CancelledError: 38 | pass 39 | finally: 40 | await stop_agents() 41 | 42 | if __name__ == "__main__": 43 | import dotenv 44 | 45 | dotenv.load_dotenv() 46 | 47 | try: 48 | asyncio.run(main()) 49 | except KeyboardInterrupt: 50 | console.print("\n[bold red]Exiting...[/bold red]") 51 | -------------------------------------------------------------------------------- /examples/multi_agent_conversation/memory.py: -------------------------------------------------------------------------------- 1 | 2 | messages_history_memory = [] -------------------------------------------------------------------------------- /examples/multi_agent_conversation/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | python-dotenv==1.0.1 3 | litellm==1.61.15 4 | rich==13.9.4 -------------------------------------------------------------------------------- /examples/multi_agent_conversation/shared.py: -------------------------------------------------------------------------------- 1 | from eggai import Channel 2 | 3 | agents_channel = Channel("cli.agents") 4 | humans_channel = Channel("cli.humans") 5 | 6 | AGENT_REGISTRY = { 7 | "PolicyAgent": { 8 | "description": "Handles policy-related inquiries.", 9 | "keywords": ["policy details", "coverage", "premiums", "policy changes", "policy number", "renewal"] 10 | }, 11 | "ClaimsAgent": { 12 | "description": "Handles claims-related inquiries.", 13 | "keywords": ["file a claim", "claim status", "claim amount", "accident", "damage", "incident"] 14 | }, 15 | "EscalationAgent": { 16 | "description": "Handles escalated inquiries and out-of-scope requests.", 17 | "keywords": ["escalate", "speak to a human", "complaint", "refund", "support", "issue", "problem"] 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /examples/safe_agents_guardrails/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /examples/safe_agents_guardrails/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | dspy-ai==2.5.43 3 | python-dotenv==1.0.1 4 | pytest==8.3.4 5 | pytest-asyncio==0.25.2 6 | guardrails-ai==0.6.2 -------------------------------------------------------------------------------- /examples/safe_agents_guardrails/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/safe_agents_guardrails/src/__init__.py -------------------------------------------------------------------------------- /examples/safe_agents_guardrails/src/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/safe_agents_guardrails/src/agents/__init__.py -------------------------------------------------------------------------------- /examples/safe_agents_guardrails/src/agents/guardrail.py: -------------------------------------------------------------------------------- 1 | from guardrails import Guard 2 | from guardrails.hub import ToxicLanguage, DetectPII 3 | 4 | toxic_pipeline = Guard().use( 5 | ToxicLanguage( 6 | threshold=0.8, 7 | validation_method="sentence", 8 | on_fail="noop" 9 | ) 10 | ) 11 | 12 | pii_pipeline = Guard().use( 13 | DetectPII, ["EMAIL_ADDRESS", "PHONE_NUMBER"], "fix" 14 | ) 15 | 16 | def guard(text: str): 17 | validation = toxic_pipeline.validate(text) 18 | if validation.validation_passed is False: 19 | return None 20 | validation = pii_pipeline.validate(text) 21 | if validation.validation_passed is False: 22 | return None 23 | return validation.validated_output 24 | 25 | 26 | if __name__ == "__main__": 27 | print(guard("What is the year of birth of David Gregory of Kinnairdy castle plus the year of birth of Michael Jackson?")) 28 | print(guard("Are you stupid?")) 29 | print(guard("My email is johnny@gmail.com")) 30 | -------------------------------------------------------------------------------- /examples/safe_agents_guardrails/src/dspy_modules/lm.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | 3 | language_model = dspy.LM('openai/gpt-4o-mini') 4 | dspy.configure(lm=language_model) -------------------------------------------------------------------------------- /examples/safe_agents_guardrails/src/dspy_modules/wiki_qa.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | from dotenv import load_dotenv 3 | 4 | from src.dspy_modules.lm import language_model 5 | 6 | 7 | def search_wikipedia(query: str): 8 | results = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')(query, k=3) 9 | return [x['text'] for x in results] 10 | 11 | wiki_qa = dspy.ReAct("question -> answer", tools=[search_wikipedia]) 12 | -------------------------------------------------------------------------------- /examples/safe_agents_guardrails/src/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai import Channel 4 | from agents.answers_agent import answers_agent 5 | 6 | agents_channel = Channel("agents") 7 | 8 | 9 | async def main(): 10 | await answers_agent.run() 11 | 12 | # Non toxic question 13 | await agents_channel.publish({ 14 | "type": "question", 15 | "payload": "What is the year of birth of David Gregory of Kinnairdy castle?" 16 | }) 17 | 18 | # Toxic question 19 | await agents_channel.publish({ 20 | "type": "question", 21 | "payload": "Are you stupid??" 22 | }) 23 | 24 | # PII question 25 | await agents_channel.publish({ 26 | "type": "question", 27 | "payload": "My email is stefano@gmail.com, is that correct?" 28 | }) 29 | 30 | try: 31 | print("Agent is running. Press Ctrl+C to stop.") 32 | await asyncio.Event().wait() 33 | except asyncio.exceptions.CancelledError: 34 | print("Task was cancelled. Cleaning up...") 35 | finally: 36 | await answers_agent.stop() 37 | await Channel.stop() 38 | 39 | 40 | if __name__ == "__main__": 41 | try: 42 | asyncio.run(main()) 43 | except KeyboardInterrupt: 44 | print("Exiting...") 45 | -------------------------------------------------------------------------------- /examples/safe_agents_guardrails/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/safe_agents_guardrails/tests/__init__.py -------------------------------------------------------------------------------- /examples/shared_context/channels.py: -------------------------------------------------------------------------------- 1 | from eggai import Channel 2 | 3 | agents_channel = Channel("AgentsChannel") 4 | humans_channel = Channel("HumansChannel") 5 | -------------------------------------------------------------------------------- /examples/shared_context/human_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent, Channel 2 | 3 | human_agent = Agent("HumanAgent") 4 | agents_channel = Channel("AgentsChannel") 5 | humans_channel = Channel("HumansChannel") 6 | 7 | @human_agent.subscribe(channel=agents_channel, filter_func=lambda msg: msg["type"] == "user_query") 8 | async def user_asks(msg): 9 | print(f"User: {msg['payload']}") 10 | 11 | 12 | @human_agent.subscribe(channel=humans_channel, filter_func=lambda msg: msg["type"] == "product_info") 13 | async def print_agents_message(msg): 14 | print(f"Search Agent:") 15 | for product in msg["payload"]: 16 | print(" - " + product["name"]) 17 | 18 | 19 | @human_agent.subscribe(channel=humans_channel, filter_func=lambda msg: msg["type"] == "related_products") 20 | async def print_recommendation(msg): 21 | print(f"Recommendation Agent:") 22 | for product in msg["payload"]: 23 | print(" - " + product["name"] + " (Reason: " + product["reason"] + ")") 24 | -------------------------------------------------------------------------------- /examples/shared_context/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eggai import Channel 4 | 5 | from channels import agents_channel 6 | from human_agent import human_agent 7 | from products_agent import products_agent 8 | from recommendation_agent import recommendation_agent 9 | 10 | 11 | async def main(): 12 | await human_agent.run() 13 | await products_agent.run() 14 | await recommendation_agent.run() 15 | 16 | await agents_channel.publish({ 17 | "id": "ID-0", 18 | "type": "user_query", 19 | "payload": "Can you recommend a smartphone, i like gaming on it. I prefer Apple if possible" 20 | }) 21 | 22 | try: 23 | print("Agent is running. Press Ctrl+C to stop.") 24 | await asyncio.Event().wait() 25 | except asyncio.exceptions.CancelledError: 26 | print("Task was cancelled. Cleaning up...") 27 | finally: 28 | await recommendation_agent.stop() 29 | await products_agent.stop() 30 | await human_agent.stop() 31 | await Channel.stop() 32 | 33 | 34 | if __name__ == "__main__": 35 | try: 36 | asyncio.run(main()) 37 | except KeyboardInterrupt: 38 | print("Exiting...") -------------------------------------------------------------------------------- /examples/shared_context/memory_db.py: -------------------------------------------------------------------------------- 1 | total_product_list = [ 2 | {"name": "iPhone 15", "category": "Smartphone", "description": "Latest Apple smartphone with A17 chip and enhanced camera."}, 3 | {"name": "Samsung Galaxy S23", "category": "Smartphone", "description": "High-performance Android smartphone with a 120Hz display."}, 4 | {"name": "Google Pixel 8", "category": "Smartphone", "description": "Smartphone with a clean Android experience and advanced AI features."}, 5 | {"name": "OnePlus 11", "category": "Smartphone", "description": "Flagship Android smartphone with fast charging and a 50MP camera."}, 6 | {"name": "Sony Xperia 1 IV", "category": "Smartphone", "description": "High-end smartphone designed for media creators with 4K OLED screen."}, 7 | {"name": "MacBook Pro 14-inch", "category": "Laptop", "description": "Powerful laptop with M1 Pro chip, Retina display, and long battery life."}, 8 | {"name": "Dell XPS 13", "category": "Laptop", "description": "Compact and lightweight laptop with an Intel Core i7 processor and stunning display."}, 9 | {"name": "HP Spectre x360", "category": "Laptop", "description": "Convertible laptop with 360-degree hinge, Intel Core i7, and AMOLED touchscreen."}, 10 | {"name": "Lenovo ThinkPad X1 Carbon", "category": "Laptop", "description": "Durable business laptop with a powerful Intel Core i7 processor and great battery life."}, 11 | {"name": "Razer Blade 15", "category": "Laptop", "description": "High-performance gaming laptop with NVIDIA GeForce RTX 3070 and 144Hz display."} 12 | ] 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/shared_context/openai_client.py: -------------------------------------------------------------------------------- 1 | from openai import OpenAI 2 | 3 | openai_client = OpenAI() -------------------------------------------------------------------------------- /examples/shared_context/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | openai==1.59.6 -------------------------------------------------------------------------------- /examples/tool_calling/.env.example: -------------------------------------------------------------------------------- 1 | LM_STUDIO_API_BASE=http://localhost:1234/v1/ 2 | LM_STUDIO_API_KEY=lm-studio 3 | REACT_AGENT_LANGUAGE_MODEL_API_BASE=http://localhost:1234/v1/ 4 | REACT_AGENT_LANGUAGE_MODEL=lm_studio/mistral-nemo-instruct-2407 -------------------------------------------------------------------------------- /examples/tool_calling/Makefile: -------------------------------------------------------------------------------- 1 | PYTHON := python3 2 | VENV_DIR := .venv 3 | REQ_FILE := requirements.txt 4 | DEV_REQ_FILE := dev-requirements.txt 5 | DOCKER_COMPOSE := docker compose 6 | SHELL := $(shell which bash) 7 | 8 | .PHONY: setup 9 | setup: 10 | @echo "Setting up the environment..." 11 | @if [ ! -d "$(VENV_DIR)" ]; then \ 12 | $(PYTHON) -m venv $(VENV_DIR); \ 13 | echo "Virtual environment created."; \ 14 | else \ 15 | echo "Virtual environment already exists."; \ 16 | fi 17 | @$(VENV_DIR)/bin/pip install --upgrade pip 18 | @if [ -f $(REQ_FILE) ]; then $(VENV_DIR)/bin/pip install -r $(REQ_FILE); fi 19 | @if [ -f $(DEV_REQ_FILE) ]; then $(VENV_DIR)/bin/pip install -r $(DEV_REQ_FILE); fi 20 | @echo "Checking dependencies..." 21 | @command -v uvx >/dev/null 2>&1 || { echo "uvx not found, installing..."; $(VENV_DIR)/bin/pip install uvx; } 22 | @command -v npx >/dev/null 2>&1 || { echo "npx not found, installing..."; npm install -g npx; } 23 | @echo "Environment setup complete." 24 | 25 | .PHONY: start-all 26 | start-all: 27 | @source $(VENV_DIR)/bin/activate && $(VENV_DIR)/bin/python src/main.py 28 | 29 | .PHONY: docker-up 30 | docker-up: 31 | @echo "Starting Docker Compose..." 32 | @$(DOCKER_COMPOSE) up -d 33 | @echo "Docker Compose started." 34 | 35 | .PHONY: docker-down 36 | docker-down: 37 | @echo "Stopping and cleaning up Docker Compose..." 38 | @$(DOCKER_COMPOSE) down -v 39 | @echo "Docker Compose stopped and cleaned up." 40 | 41 | .PHONY: clean 42 | clean: 43 | @echo "Cleaning up the environment..." 44 | @rm -rf $(VENV_DIR) 45 | @echo "Environment cleaned." 46 | 47 | .PHONY: run-react-module 48 | run-react-module: 49 | @echo "Running React module..." 50 | @source $(VENV_DIR)/bin/activate && $(VENV_DIR)/bin/python src/dspy_modules/react_module.py 51 | @echo "React module execution completed." -------------------------------------------------------------------------------- /examples/tool_calling/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.46 2 | dspy-ai==2.5.43 3 | python-dotenv==1.0.1 4 | pytest==8.3.4 5 | pytest-asyncio==0.25.2 6 | sympy==1.12.1 7 | pydantic-settings==2.4.0 8 | -------------------------------------------------------------------------------- /examples/tool_calling/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/tool_calling/src/__init__.py -------------------------------------------------------------------------------- /examples/tool_calling/src/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/tool_calling/src/agents/__init__.py -------------------------------------------------------------------------------- /examples/tool_calling/src/agents/react_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Channel, Agent 2 | from eggai.schemas import Message 3 | 4 | from dspy_modules.react_module import react_module 5 | 6 | agents_channel = Channel("agents") 7 | 8 | react_agent = Agent("AnswersAgent") 9 | 10 | 11 | @react_agent.subscribe( 12 | channel=agents_channel, filter_by_message=lambda msg: msg["type"] == "question" 13 | ) 14 | async def handle_question(msg: Message): 15 | question = msg.data.get("message") 16 | 17 | prediction = react_module(question=question) 18 | 19 | answer_message = Message( 20 | type="answer", 21 | source="react_agent", 22 | data={ 23 | "answer": prediction.answer, 24 | "numeric_answer": prediction.numeric_answer, 25 | "reasoning": prediction.reasoning, 26 | }, 27 | ) 28 | await agents_channel.publish(answer_message) 29 | -------------------------------------------------------------------------------- /examples/tool_calling/src/config.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from dotenv import load_dotenv 4 | from pydantic import Field 5 | from pydantic_settings import BaseSettings, SettingsConfigDict 6 | 7 | load_dotenv() 8 | 9 | 10 | class Settings(BaseSettings): 11 | app_name: str = Field(default="react_agent") 12 | 13 | # Language model settings 14 | language_model: str = Field(default="openai/gpt-4o-mini") 15 | language_model_api_base: Optional[str] = Field(default=None) 16 | cache_enabled: bool = Field(default=False) 17 | 18 | model_config = SettingsConfigDict( 19 | env_prefix="REACT_AGENT_", 20 | env_file=".env", 21 | env_ignore_empty=True, 22 | extra="ignore", 23 | ) 24 | 25 | 26 | settings = Settings() 27 | -------------------------------------------------------------------------------- /examples/tool_calling/src/dspy_modules/react_module.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | from src.config import Settings 3 | 4 | 5 | settings = Settings() 6 | language_model = dspy.LM( 7 | model=settings.language_model, 8 | api_base=settings.language_model_api_base, 9 | cache=settings.cache_enabled, 10 | ) 11 | dspy.configure(lm=language_model) 12 | 13 | 14 | def execute_python_code(code: str) -> float: 15 | """ 16 | Evaluates a python code and returns the result. 17 | """ 18 | return dspy.PythonInterpreter({}, import_white_list=["sympy"]).execute(code) 19 | 20 | 21 | react_module = dspy.ReAct( 22 | "question -> answer, numeric_answer: float", tools=[execute_python_code] 23 | ) 24 | 25 | if __name__ == "__main__": 26 | prediction = react_module( 27 | question="An ICE train of Deutsche Bahn travels from Cologne to Berlin at a speed of 200 km/h. A second ICE departs one hour later on the same route, but at a speed of 250 km/h. When will the second train catch up with the first train?" 28 | ) 29 | print(f"Answer: {prediction.answer}") 30 | print(f"Reasoning: {prediction.reasoning}") 31 | print(f"Numeric answer: {prediction.numeric_answer}") 32 | print(f"Trajectory: {prediction.trajectory}") 33 | -------------------------------------------------------------------------------- /examples/tool_calling/src/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Dict, Any 3 | from eggai import Channel, eggai_cleanup 4 | from eggai.schemas import Message 5 | from agents.react_agent import react_agent 6 | from eggai.transport import KafkaTransport, eggai_set_default_transport 7 | 8 | eggai_set_default_transport(lambda: KafkaTransport()) 9 | 10 | 11 | def handle_answer(event: Dict[str, Any]) -> None: 12 | """Process and display agent response in a structured format""" 13 | data = event.get("data", {}) 14 | print("\n==== Agent Response ====") 15 | print(f"Answer: {data.get('answer', 'N/A')}") 16 | print(f"Numeric: {data.get('numeric_answer', 'N/A')}") 17 | print(f"Reasoning: {data.get('reasoning', 'N/A')}") 18 | print("=======================\n") 19 | 20 | 21 | def handle_question(event: Dict[str, Any]) -> None: 22 | """Process and display the question in a structured format""" 23 | data = event.data 24 | print("\n==== User Question ====") 25 | print(f"Question: {data.get('message', 'N/A')}") 26 | print("=======================\n") 27 | 28 | 29 | async def main(): 30 | agents_channel = Channel("agents") 31 | 32 | await react_agent.start() 33 | await agents_channel.subscribe( 34 | filter_by_message=lambda msg: msg["type"] == "answer", callback=handle_answer 35 | ) 36 | message = Message( 37 | type="question", 38 | source="main", 39 | data={"message": "what's the result of 12345 multiplied by 54321?"}, 40 | ) 41 | 42 | handle_question(message) 43 | 44 | await agents_channel.publish(message) 45 | 46 | try: 47 | await asyncio.Event().wait() 48 | except (KeyboardInterrupt, asyncio.CancelledError): 49 | pass 50 | 51 | await eggai_cleanup() 52 | 53 | 54 | if __name__ == "__main__": 55 | asyncio.run(main()) 56 | -------------------------------------------------------------------------------- /examples/tool_calling/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/tool_calling/tests/__init__.py -------------------------------------------------------------------------------- /examples/tool_calling/tests/test_react_module.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from src.dspy_modules.react_module import react_module 4 | 5 | 6 | @pytest.mark.asyncio 7 | async def test_train_catch_up(): 8 | expected_answer = 5 9 | pred = react_module( 10 | question="An ICE train of Deutsche Bahn travels from Cologne to Berlin at a speed of 200 km/h. A second ICE departs one hour later on the same route, but at a speed of 250 km/h. When will the second train catch up with the first train?" 11 | ) 12 | assert pred.numeric_answer == expected_answer 13 | 14 | @pytest.mark.asyncio 15 | async def test_multiplication(): 16 | expected_answer = 670592745 17 | pred = react_module( 18 | question="what's the result of 12345 multiplied by 54321?" 19 | ) 20 | assert pred.numeric_answer == expected_answer 21 | -------------------------------------------------------------------------------- /examples/triage_agent/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /examples/triage_agent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/triage_agent/__init__.py -------------------------------------------------------------------------------- /examples/triage_agent/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | dspy-ai==2.5.43 3 | python-dotenv==1.0.1 4 | pytest==8.3.4 5 | pytest-asyncio==0.25.2 -------------------------------------------------------------------------------- /examples/triage_agent/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/triage_agent/src/__init__.py -------------------------------------------------------------------------------- /examples/triage_agent/src/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/triage_agent/src/agents/__init__.py -------------------------------------------------------------------------------- /examples/triage_agent/src/agents/triage.py: -------------------------------------------------------------------------------- 1 | from eggai import Channel, Agent 2 | from ..dspy_modules.triage_module import triage_module 3 | 4 | human_channel = Channel("human") 5 | agents_channel = Channel("agents") 6 | 7 | triage_agent = Agent("TriageAgent") 8 | 9 | 10 | @triage_agent.subscribe( 11 | channel=human_channel, filter_func=lambda msg: msg["type"] == "user_message" 12 | ) 13 | async def handle_user_message(msg): 14 | """ 15 | Handles user messages and routes them to the appropriate target agent. 16 | """ 17 | try: 18 | payload = msg["payload"] 19 | chat_messages = payload.get("chat_messages", "") 20 | response = triage_module(chat_history=chat_messages) 21 | target_agent = response.target_agent 22 | 23 | await agents_channel.publish({"target": target_agent, "payload": payload}) 24 | except Exception as e: 25 | print("Error in Triage Agent: ", e) 26 | -------------------------------------------------------------------------------- /examples/triage_agent/src/dspy_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/triage_agent/src/dspy_modules/__init__.py -------------------------------------------------------------------------------- /examples/triage_agent/src/dspy_modules/lm.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | 3 | language_model = dspy.LM("openai/gpt-4o-mini", cache=False) 4 | -------------------------------------------------------------------------------- /examples/triage_agent/src/dspy_modules/triage_module.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | import dspy 4 | from dotenv import load_dotenv 5 | 6 | from .lm import language_model 7 | from .utils import run_and_calculate_costs 8 | 9 | TargetAgent = Literal["PolicyAgent", "TicketingAgent", "TriageAgent"] 10 | 11 | dspy.configure(lm=language_model) 12 | 13 | 14 | class TriageSignature(dspy.Signature): 15 | """ 16 | Extract the intent of a user message and classify it to the appropriate target agent. 17 | 18 | Available Target Agents: 19 | - PolicyAgent: Handles policy-related queries. 20 | - TicketingAgent: Handles insurance related queries for customer support (e.g. contact information). 21 | - TriageAgent: Handles non-insurance-related queries. 22 | 23 | Fallback Rules: 24 | - Route to TicketingAgent if unsure where to send an insurance-related query. 25 | - Route to TriageAgent if the query is not insurance-related. 26 | """ 27 | 28 | chat_history: str = dspy.InputField( 29 | desc="Full chat history providing context for the classification process." 30 | ) 31 | 32 | target_agent: TargetAgent = dspy.OutputField( 33 | desc="Target agent classified for triage based on context and rules." 34 | ) 35 | 36 | 37 | triage_module = dspy.ChainOfThought(signature=TriageSignature) 38 | 39 | if __name__ == "__main__": 40 | load_dotenv() 41 | run_and_calculate_costs( 42 | triage_module, 43 | chat_history="User: I need help with my policy." 44 | ) 45 | -------------------------------------------------------------------------------- /examples/triage_agent/src/dspy_modules/utils.py: -------------------------------------------------------------------------------- 1 | from .lm import language_model 2 | 3 | 4 | def run_and_calculate_costs(program, chat_history): 5 | program(chat_history=chat_history) 6 | last_history = language_model.history[-1] 7 | cost = last_history['cost'] 8 | if cost: 9 | print(f"Cost: {cost:.10f}$") 10 | print(f"Run it {1 / cost:.0f} times to reach one dollar.") 11 | else: 12 | print("No cost. (cached)") 13 | -------------------------------------------------------------------------------- /examples/triage_agent/src/loader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | 4 | 5 | def load_dataset(file: str) -> list: 6 | # Construct the absolute path to the CSV file 7 | csv_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), file + ".csv")) 8 | 9 | # Open the CSV file and parse it using the csv.DictReader 10 | with open(csv_file_path, "r", newline='', encoding='utf-8') as csvfile: 11 | reader = csv.DictReader(csvfile) 12 | data = [] 13 | 14 | for row in reader: 15 | data.append({ 16 | "conversation": row["conversation"].replace("\\n", "\n"), 17 | "target": row["target"] 18 | }) 19 | 20 | return data 21 | 22 | if __name__ == "__main__": 23 | print(load_dataset("triage-testing")) -------------------------------------------------------------------------------- /examples/triage_agent/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/examples/triage_agent/tests/__init__.py -------------------------------------------------------------------------------- /examples/triage_agent/tests/test_intent_classifier.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from dotenv import load_dotenv 3 | 4 | from ..src.dspy_modules.triage_module import triage_module as classifier_v2 5 | from .utilities import load_data, run_evaluation 6 | 7 | 8 | def test_intent_classifier(): 9 | load_dotenv() 10 | test_dataset = load_data("triage-testing") 11 | score, results = run_evaluation(classifier_v2, test_dataset) 12 | 13 | # Track failures for debugging 14 | failures = [] 15 | for result in results: 16 | example, prediction, passed = result 17 | if not passed: 18 | failures.append(f"Chat History: {example.chat_history} - Expected {example.target_agent}, got {prediction.target_agent}") 19 | 20 | # Print failures for debugging purposes 21 | if failures: 22 | print(f"\nMisclassified examples ({len(failures)} out of {len(results)}):") 23 | for failure in failures: 24 | print(f" - {failure}") 25 | 26 | # The test should pass if we achieve > 90% accuracy 27 | # This allows for some misclassifications which is realistic for ML models 28 | assert score > 0.9, f"Success rate {score:.2f} is not greater than 90%." 29 | -------------------------------------------------------------------------------- /examples/triage_agent/tests/test_triage.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import AsyncMock 2 | 3 | import pytest 4 | from dotenv import load_dotenv 5 | 6 | from eggai import Channel 7 | 8 | from src.loader import load_dataset 9 | from ..src.agents.triage import handle_user_message 10 | 11 | @pytest.mark.asyncio 12 | async def test_triage_agent(monkeypatch): 13 | load_dotenv() 14 | 15 | dataset = load_dataset("triage-testing") 16 | 17 | total = len(dataset) 18 | success = 0 19 | 20 | for entry in dataset: 21 | conversation = entry["conversation"] 22 | expected_target = entry["target"] 23 | test_event = { 24 | "type": "user_message", 25 | "payload": {"chat_messages": conversation}, 26 | } 27 | mock_publish = AsyncMock() 28 | monkeypatch.setattr(Channel, "publish", mock_publish) 29 | await handle_user_message(test_event) 30 | args, kwargs = mock_publish.call_args_list[0] 31 | actual_target = args[0].get("target") 32 | if actual_target == expected_target: 33 | success += 1 34 | 35 | success_percentage = (success / total) * 100 36 | assert (success_percentage > 90), f"Success rate {success_percentage:.2f}% is not greater than 90%." 37 | -------------------------------------------------------------------------------- /examples/triage_agent/tests/utilities.py: -------------------------------------------------------------------------------- 1 | import dspy 2 | 3 | from src.loader import load_dataset 4 | 5 | 6 | def load_data(file: str): 7 | """ 8 | Loads the dataset and constructs the devset list of dspy.Example objects. 9 | 10 | :param path: Path to the triage-training JSON file. 11 | :return: A list of dspy.Example objects. 12 | """ 13 | devset = [] 14 | for ex in load_dataset(file): 15 | devset.append( 16 | dspy.Example( 17 | chat_history=ex["conversation"], 18 | target_agent=ex["target"] 19 | ).with_inputs("chat_history") 20 | ) 21 | return devset 22 | 23 | 24 | def run_evaluation(program, devset): 25 | """ 26 | Runs evaluation of the classifier over the given devset. 27 | 28 | :param devset: A list of dspy.Example objects to evaluate against. 29 | """ 30 | evaluator = dspy.evaluate.Evaluate( 31 | devset=devset, 32 | num_threads=10, 33 | display_progress=False, 34 | return_outputs=True, 35 | return_all_scores=True 36 | ) 37 | score, results, all_scores = evaluator(program, metric=lambda example, pred, 38 | trace=None: example.target_agent.lower() == pred.target_agent.lower()) 39 | return score, results 40 | -------------------------------------------------------------------------------- /examples/websocket_gateway/coordinator.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent, Channel 2 | 3 | agent = Agent("Coordinator") 4 | 5 | agents_channel = Channel() 6 | human_channel = Channel("human") 7 | 8 | @agent.subscribe(filter_func=lambda msg: msg["type"] == "notification") 9 | async def forward_agent_to_human(message): 10 | await human_channel.publish(message) 11 | 12 | @agent.subscribe(channel=human_channel, filter_func=lambda msg: msg["type"] == "create_order") 13 | async def forward_human_to_agents(msg): 14 | await agents_channel.publish({"type": "order_requested", "id": msg.get("id"), "payload": msg.get("payload")}) 15 | 16 | async def start_coordinator(): 17 | await agent.run() 18 | 19 | async def stop_coordinator(): 20 | await agent.stop() -------------------------------------------------------------------------------- /examples/websocket_gateway/email_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent, Channel 2 | 3 | agent = Agent("EmailAgent") 4 | channel = Channel() 5 | 6 | @agent.subscribe(filter_func=lambda event: event["type"] == "order_created") 7 | async def send_email(message): 8 | print(f"[EMAIL AGENT]: order_created event received. Sending email to customer.") 9 | 10 | 11 | @agent.subscribe(filter_func=lambda event: event["type"] == "order_created") 12 | async def send_notification(message): 13 | print(f"[EMAIL AGENT]: order_created event received. Sending notification event.") 14 | await channel.publish({ 15 | "id": message.get("id"), 16 | "type": "notification", 17 | "payload": {"message": "Order created, you will receive an email soon."} 18 | }) 19 | 20 | async def start_email_agent(): 21 | await agent.run() 22 | 23 | async def stop_email_agent(): 24 | await agent.stop() -------------------------------------------------------------------------------- /examples/websocket_gateway/gateway/server.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | from fastapi import FastAPI 3 | 4 | from .websocket_agent import plug_fastapi_websocket, start_websocket_gateway, stop_websocket_gateway 5 | 6 | api = FastAPI() 7 | config = uvicorn.Config(api, host="127.0.0.1", port=8000, log_level="info") 8 | server = uvicorn.Server(config) 9 | 10 | 11 | @api.get("/") 12 | async def read_root(): 13 | return {"Hello": "Gateway"} 14 | 15 | plug_fastapi_websocket("/ws", api) 16 | 17 | # Hook for server startup 18 | @api.on_event("startup") 19 | async def on_startup(): 20 | print("Starting WebSocket gateway...") 21 | await start_websocket_gateway() 22 | 23 | # Hook for server shutdown 24 | @api.on_event("shutdown") 25 | async def on_shutdown(): 26 | print("Stopping WebSocket gateway...") 27 | await stop_websocket_gateway() -------------------------------------------------------------------------------- /examples/websocket_gateway/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | 4 | import websockets 5 | from eggai import Channel 6 | 7 | from coordinator import start_coordinator, stop_coordinator 8 | from email_agent import start_email_agent, stop_email_agent 9 | from gateway.server import server 10 | from order_agent import start_order_agent, stop_order_agent 11 | 12 | async def main(): 13 | await start_email_agent() 14 | await start_order_agent() 15 | await start_coordinator() 16 | 17 | server_task = asyncio.create_task(server.serve()) 18 | uri = "ws://localhost:8000/ws" 19 | 20 | await asyncio.sleep(1) 21 | 22 | async with websockets.connect(uri) as websocket: 23 | reply = await websocket.recv() 24 | print(f"Connection id: {reply}") 25 | message = { 26 | "type": "create_order", 27 | "payload": { 28 | "product": "Laptop", 29 | "quantity": 3 30 | } 31 | } 32 | await websocket.send(json.dumps(message)) 33 | reply = await websocket.recv() 34 | print(f"Message id: {json.loads(reply)['id']}") 35 | 36 | reply = await websocket.recv() 37 | print(f"Reply: {reply}") 38 | 39 | await websocket.close() 40 | 41 | try: 42 | print("Agent is running. Press Ctrl+C to stop.") 43 | await asyncio.Event().wait() 44 | except asyncio.exceptions.CancelledError: 45 | print("Task was cancelled. Cleaning up...") 46 | finally: 47 | await stop_coordinator() 48 | await stop_email_agent() 49 | await stop_order_agent() 50 | await Channel.stop() 51 | server_task.cancel() 52 | 53 | 54 | if __name__ == "__main__": 55 | try: 56 | asyncio.run(main()) 57 | except KeyboardInterrupt: 58 | print("Exiting...") 59 | -------------------------------------------------------------------------------- /examples/websocket_gateway/order_agent.py: -------------------------------------------------------------------------------- 1 | from eggai import Agent, Channel 2 | 3 | agent = Agent("OrderAgent") 4 | channel = Channel() 5 | 6 | 7 | @agent.subscribe(filter_func=lambda msg: msg["type"] == "order_requested") 8 | async def create_order(msg): 9 | print(f"[ORDER AGENT]: order_requested event received. Emitting order_created event.") 10 | await channel.publish({"type": "order_created", "payload": msg.get("payload"), "id": msg.get("id")}) 11 | 12 | 13 | @agent.subscribe(filter_func=lambda msg: msg["type"] == "order_created") 14 | async def order_processing(msg): 15 | print(f"[ORDER AGENT]: order_created event received.") 16 | 17 | async def start_order_agent(): 18 | await agent.run() 19 | 20 | async def stop_order_agent(): 21 | await agent.stop() -------------------------------------------------------------------------------- /examples/websocket_gateway/requirements.txt: -------------------------------------------------------------------------------- 1 | eggai==0.1.16 2 | fastapi==0.115.6 3 | uvicorn==0.34.0 4 | websockets==14.1 -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | """EggAI test runner scripts.""" -------------------------------------------------------------------------------- /scripts/run_all_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Main entry point for running all tests. 4 | """ 5 | import sys 6 | from scripts.run_tests import run_all 7 | 8 | def main(): 9 | """Run all tests.""" 10 | try: 11 | return run_all() 12 | except Exception as e: 13 | print(f"Error during test run: {e}", file=sys.stderr) 14 | return 1 15 | 16 | if __name__ == "__main__": 17 | exit(main()) -------------------------------------------------------------------------------- /sdk/cli/__init__.py: -------------------------------------------------------------------------------- 1 | """EggAI CLI module for creating new applications.""" 2 | -------------------------------------------------------------------------------- /sdk/cli/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | EggAI CLI Entry Point 4 | 5 | Command-line interface for EggAI development tools. 6 | """ 7 | 8 | import sys 9 | 10 | try: 11 | import click 12 | except ImportError: 13 | print("Error: CLI dependencies not installed. Install with: pip install eggai[cli]") 14 | sys.exit(1) 15 | 16 | from .wizard import create_app 17 | 18 | # Get version from package 19 | try: 20 | from importlib.metadata import version 21 | 22 | __version__ = version("eggai") 23 | except ImportError: 24 | # Python < 3.8 25 | from importlib_metadata import version 26 | 27 | __version__ = version("eggai") 28 | 29 | 30 | @click.group() 31 | @click.version_option(version=__version__) 32 | def cli(): 33 | """EggAI CLI - Tools for building agent applications.""" 34 | pass 35 | 36 | 37 | # Register commands 38 | cli.add_command(create_app, name="init") 39 | 40 | 41 | def main(): 42 | """Main entry point for the CLI.""" 43 | cli() 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /sdk/cli/templates/agent.py.j2: -------------------------------------------------------------------------------- 1 | """ 2 | {{ agent_name }} Agent 3 | 4 | This agent handles {{ agent_name.lower() }}-related tasks and messages. 5 | """ 6 | 7 | import logging 8 | from pydantic import BaseModel 9 | from typing import Dict, Any 10 | 11 | from eggai import Agent, Channel 12 | from eggai.schemas import BaseMessage, Message 13 | from {{ project_name }}.models import ChatMessage 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | async def create_{{ agent_function }}() -> Agent: 19 | """Create and configure the {{ agent_name }} agent.""" 20 | agent = Agent("{{ agent_name }}") 21 | 22 | @agent.subscribe(channel=Channel("human.in"), data_type=ChatMessage) 23 | async def handle_chat_message(message: ChatMessage) -> None: 24 | """Handle chat messages from console and respond to human channel.""" 25 | # TODO: Implement your chat logic here 26 | response_text = f"Hello! You said: '{message.data}'. This is {{ agent_name }} responding." 27 | await Channel("human.out").publish(ChatMessage( 28 | source="{{ agent_name }}", 29 | data=response_text 30 | )) 31 | 32 | logger.info(f"{{ agent_name }} responded to chat message") 33 | await agent.start() 34 | logger.info("{{ agent_name }} started successfully") 35 | return agent -------------------------------------------------------------------------------- /sdk/cli/templates/agents_init.py.j2: -------------------------------------------------------------------------------- 1 | """ 2 | Agents package 3 | 4 | This package contains all the agents for the EggAI application. 5 | Each agent is defined in its own module with its specific message types and handlers. 6 | """ -------------------------------------------------------------------------------- /sdk/cli/templates/common_models.py.j2: -------------------------------------------------------------------------------- 1 | """ 2 | Data Models for {{ project_name }} 3 | 4 | Message types and data models used across agents. 5 | """ 6 | 7 | from eggai.schemas import BaseMessage 8 | 9 | 10 | class ChatMessage(BaseMessage[str]): 11 | """Message type for chat communication with string data.""" 12 | type: str = "chat" -------------------------------------------------------------------------------- /sdk/cli/templates/env.j2: -------------------------------------------------------------------------------- 1 | # Kafka Configuration 2 | KAFKA_BOOTSTRAP_SERVERS=localhost:9092 3 | 4 | # Optional: Add other environment variables here -------------------------------------------------------------------------------- /sdk/cli/templates/main.py.j2: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | 4 | from eggai import eggai_main 5 | {% if transport == "kafka" %} 6 | from eggai.transport import eggai_set_default_transport, KafkaTransport 7 | {% endif %} 8 | {% for agent in agents %} 9 | from {{ project_name }}.agents.{{ agent.filename }} import create_{{ agent.function_name }} 10 | {% endfor %} 11 | 12 | # Set up logging 13 | logging.basicConfig(level=logging.INFO) 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | @eggai_main 18 | async def main(): 19 | """Main function to start all agents.""" 20 | print("=== {{ project_name }} ===") 21 | {% if transport == "kafka" %} 22 | 23 | # Set up Kafka transport 24 | eggai_set_default_transport(lambda: KafkaTransport()) 25 | {% endif %} 26 | 27 | # Create and start all agents 28 | agents = [] 29 | {% for agent in agents %} 30 | agents.append(await create_{{ agent.function_name }}()) 31 | {% endfor %} 32 | 33 | print(f"Started {len(agents)} agent(s): {{ agents|map(attribute='name')|join(', ') }}") 34 | print("All agents are running. Press Ctrl+C to stop.") 35 | 36 | try: 37 | # Keep the application running 38 | await asyncio.Future() 39 | except (KeyboardInterrupt, asyncio.CancelledError): 40 | logger.info("Shutting down...") 41 | 42 | 43 | if __name__ == "__main__": 44 | asyncio.run(main()) -------------------------------------------------------------------------------- /sdk/cli/templates/requirements.txt.j2: -------------------------------------------------------------------------------- 1 | # EggAI SDK 2 | eggai>=0.2.1 3 | 4 | # Core dependencies 5 | pydantic>=2.0.0 6 | asyncio 7 | {%- if transport == "kafka" %} 8 | 9 | # Kafka transport dependencies 10 | faststream[kafka]>=0.5.0 11 | python-dotenv>=1.0.0 12 | {%- endif %} 13 | {%- if include_console %} 14 | 15 | # Console frontend dependencies 16 | aioconsole>=0.6.0 17 | {%- endif %} 18 | 19 | # Optional: Add your additional dependencies here 20 | # numpy 21 | # requests 22 | # openai -------------------------------------------------------------------------------- /sdk/eggai/__init__.py: -------------------------------------------------------------------------------- 1 | from .agent import Agent as Agent 2 | from .channel import Channel as Channel 3 | from .hooks import ( 4 | eggai_cleanup as eggai_cleanup, 5 | eggai_register_stop as eggai_register_stop, 6 | eggai_main as eggai_main, 7 | EggaiRunner as EggaiRunner, 8 | ) 9 | -------------------------------------------------------------------------------- /sdk/eggai/adapters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/sdk/eggai/adapters/__init__.py -------------------------------------------------------------------------------- /sdk/eggai/adapters/a2a/__init__.py: -------------------------------------------------------------------------------- 1 | """A2A Integration for EggAI.""" 2 | 3 | from .config import A2AConfig 4 | from .plugin import A2APlugin 5 | 6 | __all__ = ["A2AConfig", "A2APlugin"] 7 | -------------------------------------------------------------------------------- /sdk/eggai/adapters/a2a/config.py: -------------------------------------------------------------------------------- 1 | """A2A configuration for EggAI agents.""" 2 | 3 | from pydantic import BaseModel, Field, ConfigDict 4 | from typing import List, Optional 5 | 6 | try: 7 | from a2a.types import SecurityScheme 8 | except ImportError: 9 | # Fallback if a2a-sdk not installed 10 | SecurityScheme = dict 11 | 12 | 13 | class A2AConfig(BaseModel): 14 | """Simple A2A configuration for EggAI agents.""" 15 | 16 | agent_name: str = Field(..., description="Name of the A2A agent") 17 | description: str = Field(..., description="Description of what the agent does") 18 | version: str = Field(default="1.0.0", description="Agent version") 19 | base_url: str = Field( 20 | default="http://localhost:8080", description="Base URL for the A2A server" 21 | ) 22 | security_schemes: Optional[List[SecurityScheme]] = Field( 23 | default=None, description="Security schemes supported by the agent" 24 | ) 25 | 26 | model_config = ConfigDict(arbitrary_types_allowed=True) 27 | -------------------------------------------------------------------------------- /sdk/eggai/adapters/mcp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/sdk/eggai/adapters/mcp/__init__.py -------------------------------------------------------------------------------- /sdk/eggai/adapters/mcp/models.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, List, Any 2 | from uuid import UUID 3 | 4 | from pydantic import BaseModel 5 | 6 | from eggai.schemas import BaseMessage 7 | 8 | 9 | class ExternalTool(BaseModel): 10 | name: str 11 | description: str 12 | parameters: dict = {} 13 | return_type: dict = {} 14 | 15 | 16 | class ToolListRequest(BaseModel): 17 | call_id: UUID 18 | adapter_name: str 19 | 20 | 21 | class ToolListRequestMessage(BaseMessage[ToolListRequest]): 22 | type: Literal["ToolListRequestMessage"] = "ToolListRequestMessage" 23 | 24 | 25 | class ToolListResponse(BaseModel): 26 | call_id: UUID 27 | tools: List[ExternalTool] 28 | 29 | 30 | class ToolListResponseMessage(BaseMessage[ToolListResponse]): 31 | type: Literal["ToolListResponseMessage"] = "ToolListResponseMessage" 32 | 33 | 34 | class ToolCallRequest(BaseModel): 35 | call_id: UUID 36 | tool_name: str 37 | parameters: dict = {} 38 | 39 | 40 | class ToolCallRequestMessage(BaseMessage[ToolCallRequest]): 41 | type: Literal["ToolCallRequestMessage"] = "ToolCallRequestMessage" 42 | 43 | 44 | class ToolCallResponse(BaseModel): 45 | call_id: UUID 46 | tool_name: str 47 | data: Any = None 48 | is_error: bool = False 49 | 50 | 51 | class ToolCallResponseMessage(BaseMessage[ToolCallResponse]): 52 | type: Literal["ToolCallResponseMessage"] = "ToolCallResponseMessage" 53 | -------------------------------------------------------------------------------- /sdk/eggai/transport/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import Transport as Transport 2 | from .defaults import ( 3 | eggai_set_default_transport as eggai_set_default_transport, 4 | get_default_transport as get_default_transport, 5 | ) 6 | from .inmemory import InMemoryTransport as InMemoryTransport 7 | from .kafka import KafkaTransport as KafkaTransport 8 | -------------------------------------------------------------------------------- /sdk/eggai/transport/base.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from abc import ABC, abstractmethod 3 | from typing import Dict, Any, Callable, Union 4 | 5 | from pydantic import BaseModel 6 | 7 | 8 | class Transport(ABC): 9 | """ 10 | Abstract base for any transport. It should manage publishing, 11 | subscribing, connecting, and disconnecting. 12 | """ 13 | 14 | @abstractmethod 15 | async def connect(self): 16 | """ 17 | Connect to the underlying system. 18 | """ 19 | pass 20 | 21 | @abstractmethod 22 | async def disconnect(self): 23 | """ 24 | Cleanly disconnect from the transport. 25 | """ 26 | pass 27 | 28 | @abstractmethod 29 | async def publish(self, channel: str, message: Union[Dict[str, Any], BaseModel]): 30 | """ 31 | Publish the given message to the channel. 32 | """ 33 | pass 34 | 35 | @abstractmethod 36 | async def subscribe( 37 | self, 38 | channel: str, 39 | callback: Callable[[Dict[str, Any]], "asyncio.Future"], 40 | **kwargs, 41 | ) -> Callable: 42 | """ 43 | Subscribe to a channel with the given callback, invoked on new messages. 44 | (No-op if a consumer doesn’t exist.) 45 | """ 46 | pass 47 | -------------------------------------------------------------------------------- /sdk/eggai/transport/defaults.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Optional, Callable 3 | 4 | from .base import Transport 5 | from .inmemory import InMemoryTransport 6 | 7 | _DEFAULT_TRANSPORT_FACTORY: Optional[Callable[[], "Transport"]] = None 8 | 9 | 10 | def eggai_set_default_transport(factory: Callable[[], "Transport"]): 11 | """ 12 | Set a global function that returns a fresh Transport instance. 13 | Agents or Channels created without an explicit transport 14 | will use this factory. 15 | """ 16 | global _DEFAULT_TRANSPORT_FACTORY 17 | _DEFAULT_TRANSPORT_FACTORY = factory 18 | 19 | 20 | def get_default_transport() -> "Transport": 21 | """ 22 | Get a fresh Transport instance from the default factory. 23 | If no default transport factory is set, return an InMemoryTransport instance and print a warning. 24 | """ 25 | if _DEFAULT_TRANSPORT_FACTORY is None: 26 | print( 27 | "EggAI: Warning, no default transport factory set, InMemoryTransport will be used. Use eggai_set_default_transport() if you don't want see this warning.", 28 | file=sys.stderr, 29 | ) 30 | sys.stderr.flush() 31 | eggai_set_default_transport(lambda: InMemoryTransport()) 32 | return _DEFAULT_TRANSPORT_FACTORY() 33 | -------------------------------------------------------------------------------- /sdk/poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | -------------------------------------------------------------------------------- /sdk/requirements.txt: -------------------------------------------------------------------------------- 1 | faststream[kafka]==0.5.40 2 | pydantic==2.11.4 3 | click==8.2.2 4 | jinja2==3.1.6 -------------------------------------------------------------------------------- /sdk/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggai-tech/EggAI/2855e6df6f78b17d5fb8b8053dedc2ba430ea5d2/sdk/tests/__init__.py -------------------------------------------------------------------------------- /sdk/tests/test_namespace.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from contextlib import contextmanager 3 | 4 | from eggai import Channel 5 | import eggai.channel 6 | 7 | 8 | class TestNamespace: 9 | """Test namespace functionality in Channel class.""" 10 | 11 | @contextmanager 12 | def _override_namespace(self, namespace): 13 | """Context manager to temporarily override the global NAMESPACE.""" 14 | original = eggai.channel.NAMESPACE 15 | try: 16 | eggai.channel.NAMESPACE = namespace 17 | yield 18 | finally: 19 | eggai.channel.NAMESPACE = original 20 | 21 | @pytest.mark.parametrize( 22 | "namespace,channel_name,expected", 23 | [ 24 | (eggai.channel.NAMESPACE, None, "eggai.channel"), 25 | ("dev", "events", "dev.events"), 26 | ("test", "logs", "test.logs"), 27 | ("prod", None, "prod.channel"), 28 | ("staging", "", "staging.channel"), 29 | ], 30 | ) 31 | def test_namespace_combinations(self, namespace, channel_name, expected): 32 | """Test various namespace and channel name combinations.""" 33 | with self._override_namespace(namespace): 34 | channel = Channel(name=channel_name) 35 | assert channel._name == expected 36 | --------------------------------------------------------------------------------