Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/ahmadexe_agenix.json
T

1 line
242 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "ahmadexe/agenix", "files": {"README.md": {"76ff19e9b94fb52c98d821071642fb2fea3bce84": {"t": 1785746475.3573189, "n": 24196, "h": "81943c40e7f458e8", "c": "![Agenix Banner](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n# Agenix\n\n<p align=\"center\">\n <a href=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml\"><img src=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub\"></a>\n <a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n <a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Stars\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\nA Flutter package for building AI agents with memory, tools, and multi-agent orchestration. Define your agent's personality, give it tools, and let it handle conversations — including delegating sub-tasks across a chain of specialized agents.\n\n<video src=\"https://github.com/user-attachments/assets/d92e6763-60bd-42a1-8d7c-d1e5535656bf\" width=\"100%\" controls></video>\n\n---\n\n## Packages\n\nThis is a monorepo containing the following packages:\n\n| Package | pub.dev | Description |\n|---|---|---|\n| [`agenix`](packages/agenix/) | [![Pub](https://img.shields.io/pub/v/agenix.svg)](https://pub.dev/packages/agenix) | Core — agents, tools, LLM interface, in-memory data store |\n| [`agenix_firebase`](packages/agenix_firebase/) | [![Pub](https://img.shields.io/pub/v/agenix_firebase.svg)](https://pub.dev/packages/agenix_firebase) | Firebase (Firestore + Storage + Auth) data store backend |\n\n### Choosing a data store\n\nThe core `agenix` package ships with `DataStore.inMemory()` — a zero-dependency store for testing and prototyping. For production persistence, add a backend package:\n\n```yaml\n# Core only (no Firebase):\ndependencies:\n agenix: ^4.1.2\n\n# With Firebase persistence:\ndependencies:\n agenix: ^4.1.2\n agenix_firebase: ^1.0.4\n```\n\n```dart\nimport 'package:agenix/agenix.dart';\n\n// In-memory (no extra package needed):\nfinal store = DataStore.inMemory();\n\n// Firebase (requires agenix_firebase):\nimport 'package:agenix_firebase/agenix_firebase.dart';\nfinal store = FirebaseDataStore();\n```\n\nThis separation means apps that don't use Firebase never pull in the Firebase SDK.\n\n> **Migrating from agenix 3.x?** Replace `DataStore.firestoreDataStore()` with `FirebaseDataStore()` and add `agenix_firebase` to your pubspec. See the [core CHANGELOG](packages/agenix/CHANGELOG.md) for details.\n\n---\n\n## Table of Contents\n\n- [Architecture Overview](#architecture-overview)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Concepts](#core-concepts)\n - [Agent](#agent)\n - [LLM](#llm)\n - [DataStore (Memory)](#datastore-memory)\n - [Tools](#tools)\n - [Multi-Agent Orchestration](#multi-agent-orchestration)\n - [Agent Scopes](#agent-scopes)\n- [Error Handling](#error-handling)\n- [API Reference](#api-reference)\n- [Usage Architectures](#usage-architectures)\n- [Examples](#examples)\n- [Maintainers](#maintainers)\n\n---\n\n## Architecture Overview\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ Your Flutter App │\n│ │\n│ Agent.create(llm, dataStore, name, role) │\n│ │ │\n│ ▼ │\n│ ┌─────────┐ generateResponse() ┌───────────────┐ │\n│ │ Agent │ ◄─────────────────────► │ LLM │ │\n│ │ │ │ (Gemini/OpenAI│ │\n│ │ │ │ Groq/Anthropic│ │\n│ │ │ │ + Custom) │ │\n│ └────┬────┘ └───────────────┘ │\n│ │ │\n│ ┌────┴──────────────────────────┐ │\n│ │ │ │ │\n│ ▼ ▼ ▼ │\n│ ┌──────┐ ┌──────────┐ ┌────────────────┐ │\n│ │Tools │ │DataStore │ │Agent Registry │ │\n│ │ │ │(InMemory/ │ │(Multi-Agent │ │\n│ │ │ │ Firebase/ │ │ Orchestration) │ │\n│ │ │ │ Custom) │ │ │ │\n│ └──────┘ └──────────┘ └────────────────┘ │\n└─────────────────────────────────────────────────────────────┘\n```\n\nThe agent receives a user message, builds a structured prompt (including conversation history from the DataStore and available tools from the ToolRegistry), sends it to the LLM, and parses the response into one of three actions:\n\n1. **Direct response** — returns text to the user\n2. **Tool invocation** — runs one or more tools, optionally iterating up to 5 times before producing a final answer\n3. **Agent delegation** — hands the task to a chain of other agents, each passing its output to the next\n\n---\n\n## Installation\n\nAdd to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n agenix: ^4.1.2\n```\n\nIf you need Firebase persistence, also add:\n\n```yaml\n agenix_firebase: ^1.0.4\n```\n\nThen run:\n\n```bash\nflutter pub get\n```\n\n---\n\n## Quick Start\n\n### 1. Create system data\n\nCreate `assets/system_data.json` with your agent's personality and background knowledge:\n\n```json\n{\n \"name\": \"Lens\",\n \"role\": \"A helpful assistant for the Acme platform\",\n \"personality\": \"Friendly, concise, and knowledgeable\",\n \"instructions\": \"Always greet the user by name when possible\"\n}\n```\n\nAdd the asset to your `pubspec.yaml`:\n\n```yaml\nflutter:\n assets:\n - assets/system_data.json\n```\n\n### 2. Initialize the agent\n\n```dart\nimport 'package:agenix/agenix.dart';\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n ),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\nFor Firebase persistence:\n\n```dart\nimport 'package:agenix_firebase/agenix_firebase.dart';\n\nfinal agent = await Agent.create(\n dataStore: FirebaseDataStore(),\n llm: LLM.geminiLLM(apiKey: 'YOUR_API_KEY', modelName: 'gemini-2.0-flash'),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\n### 3. Generate a response\n\n```dart\nfinal response = await agent.generateResponse(\n convoId: 'conversation-1',\n userMessage: AgentMessage(\n content: 'What is the weather like today?',\n isFromAgent: false,\n generatedAt: DateTime.now(),\n ),\n);\n\nprint(response.content);\n```\n\n### 4. Run the app\n\n```bash\nflutter run -d chrome --dart-define=GEMINI_API_KEY=your-key-here\n```\n\n---\n\n## Core Concepts\n\n### Agent\n\nThe `Agent` is the central class. It wires together an LLM, a DataStore, a ToolRegistry, and an AgentScope.\n\n```dart\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Support Agent',\n role: 'Handles customer support queries for the e-commerce platform.',\n failureMode: FailureMode.throwError, // or FailureMode.gracefulMessage (default)\n onError: (error, stack) => logger.severe('Agent error', error, stack),\n scope: AgentScope.global, // default — or create isolated scopes\n registrationPolicy: RegistrationPolicy.throwIfExists, // default\n);\n```\n\n**Key methods:**\n\n| Method | Description |\n|---|---|\n| `generateResponse(convoId, userMessage)` | Send a user message and get back an `AgentMessage` from the agent |\n| `getMessages(conversationId)` | Retrieve all messages in a conversation |\n| `getAllConversations()` | List all conversations for the current user |\n| `deleteConversation(conversationId)` | Delete a conversation and its messages |\n| `dispose()` | Unregister the agent from its scope |\n\n**Parameters for `generateResponse`:**\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `convoId` | `String` | required | Conversation identifier |\n| `userMessage` | `AgentMessage` | required | The user's message |\n| `memoryLimit` | `int` | `10` | Max previous messages loaded as context |\n| `metaData` | `Object?` | `null` | Opaque pass-through for auth tokens, tenant IDs, etc. |\n\n---\n\n### LLM\n\nThe `LLM` abstract class defines the contract for language model providers. Agenix ships with built-in adapters for the most popular providers — pick one or implement your own.\n\n| Factory | Provider | Example model |\n|---|---|---|\n| `LLM.geminiLLM()` | Google Gemini | `gemini-2.0-flash` |\n| `LLM.openAiLLM()` | OpenAI | `gpt-4o` |\n| `LLM.anthropicLLM()` | Anthropic (Claude) | `claude-sonnet-4-5` |\n| `LLM.groqLLM()` | Groq | `llama-3.3-70b-versatile` |\n| `LLM.mistralLLM()` | Mistral AI | `mistral-large-latest` |\n| `LLM.deepseekLLM()` | DeepSeek | `deepseek-chat` |\n| `LLM.grokLLM()` | xAI (Grok) | `grok-4` |\n| `LLM.cohereLLM()` | Cohere | `command-r-plus-08-2024` |\n\n```dart\n// Google Gemini\nfinal llm = LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n config: LlmConfig(\n temperature: 0.2,\n maxOutputTokens: 2048,\n jsonMode: true,\n timeout: Duration(seconds: 60),\n ),\n);\n\n// OpenAI\nfinal llm = LLM.openAiLLM(apiKey: 'YOUR_API_KEY', modelName: 'gpt-4o');\n\n// Anthropic (Claude)\nfinal llm = LLM.anthropicLLM(apiKey: 'YOUR_API_KEY', modelName: 'claude-sonnet-4-5');\n\n// Groq — fast inference, free tier available\nfinal llm = LLM.groqLLM(apiKey: 'YOUR_API_KEY', modelName: 'llama-3.3-70b-versatile');\n\n// Mistral\nfinal llm = LLM.mistralLLM(apiKey: 'YOUR_API_KEY', modelName: 'mistral-large-latest');\n\n// DeepSeek\nfinal llm = LLM.deepseekLLM(apiKey: 'YOUR_API_KEY', modelName: 'deepseek-chat');\n\n// xAI (Grok)\nfinal llm = LLM.grokLLM(apiKey: 'YOUR_API_KEY', modelName: 'grok-4');\n\n// Cohere\nfinal llm = LLM.cohereLLM(apiKey: 'YOUR_API_KEY', modelName: 'command-r-plus-08-2024');\n```\n\n**Implementing a custom LLM** (e.g. your own backend):\n\n```dart\nclass MyCustomLLM implements LLM {\n @override\n final String modelId = 'my-model-v1';\n\n @override\n final LlmConfig config;\n\n MyCustomLLM({this.config = const LlmConfig()});\n\n @override\n Future<String> generate({\n required String prompt,\n String? systemInstruction,\n Uint8List? rawData,\n String mimeType = 'image/png',\n }) async {\n // Call your model API here\n // Must return a JSON string matching one of:\n // {\"response\": \"...\"}\n // {\"tools\": \"tool1, tool2\", \"parameters\": {...}}\n // {\"agents_chain\": [\"agent1\", \"agent2\"]}\n }\n}\n```\n\n---\n\n### DataStore (Memory)\n\nThe `DataStore` abstract class handles conversation persistence. Messages are saved after each `generateResponse` call, and loaded as context for future prompts.\n\n**Built-in implementations:**\n\n| Package | DataStore | Use Case |\n|---|---|---|\n| `agenix` | `DataStore.inMemory()` | Testing, prototyping, or non-persistent apps |\n| `agenix_firebase` | `FirebaseDataStore()` | Production apps with Firebase backend |\n\n**Firebase setup:** Add `agenix_firebase` to your pubspec, ensure `Firebase.initializeApp()` is called before creating the data store, and sign in a user via `firebase_auth`.\n\n**Implementing a custom DataStore:**\n\n```dart\nclass PostgresDataStore extends DataStore {\n @override\n Future<void> saveMessage(String convoId, AgentMessage msg, {Object? metaData}) async {\n // INSERT INTO messages ...\n }\n\n @override\n Future<List<AgentMessage>> getMessages(String conversationId, {int? limit, Object? metaData}) async {\n // SELECT * FROM messages WHERE convo_id = ? ORDER BY generated_at LIMIT ?\n }\n\n @override\n Future<void> deleteConversation(String conversationId, {Object? metaData}) async {\n // DELETE FROM messages WHERE convo_id = ?\n }\n\n @override\n Future<List<Conversation>> getConversations({Object? metaData}) async {\n // SELECT DISTINCT convo_id, last_message, last_message_time FROM ...\n }\n}\n```\n\n---\n\n### Tools\n\nTools let the agent perform actions beyond conversation — API calls, database queries, calculations, anything.\n\n**Lifecycle:**\n\n```\nUser Message → LLM decides tool is needed → Agent runs tool → Tool returns ToolResponse\n → Agent either returns result OR iterates (up to 5 rounds of tool calls)\n```\n\n#### Tool without parameters\n\n```dart\nclass NewsTool extends Tool {\n NewsTool() : super(\n name: 'news_tool',\n description: 'Fetches the latest news headlines.',\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final headlines = await NewsApi.fetchHeadlines();\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Here are today\\'s headlines: ${headlines.join(\", \")}',\n needsFurtherReasoning: true, // Agent will synthesize a natural-language answer\n );\n }\n}\n```\n\n#### Tool with parameters\n\n```dart\nclass WeatherTool extends Tool {\n WeatherTool() : super(\n name: 'weather_tool',\n description: 'Gets current weather for a given location.',\n parameters: [\n ParameterSpecification(\n name: 'location',\n type: 'string',\n description: 'City name or coordinates.',\n required: true,\n ),\n ParameterSpecification(\n name: 'units',\n type: 'string',\n description: 'Temperature unit.',\n required: false,\n defaultValue: 'celsius',\n enumValues: ['celsius', 'fahrenheit'],\n ),\n ],\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final location = params['location'] as String;\n final units = params['units'] as String? ?? 'celsius';\n final weather = await WeatherApi.get(location, units: units);\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Weather in $location: ${weather.temp}° ${weather.condition}',\n data: weather.toMap(), // Optional structured data for chaining\n );\n }\n}\n```\n\n#### Registering tools\n\n```dart\nagent.toolRegistry.registerTool(NewsTool());\nagent.toolRegistry.registerTool(WeatherTool());\n\n// Dynamically remove a tool\nagent.toolRegistry.unregisterTool('weather_tool');\n```\n\n#### ToolResponse flags\n\n| Field | Type | Description |\n|---|---|---|\n| `toolName` | `String` | Name of the tool that produced this response |\n| `isRequestSuccessful` | `bool` | Whether the tool operation succeeded |\n| `message` | `String` | Human-readable result shown to the user |\n| `data` | `Map?` | Structured data for agent chaining or further reasoning |\n| `needsFurtherReasoning` | `bool` | When `true`, the agent makes a second LLM call to synthesize the tool output into a natural-language answer |\n\n---\n\n### Multi-Agent Orchestration\n\nWhen the LLM determines a task requires multiple specialists, it returns an `agents_chain`. Agenix automatically delegates sub-tasks across agents, passing each agent's output as input to the next.\n\n```dart\n// Create specialized agents\nfinal newsAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'News Agent',\n role: 'Fetches and summarizes news articles.',\n);\n\nfinal favouritesAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Favourites Agent',\n role: 'Manages user favourites: add, remove, and list.',\n);\n\nfinal orchestrator = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Orchestrator',\n role: 'Main user-facing agent. Delegates to News Agent and Favourites Agent.',\n);\n\n// Register tools on each agent as needed\nnewsAgent.toolRegistry.registerTool(NewsTool());\nfavouritesAgent.toolRegistry.registerTool(AddFavouriteTool());\nfavouritesAgent.toolRegistry.registerTool(ListFavouritesTool());\n```\n\n**How chaining works:**\n\n```\nUser: \"Save the top headline to my favourites\"\n\n┌──────────────┐ ┌────────────┐ ┌──────────────────┐\n│ Orchestrator │────►│ News Agent │────►│ Favourites Agent │\n│ │ │ │ │ │\n│ Decides chain│ │ Fetches │ │ Saves headline │\n│ [News, Favs] │ │ headlines │ │ to favourites │\n└──────────────┘ └────────────┘ └──────────────────┘\n │ │\n │ output passes as │\n │ input to next ──────┘\n │\n ▼\n Final response\n back to user\n```\n\n**Safety guardrails:**\n\n- **Cycle detection** — if an agent appears twice in the same chain, a `ConfigException` is thrown\n- **Depth limiting** — chains are capped at 5 levels deep (`kMaxChainDepth`)\n\n---\n\n### Agent Scopes\n\nBy default, all agents register in `AgentScope.global` and can discover each other for chaining. Use custom scopes to isolate agent groups:\n\n```dart\n// Isolated scope for testing\nfinal testScope = AgentScope();\n\nfinal agentA = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent A',\n role: 'Test agent A.',\n scope: testScope,\n);\n\nfinal agentB = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent B',\n role: 'Test agent B.',\n scope: testScope,\n);\n\n// Agents in testScope can chain to each other, but NOT to agents in AgentScope.global\n```\n\n**RegistrationPolicy** controls what happens when an agent name collides:\n\n| Policy | Behavior |\n|---|---|\n| `throwIfExists` | Throws `ConfigException` (default — catches accidental duplicates) |\n| `replace` | Silently replaces the existing agent |\n| `ignore` | Keeps the existing agent, discards the new one |\n\n---\n\n## Error Handling\n\nAgenix uses a sealed exception hierarchy. Every exception is an `AgenixException`, so you can exhaustively match on the type:\n\n```dart\ntry {\n final response = await agent.generateResponse(\n convoId: 'convo-1',\n userMessage: message,\n );\n} on LlmTimeoutException catch (e) {\n // LLM call exceeded the configured timeout\n} on ResponseParseException catch (e) {\n // LLM returned malformed output after retries\n print('Raw output: ${e.rawOutput}');\n} on ToolNotFoundException catch (e) {\n // LLM referenced a tool that isn't registered\n print('Missing tool: ${e.toolName}');\n} on ToolExecutionException catch (e) {\n // A registered tool threw during execution\n print('Tool ${e.toolName} failed: ${e.message}');\n} on AgentNotFoundException catch (e) {\n // Agent chain referenced a non-existent agent\n} on DataStoreException catch (e) {\n // Persistence operation failed\n} on NotAuthenticatedException {\n // Firebase operation without a signed-in user (from agenix_firebase)\n} on ConfigException catch (e) {\n // Invalid configuration (bad system_data.json, duplicate agent name, etc.)\n}\n```\n\n**FailureMode** controls the behavior at the `generateResponse` boundary:\n\n| Mode | Behavior |\n|---|---|\n| `FailureMode.gracefulMessage` | Returns an `AgentMessage` with `isError: true` (default — safe for UI) |\n| `FailureMode.throwError` | Rethrows the typed `AgenixException` (use when you want full control) |\n\nThe `onError` callback fires in both modes, so you can always log errors centrally:\n\n```dart\nfinal agent = await Agent.create(\n // ...\n failureMode: FailureMode.gracefulMessage,\n onError: (error, stack) => crashlytics.recordError(error, stack),\n);\n```\n\n---\n\n## API Reference\n\n### Exported Classes\n\n| Class | Package | Description |\n|---|---|---|\n| `Agent` | `agenix` | Core agent with LLM, memory, tools, and multi-agent orchestration |\n| `AgentScope` | `agenix` | Isolates groups of agents that can discover and chain to each other |\n| `LLM` | `agenix` | Abstract interface for language model providers |\n| `LlmConfig` | `agenix` | Provider-neutral generation settings (temperature, tokens, timeout, etc.) |\n| `DataStore` | `agenix` | Abstract interface for conversation persistence |\n| `AgentMessage` | `agenix` | A message in a conversation (user or agent) |\n| `Conversation` | `agenix` | Summary of a conversation (last message, timestamp, ID) |\n| `Tool` | `agenix` | Abstract class to extend for custom tools |\n| `ParameterSpecification` | `agenix` | Defines a tool parameter (name, type, required, default, enum) |\n| `ToolResponse` | `agenix` | Result returned from a tool execution |\n| `ToolRegistry` | `agenix` | Per-agent registry for managing available tools |\n| `FirebaseDataStore` | `agenix_firebase` | Firestore + Storage + Auth data store implementation |\n\n### Exported Enums\n\n| Enum | Values | Description |\n|---|---|---|\n| `FailureMode` | `throwError`, `gracefulMessage` | Controls error surfacing behavior |\n| `RegistrationPolicy` | `throwIfExists`, `replace`, `ignore` | Controls duplicate agent name handling |\n\n### Sealed Exception Hierarchy\n\n```\nAgenixException (sealed)\n├── LlmException\n│ └── LlmTimeoutException\n├── ResponseParseException\n├── ToolNotFoundException\n├── ToolExecutionException\n├── AgentNotFoundException\n├── DataStoreException\n│ └── NotAuthenticatedException\n└── ConfigException\n```\n\n### Internal Constants\n\n| Constant | Value | Description |\n|---|---|---|\n| `kMaxToolIterations` | `5` | Max tool→observe→re-prompt cycles per turn |\n| `kMaxParseRetries` | `2` | Max corrective re-prompts for malformed JSON |\n| `kMaxChainDepth` | `5` | Max depth for agent chain delegation |\n\n---\n\n## Usage Architectures\n\n### Single-Agent Chat App\n\nThe simplest setup — one agent handling all user interactions.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐ ┌───────────┐\n│ Flutter │────►│ Agent │────►│ LLM │ │ DataStore │\n│ UI │◄────│ │◄────│ │ │(InMemory/ │\n└──────────┘ │ │ └───────┘ │ Firebase) │\n │ │──── save/load ────►└───────────┘\n └───────┘\n```\n\nBest for: chatbots, Q&A apps, customer support widgets.\n\n### Agent + Tools (API Integration)\n\nThe agent can call external APIs through tools.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐\n│ Flutter │────►│ Agent │────►│ LLM │\n│ UI │◄────│ │◄────│ │\n└──────────┘ │ │ └───────┘\n │ │\n │ ToolRegistry\n │ ├── WeatherTool ──► Weather API\n │ ├── NewsTool ──► News API\n │ └── DbTool ──► Database\n └───────┘\n```\n\nBest for: apps where the agent needs to fetch real-time data or trigger actions.\n\n### Multi-Agent Orchestration\n\nMultiple specialized agents collaborating on complex tasks.\n\n```\n┌──────────┐ ┌──────────────┐\n│ Flutter │────►│ Orchestrator │\n│ UI │◄────│ │\n└──────────┘ └──────┬───────┘\n │ delegates via agents_chain\n ┌─────────┼─────────┐\n ▼ ▼ ▼\n ┌─────────┐ ┌────────┐ ┌───────────┐\n │ Search │ │ Booking│ │ Favourites│\n │ Agent │ │ Agent │ │ Agent │\n │ + tools │ │ + tools│ │ + tools │\n └─────────┘ └────────┘ └───────────┘\n```\n\nBest for: complex platforms where different domains require specialized knowledge and tools.\n\n### Testing / Prototyping Setup\n\nUse `InMemoryDataStore` and scoped agents for fast, isolated development.\n\n```dart\nfinal scope = AgentScope();\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(), // No Firebase needed\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Test Agent',\n role: 'Agent under test.',\n scope: scope, // Isolated from production agents\n);\n```\n\n---\n\n## Examples\n\n| Example | Description |\n|---|---|\n| [Multi-Agent System](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system) | Three agents (Orchestrator, News, Favourites) working together |\n| [Firebase Example](packages/agenix_firebase/example/) | Single agent with tools and Firebase persistence |\n| [Custom DataStore](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example) | Implementing your own persistence backend |\n\n---\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "5849214febbbe21907792cb73d5cffbfc0a370bb": {"t": 1785764701.9576855, "n": 24196, "h": "81943c40e7f458e8", "c": "![Agenix Banner](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n# Agenix\n\n<p align=\"center\">\n <a href=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml\"><img src=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub\"></a>\n <a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n <a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Stars\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\nA Flutter package for building AI agents with memory, tools, and multi-agent orchestration. Define your agent's personality, give it tools, and let it handle conversations — including delegating sub-tasks across a chain of specialized agents.\n\n<video src=\"https://github.com/user-attachments/assets/d92e6763-60bd-42a1-8d7c-d1e5535656bf\" width=\"100%\" controls></video>\n\n---\n\n## Packages\n\nThis is a monorepo containing the following packages:\n\n| Package | pub.dev | Description |\n|---|---|---|\n| [`agenix`](packages/agenix/) | [![Pub](https://img.shields.io/pub/v/agenix.svg)](https://pub.dev/packages/agenix) | Core — agents, tools, LLM interface, in-memory data store |\n| [`agenix_firebase`](packages/agenix_firebase/) | [![Pub](https://img.shields.io/pub/v/agenix_firebase.svg)](https://pub.dev/packages/agenix_firebase) | Firebase (Firestore + Storage + Auth) data store backend |\n\n### Choosing a data store\n\nThe core `agenix` package ships with `DataStore.inMemory()` — a zero-dependency store for testing and prototyping. For production persistence, add a backend package:\n\n```yaml\n# Core only (no Firebase):\ndependencies:\n agenix: ^4.1.2\n\n# With Firebase persistence:\ndependencies:\n agenix: ^4.1.2\n agenix_firebase: ^1.0.4\n```\n\n```dart\nimport 'package:agenix/agenix.dart';\n\n// In-memory (no extra package needed):\nfinal store = DataStore.inMemory();\n\n// Firebase (requires agenix_firebase):\nimport 'package:agenix_firebase/agenix_firebase.dart';\nfinal store = FirebaseDataStore();\n```\n\nThis separation means apps that don't use Firebase never pull in the Firebase SDK.\n\n> **Migrating from agenix 3.x?** Replace `DataStore.firestoreDataStore()` with `FirebaseDataStore()` and add `agenix_firebase` to your pubspec. See the [core CHANGELOG](packages/agenix/CHANGELOG.md) for details.\n\n---\n\n## Table of Contents\n\n- [Architecture Overview](#architecture-overview)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Concepts](#core-concepts)\n - [Agent](#agent)\n - [LLM](#llm)\n - [DataStore (Memory)](#datastore-memory)\n - [Tools](#tools)\n - [Multi-Agent Orchestration](#multi-agent-orchestration)\n - [Agent Scopes](#agent-scopes)\n- [Error Handling](#error-handling)\n- [API Reference](#api-reference)\n- [Usage Architectures](#usage-architectures)\n- [Examples](#examples)\n- [Maintainers](#maintainers)\n\n---\n\n## Architecture Overview\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ Your Flutter App │\n│ │\n│ Agent.create(llm, dataStore, name, role) │\n│ │ │\n│ ▼ │\n│ ┌─────────┐ generateResponse() ┌───────────────┐ │\n│ │ Agent │ ◄─────────────────────► │ LLM │ │\n│ │ │ │ (Gemini/OpenAI│ │\n│ │ │ │ Groq/Anthropic│ │\n│ │ │ │ + Custom) │ │\n│ └────┬────┘ └───────────────┘ │\n│ │ │\n│ ┌────┴──────────────────────────┐ │\n│ │ │ │ │\n│ ▼ ▼ ▼ │\n│ ┌──────┐ ┌──────────┐ ┌────────────────┐ │\n│ │Tools │ │DataStore │ │Agent Registry │ │\n│ │ │ │(InMemory/ │ │(Multi-Agent │ │\n│ │ │ │ Firebase/ │ │ Orchestration) │ │\n│ │ │ │ Custom) │ │ │ │\n│ └──────┘ └──────────┘ └────────────────┘ │\n└─────────────────────────────────────────────────────────────┘\n```\n\nThe agent receives a user message, builds a structured prompt (including conversation history from the DataStore and available tools from the ToolRegistry), sends it to the LLM, and parses the response into one of three actions:\n\n1. **Direct response** — returns text to the user\n2. **Tool invocation** — runs one or more tools, optionally iterating up to 5 times before producing a final answer\n3. **Agent delegation** — hands the task to a chain of other agents, each passing its output to the next\n\n---\n\n## Installation\n\nAdd to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n agenix: ^4.1.2\n```\n\nIf you need Firebase persistence, also add:\n\n```yaml\n agenix_firebase: ^1.0.4\n```\n\nThen run:\n\n```bash\nflutter pub get\n```\n\n---\n\n## Quick Start\n\n### 1. Create system data\n\nCreate `assets/system_data.json` with your agent's personality and background knowledge:\n\n```json\n{\n \"name\": \"Lens\",\n \"role\": \"A helpful assistant for the Acme platform\",\n \"personality\": \"Friendly, concise, and knowledgeable\",\n \"instructions\": \"Always greet the user by name when possible\"\n}\n```\n\nAdd the asset to your `pubspec.yaml`:\n\n```yaml\nflutter:\n assets:\n - assets/system_data.json\n```\n\n### 2. Initialize the agent\n\n```dart\nimport 'package:agenix/agenix.dart';\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n ),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\nFor Firebase persistence:\n\n```dart\nimport 'package:agenix_firebase/agenix_firebase.dart';\n\nfinal agent = await Agent.create(\n dataStore: FirebaseDataStore(),\n llm: LLM.geminiLLM(apiKey: 'YOUR_API_KEY', modelName: 'gemini-2.0-flash'),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\n### 3. Generate a response\n\n```dart\nfinal response = await agent.generateResponse(\n convoId: 'conversation-1',\n userMessage: AgentMessage(\n content: 'What is the weather like today?',\n isFromAgent: false,\n generatedAt: DateTime.now(),\n ),\n);\n\nprint(response.content);\n```\n\n### 4. Run the app\n\n```bash\nflutter run -d chrome --dart-define=GEMINI_API_KEY=your-key-here\n```\n\n---\n\n## Core Concepts\n\n### Agent\n\nThe `Agent` is the central class. It wires together an LLM, a DataStore, a ToolRegistry, and an AgentScope.\n\n```dart\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Support Agent',\n role: 'Handles customer support queries for the e-commerce platform.',\n failureMode: FailureMode.throwError, // or FailureMode.gracefulMessage (default)\n onError: (error, stack) => logger.severe('Agent error', error, stack),\n scope: AgentScope.global, // default — or create isolated scopes\n registrationPolicy: RegistrationPolicy.throwIfExists, // default\n);\n```\n\n**Key methods:**\n\n| Method | Description |\n|---|---|\n| `generateResponse(convoId, userMessage)` | Send a user message and get back an `AgentMessage` from the agent |\n| `getMessages(conversationId)` | Retrieve all messages in a conversation |\n| `getAllConversations()` | List all conversations for the current user |\n| `deleteConversation(conversationId)` | Delete a conversation and its messages |\n| `dispose()` | Unregister the agent from its scope |\n\n**Parameters for `generateResponse`:**\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `convoId` | `String` | required | Conversation identifier |\n| `userMessage` | `AgentMessage` | required | The user's message |\n| `memoryLimit` | `int` | `10` | Max previous messages loaded as context |\n| `metaData` | `Object?` | `null` | Opaque pass-through for auth tokens, tenant IDs, etc. |\n\n---\n\n### LLM\n\nThe `LLM` abstract class defines the contract for language model providers. Agenix ships with built-in adapters for the most popular providers — pick one or implement your own.\n\n| Factory | Provider | Example model |\n|---|---|---|\n| `LLM.geminiLLM()` | Google Gemini | `gemini-2.0-flash` |\n| `LLM.openAiLLM()` | OpenAI | `gpt-4o` |\n| `LLM.anthropicLLM()` | Anthropic (Claude) | `claude-sonnet-4-5` |\n| `LLM.groqLLM()` | Groq | `llama-3.3-70b-versatile` |\n| `LLM.mistralLLM()` | Mistral AI | `mistral-large-latest` |\n| `LLM.deepseekLLM()` | DeepSeek | `deepseek-chat` |\n| `LLM.grokLLM()` | xAI (Grok) | `grok-4` |\n| `LLM.cohereLLM()` | Cohere | `command-r-plus-08-2024` |\n\n```dart\n// Google Gemini\nfinal llm = LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n config: LlmConfig(\n temperature: 0.2,\n maxOutputTokens: 2048,\n jsonMode: true,\n timeout: Duration(seconds: 60),\n ),\n);\n\n// OpenAI\nfinal llm = LLM.openAiLLM(apiKey: 'YOUR_API_KEY', modelName: 'gpt-4o');\n\n// Anthropic (Claude)\nfinal llm = LLM.anthropicLLM(apiKey: 'YOUR_API_KEY', modelName: 'claude-sonnet-4-5');\n\n// Groq — fast inference, free tier available\nfinal llm = LLM.groqLLM(apiKey: 'YOUR_API_KEY', modelName: 'llama-3.3-70b-versatile');\n\n// Mistral\nfinal llm = LLM.mistralLLM(apiKey: 'YOUR_API_KEY', modelName: 'mistral-large-latest');\n\n// DeepSeek\nfinal llm = LLM.deepseekLLM(apiKey: 'YOUR_API_KEY', modelName: 'deepseek-chat');\n\n// xAI (Grok)\nfinal llm = LLM.grokLLM(apiKey: 'YOUR_API_KEY', modelName: 'grok-4');\n\n// Cohere\nfinal llm = LLM.cohereLLM(apiKey: 'YOUR_API_KEY', modelName: 'command-r-plus-08-2024');\n```\n\n**Implementing a custom LLM** (e.g. your own backend):\n\n```dart\nclass MyCustomLLM implements LLM {\n @override\n final String modelId = 'my-model-v1';\n\n @override\n final LlmConfig config;\n\n MyCustomLLM({this.config = const LlmConfig()});\n\n @override\n Future<String> generate({\n required String prompt,\n String? systemInstruction,\n Uint8List? rawData,\n String mimeType = 'image/png',\n }) async {\n // Call your model API here\n // Must return a JSON string matching one of:\n // {\"response\": \"...\"}\n // {\"tools\": \"tool1, tool2\", \"parameters\": {...}}\n // {\"agents_chain\": [\"agent1\", \"agent2\"]}\n }\n}\n```\n\n---\n\n### DataStore (Memory)\n\nThe `DataStore` abstract class handles conversation persistence. Messages are saved after each `generateResponse` call, and loaded as context for future prompts.\n\n**Built-in implementations:**\n\n| Package | DataStore | Use Case |\n|---|---|---|\n| `agenix` | `DataStore.inMemory()` | Testing, prototyping, or non-persistent apps |\n| `agenix_firebase` | `FirebaseDataStore()` | Production apps with Firebase backend |\n\n**Firebase setup:** Add `agenix_firebase` to your pubspec, ensure `Firebase.initializeApp()` is called before creating the data store, and sign in a user via `firebase_auth`.\n\n**Implementing a custom DataStore:**\n\n```dart\nclass PostgresDataStore extends DataStore {\n @override\n Future<void> saveMessage(String convoId, AgentMessage msg, {Object? metaData}) async {\n // INSERT INTO messages ...\n }\n\n @override\n Future<List<AgentMessage>> getMessages(String conversationId, {int? limit, Object? metaData}) async {\n // SELECT * FROM messages WHERE convo_id = ? ORDER BY generated_at LIMIT ?\n }\n\n @override\n Future<void> deleteConversation(String conversationId, {Object? metaData}) async {\n // DELETE FROM messages WHERE convo_id = ?\n }\n\n @override\n Future<List<Conversation>> getConversations({Object? metaData}) async {\n // SELECT DISTINCT convo_id, last_message, last_message_time FROM ...\n }\n}\n```\n\n---\n\n### Tools\n\nTools let the agent perform actions beyond conversation — API calls, database queries, calculations, anything.\n\n**Lifecycle:**\n\n```\nUser Message → LLM decides tool is needed → Agent runs tool → Tool returns ToolResponse\n → Agent either returns result OR iterates (up to 5 rounds of tool calls)\n```\n\n#### Tool without parameters\n\n```dart\nclass NewsTool extends Tool {\n NewsTool() : super(\n name: 'news_tool',\n description: 'Fetches the latest news headlines.',\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final headlines = await NewsApi.fetchHeadlines();\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Here are today\\'s headlines: ${headlines.join(\", \")}',\n needsFurtherReasoning: true, // Agent will synthesize a natural-language answer\n );\n }\n}\n```\n\n#### Tool with parameters\n\n```dart\nclass WeatherTool extends Tool {\n WeatherTool() : super(\n name: 'weather_tool',\n description: 'Gets current weather for a given location.',\n parameters: [\n ParameterSpecification(\n name: 'location',\n type: 'string',\n description: 'City name or coordinates.',\n required: true,\n ),\n ParameterSpecification(\n name: 'units',\n type: 'string',\n description: 'Temperature unit.',\n required: false,\n defaultValue: 'celsius',\n enumValues: ['celsius', 'fahrenheit'],\n ),\n ],\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final location = params['location'] as String;\n final units = params['units'] as String? ?? 'celsius';\n final weather = await WeatherApi.get(location, units: units);\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Weather in $location: ${weather.temp}° ${weather.condition}',\n data: weather.toMap(), // Optional structured data for chaining\n );\n }\n}\n```\n\n#### Registering tools\n\n```dart\nagent.toolRegistry.registerTool(NewsTool());\nagent.toolRegistry.registerTool(WeatherTool());\n\n// Dynamically remove a tool\nagent.toolRegistry.unregisterTool('weather_tool');\n```\n\n#### ToolResponse flags\n\n| Field | Type | Description |\n|---|---|---|\n| `toolName` | `String` | Name of the tool that produced this response |\n| `isRequestSuccessful` | `bool` | Whether the tool operation succeeded |\n| `message` | `String` | Human-readable result shown to the user |\n| `data` | `Map?` | Structured data for agent chaining or further reasoning |\n| `needsFurtherReasoning` | `bool` | When `true`, the agent makes a second LLM call to synthesize the tool output into a natural-language answer |\n\n---\n\n### Multi-Agent Orchestration\n\nWhen the LLM determines a task requires multiple specialists, it returns an `agents_chain`. Agenix automatically delegates sub-tasks across agents, passing each agent's output as input to the next.\n\n```dart\n// Create specialized agents\nfinal newsAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'News Agent',\n role: 'Fetches and summarizes news articles.',\n);\n\nfinal favouritesAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Favourites Agent',\n role: 'Manages user favourites: add, remove, and list.',\n);\n\nfinal orchestrator = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Orchestrator',\n role: 'Main user-facing agent. Delegates to News Agent and Favourites Agent.',\n);\n\n// Register tools on each agent as needed\nnewsAgent.toolRegistry.registerTool(NewsTool());\nfavouritesAgent.toolRegistry.registerTool(AddFavouriteTool());\nfavouritesAgent.toolRegistry.registerTool(ListFavouritesTool());\n```\n\n**How chaining works:**\n\n```\nUser: \"Save the top headline to my favourites\"\n\n┌──────────────┐ ┌────────────┐ ┌──────────────────┐\n│ Orchestrator │────►│ News Agent │────►│ Favourites Agent │\n│ │ │ │ │ │\n│ Decides chain│ │ Fetches │ │ Saves headline │\n│ [News, Favs] │ │ headlines │ │ to favourites │\n└──────────────┘ └────────────┘ └──────────────────┘\n │ │\n │ output passes as │\n │ input to next ──────┘\n │\n ▼\n Final response\n back to user\n```\n\n**Safety guardrails:**\n\n- **Cycle detection** — if an agent appears twice in the same chain, a `ConfigException` is thrown\n- **Depth limiting** — chains are capped at 5 levels deep (`kMaxChainDepth`)\n\n---\n\n### Agent Scopes\n\nBy default, all agents register in `AgentScope.global` and can discover each other for chaining. Use custom scopes to isolate agent groups:\n\n```dart\n// Isolated scope for testing\nfinal testScope = AgentScope();\n\nfinal agentA = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent A',\n role: 'Test agent A.',\n scope: testScope,\n);\n\nfinal agentB = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent B',\n role: 'Test agent B.',\n scope: testScope,\n);\n\n// Agents in testScope can chain to each other, but NOT to agents in AgentScope.global\n```\n\n**RegistrationPolicy** controls what happens when an agent name collides:\n\n| Policy | Behavior |\n|---|---|\n| `throwIfExists` | Throws `ConfigException` (default — catches accidental duplicates) |\n| `replace` | Silently replaces the existing agent |\n| `ignore` | Keeps the existing agent, discards the new one |\n\n---\n\n## Error Handling\n\nAgenix uses a sealed exception hierarchy. Every exception is an `AgenixException`, so you can exhaustively match on the type:\n\n```dart\ntry {\n final response = await agent.generateResponse(\n convoId: 'convo-1',\n userMessage: message,\n );\n} on LlmTimeoutException catch (e) {\n // LLM call exceeded the configured timeout\n} on ResponseParseException catch (e) {\n // LLM returned malformed output after retries\n print('Raw output: ${e.rawOutput}');\n} on ToolNotFoundException catch (e) {\n // LLM referenced a tool that isn't registered\n print('Missing tool: ${e.toolName}');\n} on ToolExecutionException catch (e) {\n // A registered tool threw during execution\n print('Tool ${e.toolName} failed: ${e.message}');\n} on AgentNotFoundException catch (e) {\n // Agent chain referenced a non-existent agent\n} on DataStoreException catch (e) {\n // Persistence operation failed\n} on NotAuthenticatedException {\n // Firebase operation without a signed-in user (from agenix_firebase)\n} on ConfigException catch (e) {\n // Invalid configuration (bad system_data.json, duplicate agent name, etc.)\n}\n```\n\n**FailureMode** controls the behavior at the `generateResponse` boundary:\n\n| Mode | Behavior |\n|---|---|\n| `FailureMode.gracefulMessage` | Returns an `AgentMessage` with `isError: true` (default — safe for UI) |\n| `FailureMode.throwError` | Rethrows the typed `AgenixException` (use when you want full control) |\n\nThe `onError` callback fires in both modes, so you can always log errors centrally:\n\n```dart\nfinal agent = await Agent.create(\n // ...\n failureMode: FailureMode.gracefulMessage,\n onError: (error, stack) => crashlytics.recordError(error, stack),\n);\n```\n\n---\n\n## API Reference\n\n### Exported Classes\n\n| Class | Package | Description |\n|---|---|---|\n| `Agent` | `agenix` | Core agent with LLM, memory, tools, and multi-agent orchestration |\n| `AgentScope` | `agenix` | Isolates groups of agents that can discover and chain to each other |\n| `LLM` | `agenix` | Abstract interface for language model providers |\n| `LlmConfig` | `agenix` | Provider-neutral generation settings (temperature, tokens, timeout, etc.) |\n| `DataStore` | `agenix` | Abstract interface for conversation persistence |\n| `AgentMessage` | `agenix` | A message in a conversation (user or agent) |\n| `Conversation` | `agenix` | Summary of a conversation (last message, timestamp, ID) |\n| `Tool` | `agenix` | Abstract class to extend for custom tools |\n| `ParameterSpecification` | `agenix` | Defines a tool parameter (name, type, required, default, enum) |\n| `ToolResponse` | `agenix` | Result returned from a tool execution |\n| `ToolRegistry` | `agenix` | Per-agent registry for managing available tools |\n| `FirebaseDataStore` | `agenix_firebase` | Firestore + Storage + Auth data store implementation |\n\n### Exported Enums\n\n| Enum | Values | Description |\n|---|---|---|\n| `FailureMode` | `throwError`, `gracefulMessage` | Controls error surfacing behavior |\n| `RegistrationPolicy` | `throwIfExists`, `replace`, `ignore` | Controls duplicate agent name handling |\n\n### Sealed Exception Hierarchy\n\n```\nAgenixException (sealed)\n├── LlmException\n│ └── LlmTimeoutException\n├── ResponseParseException\n├── ToolNotFoundException\n├── ToolExecutionException\n├── AgentNotFoundException\n├── DataStoreException\n│ └── NotAuthenticatedException\n└── ConfigException\n```\n\n### Internal Constants\n\n| Constant | Value | Description |\n|---|---|---|\n| `kMaxToolIterations` | `5` | Max tool→observe→re-prompt cycles per turn |\n| `kMaxParseRetries` | `2` | Max corrective re-prompts for malformed JSON |\n| `kMaxChainDepth` | `5` | Max depth for agent chain delegation |\n\n---\n\n## Usage Architectures\n\n### Single-Agent Chat App\n\nThe simplest setup — one agent handling all user interactions.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐ ┌───────────┐\n│ Flutter │────►│ Agent │────►│ LLM │ │ DataStore │\n│ UI │◄────│ │◄────│ │ │(InMemory/ │\n└──────────┘ │ │ └───────┘ │ Firebase) │\n │ │──── save/load ────►└───────────┘\n └───────┘\n```\n\nBest for: chatbots, Q&A apps, customer support widgets.\n\n### Agent + Tools (API Integration)\n\nThe agent can call external APIs through tools.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐\n│ Flutter │────►│ Agent │────►│ LLM │\n│ UI │◄────│ │◄────│ │\n└──────────┘ │ │ └───────┘\n │ │\n │ ToolRegistry\n │ ├── WeatherTool ──► Weather API\n │ ├── NewsTool ──► News API\n │ └── DbTool ──► Database\n └───────┘\n```\n\nBest for: apps where the agent needs to fetch real-time data or trigger actions.\n\n### Multi-Agent Orchestration\n\nMultiple specialized agents collaborating on complex tasks.\n\n```\n┌──────────┐ ┌──────────────┐\n│ Flutter │────►│ Orchestrator │\n│ UI │◄────│ │\n└──────────┘ └──────┬───────┘\n │ delegates via agents_chain\n ┌─────────┼─────────┐\n ▼ ▼ ▼\n ┌─────────┐ ┌────────┐ ┌───────────┐\n │ Search │ │ Booking│ │ Favourites│\n │ Agent │ │ Agent │ │ Agent │\n │ + tools │ │ + tools│ │ + tools │\n └─────────┘ └────────┘ └───────────┘\n```\n\nBest for: complex platforms where different domains require specialized knowledge and tools.\n\n### Testing / Prototyping Setup\n\nUse `InMemoryDataStore` and scoped agents for fast, isolated development.\n\n```dart\nfinal scope = AgentScope();\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(), // No Firebase needed\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Test Agent',\n role: 'Agent under test.',\n scope: scope, // Isolated from production agents\n);\n```\n\n---\n\n## Examples\n\n| Example | Description |\n|---|---|\n| [Multi-Agent System](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system) | Three agents (Orchestrator, News, Favourites) working together |\n| [Firebase Example](packages/agenix_firebase/example/) | Single agent with tools and Firebase persistence |\n| [Custom DataStore](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example) | Implementing your own persistence backend |\n\n---\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "6e50d50a5e1417e414551fde2e4329e5531d5a86": {"t": 1785764703.3561323, "n": 24196, "h": "a672d73724b3f3df", "c": "![Agenix Banner](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n# Agenix\n\n<p align=\"center\">\n <a href=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml\"><img src=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub\"></a>\n <a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n <a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Stars\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\nA Flutter package for building AI agents with memory, tools, and multi-agent orchestration. Define your agent's personality, give it tools, and let it handle conversations — including delegating sub-tasks across a chain of specialized agents.\n\n<video src=\"https://github.com/user-attachments/assets/d92e6763-60bd-42a1-8d7c-d1e5535656bf\" width=\"100%\" controls></video>\n\n---\n\n## Packages\n\nThis is a monorepo containing the following packages:\n\n| Package | pub.dev | Description |\n|---|---|---|\n| [`agenix`](packages/agenix/) | [![Pub](https://img.shields.io/pub/v/agenix.svg)](https://pub.dev/packages/agenix) | Core — agents, tools, LLM interface, in-memory data store |\n| [`agenix_firebase`](packages/agenix_firebase/) | [![Pub](https://img.shields.io/pub/v/agenix_firebase.svg)](https://pub.dev/packages/agenix_firebase) | Firebase (Firestore + Storage + Auth) data store backend |\n\n### Choosing a data store\n\nThe core `agenix` package ships with `DataStore.inMemory()` — a zero-dependency store for testing and prototyping. For production persistence, add a backend package:\n\n```yaml\n# Core only (no Firebase):\ndependencies:\n agenix: ^4.1.1\n\n# With Firebase persistence:\ndependencies:\n agenix: ^4.1.1\n agenix_firebase: ^1.0.3\n```\n\n```dart\nimport 'package:agenix/agenix.dart';\n\n// In-memory (no extra package needed):\nfinal store = DataStore.inMemory();\n\n// Firebase (requires agenix_firebase):\nimport 'package:agenix_firebase/agenix_firebase.dart';\nfinal store = FirebaseDataStore();\n```\n\nThis separation means apps that don't use Firebase never pull in the Firebase SDK.\n\n> **Migrating from agenix 3.x?** Replace `DataStore.firestoreDataStore()` with `FirebaseDataStore()` and add `agenix_firebase` to your pubspec. See the [core CHANGELOG](packages/agenix/CHANGELOG.md) for details.\n\n---\n\n## Table of Contents\n\n- [Architecture Overview](#architecture-overview)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Concepts](#core-concepts)\n - [Agent](#agent)\n - [LLM](#llm)\n - [DataStore (Memory)](#datastore-memory)\n - [Tools](#tools)\n - [Multi-Agent Orchestration](#multi-agent-orchestration)\n - [Agent Scopes](#agent-scopes)\n- [Error Handling](#error-handling)\n- [API Reference](#api-reference)\n- [Usage Architectures](#usage-architectures)\n- [Examples](#examples)\n- [Maintainers](#maintainers)\n\n---\n\n## Architecture Overview\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ Your Flutter App │\n│ │\n│ Agent.create(llm, dataStore, name, role) │\n│ │ │\n│ ▼ │\n│ ┌─────────┐ generateResponse() ┌───────────────┐ │\n│ │ Agent │ ◄─────────────────────► │ LLM │ │\n│ │ │ │ (Gemini/OpenAI│ │\n│ │ │ │ Groq/Anthropic│ │\n│ │ │ │ + Custom) │ │\n│ └────┬────┘ └───────────────┘ │\n│ │ │\n│ ┌────┴──────────────────────────┐ │\n│ │ │ │ │\n│ ▼ ▼ ▼ │\n│ ┌──────┐ ┌──────────┐ ┌────────────────┐ │\n│ │Tools │ │DataStore │ │Agent Registry │ │\n│ │ │ │(InMemory/ │ │(Multi-Agent │ │\n│ │ │ │ Firebase/ │ │ Orchestration) │ │\n│ │ │ │ Custom) │ │ │ │\n│ └──────┘ └──────────┘ └────────────────┘ │\n└─────────────────────────────────────────────────────────────┘\n```\n\nThe agent receives a user message, builds a structured prompt (including conversation history from the DataStore and available tools from the ToolRegistry), sends it to the LLM, and parses the response into one of three actions:\n\n1. **Direct response** — returns text to the user\n2. **Tool invocation** — runs one or more tools, optionally iterating up to 5 times before producing a final answer\n3. **Agent delegation** — hands the task to a chain of other agents, each passing its output to the next\n\n---\n\n## Installation\n\nAdd to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n agenix: ^4.1.1\n```\n\nIf you need Firebase persistence, also add:\n\n```yaml\n agenix_firebase: ^1.0.3\n```\n\nThen run:\n\n```bash\nflutter pub get\n```\n\n---\n\n## Quick Start\n\n### 1. Create system data\n\nCreate `assets/system_data.json` with your agent's personality and background knowledge:\n\n```json\n{\n \"name\": \"Lens\",\n \"role\": \"A helpful assistant for the Acme platform\",\n \"personality\": \"Friendly, concise, and knowledgeable\",\n \"instructions\": \"Always greet the user by name when possible\"\n}\n```\n\nAdd the asset to your `pubspec.yaml`:\n\n```yaml\nflutter:\n assets:\n - assets/system_data.json\n```\n\n### 2. Initialize the agent\n\n```dart\nimport 'package:agenix/agenix.dart';\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n ),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\nFor Firebase persistence:\n\n```dart\nimport 'package:agenix_firebase/agenix_firebase.dart';\n\nfinal agent = await Agent.create(\n dataStore: FirebaseDataStore(),\n llm: LLM.geminiLLM(apiKey: 'YOUR_API_KEY', modelName: 'gemini-2.0-flash'),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\n### 3. Generate a response\n\n```dart\nfinal response = await agent.generateResponse(\n convoId: 'conversation-1',\n userMessage: AgentMessage(\n content: 'What is the weather like today?',\n isFromAgent: false,\n generatedAt: DateTime.now(),\n ),\n);\n\nprint(response.content);\n```\n\n### 4. Run the app\n\n```bash\nflutter run -d chrome --dart-define=GEMINI_API_KEY=your-key-here\n```\n\n---\n\n## Core Concepts\n\n### Agent\n\nThe `Agent` is the central class. It wires together an LLM, a DataStore, a ToolRegistry, and an AgentScope.\n\n```dart\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Support Agent',\n role: 'Handles customer support queries for the e-commerce platform.',\n failureMode: FailureMode.throwError, // or FailureMode.gracefulMessage (default)\n onError: (error, stack) => logger.severe('Agent error', error, stack),\n scope: AgentScope.global, // default — or create isolated scopes\n registrationPolicy: RegistrationPolicy.throwIfExists, // default\n);\n```\n\n**Key methods:**\n\n| Method | Description |\n|---|---|\n| `generateResponse(convoId, userMessage)` | Send a user message and get back an `AgentMessage` from the agent |\n| `getMessages(conversationId)` | Retrieve all messages in a conversation |\n| `getAllConversations()` | List all conversations for the current user |\n| `deleteConversation(conversationId)` | Delete a conversation and its messages |\n| `dispose()` | Unregister the agent from its scope |\n\n**Parameters for `generateResponse`:**\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `convoId` | `String` | required | Conversation identifier |\n| `userMessage` | `AgentMessage` | required | The user's message |\n| `memoryLimit` | `int` | `10` | Max previous messages loaded as context |\n| `metaData` | `Object?` | `null` | Opaque pass-through for auth tokens, tenant IDs, etc. |\n\n---\n\n### LLM\n\nThe `LLM` abstract class defines the contract for language model providers. Agenix ships with built-in adapters for the most popular providers — pick one or implement your own.\n\n| Factory | Provider | Example model |\n|---|---|---|\n| `LLM.geminiLLM()` | Google Gemini | `gemini-2.0-flash` |\n| `LLM.openAiLLM()` | OpenAI | `gpt-4o` |\n| `LLM.anthropicLLM()` | Anthropic (Claude) | `claude-sonnet-4-5` |\n| `LLM.groqLLM()` | Groq | `llama-3.3-70b-versatile` |\n| `LLM.mistralLLM()` | Mistral AI | `mistral-large-latest` |\n| `LLM.deepseekLLM()` | DeepSeek | `deepseek-chat` |\n| `LLM.grokLLM()` | xAI (Grok) | `grok-4` |\n| `LLM.cohereLLM()` | Cohere | `command-r-plus-08-2024` |\n\n```dart\n// Google Gemini\nfinal llm = LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n config: LlmConfig(\n temperature: 0.2,\n maxOutputTokens: 2048,\n jsonMode: true,\n timeout: Duration(seconds: 60),\n ),\n);\n\n// OpenAI\nfinal llm = LLM.openAiLLM(apiKey: 'YOUR_API_KEY', modelName: 'gpt-4o');\n\n// Anthropic (Claude)\nfinal llm = LLM.anthropicLLM(apiKey: 'YOUR_API_KEY', modelName: 'claude-sonnet-4-5');\n\n// Groq — fast inference, free tier available\nfinal llm = LLM.groqLLM(apiKey: 'YOUR_API_KEY', modelName: 'llama-3.3-70b-versatile');\n\n// Mistral\nfinal llm = LLM.mistralLLM(apiKey: 'YOUR_API_KEY', modelName: 'mistral-large-latest');\n\n// DeepSeek\nfinal llm = LLM.deepseekLLM(apiKey: 'YOUR_API_KEY', modelName: 'deepseek-chat');\n\n// xAI (Grok)\nfinal llm = LLM.grokLLM(apiKey: 'YOUR_API_KEY', modelName: 'grok-4');\n\n// Cohere\nfinal llm = LLM.cohereLLM(apiKey: 'YOUR_API_KEY', modelName: 'command-r-plus-08-2024');\n```\n\n**Implementing a custom LLM** (e.g. your own backend):\n\n```dart\nclass MyCustomLLM implements LLM {\n @override\n final String modelId = 'my-model-v1';\n\n @override\n final LlmConfig config;\n\n MyCustomLLM({this.config = const LlmConfig()});\n\n @override\n Future<String> generate({\n required String prompt,\n String? systemInstruction,\n Uint8List? rawData,\n String mimeType = 'image/png',\n }) async {\n // Call your model API here\n // Must return a JSON string matching one of:\n // {\"response\": \"...\"}\n // {\"tools\": \"tool1, tool2\", \"parameters\": {...}}\n // {\"agents_chain\": [\"agent1\", \"agent2\"]}\n }\n}\n```\n\n---\n\n### DataStore (Memory)\n\nThe `DataStore` abstract class handles conversation persistence. Messages are saved after each `generateResponse` call, and loaded as context for future prompts.\n\n**Built-in implementations:**\n\n| Package | DataStore | Use Case |\n|---|---|---|\n| `agenix` | `DataStore.inMemory()` | Testing, prototyping, or non-persistent apps |\n| `agenix_firebase` | `FirebaseDataStore()` | Production apps with Firebase backend |\n\n**Firebase setup:** Add `agenix_firebase` to your pubspec, ensure `Firebase.initializeApp()` is called before creating the data store, and sign in a user via `firebase_auth`.\n\n**Implementing a custom DataStore:**\n\n```dart\nclass PostgresDataStore extends DataStore {\n @override\n Future<void> saveMessage(String convoId, AgentMessage msg, {Object? metaData}) async {\n // INSERT INTO messages ...\n }\n\n @override\n Future<List<AgentMessage>> getMessages(String conversationId, {int? limit, Object? metaData}) async {\n // SELECT * FROM messages WHERE convo_id = ? ORDER BY generated_at LIMIT ?\n }\n\n @override\n Future<void> deleteConversation(String conversationId, {Object? metaData}) async {\n // DELETE FROM messages WHERE convo_id = ?\n }\n\n @override\n Future<List<Conversation>> getConversations({Object? metaData}) async {\n // SELECT DISTINCT convo_id, last_message, last_message_time FROM ...\n }\n}\n```\n\n---\n\n### Tools\n\nTools let the agent perform actions beyond conversation — API calls, database queries, calculations, anything.\n\n**Lifecycle:**\n\n```\nUser Message → LLM decides tool is needed → Agent runs tool → Tool returns ToolResponse\n → Agent either returns result OR iterates (up to 5 rounds of tool calls)\n```\n\n#### Tool without parameters\n\n```dart\nclass NewsTool extends Tool {\n NewsTool() : super(\n name: 'news_tool',\n description: 'Fetches the latest news headlines.',\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final headlines = await NewsApi.fetchHeadlines();\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Here are today\\'s headlines: ${headlines.join(\", \")}',\n needsFurtherReasoning: true, // Agent will synthesize a natural-language answer\n );\n }\n}\n```\n\n#### Tool with parameters\n\n```dart\nclass WeatherTool extends Tool {\n WeatherTool() : super(\n name: 'weather_tool',\n description: 'Gets current weather for a given location.',\n parameters: [\n ParameterSpecification(\n name: 'location',\n type: 'string',\n description: 'City name or coordinates.',\n required: true,\n ),\n ParameterSpecification(\n name: 'units',\n type: 'string',\n description: 'Temperature unit.',\n required: false,\n defaultValue: 'celsius',\n enumValues: ['celsius', 'fahrenheit'],\n ),\n ],\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final location = params['location'] as String;\n final units = params['units'] as String? ?? 'celsius';\n final weather = await WeatherApi.get(location, units: units);\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Weather in $location: ${weather.temp}° ${weather.condition}',\n data: weather.toMap(), // Optional structured data for chaining\n );\n }\n}\n```\n\n#### Registering tools\n\n```dart\nagent.toolRegistry.registerTool(NewsTool());\nagent.toolRegistry.registerTool(WeatherTool());\n\n// Dynamically remove a tool\nagent.toolRegistry.unregisterTool('weather_tool');\n```\n\n#### ToolResponse flags\n\n| Field | Type | Description |\n|---|---|---|\n| `toolName` | `String` | Name of the tool that produced this response |\n| `isRequestSuccessful` | `bool` | Whether the tool operation succeeded |\n| `message` | `String` | Human-readable result shown to the user |\n| `data` | `Map?` | Structured data for agent chaining or further reasoning |\n| `needsFurtherReasoning` | `bool` | When `true`, the agent makes a second LLM call to synthesize the tool output into a natural-language answer |\n\n---\n\n### Multi-Agent Orchestration\n\nWhen the LLM determines a task requires multiple specialists, it returns an `agents_chain`. Agenix automatically delegates sub-tasks across agents, passing each agent's output as input to the next.\n\n```dart\n// Create specialized agents\nfinal newsAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'News Agent',\n role: 'Fetches and summarizes news articles.',\n);\n\nfinal favouritesAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Favourites Agent',\n role: 'Manages user favourites: add, remove, and list.',\n);\n\nfinal orchestrator = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Orchestrator',\n role: 'Main user-facing agent. Delegates to News Agent and Favourites Agent.',\n);\n\n// Register tools on each agent as needed\nnewsAgent.toolRegistry.registerTool(NewsTool());\nfavouritesAgent.toolRegistry.registerTool(AddFavouriteTool());\nfavouritesAgent.toolRegistry.registerTool(ListFavouritesTool());\n```\n\n**How chaining works:**\n\n```\nUser: \"Save the top headline to my favourites\"\n\n┌──────────────┐ ┌────────────┐ ┌──────────────────┐\n│ Orchestrator │────►│ News Agent │────►│ Favourites Agent │\n│ │ │ │ │ │\n│ Decides chain│ │ Fetches │ │ Saves headline │\n│ [News, Favs] │ │ headlines │ │ to favourites │\n└──────────────┘ └────────────┘ └──────────────────┘\n │ │\n │ output passes as │\n │ input to next ──────┘\n │\n ▼\n Final response\n back to user\n```\n\n**Safety guardrails:**\n\n- **Cycle detection** — if an agent appears twice in the same chain, a `ConfigException` is thrown\n- **Depth limiting** — chains are capped at 5 levels deep (`kMaxChainDepth`)\n\n---\n\n### Agent Scopes\n\nBy default, all agents register in `AgentScope.global` and can discover each other for chaining. Use custom scopes to isolate agent groups:\n\n```dart\n// Isolated scope for testing\nfinal testScope = AgentScope();\n\nfinal agentA = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent A',\n role: 'Test agent A.',\n scope: testScope,\n);\n\nfinal agentB = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent B',\n role: 'Test agent B.',\n scope: testScope,\n);\n\n// Agents in testScope can chain to each other, but NOT to agents in AgentScope.global\n```\n\n**RegistrationPolicy** controls what happens when an agent name collides:\n\n| Policy | Behavior |\n|---|---|\n| `throwIfExists` | Throws `ConfigException` (default — catches accidental duplicates) |\n| `replace` | Silently replaces the existing agent |\n| `ignore` | Keeps the existing agent, discards the new one |\n\n---\n\n## Error Handling\n\nAgenix uses a sealed exception hierarchy. Every exception is an `AgenixException`, so you can exhaustively match on the type:\n\n```dart\ntry {\n final response = await agent.generateResponse(\n convoId: 'convo-1',\n userMessage: message,\n );\n} on LlmTimeoutException catch (e) {\n // LLM call exceeded the configured timeout\n} on ResponseParseException catch (e) {\n // LLM returned malformed output after retries\n print('Raw output: ${e.rawOutput}');\n} on ToolNotFoundException catch (e) {\n // LLM referenced a tool that isn't registered\n print('Missing tool: ${e.toolName}');\n} on ToolExecutionException catch (e) {\n // A registered tool threw during execution\n print('Tool ${e.toolName} failed: ${e.message}');\n} on AgentNotFoundException catch (e) {\n // Agent chain referenced a non-existent agent\n} on DataStoreException catch (e) {\n // Persistence operation failed\n} on NotAuthenticatedException {\n // Firebase operation without a signed-in user (from agenix_firebase)\n} on ConfigException catch (e) {\n // Invalid configuration (bad system_data.json, duplicate agent name, etc.)\n}\n```\n\n**FailureMode** controls the behavior at the `generateResponse` boundary:\n\n| Mode | Behavior |\n|---|---|\n| `FailureMode.gracefulMessage` | Returns an `AgentMessage` with `isError: true` (default — safe for UI) |\n| `FailureMode.throwError` | Rethrows the typed `AgenixException` (use when you want full control) |\n\nThe `onError` callback fires in both modes, so you can always log errors centrally:\n\n```dart\nfinal agent = await Agent.create(\n // ...\n failureMode: FailureMode.gracefulMessage,\n onError: (error, stack) => crashlytics.recordError(error, stack),\n);\n```\n\n---\n\n## API Reference\n\n### Exported Classes\n\n| Class | Package | Description |\n|---|---|---|\n| `Agent` | `agenix` | Core agent with LLM, memory, tools, and multi-agent orchestration |\n| `AgentScope` | `agenix` | Isolates groups of agents that can discover and chain to each other |\n| `LLM` | `agenix` | Abstract interface for language model providers |\n| `LlmConfig` | `agenix` | Provider-neutral generation settings (temperature, tokens, timeout, etc.) |\n| `DataStore` | `agenix` | Abstract interface for conversation persistence |\n| `AgentMessage` | `agenix` | A message in a conversation (user or agent) |\n| `Conversation` | `agenix` | Summary of a conversation (last message, timestamp, ID) |\n| `Tool` | `agenix` | Abstract class to extend for custom tools |\n| `ParameterSpecification` | `agenix` | Defines a tool parameter (name, type, required, default, enum) |\n| `ToolResponse` | `agenix` | Result returned from a tool execution |\n| `ToolRegistry` | `agenix` | Per-agent registry for managing available tools |\n| `FirebaseDataStore` | `agenix_firebase` | Firestore + Storage + Auth data store implementation |\n\n### Exported Enums\n\n| Enum | Values | Description |\n|---|---|---|\n| `FailureMode` | `throwError`, `gracefulMessage` | Controls error surfacing behavior |\n| `RegistrationPolicy` | `throwIfExists`, `replace`, `ignore` | Controls duplicate agent name handling |\n\n### Sealed Exception Hierarchy\n\n```\nAgenixException (sealed)\n├── LlmException\n│ └── LlmTimeoutException\n├── ResponseParseException\n├── ToolNotFoundException\n├── ToolExecutionException\n├── AgentNotFoundException\n├── DataStoreException\n│ └── NotAuthenticatedException\n└── ConfigException\n```\n\n### Internal Constants\n\n| Constant | Value | Description |\n|---|---|---|\n| `kMaxToolIterations` | `5` | Max tool→observe→re-prompt cycles per turn |\n| `kMaxParseRetries` | `2` | Max corrective re-prompts for malformed JSON |\n| `kMaxChainDepth` | `5` | Max depth for agent chain delegation |\n\n---\n\n## Usage Architectures\n\n### Single-Agent Chat App\n\nThe simplest setup — one agent handling all user interactions.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐ ┌───────────┐\n│ Flutter │────►│ Agent │────►│ LLM │ │ DataStore │\n│ UI │◄────│ │◄────│ │ │(InMemory/ │\n└──────────┘ │ │ └───────┘ │ Firebase) │\n │ │──── save/load ────►└───────────┘\n └───────┘\n```\n\nBest for: chatbots, Q&A apps, customer support widgets.\n\n### Agent + Tools (API Integration)\n\nThe agent can call external APIs through tools.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐\n│ Flutter │────►│ Agent │────►│ LLM │\n│ UI │◄────│ │◄────│ │\n└──────────┘ │ │ └───────┘\n │ │\n │ ToolRegistry\n │ ├── WeatherTool ──► Weather API\n │ ├── NewsTool ──► News API\n │ └── DbTool ──► Database\n └───────┘\n```\n\nBest for: apps where the agent needs to fetch real-time data or trigger actions.\n\n### Multi-Agent Orchestration\n\nMultiple specialized agents collaborating on complex tasks.\n\n```\n┌──────────┐ ┌──────────────┐\n│ Flutter │────►│ Orchestrator │\n│ UI │◄────│ │\n└──────────┘ └──────┬───────┘\n │ delegates via agents_chain\n ┌─────────┼─────────┐\n ▼ ▼ ▼\n ┌─────────┐ ┌────────┐ ┌───────────┐\n │ Search │ │ Booking│ │ Favourites│\n │ Agent │ │ Agent │ │ Agent │\n │ + tools │ │ + tools│ │ + tools │\n └─────────┘ └────────┘ └───────────┘\n```\n\nBest for: complex platforms where different domains require specialized knowledge and tools.\n\n### Testing / Prototyping Setup\n\nUse `InMemoryDataStore` and scoped agents for fast, isolated development.\n\n```dart\nfinal scope = AgentScope();\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(), // No Firebase needed\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Test Agent',\n role: 'Agent under test.',\n scope: scope, // Isolated from production agents\n);\n```\n\n---\n\n## Examples\n\n| Example | Description |\n|---|---|\n| [Multi-Agent System](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system) | Three agents (Orchestrator, News, Favourites) working together |\n| [Firebase Example](packages/agenix_firebase/example/) | Single agent with tools and Firebase persistence |\n| [Custom DataStore](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example) | Implementing your own persistence backend |\n\n---\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "3e240db42f9c5c85b031b27ebd18562b78e626f8": {"t": 1785764704.9284453, "n": 22995, "h": "2aa9faee5b3ee8eb", "c": "![Agenix Banner](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n# Agenix\n\n<p align=\"center\">\n <a href=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml\"><img src=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub\"></a>\n <a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n <a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Stars\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\nA Flutter package for building AI agents with memory, tools, and multi-agent orchestration. Define your agent's personality, give it tools, and let it handle conversations — including delegating sub-tasks across a chain of specialized agents.\n\n<video src=\"https://github.com/user-attachments/assets/d92e6763-60bd-42a1-8d7c-d1e5535656bf\" width=\"100%\" controls></video>\n\n---\n\n## Packages\n\nThis is a monorepo containing the following packages:\n\n| Package | pub.dev | Description |\n|---|---|---|\n| [`agenix`](packages/agenix/) | [![Pub](https://img.shields.io/pub/v/agenix.svg)](https://pub.dev/packages/agenix) | Core — agents, tools, LLM interface, in-memory data store |\n| [`agenix_firebase`](packages/agenix_firebase/) | [![Pub](https://img.shields.io/pub/v/agenix_firebase.svg)](https://pub.dev/packages/agenix_firebase) | Firebase (Firestore + Storage + Auth) data store backend |\n\n### Choosing a data store\n\nThe core `agenix` package ships with `DataStore.inMemory()` — a zero-dependency store for testing and prototyping. For production persistence, add a backend package:\n\n```yaml\n# Core only (no Firebase):\ndependencies:\n agenix: ^4.1.0\n\n# With Firebase persistence:\ndependencies:\n agenix: ^4.1.0\n agenix_firebase: ^1.0.2\n```\n\n```dart\nimport 'package:agenix/agenix.dart';\n\n// In-memory (no extra package needed):\nfinal store = DataStore.inMemory();\n\n// Firebase (requires agenix_firebase):\nimport 'package:agenix_firebase/agenix_firebase.dart';\nfinal store = FirebaseDataStore();\n```\n\nThis separation means apps that don't use Firebase never pull in the Firebase SDK.\n\n> **Migrating from agenix 3.x?** Replace `DataStore.firestoreDataStore()` with `FirebaseDataStore()` and add `agenix_firebase` to your pubspec. See the [core CHANGELOG](packages/agenix/CHANGELOG.md) for details.\n\n---\n\n## Table of Contents\n\n- [Architecture Overview](#architecture-overview)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Concepts](#core-concepts)\n - [Agent](#agent)\n - [LLM](#llm)\n - [DataStore (Memory)](#datastore-memory)\n - [Tools](#tools)\n - [Multi-Agent Orchestration](#multi-agent-orchestration)\n - [Agent Scopes](#agent-scopes)\n- [Error Handling](#error-handling)\n- [API Reference](#api-reference)\n- [Usage Architectures](#usage-architectures)\n- [Examples](#examples)\n- [Maintainers](#maintainers)\n\n---\n\n## Architecture Overview\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ Your Flutter App │\n│ │\n│ Agent.create(llm, dataStore, name, role) │\n│ │ │\n│ ▼ │\n│ ┌─────────┐ generateResponse() ┌───────────────┐ │\n│ │ Agent │ ◄─────────────────────► │ LLM │ │\n│ │ │ │ (Gemini / │ │\n│ │ │ │ Custom) │ │\n│ └────┬────┘ └───────────────┘ │\n│ │ │\n│ ┌────┴──────────────────────────┐ │\n│ │ │ │ │\n│ ▼ ▼ ▼ │\n│ ┌──────┐ ┌──────────┐ ┌────────────────┐ │\n│ │Tools │ │DataStore │ │Agent Registry │ │\n│ │ │ │(InMemory/ │ │(Multi-Agent │ │\n│ │ │ │ Firebase/ │ │ Orchestration) │ │\n│ │ │ │ Custom) │ │ │ │\n│ └──────┘ └──────────┘ └────────────────┘ │\n└─────────────────────────────────────────────────────────────┘\n```\n\nThe agent receives a user message, builds a structured prompt (including conversation history from the DataStore and available tools from the ToolRegistry), sends it to the LLM, and parses the response into one of three actions:\n\n1. **Direct response** — returns text to the user\n2. **Tool invocation** — runs one or more tools, optionally iterating up to 5 times before producing a final answer\n3. **Agent delegation** — hands the task to a chain of other agents, each passing its output to the next\n\n---\n\n## Installation\n\nAdd to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n agenix: ^4.1.0\n```\n\nIf you need Firebase persistence, also add:\n\n```yaml\n agenix_firebase: ^1.0.2\n```\n\nThen run:\n\n```bash\nflutter pub get\n```\n\n---\n\n## Quick Start\n\n### 1. Create system data\n\nCreate `assets/system_data.json` with your agent's personality and background knowledge:\n\n```json\n{\n \"name\": \"Lens\",\n \"role\": \"A helpful assistant for the Acme platform\",\n \"personality\": \"Friendly, concise, and knowledgeable\",\n \"instructions\": \"Always greet the user by name when possible\"\n}\n```\n\nAdd the asset to your `pubspec.yaml`:\n\n```yaml\nflutter:\n assets:\n - assets/system_data.json\n```\n\n### 2. Initialize the agent\n\n```dart\nimport 'package:agenix/agenix.dart';\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n ),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\nFor Firebase persistence:\n\n```dart\nimport 'package:agenix_firebase/agenix_firebase.dart';\n\nfinal agent = await Agent.create(\n dataStore: FirebaseDataStore(),\n llm: LLM.geminiLLM(apiKey: 'YOUR_API_KEY', modelName: 'gemini-2.0-flash'),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\n### 3. Generate a response\n\n```dart\nfinal response = await agent.generateResponse(\n convoId: 'conversation-1',\n userMessage: AgentMessage(\n content: 'What is the weather like today?',\n isFromAgent: false,\n generatedAt: DateTime.now(),\n ),\n);\n\nprint(response.content);\n```\n\n### 4. Run the app\n\n```bash\nflutter run -d chrome --dart-define=GEMINI_API_KEY=your-key-here\n```\n\n---\n\n## Core Concepts\n\n### Agent\n\nThe `Agent` is the central class. It wires together an LLM, a DataStore, a ToolRegistry, and an AgentScope.\n\n```dart\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Support Agent',\n role: 'Handles customer support queries for the e-commerce platform.',\n failureMode: FailureMode.throwError, // or FailureMode.gracefulMessage (default)\n onError: (error, stack) => logger.severe('Agent error', error, stack),\n scope: AgentScope.global, // default — or create isolated scopes\n registrationPolicy: RegistrationPolicy.throwIfExists, // default\n);\n```\n\n**Key methods:**\n\n| Method | Description |\n|---|---|\n| `generateResponse(convoId, userMessage)` | Send a user message and get back an `AgentMessage` from the agent |\n| `getMessages(conversationId)` | Retrieve all messages in a conversation |\n| `getAllConversations()` | List all conversations for the current user |\n| `deleteConversation(conversationId)` | Delete a conversation and its messages |\n| `dispose()` | Unregister the agent from its scope |\n\n**Parameters for `generateResponse`:**\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `convoId` | `String` | required | Conversation identifier |\n| `userMessage` | `AgentMessage` | required | The user's message |\n| `memoryLimit` | `int` | `10` | Max previous messages loaded as context |\n| `metaData` | `Object?` | `null` | Opaque pass-through for auth tokens, tenant IDs, etc. |\n\n---\n\n### LLM\n\nThe `LLM` abstract class defines the contract for language model providers. Agenix ships with Gemini; implement the interface for other providers.\n\n```dart\n// Built-in Gemini\nfinal llm = LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n config: LlmConfig(\n temperature: 0.2, // Low for structured JSON output\n maxOutputTokens: 2048,\n topP: 0.95,\n topK: 40,\n jsonMode: true, // Request native JSON output mode\n timeout: Duration(seconds: 60),\n ),\n);\n```\n\n**Implementing a custom LLM:**\n\n```dart\nclass MyCustomLLM implements LLM {\n @override\n final String modelId = 'my-model-v1';\n\n @override\n final LlmConfig config;\n\n MyCustomLLM({this.config = const LlmConfig()});\n\n @override\n Future<String> generate({\n required String prompt,\n String? systemInstruction,\n Uint8List? rawData,\n String mimeType = 'image/png',\n }) async {\n // Call your model API here\n // Must return a JSON string matching one of:\n // {\"response\": \"...\"}\n // {\"tools\": \"tool1, tool2\", \"parameters\": {...}}\n // {\"agents_chain\": [\"agent1\", \"agent2\"]}\n }\n}\n```\n\n---\n\n### DataStore (Memory)\n\nThe `DataStore` abstract class handles conversation persistence. Messages are saved after each `generateResponse` call, and loaded as context for future prompts.\n\n**Built-in implementations:**\n\n| Package | DataStore | Use Case |\n|---|---|---|\n| `agenix` | `DataStore.inMemory()` | Testing, prototyping, or non-persistent apps |\n| `agenix_firebase` | `FirebaseDataStore()` | Production apps with Firebase backend |\n\n**Firebase setup:** Add `agenix_firebase` to your pubspec, ensure `Firebase.initializeApp()` is called before creating the data store, and sign in a user via `firebase_auth`.\n\n**Implementing a custom DataStore:**\n\n```dart\nclass PostgresDataStore extends DataStore {\n @override\n Future<void> saveMessage(String convoId, AgentMessage msg, {Object? metaData}) async {\n // INSERT INTO messages ...\n }\n\n @override\n Future<List<AgentMessage>> getMessages(String conversationId, {int? limit, Object? metaData}) async {\n // SELECT * FROM messages WHERE convo_id = ? ORDER BY generated_at LIMIT ?\n }\n\n @override\n Future<void> deleteConversation(String conversationId, {Object? metaData}) async {\n // DELETE FROM messages WHERE convo_id = ?\n }\n\n @override\n Future<List<Conversation>> getConversations({Object? metaData}) async {\n // SELECT DISTINCT convo_id, last_message, last_message_time FROM ...\n }\n}\n```\n\n---\n\n### Tools\n\nTools let the agent perform actions beyond conversation — API calls, database queries, calculations, anything.\n\n**Lifecycle:**\n\n```\nUser Message → LLM decides tool is needed → Agent runs tool → Tool returns ToolResponse\n → Agent either returns result OR iterates (up to 5 rounds of tool calls)\n```\n\n#### Tool without parameters\n\n```dart\nclass NewsTool extends Tool {\n NewsTool() : super(\n name: 'news_tool',\n description: 'Fetches the latest news headlines.',\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final headlines = await NewsApi.fetchHeadlines();\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Here are today\\'s headlines: ${headlines.join(\", \")}',\n needsFurtherReasoning: true, // Agent will synthesize a natural-language answer\n );\n }\n}\n```\n\n#### Tool with parameters\n\n```dart\nclass WeatherTool extends Tool {\n WeatherTool() : super(\n name: 'weather_tool',\n description: 'Gets current weather for a given location.',\n parameters: [\n ParameterSpecification(\n name: 'location',\n type: 'string',\n description: 'City name or coordinates.',\n required: true,\n ),\n ParameterSpecification(\n name: 'units',\n type: 'string',\n description: 'Temperature unit.',\n required: false,\n defaultValue: 'celsius',\n enumValues: ['celsius', 'fahrenheit'],\n ),\n ],\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final location = params['location'] as String;\n final units = params['units'] as String? ?? 'celsius';\n final weather = await WeatherApi.get(location, units: units);\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Weather in $location: ${weather.temp}° ${weather.condition}',\n data: weather.toMap(), // Optional structured data for chaining\n );\n }\n}\n```\n\n#### Registering tools\n\n```dart\nagent.toolRegistry.registerTool(NewsTool());\nagent.toolRegistry.registerTool(WeatherTool());\n\n// Dynamically remove a tool\nagent.toolRegistry.unregisterTool('weather_tool');\n```\n\n#### ToolResponse flags\n\n| Field | Type | Description |\n|---|---|---|\n| `toolName` | `String` | Name of the tool that produced this response |\n| `isRequestSuccessful` | `bool` | Whether the tool operation succeeded |\n| `message` | `String` | Human-readable result shown to the user |\n| `data` | `Map?` | Structured data for agent chaining or further reasoning |\n| `needsFurtherReasoning` | `bool` | When `true`, the agent makes a second LLM call to synthesize the tool output into a natural-language answer |\n\n---\n\n### Multi-Agent Orchestration\n\nWhen the LLM determines a task requires multiple specialists, it returns an `agents_chain`. Agenix automatically delegates sub-tasks across agents, passing each agent's output as input to the next.\n\n```dart\n// Create specialized agents\nfinal newsAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'News Agent',\n role: 'Fetches and summarizes news articles.',\n);\n\nfinal favouritesAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Favourites Agent',\n role: 'Manages user favourites: add, remove, and list.',\n);\n\nfinal orchestrator = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Orchestrator',\n role: 'Main user-facing agent. Delegates to News Agent and Favourites Agent.',\n);\n\n// Register tools on each agent as needed\nnewsAgent.toolRegistry.registerTool(NewsTool());\nfavouritesAgent.toolRegistry.registerTool(AddFavouriteTool());\nfavouritesAgent.toolRegistry.registerTool(ListFavouritesTool());\n```\n\n**How chaining works:**\n\n```\nUser: \"Save the top headline to my favourites\"\n\n┌──────────────┐ ┌────────────┐ ┌──────────────────┐\n│ Orchestrator │────►│ News Agent │────►│ Favourites Agent │\n│ │ │ │ │ │\n│ Decides chain│ │ Fetches │ │ Saves headline │\n│ [News, Favs] │ │ headlines │ │ to favourites │\n└──────────────┘ └────────────┘ └──────────────────┘\n │ │\n │ output passes as │\n │ input to next ──────┘\n │\n ▼\n Final response\n back to user\n```\n\n**Safety guardrails:**\n\n- **Cycle detection** — if an agent appears twice in the same chain, a `ConfigException` is thrown\n- **Depth limiting** — chains are capped at 5 levels deep (`kMaxChainDepth`)\n\n---\n\n### Agent Scopes\n\nBy default, all agents register in `AgentScope.global` and can discover each other for chaining. Use custom scopes to isolate agent groups:\n\n```dart\n// Isolated scope for testing\nfinal testScope = AgentScope();\n\nfinal agentA = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent A',\n role: 'Test agent A.',\n scope: testScope,\n);\n\nfinal agentB = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent B',\n role: 'Test agent B.',\n scope: testScope,\n);\n\n// Agents in testScope can chain to each other, but NOT to agents in AgentScope.global\n```\n\n**RegistrationPolicy** controls what happens when an agent name collides:\n\n| Policy | Behavior |\n|---|---|\n| `throwIfExists` | Throws `ConfigException` (default — catches accidental duplicates) |\n| `replace` | Silently replaces the existing agent |\n| `ignore` | Keeps the existing agent, discards the new one |\n\n---\n\n## Error Handling\n\nAgenix uses a sealed exception hierarchy. Every exception is an `AgenixException`, so you can exhaustively match on the type:\n\n```dart\ntry {\n final response = await agent.generateResponse(\n convoId: 'convo-1',\n userMessage: message,\n );\n} on LlmTimeoutException catch (e) {\n // LLM call exceeded the configured timeout\n} on ResponseParseException catch (e) {\n // LLM returned malformed output after retries\n print('Raw output: ${e.rawOutput}');\n} on ToolNotFoundException catch (e) {\n // LLM referenced a tool that isn't registered\n print('Missing tool: ${e.toolName}');\n} on ToolExecutionException catch (e) {\n // A registered tool threw during execution\n print('Tool ${e.toolName} failed: ${e.message}');\n} on AgentNotFoundException catch (e) {\n // Agent chain referenced a non-existent agent\n} on DataStoreException catch (e) {\n // Persistence operation failed\n} on NotAuthenticatedException {\n // Firebase operation without a signed-in user (from agenix_firebase)\n} on ConfigException catch (e) {\n // Invalid configuration (bad system_data.json, duplicate agent name, etc.)\n}\n```\n\n**FailureMode** controls the behavior at the `generateResponse` boundary:\n\n| Mode | Behavior |\n|---|---|\n| `FailureMode.gracefulMessage` | Returns an `AgentMessage` with `isError: true` (default — safe for UI) |\n| `FailureMode.throwError` | Rethrows the typed `AgenixException` (use when you want full control) |\n\nThe `onError` callback fires in both modes, so you can always log errors centrally:\n\n```dart\nfinal agent = await Agent.create(\n // ...\n failureMode: FailureMode.gracefulMessage,\n onError: (error, stack) => crashlytics.recordError(error, stack),\n);\n```\n\n---\n\n## API Reference\n\n### Exported Classes\n\n| Class | Package | Description |\n|---|---|---|\n| `Agent` | `agenix` | Core agent with LLM, memory, tools, and multi-agent orchestration |\n| `AgentScope` | `agenix` | Isolates groups of agents that can discover and chain to each other |\n| `LLM` | `agenix` | Abstract interface for language model providers |\n| `LlmConfig` | `agenix` | Provider-neutral generation settings (temperature, tokens, timeout, etc.) |\n| `DataStore` | `agenix` | Abstract interface for conversation persistence |\n| `AgentMessage` | `agenix` | A message in a conversation (user or agent) |\n| `Conversation` | `agenix` | Summary of a conversation (last message, timestamp, ID) |\n| `Tool` | `agenix` | Abstract class to extend for custom tools |\n| `ParameterSpecification` | `agenix` | Defines a tool parameter (name, type, required, default, enum) |\n| `ToolResponse` | `agenix` | Result returned from a tool execution |\n| `ToolRegistry` | `agenix` | Per-agent registry for managing available tools |\n| `FirebaseDataStore` | `agenix_firebase` | Firestore + Storage + Auth data store implementation |\n\n### Exported Enums\n\n| Enum | Values | Description |\n|---|---|---|\n| `FailureMode` | `throwError`, `gracefulMessage` | Controls error surfacing behavior |\n| `RegistrationPolicy` | `throwIfExists`, `replace`, `ignore` | Controls duplicate agent name handling |\n\n### Sealed Exception Hierarchy\n\n```\nAgenixException (sealed)\n├── LlmException\n│ └── LlmTimeoutException\n├── ResponseParseException\n├── ToolNotFoundException\n├── ToolExecutionException\n├── AgentNotFoundException\n├── DataStoreException\n│ └── NotAuthenticatedException\n└── ConfigException\n```\n\n### Internal Constants\n\n| Constant | Value | Description |\n|---|---|---|\n| `kMaxToolIterations` | `5` | Max tool→observe→re-prompt cycles per turn |\n| `kMaxParseRetries` | `2` | Max corrective re-prompts for malformed JSON |\n| `kMaxChainDepth` | `5` | Max depth for agent chain delegation |\n\n---\n\n## Usage Architectures\n\n### Single-Agent Chat App\n\nThe simplest setup — one agent handling all user interactions.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐ ┌───────────┐\n│ Flutter │────►│ Agent │────►│ LLM │ │ DataStore │\n│ UI │◄────│ │◄────│ │ │(InMemory/ │\n└──────────┘ │ │ └───────┘ │ Firebase) │\n │ │──── save/load ────►└───────────┘\n └───────┘\n```\n\nBest for: chatbots, Q&A apps, customer support widgets.\n\n### Agent + Tools (API Integration)\n\nThe agent can call external APIs through tools.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐\n│ Flutter │────►│ Agent │────►│ LLM │\n│ UI │◄────│ │◄────│ │\n└──────────┘ │ │ └───────┘\n │ │\n │ ToolRegistry\n │ ├── WeatherTool ──► Weather API\n │ ├── NewsTool ──► News API\n │ └── DbTool ──► Database\n └───────┘\n```\n\nBest for: apps where the agent needs to fetch real-time data or trigger actions.\n\n### Multi-Agent Orchestration\n\nMultiple specialized agents collaborating on complex tasks.\n\n```\n┌──────────┐ ┌──────────────┐\n│ Flutter │────►│ Orchestrator │\n│ UI │◄────│ │\n└──────────┘ └──────┬───────┘\n │ delegates via agents_chain\n ┌─────────┼─────────┐\n ▼ ▼ ▼\n ┌─────────┐ ┌────────┐ ┌───────────┐\n │ Search │ │ Booking│ │ Favourites│\n │ Agent │ │ Agent │ │ Agent │\n │ + tools │ │ + tools│ │ + tools │\n └─────────┘ └────────┘ └───────────┘\n```\n\nBest for: complex platforms where different domains require specialized knowledge and tools.\n\n### Testing / Prototyping Setup\n\nUse `InMemoryDataStore` and scoped agents for fast, isolated development.\n\n```dart\nfinal scope = AgentScope();\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(), // No Firebase needed\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Test Agent',\n role: 'Agent under test.',\n scope: scope, // Isolated from production agents\n);\n```\n\n---\n\n## Examples\n\n| Example | Description |\n|---|---|\n| [Multi-Agent System](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system) | Three agents (Orchestrator, News, Favourites) working together |\n| [Firebase Example](packages/agenix_firebase/example/) | Single agent with tools and Firebase persistence |\n| [Custom DataStore](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example) | Implementing your own persistence backend |\n\n---\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "183096940158b04f11a70fb36ee83b7e94d4ace4": {"t": 1785764706.928273, "n": 22995, "h": "b5594657a538361d", "c": "![Agenix Banner](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n# Agenix\n\n<p align=\"center\">\n <a href=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml\"><img src=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub\"></a>\n <a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n <a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Stars\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\nA Flutter package for building AI agents with memory, tools, and multi-agent orchestration. Define your agent's personality, give it tools, and let it handle conversations — including delegating sub-tasks across a chain of specialized agents.\n\n<video src=\"https://github.com/user-attachments/assets/d92e6763-60bd-42a1-8d7c-d1e5535656bf\" width=\"100%\" controls></video>\n\n---\n\n## Packages\n\nThis is a monorepo containing the following packages:\n\n| Package | pub.dev | Description |\n|---|---|---|\n| [`agenix`](packages/agenix/) | [![Pub](https://img.shields.io/pub/v/agenix.svg)](https://pub.dev/packages/agenix) | Core — agents, tools, LLM interface, in-memory data store |\n| [`agenix_firebase`](packages/agenix_firebase/) | [![Pub](https://img.shields.io/pub/v/agenix_firebase.svg)](https://pub.dev/packages/agenix_firebase) | Firebase (Firestore + Storage + Auth) data store backend |\n\n### Choosing a data store\n\nThe core `agenix` package ships with `DataStore.inMemory()` — a zero-dependency store for testing and prototyping. For production persistence, add a backend package:\n\n```yaml\n# Core only (no Firebase):\ndependencies:\n agenix: ^4.0.0\n\n# With Firebase persistence:\ndependencies:\n agenix: ^4.0.0\n agenix_firebase: ^1.0.0\n```\n\n```dart\nimport 'package:agenix/agenix.dart';\n\n// In-memory (no extra package needed):\nfinal store = DataStore.inMemory();\n\n// Firebase (requires agenix_firebase):\nimport 'package:agenix_firebase/agenix_firebase.dart';\nfinal store = FirebaseDataStore();\n```\n\nThis separation means apps that don't use Firebase never pull in the Firebase SDK.\n\n> **Migrating from agenix 3.x?** Replace `DataStore.firestoreDataStore()` with `FirebaseDataStore()` and add `agenix_firebase` to your pubspec. See the [core CHANGELOG](packages/agenix/CHANGELOG.md) for details.\n\n---\n\n## Table of Contents\n\n- [Architecture Overview](#architecture-overview)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Concepts](#core-concepts)\n - [Agent](#agent)\n - [LLM](#llm)\n - [DataStore (Memory)](#datastore-memory)\n - [Tools](#tools)\n - [Multi-Agent Orchestration](#multi-agent-orchestration)\n - [Agent Scopes](#agent-scopes)\n- [Error Handling](#error-handling)\n- [API Reference](#api-reference)\n- [Usage Architectures](#usage-architectures)\n- [Examples](#examples)\n- [Maintainers](#maintainers)\n\n---\n\n## Architecture Overview\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ Your Flutter App │\n│ │\n│ Agent.create(llm, dataStore, name, role) │\n│ │ │\n│ ▼ │\n│ ┌─────────┐ generateResponse() ┌───────────────┐ │\n│ │ Agent │ ◄─────────────────────► │ LLM │ │\n│ │ │ │ (Gemini / │ │\n│ │ │ │ Custom) │ │\n│ └────┬────┘ └───────────────┘ │\n│ │ │\n│ ┌────┴──────────────────────────┐ │\n│ │ │ │ │\n│ ▼ ▼ ▼ │\n│ ┌──────┐ ┌──────────┐ ┌────────────────┐ │\n│ │Tools │ │DataStore │ │Agent Registry │ │\n│ │ │ │(InMemory/ │ │(Multi-Agent │ │\n│ │ │ │ Firebase/ │ │ Orchestration) │ │\n│ │ │ │ Custom) │ │ │ │\n│ └──────┘ └──────────┘ └────────────────┘ │\n└─────────────────────────────────────────────────────────────┘\n```\n\nThe agent receives a user message, builds a structured prompt (including conversation history from the DataStore and available tools from the ToolRegistry), sends it to the LLM, and parses the response into one of three actions:\n\n1. **Direct response** — returns text to the user\n2. **Tool invocation** — runs one or more tools, optionally iterating up to 5 times before producing a final answer\n3. **Agent delegation** — hands the task to a chain of other agents, each passing its output to the next\n\n---\n\n## Installation\n\nAdd to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n agenix: ^4.0.0\n```\n\nIf you need Firebase persistence, also add:\n\n```yaml\n agenix_firebase: ^1.0.0\n```\n\nThen run:\n\n```bash\nflutter pub get\n```\n\n---\n\n## Quick Start\n\n### 1. Create system data\n\nCreate `assets/system_data.json` with your agent's personality and background knowledge:\n\n```json\n{\n \"name\": \"Lens\",\n \"role\": \"A helpful assistant for the Acme platform\",\n \"personality\": \"Friendly, concise, and knowledgeable\",\n \"instructions\": \"Always greet the user by name when possible\"\n}\n```\n\nAdd the asset to your `pubspec.yaml`:\n\n```yaml\nflutter:\n assets:\n - assets/system_data.json\n```\n\n### 2. Initialize the agent\n\n```dart\nimport 'package:agenix/agenix.dart';\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n ),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\nFor Firebase persistence:\n\n```dart\nimport 'package:agenix_firebase/agenix_firebase.dart';\n\nfinal agent = await Agent.create(\n dataStore: FirebaseDataStore(),\n llm: LLM.geminiLLM(apiKey: 'YOUR_API_KEY', modelName: 'gemini-2.0-flash'),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\n### 3. Generate a response\n\n```dart\nfinal response = await agent.generateResponse(\n convoId: 'conversation-1',\n userMessage: AgentMessage(\n content: 'What is the weather like today?',\n isFromAgent: false,\n generatedAt: DateTime.now(),\n ),\n);\n\nprint(response.content);\n```\n\n### 4. Run the app\n\n```bash\nflutter run -d chrome --dart-define=GEMINI_API_KEY=your-key-here\n```\n\n---\n\n## Core Concepts\n\n### Agent\n\nThe `Agent` is the central class. It wires together an LLM, a DataStore, a ToolRegistry, and an AgentScope.\n\n```dart\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Support Agent',\n role: 'Handles customer support queries for the e-commerce platform.',\n failureMode: FailureMode.throwError, // or FailureMode.gracefulMessage (default)\n onError: (error, stack) => logger.severe('Agent error', error, stack),\n scope: AgentScope.global, // default — or create isolated scopes\n registrationPolicy: RegistrationPolicy.throwIfExists, // default\n);\n```\n\n**Key methods:**\n\n| Method | Description |\n|---|---|\n| `generateResponse(convoId, userMessage)` | Send a user message and get back an `AgentMessage` from the agent |\n| `getMessages(conversationId)` | Retrieve all messages in a conversation |\n| `getAllConversations()` | List all conversations for the current user |\n| `deleteConversation(conversationId)` | Delete a conversation and its messages |\n| `dispose()` | Unregister the agent from its scope |\n\n**Parameters for `generateResponse`:**\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `convoId` | `String` | required | Conversation identifier |\n| `userMessage` | `AgentMessage` | required | The user's message |\n| `memoryLimit` | `int` | `10` | Max previous messages loaded as context |\n| `metaData` | `Object?` | `null` | Opaque pass-through for auth tokens, tenant IDs, etc. |\n\n---\n\n### LLM\n\nThe `LLM` abstract class defines the contract for language model providers. Agenix ships with Gemini; implement the interface for other providers.\n\n```dart\n// Built-in Gemini\nfinal llm = LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n config: LlmConfig(\n temperature: 0.2, // Low for structured JSON output\n maxOutputTokens: 2048,\n topP: 0.95,\n topK: 40,\n jsonMode: true, // Request native JSON output mode\n timeout: Duration(seconds: 60),\n ),\n);\n```\n\n**Implementing a custom LLM:**\n\n```dart\nclass MyCustomLLM implements LLM {\n @override\n final String modelId = 'my-model-v1';\n\n @override\n final LlmConfig config;\n\n MyCustomLLM({this.config = const LlmConfig()});\n\n @override\n Future<String> generate({\n required String prompt,\n String? systemInstruction,\n Uint8List? rawData,\n String mimeType = 'image/png',\n }) async {\n // Call your model API here\n // Must return a JSON string matching one of:\n // {\"response\": \"...\"}\n // {\"tools\": \"tool1, tool2\", \"parameters\": {...}}\n // {\"agents_chain\": [\"agent1\", \"agent2\"]}\n }\n}\n```\n\n---\n\n### DataStore (Memory)\n\nThe `DataStore` abstract class handles conversation persistence. Messages are saved after each `generateResponse` call, and loaded as context for future prompts.\n\n**Built-in implementations:**\n\n| Package | DataStore | Use Case |\n|---|---|---|\n| `agenix` | `DataStore.inMemory()` | Testing, prototyping, or non-persistent apps |\n| `agenix_firebase` | `FirebaseDataStore()` | Production apps with Firebase backend |\n\n**Firebase setup:** Add `agenix_firebase` to your pubspec, ensure `Firebase.initializeApp()` is called before creating the data store, and sign in a user via `firebase_auth`.\n\n**Implementing a custom DataStore:**\n\n```dart\nclass PostgresDataStore extends DataStore {\n @override\n Future<void> saveMessage(String convoId, AgentMessage msg, {Object? metaData}) async {\n // INSERT INTO messages ...\n }\n\n @override\n Future<List<AgentMessage>> getMessages(String conversationId, {int? limit, Object? metaData}) async {\n // SELECT * FROM messages WHERE convo_id = ? ORDER BY generated_at LIMIT ?\n }\n\n @override\n Future<void> deleteConversation(String conversationId, {Object? metaData}) async {\n // DELETE FROM messages WHERE convo_id = ?\n }\n\n @override\n Future<List<Conversation>> getConversations({Object? metaData}) async {\n // SELECT DISTINCT convo_id, last_message, last_message_time FROM ...\n }\n}\n```\n\n---\n\n### Tools\n\nTools let the agent perform actions beyond conversation — API calls, database queries, calculations, anything.\n\n**Lifecycle:**\n\n```\nUser Message → LLM decides tool is needed → Agent runs tool → Tool returns ToolResponse\n → Agent either returns result OR iterates (up to 5 rounds of tool calls)\n```\n\n#### Tool without parameters\n\n```dart\nclass NewsTool extends Tool {\n NewsTool() : super(\n name: 'news_tool',\n description: 'Fetches the latest news headlines.',\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final headlines = await NewsApi.fetchHeadlines();\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Here are today\\'s headlines: ${headlines.join(\", \")}',\n needsFurtherReasoning: true, // Agent will synthesize a natural-language answer\n );\n }\n}\n```\n\n#### Tool with parameters\n\n```dart\nclass WeatherTool extends Tool {\n WeatherTool() : super(\n name: 'weather_tool',\n description: 'Gets current weather for a given location.',\n parameters: [\n ParameterSpecification(\n name: 'location',\n type: 'string',\n description: 'City name or coordinates.',\n required: true,\n ),\n ParameterSpecification(\n name: 'units',\n type: 'string',\n description: 'Temperature unit.',\n required: false,\n defaultValue: 'celsius',\n enumValues: ['celsius', 'fahrenheit'],\n ),\n ],\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final location = params['location'] as String;\n final units = params['units'] as String? ?? 'celsius';\n final weather = await WeatherApi.get(location, units: units);\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Weather in $location: ${weather.temp}° ${weather.condition}',\n data: weather.toMap(), // Optional structured data for chaining\n );\n }\n}\n```\n\n#### Registering tools\n\n```dart\nagent.toolRegistry.registerTool(NewsTool());\nagent.toolRegistry.registerTool(WeatherTool());\n\n// Dynamically remove a tool\nagent.toolRegistry.unregisterTool('weather_tool');\n```\n\n#### ToolResponse flags\n\n| Field | Type | Description |\n|---|---|---|\n| `toolName` | `String` | Name of the tool that produced this response |\n| `isRequestSuccessful` | `bool` | Whether the tool operation succeeded |\n| `message` | `String` | Human-readable result shown to the user |\n| `data` | `Map?` | Structured data for agent chaining or further reasoning |\n| `needsFurtherReasoning` | `bool` | When `true`, the agent makes a second LLM call to synthesize the tool output into a natural-language answer |\n\n---\n\n### Multi-Agent Orchestration\n\nWhen the LLM determines a task requires multiple specialists, it returns an `agents_chain`. Agenix automatically delegates sub-tasks across agents, passing each agent's output as input to the next.\n\n```dart\n// Create specialized agents\nfinal newsAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'News Agent',\n role: 'Fetches and summarizes news articles.',\n);\n\nfinal favouritesAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Favourites Agent',\n role: 'Manages user favourites: add, remove, and list.',\n);\n\nfinal orchestrator = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Orchestrator',\n role: 'Main user-facing agent. Delegates to News Agent and Favourites Agent.',\n);\n\n// Register tools on each agent as needed\nnewsAgent.toolRegistry.registerTool(NewsTool());\nfavouritesAgent.toolRegistry.registerTool(AddFavouriteTool());\nfavouritesAgent.toolRegistry.registerTool(ListFavouritesTool());\n```\n\n**How chaining works:**\n\n```\nUser: \"Save the top headline to my favourites\"\n\n┌──────────────┐ ┌────────────┐ ┌──────────────────┐\n│ Orchestrator │────►│ News Agent │────►│ Favourites Agent │\n│ │ │ │ │ │\n│ Decides chain│ │ Fetches │ │ Saves headline │\n│ [News, Favs] │ │ headlines │ │ to favourites │\n└──────────────┘ └────────────┘ └──────────────────┘\n │ │\n │ output passes as │\n │ input to next ──────┘\n │\n ▼\n Final response\n back to user\n```\n\n**Safety guardrails:**\n\n- **Cycle detection** — if an agent appears twice in the same chain, a `ConfigException` is thrown\n- **Depth limiting** — chains are capped at 5 levels deep (`kMaxChainDepth`)\n\n---\n\n### Agent Scopes\n\nBy default, all agents register in `AgentScope.global` and can discover each other for chaining. Use custom scopes to isolate agent groups:\n\n```dart\n// Isolated scope for testing\nfinal testScope = AgentScope();\n\nfinal agentA = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent A',\n role: 'Test agent A.',\n scope: testScope,\n);\n\nfinal agentB = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent B',\n role: 'Test agent B.',\n scope: testScope,\n);\n\n// Agents in testScope can chain to each other, but NOT to agents in AgentScope.global\n```\n\n**RegistrationPolicy** controls what happens when an agent name collides:\n\n| Policy | Behavior |\n|---|---|\n| `throwIfExists` | Throws `ConfigException` (default — catches accidental duplicates) |\n| `replace` | Silently replaces the existing agent |\n| `ignore` | Keeps the existing agent, discards the new one |\n\n---\n\n## Error Handling\n\nAgenix uses a sealed exception hierarchy. Every exception is an `AgenixException`, so you can exhaustively match on the type:\n\n```dart\ntry {\n final response = await agent.generateResponse(\n convoId: 'convo-1',\n userMessage: message,\n );\n} on LlmTimeoutException catch (e) {\n // LLM call exceeded the configured timeout\n} on ResponseParseException catch (e) {\n // LLM returned malformed output after retries\n print('Raw output: ${e.rawOutput}');\n} on ToolNotFoundException catch (e) {\n // LLM referenced a tool that isn't registered\n print('Missing tool: ${e.toolName}');\n} on ToolExecutionException catch (e) {\n // A registered tool threw during execution\n print('Tool ${e.toolName} failed: ${e.message}');\n} on AgentNotFoundException catch (e) {\n // Agent chain referenced a non-existent agent\n} on DataStoreException catch (e) {\n // Persistence operation failed\n} on NotAuthenticatedException {\n // Firebase operation without a signed-in user (from agenix_firebase)\n} on ConfigException catch (e) {\n // Invalid configuration (bad system_data.json, duplicate agent name, etc.)\n}\n```\n\n**FailureMode** controls the behavior at the `generateResponse` boundary:\n\n| Mode | Behavior |\n|---|---|\n| `FailureMode.gracefulMessage` | Returns an `AgentMessage` with `isError: true` (default — safe for UI) |\n| `FailureMode.throwError` | Rethrows the typed `AgenixException` (use when you want full control) |\n\nThe `onError` callback fires in both modes, so you can always log errors centrally:\n\n```dart\nfinal agent = await Agent.create(\n // ...\n failureMode: FailureMode.gracefulMessage,\n onError: (error, stack) => crashlytics.recordError(error, stack),\n);\n```\n\n---\n\n## API Reference\n\n### Exported Classes\n\n| Class | Package | Description |\n|---|---|---|\n| `Agent` | `agenix` | Core agent with LLM, memory, tools, and multi-agent orchestration |\n| `AgentScope` | `agenix` | Isolates groups of agents that can discover and chain to each other |\n| `LLM` | `agenix` | Abstract interface for language model providers |\n| `LlmConfig` | `agenix` | Provider-neutral generation settings (temperature, tokens, timeout, etc.) |\n| `DataStore` | `agenix` | Abstract interface for conversation persistence |\n| `AgentMessage` | `agenix` | A message in a conversation (user or agent) |\n| `Conversation` | `agenix` | Summary of a conversation (last message, timestamp, ID) |\n| `Tool` | `agenix` | Abstract class to extend for custom tools |\n| `ParameterSpecification` | `agenix` | Defines a tool parameter (name, type, required, default, enum) |\n| `ToolResponse` | `agenix` | Result returned from a tool execution |\n| `ToolRegistry` | `agenix` | Per-agent registry for managing available tools |\n| `FirebaseDataStore` | `agenix_firebase` | Firestore + Storage + Auth data store implementation |\n\n### Exported Enums\n\n| Enum | Values | Description |\n|---|---|---|\n| `FailureMode` | `throwError`, `gracefulMessage` | Controls error surfacing behavior |\n| `RegistrationPolicy` | `throwIfExists`, `replace`, `ignore` | Controls duplicate agent name handling |\n\n### Sealed Exception Hierarchy\n\n```\nAgenixException (sealed)\n├── LlmException\n│ └── LlmTimeoutException\n├── ResponseParseException\n├── ToolNotFoundException\n├── ToolExecutionException\n├── AgentNotFoundException\n├── DataStoreException\n│ └── NotAuthenticatedException\n└── ConfigException\n```\n\n### Internal Constants\n\n| Constant | Value | Description |\n|---|---|---|\n| `kMaxToolIterations` | `5` | Max tool→observe→re-prompt cycles per turn |\n| `kMaxParseRetries` | `2` | Max corrective re-prompts for malformed JSON |\n| `kMaxChainDepth` | `5` | Max depth for agent chain delegation |\n\n---\n\n## Usage Architectures\n\n### Single-Agent Chat App\n\nThe simplest setup — one agent handling all user interactions.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐ ┌───────────┐\n│ Flutter │────►│ Agent │────►│ LLM │ │ DataStore │\n│ UI │◄────│ │◄────│ │ │(InMemory/ │\n└──────────┘ │ │ └───────┘ │ Firebase) │\n │ │──── save/load ────►└───────────┘\n └───────┘\n```\n\nBest for: chatbots, Q&A apps, customer support widgets.\n\n### Agent + Tools (API Integration)\n\nThe agent can call external APIs through tools.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐\n│ Flutter │────►│ Agent │────►│ LLM │\n│ UI │◄────│ │◄────│ │\n└──────────┘ │ │ └───────┘\n │ │\n │ ToolRegistry\n │ ├── WeatherTool ──► Weather API\n │ ├── NewsTool ──► News API\n │ └── DbTool ──► Database\n └───────┘\n```\n\nBest for: apps where the agent needs to fetch real-time data or trigger actions.\n\n### Multi-Agent Orchestration\n\nMultiple specialized agents collaborating on complex tasks.\n\n```\n┌──────────┐ ┌──────────────┐\n│ Flutter │────►│ Orchestrator │\n│ UI │◄────│ │\n└──────────┘ └──────┬───────┘\n │ delegates via agents_chain\n ┌─────────┼─────────┐\n ▼ ▼ ▼\n ┌─────────┐ ┌────────┐ ┌───────────┐\n │ Search │ │ Booking│ │ Favourites│\n │ Agent │ │ Agent │ │ Agent │\n │ + tools │ │ + tools│ │ + tools │\n └─────────┘ └────────┘ └───────────┘\n```\n\nBest for: complex platforms where different domains require specialized knowledge and tools.\n\n### Testing / Prototyping Setup\n\nUse `InMemoryDataStore` and scoped agents for fast, isolated development.\n\n```dart\nfinal scope = AgentScope();\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(), // No Firebase needed\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Test Agent',\n role: 'Agent under test.',\n scope: scope, // Isolated from production agents\n);\n```\n\n---\n\n## Examples\n\n| Example | Description |\n|---|---|\n| [Multi-Agent System](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system) | Three agents (Orchestrator, News, Favourites) working together |\n| [Firebase Example](packages/agenix_firebase/example/) | Single agent with tools and Firebase persistence |\n| [Custom DataStore](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example) | Implementing your own persistence backend |\n\n---\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "a9bb14f36d677f29d4c9a406f97e52fc66a62feb": {"t": 1785764708.8011029, "n": 23561, "h": "a5d6052805d4280e", "c": "![Agenix Banner](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n# Agenix\n\n<p align=\"center\">\n <a href=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml\"><img src=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub\"></a>\n <a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n <a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Stars\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\nA Flutter package for building AI agents with memory, tools, and multi-agent orchestration. Define your agent's personality, give it tools, and let it handle conversations — including delegating sub-tasks across a chain of specialized agents.\n\n---\n\n## Packages\n\nThis is a monorepo containing the following packages:\n\n| Package | pub.dev | Description |\n|---|---|---|\n| [`agenix`](packages/agenix/) | [![Pub](https://img.shields.io/pub/v/agenix.svg)](https://pub.dev/packages/agenix) | Core — agents, tools, LLM interface, in-memory data store |\n| [`agenix_firebase`](packages/agenix_firebase/) | [![Pub](https://img.shields.io/pub/v/agenix_firebase.svg)](https://pub.dev/packages/agenix_firebase) | Firebase (Firestore + Storage + Auth) data store backend |\n\n### Choosing a data store\n\nThe core `agenix` package ships with `DataStore.inMemory()` — a zero-dependency store for testing and prototyping. For production persistence, add a backend package:\n\n```yaml\n# Core only (no Firebase):\ndependencies:\n agenix: ^4.0.0\n\n# With Firebase persistence:\ndependencies:\n agenix: ^4.0.0\n agenix_firebase: ^1.0.0\n```\n\n```dart\nimport 'package:agenix/agenix.dart';\n\n// In-memory (no extra package needed):\nfinal store = DataStore.inMemory();\n\n// Firebase (requires agenix_firebase):\nimport 'package:agenix_firebase/agenix_firebase.dart';\nfinal store = FirebaseDataStore();\n```\n\nThis separation means apps that don't use Firebase never pull in the Firebase SDK.\n\n> **Migrating from agenix 3.x?** Replace `DataStore.firestoreDataStore()` with `FirebaseDataStore()` and add `agenix_firebase` to your pubspec. See the [core CHANGELOG](packages/agenix/CHANGELOG.md) for details.\n\n---\n\n## Table of Contents\n\n- [Architecture Overview](#architecture-overview)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Concepts](#core-concepts)\n - [Agent](#agent)\n - [LLM](#llm)\n - [DataStore (Memory)](#datastore-memory)\n - [Tools](#tools)\n - [Multi-Agent Orchestration](#multi-agent-orchestration)\n - [Agent Scopes](#agent-scopes)\n- [Error Handling](#error-handling)\n- [API Reference](#api-reference)\n- [Usage Architectures](#usage-architectures)\n- [Examples](#examples)\n- [Maintainers](#maintainers)\n\n---\n\n## Architecture Overview\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ Your Flutter App │\n│ │\n│ Agent.create(llm, dataStore, name, role) │\n│ │ │\n│ ▼ │\n│ ┌─────────┐ generateResponse() ┌───────────────┐ │\n│ │ Agent │ ◄─────────────────────► │ LLM │ │\n│ │ │ │ (Gemini / │ │\n│ │ │ │ Custom) │ │\n│ └────┬────┘ └───────────────┘ │\n│ │ │\n│ ┌────┴──────────────────────────┐ │\n│ │ │ │ │\n│ ▼ ▼ ▼ │\n│ ┌──────┐ ┌──────────┐ ┌────────────────┐ │\n│ │Tools │ │DataStore │ │Agent Registry │ │\n│ │ │ │(InMemory/ │ │(Multi-Agent │ │\n│ │ │ │ Firebase/ │ │ Orchestration) │ │\n│ │ │ │ Custom) │ │ │ │\n│ └──────┘ └──────────┘ └────────────────┘ │\n└─────────────────────────────────────────────────────────────┘\n```\n\nThe agent receives a user message, builds a structured prompt (including conversation history from the DataStore and available tools from the ToolRegistry), sends it to the LLM, and parses the response into one of three actions:\n\n1. **Direct response** — returns text to the user\n2. **Tool invocation** — runs one or more tools, optionally iterating up to 5 times before producing a final answer\n3. **Agent delegation** — hands the task to a chain of other agents, each passing its output to the next\n\n---\n\n## Installation\n\nAdd to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n agenix: ^4.0.0\n```\n\nIf you need Firebase persistence, also add:\n\n```yaml\n agenix_firebase: ^1.0.0\n```\n\nThen run:\n\n```bash\nflutter pub get\n```\n\n---\n\n## Quick Start\n\n### 1. Create system data\n\nCreate `assets/system_data.json` with your agent's personality and background knowledge:\n\n```json\n{\n \"name\": \"Lens\",\n \"role\": \"A helpful assistant for the Acme platform\",\n \"personality\": \"Friendly, concise, and knowledgeable\",\n \"instructions\": \"Always greet the user by name when possible\"\n}\n```\n\nAdd the asset to your `pubspec.yaml`:\n\n```yaml\nflutter:\n assets:\n - assets/system_data.json\n```\n\n### 2. Initialize the agent\n\n```dart\nimport 'package:agenix/agenix.dart';\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n ),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\nFor Firebase persistence:\n\n```dart\nimport 'package:agenix_firebase/agenix_firebase.dart';\n\nfinal agent = await Agent.create(\n dataStore: FirebaseDataStore(),\n llm: LLM.geminiLLM(apiKey: 'YOUR_API_KEY', modelName: 'gemini-2.0-flash'),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\n### 3. Generate a response\n\n```dart\nfinal response = await agent.generateResponse(\n convoId: 'conversation-1',\n userMessage: AgentMessage(\n content: 'What is the weather like today?',\n isFromAgent: false,\n generatedAt: DateTime.now(),\n ),\n);\n\nprint(response.content);\n```\n\n### 4. Run the app\n\n```bash\nflutter run -d chrome --dart-define=GEMINI_API_KEY=your-key-here\n```\n\n---\n\n## Core Concepts\n\n### Agent\n\nThe `Agent` is the central class. It wires together an LLM, a DataStore, a ToolRegistry, and an AgentScope.\n\n```dart\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Support Agent',\n role: 'Handles customer support queries for the e-commerce platform.',\n failureMode: FailureMode.throwError, // or FailureMode.gracefulMessage (default)\n onError: (error, stack) => logger.severe('Agent error', error, stack),\n scope: AgentScope.global, // default — or create isolated scopes\n registrationPolicy: RegistrationPolicy.throwIfExists, // default\n);\n```\n\n**Key methods:**\n\n| Method | Description |\n|---|---|\n| `generateResponse(convoId, userMessage)` | Send a user message and get back an `AgentMessage` from the agent |\n| `getMessages(conversationId)` | Retrieve all messages in a conversation |\n| `getAllConversations()` | List all conversations for the current user |\n| `deleteConversation(conversationId)` | Delete a conversation and its messages |\n| `dispose()` | Unregister the agent from its scope |\n\n**Parameters for `generateResponse`:**\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `convoId` | `String` | required | Conversation identifier |\n| `userMessage` | `AgentMessage` | required | The user's message |\n| `memoryLimit` | `int` | `10` | Max previous messages loaded as context |\n| `metaData` | `Object?` | `null` | Opaque pass-through for auth tokens, tenant IDs, etc. |\n\n---\n\n### LLM\n\nThe `LLM` abstract class defines the contract for language model providers. Agenix ships with Gemini; implement the interface for other providers.\n\n```dart\n// Built-in Gemini\nfinal llm = LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n config: LlmConfig(\n temperature: 0.2, // Low for structured JSON output\n maxOutputTokens: 2048,\n topP: 0.95,\n topK: 40,\n jsonMode: true, // Request native JSON output mode\n timeout: Duration(seconds: 60),\n ),\n);\n```\n\n**Implementing a custom LLM:**\n\n```dart\nclass MyCustomLLM implements LLM {\n @override\n final String modelId = 'my-model-v1';\n\n @override\n final LlmConfig config;\n\n MyCustomLLM({this.config = const LlmConfig()});\n\n @override\n Future<String> generate({\n required String prompt,\n String? systemInstruction,\n Uint8List? rawData,\n String mimeType = 'image/png',\n }) async {\n // Call your model API here\n // Must return a JSON string matching one of:\n // {\"response\": \"...\"}\n // {\"tools\": \"tool1, tool2\", \"parameters\": {...}}\n // {\"agents_chain\": [\"agent1\", \"agent2\"]}\n }\n}\n```\n\n---\n\n### DataStore (Memory)\n\nThe `DataStore` abstract class handles conversation persistence. Messages are saved after each `generateResponse` call, and loaded as context for future prompts.\n\n**Built-in implementations:**\n\n| Package | DataStore | Use Case |\n|---|---|---|\n| `agenix` | `DataStore.inMemory()` | Testing, prototyping, or non-persistent apps |\n| `agenix_firebase` | `FirebaseDataStore()` | Production apps with Firebase backend |\n\n**Firebase setup:** Add `agenix_firebase` to your pubspec, ensure `Firebase.initializeApp()` is called before creating the data store, and sign in a user via `firebase_auth`.\n\n**Implementing a custom DataStore:**\n\n```dart\nclass PostgresDataStore extends DataStore {\n @override\n Future<void> saveMessage(String convoId, AgentMessage msg, {Object? metaData}) async {\n // INSERT INTO messages ...\n }\n\n @override\n Future<List<AgentMessage>> getMessages(String conversationId, {int? limit, Object? metaData}) async {\n // SELECT * FROM messages WHERE convo_id = ? ORDER BY generated_at LIMIT ?\n }\n\n @override\n Future<void> deleteConversation(String conversationId, {Object? metaData}) async {\n // DELETE FROM messages WHERE convo_id = ?\n }\n\n @override\n Future<List<Conversation>> getConversations({Object? metaData}) async {\n // SELECT DISTINCT convo_id, last_message, last_message_time FROM ...\n }\n}\n```\n\n---\n\n### Tools\n\nTools let the agent perform actions beyond conversation — API calls, database queries, calculations, anything.\n\n**Lifecycle:**\n\n```\nUser Message → LLM decides tool is needed → Agent runs tool → Tool returns ToolResponse\n → Agent either returns result OR iterates (up to 5 rounds of tool calls)\n```\n\n#### Tool without parameters\n\n```dart\nclass NewsTool extends Tool {\n NewsTool() : super(\n name: 'news_tool',\n description: 'Fetches the latest news headlines.',\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final headlines = await NewsApi.fetchHeadlines();\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Here are today\\'s headlines: ${headlines.join(\", \")}',\n needsFurtherReasoning: true, // Agent will synthesize a natural-language answer\n );\n }\n}\n```\n\n#### Tool with parameters\n\n```dart\nclass WeatherTool extends Tool {\n WeatherTool() : super(\n name: 'weather_tool',\n description: 'Gets current weather for a given location.',\n parameters: [\n ParameterSpecification(\n name: 'location',\n type: 'string',\n description: 'City name or coordinates.',\n required: true,\n ),\n ParameterSpecification(\n name: 'units',\n type: 'string',\n description: 'Temperature unit.',\n required: false,\n defaultValue: 'celsius',\n enumValues: ['celsius', 'fahrenheit'],\n ),\n ],\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final location = params['location'] as String;\n final units = params['units'] as String? ?? 'celsius';\n final weather = await WeatherApi.get(location, units: units);\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Weather in $location: ${weather.temp}° ${weather.condition}',\n data: weather.toMap(), // Optional structured data for chaining\n );\n }\n}\n```\n\n#### Registering tools\n\n```dart\nagent.toolRegistry.registerTool(NewsTool());\nagent.toolRegistry.registerTool(WeatherTool());\n\n// Dynamically remove a tool\nagent.toolRegistry.unregisterTool('weather_tool');\n```\n\n#### ToolResponse flags\n\n| Field | Type | Description |\n|---|---|---|\n| `toolName` | `String` | Name of the tool that produced this response |\n| `isRequestSuccessful` | `bool` | Whether the tool operation succeeded |\n| `message` | `String` | Human-readable result shown to the user |\n| `data` | `Map?` | Structured data for agent chaining or further reasoning |\n| `needsFurtherReasoning` | `bool` | When `true`, the agent makes a second LLM call to synthesize the tool output into a natural-language answer |\n\n---\n\n### Multi-Agent Orchestration\n\nWhen the LLM determines a task requires multiple specialists, it returns an `agents_chain`. Agenix automatically delegates sub-tasks across agents, passing each agent's output as input to the next.\n\n```dart\n// Create specialized agents\nfinal newsAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'News Agent',\n role: 'Fetches and summarizes news articles.',\n);\n\nfinal favouritesAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Favourites Agent',\n role: 'Manages user favourites: add, remove, and list.',\n);\n\nfinal orchestrator = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Orchestrator',\n role: 'Main user-facing agent. Delegates to News Agent and Favourites Agent.',\n);\n\n// Register tools on each agent as needed\nnewsAgent.toolRegistry.registerTool(NewsTool());\nfavouritesAgent.toolRegistry.registerTool(AddFavouriteTool());\nfavouritesAgent.toolRegistry.registerTool(ListFavouritesTool());\n```\n\n**How chaining works:**\n\n```\nUser: \"Save the top headline to my favourites\"\n\n┌──────────────┐ ┌────────────┐ ┌──────────────────┐\n│ Orchestrator │────►│ News Agent │────►│ Favourites Agent │\n│ │ │ │ │ │\n│ Decides chain│ │ Fetches │ │ Saves headline │\n│ [News, Favs] │ │ headlines │ │ to favourites │\n└──────────────┘ └────────────┘ └──────────────────┘\n │ │\n │ output passes as │\n │ input to next ──────┘\n │\n ▼\n Final response\n back to user\n```\n\n**Safety guardrails:**\n\n- **Cycle detection** — if an agent appears twice in the same chain, a `ConfigException` is thrown\n- **Depth limiting** — chains are capped at 5 levels deep (`kMaxChainDepth`)\n\n---\n\n### Agent Scopes\n\nBy default, all agents register in `AgentScope.global` and can discover each other for chaining. Use custom scopes to isolate agent groups:\n\n```dart\n// Isolated scope for testing\nfinal testScope = AgentScope();\n\nfinal agentA = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent A',\n role: 'Test agent A.',\n scope: testScope,\n);\n\nfinal agentB = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent B',\n role: 'Test agent B.',\n scope: testScope,\n);\n\n// Agents in testScope can chain to each other, but NOT to agents in AgentScope.global\n```\n\n**RegistrationPolicy** controls what happens when an agent name collides:\n\n| Policy | Behavior |\n|---|---|\n| `throwIfExists` | Throws `ConfigException` (default — catches accidental duplicates) |\n| `replace` | Silently replaces the existing agent |\n| `ignore` | Keeps the existing agent, discards the new one |\n\n---\n\n## Error Handling\n\nAgenix uses a sealed exception hierarchy. Every exception is an `AgenixException`, so you can exhaustively match on the type:\n\n```dart\ntry {\n final response = await agent.generateResponse(\n convoId: 'convo-1',\n userMessage: message,\n );\n} on LlmTimeoutException catch (e) {\n // LLM call exceeded the configured timeout\n} on ResponseParseException catch (e) {\n // LLM returned malformed output after retries\n print('Raw output: ${e.rawOutput}');\n} on ToolNotFoundException catch (e) {\n // LLM referenced a tool that isn't registered\n print('Missing tool: ${e.toolName}');\n} on ToolExecutionException catch (e) {\n // A registered tool threw during execution\n print('Tool ${e.toolName} failed: ${e.message}');\n} on AgentNotFoundException catch (e) {\n // Agent chain referenced a non-existent agent\n} on DataStoreException catch (e) {\n // Persistence operation failed\n} on NotAuthenticatedException {\n // Firebase operation without a signed-in user (from agenix_firebase)\n} on ConfigException catch (e) {\n // Invalid configuration (bad system_data.json, duplicate agent name, etc.)\n}\n```\n\n**FailureMode** controls the behavior at the `generateResponse` boundary:\n\n| Mode | Behavior |\n|---|---|\n| `FailureMode.gracefulMessage` | Returns an `AgentMessage` with `isError: true` (default — safe for UI) |\n| `FailureMode.throwError` | Rethrows the typed `AgenixException` (use when you want full control) |\n\nThe `onError` callback fires in both modes, so you can always log errors centrally:\n\n```dart\nfinal agent = await Agent.create(\n // ...\n failureMode: FailureMode.gracefulMessage,\n onError: (error, stack) => crashlytics.recordError(error, stack),\n);\n```\n\n---\n\n## API Reference\n\n### Exported Classes\n\n| Class | Package | Description |\n|---|---|---|\n| `Agent` | `agenix` | Core agent with LLM, memory, tools, and multi-agent orchestration |\n| `AgentScope` | `agenix` | Isolates groups of agents that can discover and chain to each other |\n| `LLM` | `agenix` | Abstract interface for language model providers |\n| `LlmConfig` | `agenix` | Provider-neutral generation settings (temperature, tokens, timeout, etc.) |\n| `DataStore` | `agenix` | Abstract interface for conversation persistence |\n| `AgentMessage` | `agenix` | A message in a conversation (user or agent) |\n| `Conversation` | `agenix` | Summary of a conversation (last message, timestamp, ID) |\n| `Tool` | `agenix` | Abstract class to extend for custom tools |\n| `ParameterSpecification` | `agenix` | Defines a tool parameter (name, type, required, default, enum) |\n| `ToolResponse` | `agenix` | Result returned from a tool execution |\n| `ToolRegistry` | `agenix` | Per-agent registry for managing available tools |\n| `FirebaseDataStore` | `agenix_firebase` | Firestore + Storage + Auth data store implementation |\n\n### Exported Enums\n\n| Enum | Values | Description |\n|---|---|---|\n| `FailureMode` | `throwError`, `gracefulMessage` | Controls error surfacing behavior |\n| `RegistrationPolicy` | `throwIfExists`, `replace`, `ignore` | Controls duplicate agent name handling |\n\n### Sealed Exception Hierarchy\n\n```\nAgenixException (sealed)\n├── LlmException\n│ └── LlmTimeoutException\n├── ResponseParseException\n├── ToolNotFoundException\n├── ToolExecutionException\n├── AgentNotFoundException\n├── DataStoreException\n│ └── NotAuthenticatedException\n└── ConfigException\n```\n\n### Internal Constants\n\n| Constant | Value | Description |\n|---|---|---|\n| `kMaxToolIterations` | `5` | Max tool→observe→re-prompt cycles per turn |\n| `kMaxParseRetries` | `2` | Max corrective re-prompts for malformed JSON |\n| `kMaxChainDepth` | `5` | Max depth for agent chain delegation |\n\n---\n\n## Usage Architectures\n\n### Single-Agent Chat App\n\nThe simplest setup — one agent handling all user interactions.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐ ┌───────────┐\n│ Flutter │────►│ Agent │────►│ LLM │ │ DataStore │\n│ UI │◄────│ │◄────│ │ │(InMemory/ │\n└──────────┘ │ │ └───────┘ │ Firebase) │\n │ │──── save/load ────►└───────────┘\n └───────┘\n```\n\nBest for: chatbots, Q&A apps, customer support widgets.\n\n### Agent + Tools (API Integration)\n\nThe agent can call external APIs through tools.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐\n│ Flutter │────►│ Agent │────►│ LLM │\n│ UI │◄────│ │◄────│ │\n└──────────┘ │ │ └───────┘\n │ │\n │ ToolRegistry\n │ ├── WeatherTool ──► Weather API\n │ ├── NewsTool ──► News API\n │ └── DbTool ──► Database\n └───────┘\n```\n\nBest for: apps where the agent needs to fetch real-time data or trigger actions.\n\n### Multi-Agent Orchestration\n\nMultiple specialized agents collaborating on complex tasks.\n\n```\n┌──────────┐ ┌──────────────┐\n│ Flutter │────►│ Orchestrator │\n│ UI │◄────│ │\n└──────────┘ └──────┬───────┘\n │ delegates via agents_chain\n ┌─────────┼─────────┐\n ▼ ▼ ▼\n ┌─────────┐ ┌────────┐ ┌───────────┐\n │ Search │ │ Booking│ │ Favourites│\n │ Agent │ │ Agent │ │ Agent │\n │ + tools │ │ + tools│ │ + tools │\n └─────────┘ └────────┘ └───────────┘\n```\n\nBest for: complex platforms where different domains require specialized knowledge and tools.\n\n### Testing / Prototyping Setup\n\nUse `InMemoryDataStore` and scoped agents for fast, isolated development.\n\n```dart\nfinal scope = AgentScope();\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(), // No Firebase needed\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Test Agent',\n role: 'Agent under test.',\n scope: scope, // Isolated from production agents\n);\n```\n\n---\n\n## Examples\n\n| Example | Description |\n|---|---|\n| [Multi-Agent System](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system) | Three agents (Orchestrator, News, Favourites) working together |\n| [Firebase Example](packages/agenix_firebase/example/) | Single agent with tools and Firebase persistence |\n| [Custom DataStore](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example) | Implementing your own persistence backend |\n\n### Multi-Agent System Demo\n\nIn this example three agents collaborate:\n1. **Orchestrator** — communicates with the end user\n2. **News Agent** — handles News API operations\n3. **Favourites Agent** — manages user favourites (add, remove, list)\n\nhttps://github.com/user-attachments/assets/f79cf6ac-6913-49a7-982a-bd7b975599b7\n\n#### Workflow\n\n![Multi-Agent Workflow](https://github.com/user-attachments/assets/8ad9f4ac-018a-4092-bf8c-4fc72da81673)\n\n### Single-Agent Demo\n\nAn agentic app powered by \"Lens\" — an AI agent with a specific personality that can answer platform questions and perform tasks using tools.\n\nhttps://github.com/user-attachments/assets/bcb56da8-4285-4661-af52-ee8dd6f31d08\n\n---\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "95ffd2b2c8ba9cec849acba88f443bef27757c18": {"t": 1785764710.633637, "n": 21467, "h": "fc35ecc50b3091c8", "c": "![Agenix Banner](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n# Agenix\n\n<p align=\"center\">\n <a href=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml\"><img src=\"https://github.com/ahmadexe/agenix/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub\"></a>\n <a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n <a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Stars\"></a>\n <a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\nA Flutter package for building AI agents with memory, tools, and multi-agent orchestration. Define your agent's personality, give it tools, and let it handle conversations — including delegating sub-tasks across a chain of specialized agents.\n\n---\n\n## Table of Contents\n\n- [Architecture Overview](#architecture-overview)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Core Concepts](#core-concepts)\n - [Agent](#agent)\n - [LLM](#llm)\n - [DataStore (Memory)](#datastore-memory)\n - [Tools](#tools)\n - [Multi-Agent Orchestration](#multi-agent-orchestration)\n - [Agent Scopes](#agent-scopes)\n- [Error Handling](#error-handling)\n- [API Reference](#api-reference)\n- [Usage Architectures](#usage-architectures)\n- [Examples](#examples)\n- [Maintainers](#maintainers)\n\n---\n\n## Architecture Overview\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ Your Flutter App │\n│ │\n│ Agent.create(llm, dataStore, name, role) │\n│ │ │\n│ ▼ │\n│ ┌─────────┐ generateResponse() ┌───────────────┐ │\n│ │ Agent │ ◄─────────────────────► │ LLM │ │\n│ │ │ │ (Gemini / │ │\n│ │ │ │ Custom) │ │\n│ └────┬────┘ └───────────────┘ │\n│ │ │\n│ ┌────┴──────────────────────────┐ │\n│ │ │ │ │\n│ ▼ ▼ ▼ │\n│ ┌──────┐ ┌──────────┐ ┌────────────────┐ │\n│ │Tools │ │DataStore │ │Agent Registry │ │\n│ │ │ │(Firebase/ │ │(Multi-Agent │ │\n│ │ │ │ InMemory/ │ │ Orchestration) │ │\n│ │ │ │ Custom) │ │ │ │\n│ └──────┘ └──────────┘ └────────────────┘ │\n└─────────────────────────────────────────────────────────────┘\n```\n\nThe agent receives a user message, builds a structured prompt (including conversation history from the DataStore and available tools from the ToolRegistry), sends it to the LLM, and parses the response into one of three actions:\n\n1. **Direct response** — returns text to the user\n2. **Tool invocation** — runs one or more tools, optionally iterating up to 5 times before producing a final answer\n3. **Agent delegation** — hands the task to a chain of other agents, each passing its output to the next\n\n---\n\n## Installation\n\nAdd to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n agenix: ^4.0.0\n```\n\nThen run:\n\n```bash\nflutter pub get\n```\n\n---\n\n## Quick Start\n\n### 1. Create system data\n\nCreate `assets/system_data.json` with your agent's personality and background knowledge:\n\n```json\n{\n \"name\": \"Lens\",\n \"role\": \"A helpful assistant for the Acme platform\",\n \"personality\": \"Friendly, concise, and knowledgeable\",\n \"instructions\": \"Always greet the user by name when possible\"\n}\n```\n\nAdd the asset to your `pubspec.yaml`:\n\n```yaml\nflutter:\n assets:\n - assets/system_data.json\n```\n\n### 2. Initialize the agent\n\n```dart\nimport 'package:agenix/agenix.dart';\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(), // or DataStore.firestoreDataStore()\n llm: LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n ),\n name: 'Assistant',\n role: 'General purpose assistant for the platform.',\n);\n```\n\n### 3. Generate a response\n\n```dart\nfinal response = await agent.generateResponse(\n convoId: 'conversation-1',\n userMessage: AgentMessage(\n content: 'What is the weather like today?',\n isFromAgent: false,\n generatedAt: DateTime.now(),\n ),\n);\n\nprint(response.content);\n```\n\n### 4. Run the app\n\n```bash\nflutter run -d chrome --dart-define=GEMINI_API_KEY=your-key-here\n```\n\n---\n\n## Core Concepts\n\n### Agent\n\nThe `Agent` is the central class. It wires together an LLM, a DataStore, a ToolRegistry, and an AgentScope.\n\n```dart\nfinal agent = await Agent.create(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Support Agent',\n role: 'Handles customer support queries for the e-commerce platform.',\n failureMode: FailureMode.throwError, // or FailureMode.gracefulMessage (default)\n onError: (error, stack) => logger.severe('Agent error', error, stack),\n scope: AgentScope.global, // default — or create isolated scopes\n registrationPolicy: RegistrationPolicy.throwIfExists, // default\n);\n```\n\n**Key methods:**\n\n| Method | Description |\n|---|---|\n| `generateResponse(convoId, userMessage)` | Send a user message and get back an `AgentMessage` from the agent |\n| `getMessages(conversationId)` | Retrieve all messages in a conversation |\n| `getAllConversations()` | List all conversations for the current user |\n| `deleteConversation(conversationId)` | Delete a conversation and its messages |\n| `dispose()` | Unregister the agent from its scope |\n\n**Parameters for `generateResponse`:**\n\n| Parameter | Type | Default | Description |\n|---|---|---|---|\n| `convoId` | `String` | required | Conversation identifier |\n| `userMessage` | `AgentMessage` | required | The user's message |\n| `memoryLimit` | `int` | `10` | Max previous messages loaded as context |\n| `metaData` | `Object?` | `null` | Opaque pass-through for auth tokens, tenant IDs, etc. |\n\n---\n\n### LLM\n\nThe `LLM` abstract class defines the contract for language model providers. Agenix ships with Gemini; implement the interface for other providers.\n\n```dart\n// Built-in Gemini\nfinal llm = LLM.geminiLLM(\n apiKey: 'YOUR_API_KEY',\n modelName: 'gemini-2.0-flash',\n config: LlmConfig(\n temperature: 0.2, // Low for structured JSON output\n maxOutputTokens: 2048,\n topP: 0.95,\n topK: 40,\n jsonMode: true, // Request native JSON output mode\n timeout: Duration(seconds: 60),\n ),\n);\n```\n\n**Implementing a custom LLM:**\n\n```dart\nclass MyCustomLLM implements LLM {\n @override\n final String modelId = 'my-model-v1';\n\n @override\n final LlmConfig config;\n\n MyCustomLLM({this.config = const LlmConfig()});\n\n @override\n Future<String> generate({\n required String prompt,\n String? systemInstruction,\n Uint8List? rawData,\n String mimeType = 'image/png',\n }) async {\n // Call your model API here\n // Must return a JSON string matching one of:\n // {\"response\": \"...\"}\n // {\"tools\": \"tool1, tool2\", \"parameters\": {...}}\n // {\"agents_chain\": [\"agent1\", \"agent2\"]}\n }\n}\n```\n\n---\n\n### DataStore (Memory)\n\nThe `DataStore` abstract class handles conversation persistence. Messages are saved after each `generateResponse` call, and loaded as context for future prompts.\n\n**Built-in implementations:**\n\n| DataStore | Use Case |\n|---|---|\n| `DataStore.firestoreDataStore()` | Production apps with Firebase backend |\n| `DataStore.inMemory()` | Testing, prototyping, or non-persistent apps |\n\n**Firebase setup:** Ensure `Firebase.initializeApp()` is called before creating the data store. Requires an authenticated user via `firebase_auth`.\n\n**Implementing a custom DataStore:**\n\n```dart\nclass PostgresDataStore extends DataStore {\n @override\n Future<void> saveMessage(String convoId, AgentMessage msg, {Object? metaData}) async {\n // INSERT INTO messages ...\n }\n\n @override\n Future<List<AgentMessage>> getMessages(String conversationId, {int? limit, Object? metaData}) async {\n // SELECT * FROM messages WHERE convo_id = ? ORDER BY generated_at LIMIT ?\n }\n\n @override\n Future<void> deleteConversation(String conversationId, {Object? metaData}) async {\n // DELETE FROM messages WHERE convo_id = ?\n }\n\n @override\n Future<List<Conversation>> getConversations({Object? metaData}) async {\n // SELECT DISTINCT convo_id, last_message, last_message_time FROM ...\n }\n}\n```\n\n---\n\n### Tools\n\nTools let the agent perform actions beyond conversation — API calls, database queries, calculations, anything.\n\n**Lifecycle:**\n\n```\nUser Message → LLM decides tool is needed → Agent runs tool → Tool returns ToolResponse\n → Agent either returns result OR iterates (up to 5 rounds of tool calls)\n```\n\n#### Tool without parameters\n\n```dart\nclass NewsTool extends Tool {\n NewsTool() : super(\n name: 'news_tool',\n description: 'Fetches the latest news headlines.',\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final headlines = await NewsApi.fetchHeadlines();\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Here are today\\'s headlines: ${headlines.join(\", \")}',\n needsFurtherReasoning: true, // Agent will synthesize a natural-language answer\n );\n }\n}\n```\n\n#### Tool with parameters\n\n```dart\nclass WeatherTool extends Tool {\n WeatherTool() : super(\n name: 'weather_tool',\n description: 'Gets current weather for a given location.',\n parameters: [\n ParameterSpecification(\n name: 'location',\n type: 'string',\n description: 'City name or coordinates.',\n required: true,\n ),\n ParameterSpecification(\n name: 'units',\n type: 'string',\n description: 'Temperature unit.',\n required: false,\n defaultValue: 'celsius',\n enumValues: ['celsius', 'fahrenheit'],\n ),\n ],\n );\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n final location = params['location'] as String;\n final units = params['units'] as String? ?? 'celsius';\n final weather = await WeatherApi.get(location, units: units);\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message: 'Weather in $location: ${weather.temp}° ${weather.condition}',\n data: weather.toMap(), // Optional structured data for chaining\n );\n }\n}\n```\n\n#### Registering tools\n\n```dart\nagent.toolRegistry.registerTool(NewsTool());\nagent.toolRegistry.registerTool(WeatherTool());\n\n// Dynamically remove a tool\nagent.toolRegistry.unregisterTool('weather_tool');\n```\n\n#### ToolResponse flags\n\n| Field | Type | Description |\n|---|---|---|\n| `toolName` | `String` | Name of the tool that produced this response |\n| `isRequestSuccessful` | `bool` | Whether the tool operation succeeded |\n| `message` | `String` | Human-readable result shown to the user |\n| `data` | `Map?` | Structured data for agent chaining or further reasoning |\n| `needsFurtherReasoning` | `bool` | When `true`, the agent makes a second LLM call to synthesize the tool output into a natural-language answer |\n\n---\n\n### Multi-Agent Orchestration\n\nWhen the LLM determines a task requires multiple specialists, it returns an `agents_chain`. Agenix automatically delegates sub-tasks across agents, passing each agent's output as input to the next.\n\n```dart\n// Create specialized agents\nfinal newsAgent = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'News Agent',\n role: 'Fetches and summarizes news articles.',\n);\n\nfinal favouritesAgent = await Agent.create(\n dataStore: DataStore.firestoreDataStore(),\n llm: llm,\n name: 'Favourites Agent',\n role: 'Manages user favourites: add, remove, and list.',\n);\n\nfinal orchestrator = await Agent.create(\n dataStore: DataStore.firestoreDataStore(),\n llm: llm,\n name: 'Orchestrator',\n role: 'Main user-facing agent. Delegates to News Agent and Favourites Agent.',\n);\n\n// Register tools on each agent as needed\nnewsAgent.toolRegistry.registerTool(NewsTool());\nfavouritesAgent.toolRegistry.registerTool(AddFavouriteTool());\nfavouritesAgent.toolRegistry.registerTool(ListFavouritesTool());\n```\n\n**How chaining works:**\n\n```\nUser: \"Save the top headline to my favourites\"\n\n┌──────────────┐ ┌────────────┐ ┌──────────────────┐\n│ Orchestrator │────►│ News Agent │────►│ Favourites Agent │\n│ │ │ │ │ │\n│ Decides chain│ │ Fetches │ │ Saves headline │\n│ [News, Favs] │ │ headlines │ │ to favourites │\n└──────────────┘ └────────────┘ └──────────────────┘\n │ │\n │ output passes as │\n │ input to next ──────┘\n │\n ▼\n Final response\n back to user\n```\n\n**Safety guardrails:**\n\n- **Cycle detection** — if an agent appears twice in the same chain, a `ConfigException` is thrown\n- **Depth limiting** — chains are capped at 5 levels deep (`kMaxChainDepth`)\n\n---\n\n### Agent Scopes\n\nBy default, all agents register in `AgentScope.global` and can discover each other for chaining. Use custom scopes to isolate agent groups:\n\n```dart\n// Isolated scope for testing\nfinal testScope = AgentScope();\n\nfinal agentA = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent A',\n role: 'Test agent A.',\n scope: testScope,\n);\n\nfinal agentB = await Agent.create(\n dataStore: DataStore.inMemory(),\n llm: llm,\n name: 'Agent B',\n role: 'Test agent B.',\n scope: testScope,\n);\n\n// Agents in testScope can chain to each other, but NOT to agents in AgentScope.global\n```\n\n**RegistrationPolicy** controls what happens when an agent name collides:\n\n| Policy | Behavior |\n|---|---|\n| `throwIfExists` | Throws `ConfigException` (default — catches accidental duplicates) |\n| `replace` | Silently replaces the existing agent |\n| `ignore` | Keeps the existing agent, discards the new one |\n\n---\n\n## Error Handling\n\nAgenix uses a sealed exception hierarchy. Every exception is an `AgenixException`, so you can exhaustively match on the type:\n\n```dart\ntry {\n final response = await agent.generateResponse(\n convoId: 'convo-1',\n userMessage: message,\n );\n} on LlmTimeoutException catch (e) {\n // LLM call exceeded the configured timeout\n} on ResponseParseException catch (e) {\n // LLM returned malformed output after retries\n print('Raw output: ${e.rawOutput}');\n} on ToolNotFoundException catch (e) {\n // LLM referenced a tool that isn't registered\n print('Missing tool: ${e.toolName}');\n} on ToolExecutionException catch (e) {\n // A registered tool threw during execution\n print('Tool ${e.toolName} failed: ${e.message}');\n} on AgentNotFoundException catch (e) {\n // Agent chain referenced a non-existent agent\n} on DataStoreException catch (e) {\n // Persistence operation failed\n} on NotAuthenticatedException {\n // Firebase operation without a signed-in user\n} on ConfigException catch (e) {\n // Invalid configuration (bad system_data.json, duplicate agent name, etc.)\n}\n```\n\n**FailureMode** controls the behavior at the `generateResponse` boundary:\n\n| Mode | Behavior |\n|---|---|\n| `FailureMode.gracefulMessage` | Returns an `AgentMessage` with `isError: true` (default — safe for UI) |\n| `FailureMode.throwError` | Rethrows the typed `AgenixException` (use when you want full control) |\n\nThe `onError` callback fires in both modes, so you can always log errors centrally:\n\n```dart\nfinal agent = await Agent.create(\n // ...\n failureMode: FailureMode.gracefulMessage,\n onError: (error, stack) => crashlytics.recordError(error, stack),\n);\n```\n\n---\n\n## API Reference\n\n### Exported Classes\n\n| Class | Description |\n|---|---|\n| `Agent` | Core agent with LLM, memory, tools, and multi-agent orchestration |\n| `AgentScope` | Isolates groups of agents that can discover and chain to each other |\n| `LLM` | Abstract interface for language model providers |\n| `LlmConfig` | Provider-neutral generation settings (temperature, tokens, timeout, etc.) |\n| `DataStore` | Abstract interface for conversation persistence |\n| `AgentMessage` | A message in a conversation (user or agent) |\n| `Conversation` | Summary of a conversation (last message, timestamp, ID) |\n| `Tool` | Abstract class to extend for custom tools |\n| `ParameterSpecification` | Defines a tool parameter (name, type, required, default, enum) |\n| `ToolResponse` | Result returned from a tool execution |\n| `ToolRegistry` | Per-agent registry for managing available tools |\n\n### Exported Enums\n\n| Enum | Values | Description |\n|---|---|---|\n| `FailureMode` | `throwError`, `gracefulMessage` | Controls error surfacing behavior |\n| `RegistrationPolicy` | `throwIfExists`, `replace`, `ignore` | Controls duplicate agent name handling |\n\n### Sealed Exception Hierarchy\n\n```\nAgenixException (sealed)\n├── LlmException\n│ └── LlmTimeoutException\n├── ResponseParseException\n├── ToolNotFoundException\n├── ToolExecutionException\n├── AgentNotFoundException\n├── DataStoreException\n│ └── NotAuthenticatedException\n└── ConfigException\n```\n\n### Internal Constants\n\n| Constant | Value | Description |\n|---|---|---|\n| `kMaxToolIterations` | `5` | Max tool→observe→re-prompt cycles per turn |\n| `kMaxParseRetries` | `2` | Max corrective re-prompts for malformed JSON |\n| `kMaxChainDepth` | `5` | Max depth for agent chain delegation |\n\n---\n\n## Usage Architectures\n\n### Single-Agent Chat App\n\nThe simplest setup — one agent handling all user interactions.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐ ┌───────────┐\n│ Flutter │────►│ Agent │────►│ LLM │ │ DataStore │\n│ UI │◄────│ │◄────│ │ │ (Firebase)│\n└──────────┘ │ │ └───────┘ └───────────┘\n │ │──── save/load ────►\n └───────┘\n```\n\nBest for: chatbots, Q&A apps, customer support widgets.\n\n### Agent + Tools (API Integration)\n\nThe agent can call external APIs through tools.\n\n```\n┌──────────┐ ┌───────┐ ┌───────┐\n│ Flutter │────►│ Agent │────►│ LLM │\n│ UI │◄────│ │◄────│ │\n└──────────┘ │ │ └───────┘\n │ │\n │ ToolRegistry\n │ ├── WeatherTool ──► Weather API\n │ ├── NewsTool ──► News API\n │ └── DbTool ──► Database\n └───────┘\n```\n\nBest for: apps where the agent needs to fetch real-time data or trigger actions.\n\n### Multi-Agent Orchestration\n\nMultiple specialized agents collaborating on complex tasks.\n\n```\n┌──────────┐ ┌──────────────┐\n│ Flutter │────►│ Orchestrator │\n│ UI │◄────│ │\n└──────────┘ └──────┬───────┘\n │ delegates via agents_chain\n ┌─────────┼─────────┐\n ▼ ▼ ▼\n ┌─────────┐ ┌────────┐ ┌───────────┐\n │ Search │ │ Booking│ │ Favourites│\n │ Agent │ │ Agent │ │ Agent │\n │ + tools │ │ + tools│ │ + tools │\n └─────────┘ └────────┘ └───────────┘\n```\n\nBest for: complex platforms where different domains require specialized knowledge and tools.\n\n### Testing / Prototyping Setup\n\nUse `InMemoryDataStore` and scoped agents for fast, isolated development.\n\n```dart\nfinal scope = AgentScope();\n\nfinal agent = await Agent.create(\n dataStore: DataStore.inMemory(), // No Firebase needed\n llm: LLM.geminiLLM(apiKey: key, modelName: 'gemini-2.0-flash'),\n name: 'Test Agent',\n role: 'Agent under test.',\n scope: scope, // Isolated from production agents\n);\n```\n\n---\n\n## Examples\n\n| Example | Description |\n|---|---|\n| [Multi-Agent System](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system) | Three agents (Orchestrator, News, Favourites) working together |\n| [Basic Usage](https://github.com/ahmadexe/agenix/tree/main/example) | Single agent with tools |\n| [Custom DataStore](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example) | Implementing your own persistence backend |\n\n### Multi-Agent System Demo\n\nIn this example three agents collaborate:\n1. **Orchestrator** — communicates with the end user\n2. **News Agent** — handles News API operations\n3. **Favourites Agent** — manages user favourites (add, remove, list)\n\nhttps://github.com/user-attachments/assets/f79cf6ac-6913-49a7-982a-bd7b975599b7\n\n#### Workflow\n\n![Multi-Agent Workflow](https://github.com/user-attachments/assets/8ad9f4ac-018a-4092-bf8c-4fc72da81673)\n\n### Single-Agent Demo\n\nAn agentic app powered by \"Lens\" — an AI agent with a specific personality that can answer platform questions and perform tasks using tools.\n\nhttps://github.com/user-attachments/assets/bcb56da8-4285-4661-af52-ee8dd6f31d08\n\n---\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "37457c558039f63cae7c3e72a97968fca9f996e4": {"t": 1785764712.2164342, "n": 8333, "h": "c9d8c57ba7e55494", "c": "![Screenshot 2025-05-28 at 4 49 43 PM](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n\n# Agenix\n\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub Dev\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n\n## Overview\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. It comes with various Datastores to store your messages, various LLMs to act as the base of your agentic app. Just define the background data of your Agentic app, your tools and you are good to go!\n\n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n5. Tool Registry: Whatever tool you have, don't forget to add them to the registry!\n\n\n## How to Use?\n\n### Initialization\nAn agentic app runs using an AI Agent, your AI agent should have some background knowledge about your application and what job is it performing. To provide this knowledge add a file called **system_data.json**, in this file define the name of the agent, it's role in the app, it's personality and anything else you want to add. You can basically customize this file as per your wish.\nLocation of the file\n\n\n**assets/system_data.json**\n\n\nIn the main function or in your bloc or the point of contact to your agent, add the following lines to initialize the Agent. This current example initializes the agent using firebase firestore as DataStore and Gemini as the LLM. You can swap them for your own implementations. You can create as many agents as you want, agenix will keep track of them, internally agenix will delegate tasks to the most appropriate agent. Agenix can also engage multiple agents in a chain to perform a sequence of sub tasks.\n```\nfinal agent = await Agent.create(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: apiKey, modelName: 'gemini-1.5-flash'),\n name: 'General Purpose Agent',\n role: 'This is the main agent for the platform.',\n );\n```\n\nDefine your key and run:\n```\nflutter run -d chrome --dart-define=GEMINI_API_KEY=Your-Gemini-Key\n```\n\n\n### Generating Response\nTo get a response from the Agent, call the agent.generateResponse method.\n```\nfinal res = await agent.generateResponse(\n convoId: '1',\n userMessage: userMessage,\n);\n```\n\n\n### Building a tool\nThe Agent will be capable enough to maintain context using previous messages in a conversation, understand and intelligently respond to user's prompt, but to perform any specific action, like hit an API endpoint, or run a database query, you will need to build and register tools.\n\nThere are 2 kinds of tools.\n1. Tools without parameters.\n2. Tools with parameters.\n\nYou can build them something like this. \n```\nclass NewsTool extends Tool {\n NewsTool({required super.name, required super.description});\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n final apiResponse = {\n 'headline': 'Flutter is Awesome!',\n 'details':\n 'Flutter 3.0 has been released with amazing features. The latest flutter version is 3.32, check it out!',\n };\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message:\n 'Breaking News: ${apiResponse['headline']}. \\n${apiResponse['details']}',\n data:\n apiResponse, // The data field is optional you can return data if it is required.\n needsFurtherReasoning:\n true, // Set this to true if the tool needs further reasoning\n );\n }\n}\n```\n\nThe above tool uses no params, but to use a tool with params. Do something like this.\n```\nclass WeatherTool extends Tool {\n WeatherTool({\n required super.name,\n required super.description,\n required super.parameters,\n });\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n final apiResponse = {'temperature': 25, 'condition': 'Sunny'};\n final location = params['location'] as String?;\n\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message:\n 'The weather in $location is ${apiResponse['condition']} with a temperature of ${apiResponse['temperature']}°C.',\n );\n }\n}\n```\n\nOnce you have defined yout tools, register them as follows:\n```\nToolRegistry().registerTool(\n NewsTool(\n name: 'news_tool',\n description:\n 'This tool should be used if the user asks for news of any sort.',\n ),\n );\nToolRegistry().registerTool(\n WeatherTool(\n name: 'weather_tool',\n description:\n 'This tool should be used if the user asks for the weather.',\n parameters: [\n ParamSpec(\n name: 'location',\n type: 'String',\n description: 'The location for which to get the weather.',\n required: true,\n ),\n ],\n ),\n);\n```\n\nOnce a tool is defined and registered Agenix is capable enough to hit them when required, deduce the parameters from the input, or ask for the parameters if they are required. If your tool fails to perform the intended task, you can try adding a more defined description. If a required task falls under the responsibilities of multiple agents, agenix will engage them in a chain and delegate the sub-tasks to the respective agents. Agenix will manage the chain itself and use the output from one agent as the input of the other. \n\n\n## Examples\n1. [Example of Multi Agent Systems Built Using Agenix](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system)\n2. [Basic usage of Agenix](https://github.com/ahmadexe/agenix/tree/main/example)\n3. [Using Agenix with Custom Data Store](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example)\n\n\n## Visuals\n\n### Multi Agents System\nIn this example three agents are working together:\n1. Orchestrator: The agent that is responsible for communicating with the end user.\n2. News Agent: The agent that is responsible for dealing with News API operations.\n3. Favourites Agent: The agent that manages user's favourites, marking something as favourite, removing something from favourite or fetching the user favourites!\n\nhttps://github.com/user-attachments/assets/f79cf6ac-6913-49a7-982a-bd7b975599b7\n\n\n#### Workflow:\n\n\n![flow](https://github.com/user-attachments/assets/8ad9f4ac-018a-4092-bf8c-4fc72da81673)\n\n\n\n### Agentic App\nAn agentic app is basically an application that is powered by an AI agent, this AI agent can perform tasks for the user in the platform, using pre defined tools. The following example shows how an agent \"Lens\" can have a sepcific personality, it can answer questions about the platform, perform tasks using tools, etc.\n\n\nhttps://github.com/user-attachments/assets/bcb56da8-4285-4661-af52-ee8dd6f31d08\n\n\n## Maintainers\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "63f7e7953eae77afe03a54e85b28f54ab90c75fe": {"t": 1785764713.6049063, "n": 7805, "h": "16baccff7e5b301c", "c": "![Screenshot 2025-05-28 at 4 49 43 PM](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n\n# Agenix\n\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub Dev\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n\n## Overview\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. It comes with various Datastores to store your messages, various LLMs to act as the base of your agentic app. Just define the background data of your Agentic app, your tools and you are good to go!\n\n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n5. Tool Registry: Whatever tool you have, don't forget to add them to the registry!\n\n\n## How to Use?\n\n### Initialization\nAn agentic app runs using an AI Agent, your AI agent should have some background knowledge about your application and what job is it performing. To provide this knowledge add a file called **system_data.json**, in this file define the name of the agent, it's role in the app, it's personality and anything else you want to add. You can basically customize this file as per your wish.\nLocation of the file\n\n\n**assets/system_data.json**\n\n\nIn the main function or in your bloc or the point of contact to your agent, add the following lines to initialize the Agent. This current example initializes the agent using firebase firestore as DataStore and Gemini as the LLM. You can swap them for your own implementations. You can create as many agents as you want, agenix will keep track of them, internally agenix will delegate tasks to the most appropriate agent. Agenix can also engage multiple agents in a chain to perform a sequence of sub tasks.\n```\nfinal agent = await Agent.create(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: apiKey, modelName: 'gemini-1.5-flash'),\n name: 'General Purpose Agent',\n role: 'This is the main agent for the platform.',\n );\n```\n\nDefine your key and run:\n```\nflutter run -d chrome --dart-define=GEMINI_API_KEY=Your-Gemini-Key\n```\n\n\n### Generating Response\nTo get a response from the Agent, call the agent.generateResponse method.\n```\nfinal res = await agent.generateResponse(\n convoId: '1',\n userMessage: userMessage,\n);\n```\n\n\n### Building a tool\nThe Agent will be capable enough to maintain context using previous messages in a conversation, understand and intelligently respond to user's prompt, but to perform any specific action, like hit an API endpoint, or run a database query, you will need to build and register tools.\n\nThere are 2 kinds of tools.\n1. Tools without parameters.\n2. Tools with parameters.\n\nYou can build them something like this. \n```\nclass NewsTool extends Tool {\n NewsTool({required super.name, required super.description});\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n final apiResponse = {\n 'headline': 'Flutter is Awesome!',\n 'details':\n 'Flutter 3.0 has been released with amazing features. The latest flutter version is 3.32, check it out!',\n };\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message:\n 'Breaking News: ${apiResponse['headline']}. \\n${apiResponse['details']}',\n data:\n apiResponse, // The data field is optional you can return data if it is required.\n needsFurtherReasoning:\n true, // Set this to true if the tool needs further reasoning\n );\n }\n}\n```\n\nThe above tool uses no params, but to use a tool with params. Do something like this.\n```\nclass WeatherTool extends Tool {\n WeatherTool({\n required super.name,\n required super.description,\n required super.parameters,\n });\n\n @override\n Future<ToolResponse> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n final apiResponse = {'temperature': 25, 'condition': 'Sunny'};\n final location = params['location'] as String?;\n\n return ToolResponse(\n toolName: name,\n isRequestSuccessful: true,\n message:\n 'The weather in $location is ${apiResponse['condition']} with a temperature of ${apiResponse['temperature']}°C.',\n );\n }\n}\n```\n\nOnce you have defined yout tools, register them as follows:\n```\nToolRegistry().registerTool(\n NewsTool(\n name: 'news_tool',\n description:\n 'This tool should be used if the user asks for news of any sort.',\n ),\n );\nToolRegistry().registerTool(\n WeatherTool(\n name: 'weather_tool',\n description:\n 'This tool should be used if the user asks for the weather.',\n parameters: [\n ParamSpec(\n name: 'location',\n type: 'String',\n description: 'The location for which to get the weather.',\n required: true,\n ),\n ],\n ),\n);\n```\n\nOnce a tool is defined and registered Agenix is capable enough to hit them when required, deduce the parameters from the input, or ask for the parameters if they are required. If your tool fails to perform the intended task, you can try adding a more defined description. If a required task falls under the responsibilities of multiple agents, agenix will engage them in a chain and delegate the sub-tasks to the respective agents. Agenix will manage the chain itself and use the output from one agent as the input of the other. \n\n\n## Examples\n1. [Example of Multi Agent Systems Built Using Agenix](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system)\n2. [Basic usage of Agenix](https://github.com/ahmadexe/agenix/tree/main/example)\n3. [Using Agenix with Custom Data Store](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example)\n\n\n## Visuals\n\n### Multi Agents System\nIn this example three agents are working together:\n1. Orchestrator: The agent that is responsible for communicating with the end user.\n2. News Agent: The agent that is responsible for dealing with News API operations.\n3. Favourites Agent: The agent that manages user's favourites, marking something as favourite, removing something from favourite or fetching the user favourites!\n\nhttps://github.com/user-attachments/assets/f79cf6ac-6913-49a7-982a-bd7b975599b7\n\n\n## Maintainers\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "79a751acda3b1fc4cd1c0b0b30dcd1a77c772d37": {"t": 1785764715.092176, "n": 6096, "h": "0cdcc83667ee024b", "c": "![Screenshot 2025-05-28 at 4 49 43 PM](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n\n# Agenix\n\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub Dev\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n\n## Overview\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. It comes with various Datastores to store your messages, various LLMs to act as the base of your agentic app. Just define the data of your Agentic app, your tools and you are good to go!\n\n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n5. Tool Registry: Whatever tool you have, don't forget to add them to the registry!\n\n\n## How to Use?\n\n### Initialization\nAn agentic app runs using an AI Agent, your AI agent should have some background knowledge about your application and what job is it performing. To provide this knowledge add a file called **system_data.json**, in this file define the name of the agent, it's role in the app, it's personality and anything else you want to add. You can basically customize this file as per your wish.\nLocation of the file\n\n\n**assets/system_data.json**\n\n\nIn the main function or in your bloc or the point of contact to your agent, add the following lines to initialize the Agent.\n```\nfinal agent = Agent();\nagent.init(\n dataStore: YourDataStore(),\n llm: YourLLM(),\n);\n```\n\nIf you want to use Firebase Firestore as your DataStore, and Gemini as your LLM, you can do something like this:\n```\nfinal agent = Agent();\nconst apiKey = String.fromEnvironment('GEMINI_API_KEY');\nagent.init(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: apiKey, modelName: 'gemini-1.5-flash'),\n);\n```\n\nRun and define your key:\n```\nflutter run -d chrome --dart-define=GEMINI_API_KEY=Your-Gemini-Key\n```\n\n\n### Generating Response\nTo get a response from the Agent, call the agent.generateResponse method.\n```\nfinal res = await Agent().generateResponse(\n convoId: '1',\n userMessage: userMessage,\n);\n```\n\n\n### Building a tool\nThe Agent will be capable enough to maintain context using previous messages in a conversation, understand and intelligently respond to user's prompt, but to perform any specific action, like hit an API endpoint, or run a database query, you will need to build and register tools.\n\nThere are 2 kinds of tools.\n1. Tools without parameters.\n2. Tools with parameters.\n\nYou can build them something like this. \n```\nclass NewsTool extends Tool {\n NewsTool({required super.name, required super.description});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'title': 'Breaking News',\n 'description': 'This is a sample news description.',\n }; \n }\n}\n```\n\nThe above tool uses no params, but to use a tool with params. Do something like this.\n```\nclass WeatherTool extends Tool {\n WeatherTool({required super.name, required super.description, required super.parameters});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'temperature': '25°C',\n 'condition': 'Sunny',\n 'location': params['location'],\n };\n }\n}\n```\n\nOnce you have defined yout tools, register them as follows:\n```\nToolRegistry().registerTool(\n NewsTool(\n name: 'news_tool',\n description:\n 'This tool should be used if the user asks for news of any sort.',\n ),\n );\nToolRegistry().registerTool(\n WeatherTool(\n name: 'weather_tool',\n description:\n 'This tool should be used if the user asks for the weather.',\n parameters: [\n ParamSpec(\n name: 'location',\n type: 'String',\n description: 'The location for which to get the weather.',\n required: true,\n ),\n ],\n ),\n);\n```\n\nOnce a tool is defined Agenix is capable enough to hit them when required, deduce the parameters from the input, or ask for the parameters if they are required. If your tool fails to perform the intended task, you can try adding a more defined description.\n\n\n## Examples\n1. [Example of Multi Agent Systems Built Using Agenix](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system)\n2. [Basic usage of Agenix](https://github.com/ahmadexe/agenix/tree/main/example)\n3. [Using Agenix with Custom Data Store](https://github.com/ahmadexe/agenix-examples/tree/main/custom_data_source_example)\n\n\n## Maintainers\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "44e18e3617acd7b22f0f82ad86fa350a757f1b15": {"t": 1785764716.3455207, "n": 5967, "h": "ed1444e140c0888c", "c": "![Screenshot 2025-05-28 at 4 49 43 PM](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n\n# Agenix\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub Dev\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n## Overview\n\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. It comes with various Datastores to store your messages, various LLMs to act as the base of your agentic app. Just define the data of your Agentic app, your tools and you are good to go!\n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n5. Tool Registry: Whatever tool you have, don't forget to add them to the registry!\n\n## How to Use?\n\n### Initialization\nAn agentic app runs using an AI Agent, your AI agent should have some background knowledge about your application and what job is it performing. To provide this knowledge add a file called **system_data.json**, in this file define the name of the agent, it's role in the app, it's personality and anything else you want to add. You can basically customize this file as per your wish.\nLocation of the file\n\n\n**assets/system_data.json**\n\n\nIn the main function or in your bloc or the point of contact to your agent, add the following lines to initialize the Agent.\n```\nfinal agent = Agent();\nagent.init(\n dataStore: YourDataStore(),\n llm: YourLLM(),\n);\n```\n\nIf you want to use Firebase Firestore as your DataStore, and Gemini as your LLM, you can do something like this:\n```\nfinal agent = Agent();\nconst apiKey = String.fromEnvironment('GEMINI_API_KEY');\nagent.init(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: apiKey, modelName: 'gemini-1.5-flash'),\n);\n```\n\nRun and define your key:\n```\nflutter run -d chrome --dart-define=GEMINI_API_KEY=Your-Gemini-Key\n```\n\n### Generating Response\nTo get a response from the Agent, call the agent.generateResponse method.\n```\nfinal res = await Agent().generateResponse(\n convoId: '1',\n userMessage: userMessage,\n);\n```\n\n### Building a tool\nThe Agent will be capable enough to maintain context using previous messages in a conversation, understand and intelligently respond to user's prompt, but to perform any specific action, like hit an API endpoint, or run a database query, you will need to build and register tools.\n\nThere are 2 kinds of tools.\n1. Tools without parameters.\n2. Tools with parameters.\n\nYou can build them something like this. \n```\nclass NewsTool extends Tool {\n NewsTool({required super.name, required super.description});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'title': 'Breaking News',\n 'description': 'This is a sample news description.',\n }; \n }\n}\n```\n\nThe above tool uses no params, but to use a tool with params. Do something like this.\n```\nclass WeatherTool extends Tool {\n WeatherTool({required super.name, required super.description, required super.parameters});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'temperature': '25°C',\n 'condition': 'Sunny',\n 'location': params['location'],\n };\n }\n}\n```\n\nOnce you have defined yout tools, register them as follows:\n```\nToolRegistry().registerTool(\n NewsTool(\n name: 'news_tool',\n description:\n 'This tool should be used if the user asks for news of any sort.',\n ),\n );\nToolRegistry().registerTool(\n WeatherTool(\n name: 'weather_tool',\n description:\n 'This tool should be used if the user asks for the weather.',\n parameters: [\n ParamSpec(\n name: 'location',\n type: 'String',\n description: 'The location for which to get the weather.',\n required: true,\n ),\n ],\n ),\n);\n```\n\nOnce a tool is defined Agenix is capable enough to hit them when required, deduce the parameters from the input, or ask for the parameters if they are required. If your tool fails to perform the intended task, you can try adding a more defined description.\n\n## Examples\n1. [Example of Multi Agent Systems Built Using Agenix](https://github.com/ahmadexe/agenix-examples/tree/main/multi_agent_system)\n2. [Basic usage of Agenix](https://github.com/ahmadexe/agenix/tree/main/example)\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "6c3b6c5348bd5ca2b19bba6bfcd942a4e081b2f7": {"t": 1785764717.8792746, "n": 5835, "h": "b8c987f722bbfac9", "c": "![Screenshot 2025-05-28 at 4 49 43 PM](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n\n# Agenix\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub Dev\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n## Overview\n\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. It comes with various Datastores to store your messages, various LLMs to act as the base of your agentic app. Just define the data of your Agentic app, your tools and you are good to go!\n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n5. Tool Registry: Whatever tool you have, don't forget to add them to the registry!\n\n## How to Use?\n\n### Initialization\nAn agentic app runs using an AI Agent, your AI agent should have some background knowledge about your application and what job is it performing. To provide this knowledge add a file called **system_data.json**, in this file define the name of the agent, it's role in the app, it's personality and anything else you want to add. You can basically customize this file as per your wish.\nLocation of the file\n\n\n**assets/system_data.json**\n\n\nIn the main function or in your bloc or the point of contact to your agent, add the following lines to initialize the Agent.\n```\nfinal agent = Agent();\nagent.init(\n dataStore: YourDataStore(),\n llm: YourLLM(),\n);\n```\n\nIf you want to use Firebase Firestore as your DataStore, and Gemini as your LLM, you can do something like this:\n```\nfinal agent = Agent();\nconst apiKey = String.fromEnvironment('GEMINI_API_KEY');\nagent.init(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: apiKey, modelName: 'gemini-1.5-flash'),\n);\n```\n\nRun and define your key:\n```\nflutter run -d chrome --dart-define=GEMINI_API_KEY=Your-Gemini-Key\n```\n\n### Generating Response\nTo get a response from the Agent, call the agent.generateResponse method.\n```\nfinal res = await Agent().generateResponse(\n convoId: '1',\n userMessage: userMessage,\n);\n```\n\n### Building a tool\nThe Agent will be capable enough to maintain context using previous messages in a conversation, understand and intelligently respond to user's prompt, but to perform any specific action, like hit an API endpoint, or run a database query, you will need to build and register tools.\n\nThere are 2 kinds of tools.\n1. Tools without parameters.\n2. Tools with parameters.\n\nYou can build them something like this. \n```\nclass NewsTool extends Tool {\n NewsTool({required super.name, required super.description});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'title': 'Breaking News',\n 'description': 'This is a sample news description.',\n }; \n }\n}\n```\n\nThe above tool uses no params, but to use a tool with params. Do something like this.\n```\nclass WeatherTool extends Tool {\n WeatherTool({required super.name, required super.description, required super.parameters});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'temperature': '25°C',\n 'condition': 'Sunny',\n 'location': params['location'],\n };\n }\n}\n```\n\nOnce you have defined yout tools, register them as follows:\n```\nToolRegistry().registerTool(\n NewsTool(\n name: 'news_tool',\n description:\n 'This tool should be used if the user asks for news of any sort.',\n ),\n );\nToolRegistry().registerTool(\n WeatherTool(\n name: 'weather_tool',\n description:\n 'This tool should be used if the user asks for the weather.',\n parameters: [\n ParamSpec(\n name: 'location',\n type: 'String',\n description: 'The location for which to get the weather.',\n required: true,\n ),\n ],\n ),\n);\n```\n\nOnce a tool is defined Agenix is capable enough to hit them when required, deduce the parameters from the input, or ask for the parameters if they are required. If your tool fails to perform the intended task, you can try adding a more defined description.\n\n## Examples\nTo view full felde examples, please take a look inside the example directory!\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "f40051a2a794dbbf8ed644e32236e49836376d57": {"t": 1785764719.2608335, "n": 5835, "h": "67b567ccaead9994", "c": "![Screenshot 2025-05-28 at 4 49 43 PM](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n\n# Agenix\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub Dev\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/badge/platform-Flutter%20%7C%20Dart-blue\" alt=\"Platform\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n## Overview\n\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. It comes with various Datastores to store your messages, various LLMs to act as the base of your agentic app. Just define the data of your Agentic app, your tools and you are good to go!\n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n5. Tool Registry: Whatever tool you have, don't forget to add them to the registry!\n\n## How to Use?\n\n### Initialization\nAn agentic app runs using an AI Agent, your AI agent should have some background knowledge about your application and what job is it performing. To provide this knowledge add a file called **system_data.json**, in this file define the name of the agent, it's role in the app, it's personality and anything else you want to add. You can basically customize this file as per your wish.\nLocation of the file\n\n\n**assets/system_data.json**\n\n\nIn the main function or in your bloc or the point of contact to your agent, add the following lines to initialize the Agent.\n```\nfinal agent = Agent();\nagent.init(\n dataStore: YourDataStore(),\n llm: YourLLM(),\n);\n```\n\nIf you want to use Firebase Firestore as your DataStore, and Gemini as your LLM, you can do something like this:\n```\nfinal agent = Agent();\nconst apiKey = String.fromEnvironment('GEMINI_API_KEY');\nagent.init(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: apiKey, modelName: 'gemini-1.5-flash'),\n);\n```\n\nRun and define your key:\n```\nflutter run -d chrome --dart-define=GEMINI_API_KEY=Your-Gemini-Key\n```\n\n### Generating Response\nTo get a response from the Agent, call the agent.generateResponse method.\n```\nfinal res = await Agent().generateResponse(\n convoId: '1',\n userMessage: userMessage,\n);\n```\n\n### Building a tool\nThe Agent will be capable enough to maintain context using previous messages in a conversation, understand and intelligently respond to user's prompt, but to perform any specific action, like hit an API endpoint, or run a database query, you will need to build and register tools.\n\nThere are 2 kinds of tools.\n1. Tools without parameters.\n2. Tools with parameters.\n\nYou can build them something like this. \n```\nclass NewsTool extends Tool {\n NewsTool({required super.name, required super.description});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'title': 'Breaking News',\n 'description': 'This is a sample news description.',\n }; \n }\n}\n```\n\nThe above tool uses no params, but to use a tool with params. Do something like this.\n```\nclass WeatherTool extends Tool {\n WeatherTool({required super.name, required super.description, required super.parameters});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'temperature': '25°C',\n 'condition': 'Sunny',\n 'location': params['location'],\n };\n }\n}\n```\n\nOnce you have defined yout tools, register them as follows:\n```\nToolRegistry().registerTool(\n NewsTool(\n name: 'news_tool',\n description:\n 'This tool should be used if the user asks for news of any sort.',\n ),\n );\nToolRegistry().registerTool(\n WeatherTool(\n name: 'weather_tool',\n description:\n 'This tool should be used if the user asks for the weather.',\n parameters: [\n ParamSpec(\n name: 'location',\n type: 'String',\n description: 'The location for which to get the weather.',\n required: true,\n ),\n ],\n ),\n);\n```\n\nOnce a tool is defined Agenix is capable enough to hit them when required, deduce the parameters from the input, or ask for the parameters if they are required. If your tool fails to perform the intended task, you can try adding a more defined description.\n\n## Examples\nTo view full feldg examples, please take a look inside the example directory!\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "81ee71cd51fddb880a7c58bd276ca47dc83933b5": {"t": 1785764720.7976754, "n": 5700, "h": "ce0ed03a1537f9b9", "c": "![Screenshot 2025-05-28 at 4 49 43 PM](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n\n# Agenix\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://pub.dev/packages/agenix\"><img src=\"https://img.shields.io/pub/v/agenix.svg\" alt=\"Pub Dev\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n## Overview\n\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. It comes with various Datastores to store your messages, various LLMs to act as the base of your agentic app. Just define the data of your Agentic app, your tools and you are good to go!\n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n5. Tool Registry: Whatever tool you have, don't forget to add them to the registry!\n\n## How to Use?\n\n### Initialization\nAn agentic app runs using an AI Agent, your AI agent should have some background knowledge about your application and what job is it performing. To provide this knowledge add a file called **system_data.json**, in this file define the name of the agent, it's role in the app, it's personality and anything else you want to add. You can basically customize this file as per your wish.\nLocation of the file\n\n\n**assets/system_data.json**\n\n\nIn the main function or in your bloc or the point of contact to your agent, add the following lines to initialize the Agent.\n```\nfinal agent = Agent();\nagent.init(\n dataStore: YourDataStore(),\n llm: YourLLM(),\n);Ï\n```\n\nIf you want to use Firebase Firestore as your DataStore, and Gemini as your LLM, you can do something like this:\n```\nfinal agent = Agent();\nconst apiKey = String.fromEnvironment('GEMINI_API_KEY');\nagent.init(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: apiKey, modelName: 'gemini-1.5-flash'),\n);\n```\n\nTo get the key run using:\n```\nflutter run -d chrome --dart-define=GEMINI_API_KEY=Your-Gemini-Key\n```\n\n### Generating Response\nTo get a response from the Agent, call the agent.generateResponse method.\n```\nfinal res = await Agent().generateResponse(\n convoId: '1',\n userMessage: userMessage,\n);\n```\n\n### Building a tool\nThe Agent will be capable enough to maintain context using previous messages in a conversation, understand and intelligently respond to user's prompt, but to perform any specific action, like hit an API endpoint, or run a database query, you will need to build and register tools.\n\nThere are 2 kinds of tools.\n1. Tools without parameters.\n2. Tools with parameters.\n\nYou can build them something like this. \n```\nclass NewsTool extends Tool {\n NewsTool({required super.name, required super.description});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'title': 'Breaking News',\n 'description': 'This is a sample news description.',\n }; \n }\n}\n```\n\nThe above tool uses no params, but to use a tool with params. Do something like this.\n```\nclass WeatherTool extends Tool {\n WeatherTool({required super.name, required super.description, required super.parameters});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'temperature': '25°C',\n 'condition': 'Sunny',\n 'location': params['location'],\n };\n }\n}\n```\n\nOnce you have defined yout tools, register them as follows:\n```\nToolRegistry().registerTool(\n NewsTool(\n name: 'news_tool',\n description:\n 'This tool should be used if the user asks for news of any sort.',\n ),\n );\nToolRegistry().registerTool(\n WeatherTool(\n name: 'weather_tool',\n description:\n 'This tool should be used if the user asks for the weather.',\n parameters: [\n ParamSpec(\n name: 'location',\n type: 'String',\n description: 'The location for which to get the weather.',\n required: true,\n ),\n ],\n ),\n);\n```\n\nOnce a tool is defined Agenix is capable enough to hit them when required, deduce the parameters from the input, or ask for the parameters if they are required. If your tool fails to perform the intended task, you can try adding a more defined description.\n\n## Examples\nTo view full feldg examples, please take a look inside the example directory!\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "d965066d866ccae276d4f0650054233e87415d5f": {"t": 1785764722.3470848, "n": 5588, "h": "fef6c9b6a97d38ba", "c": "![Screenshot 2025-05-28 at 4 49 43 PM](https://github.com/user-attachments/assets/fbb110c9-6019-440b-b6c4-37d86dea725f)\n\n\n# Agenix\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n## Overview\n\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. It comes with various Datastores to store your messages, various LLMs to act as the base of your agentic app. Just define the data of your Agentic app, your tools and you are good to go!\n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n5. Tool Registry: Whatever tool you have, don't forget to add them to the registry!\n\n## How to Use?\n\n### Initialization\nAn agentic app runs using an AI Agent, your AI agent should have some background knowledge about your application and what job is it performing. To provide this knowledge add a file called **system_data.json**, in this file define the name of the agent, it's role in the app, it's personality and anything else you want to add. You can basically customize this file as per your wish.\nLocation of the file\n\n\n**assets/system_data.json**\n\n\nIn the main function or in your bloc or the point of contact to your agent, add the following lines to initialize the Agent.\n```\nfinal agent = Agent();\nagent.init(\n dataStore: YourDataStore(),\n llm: YourLLM(),\n);Ï\n```\n\nIf you want to use Firebase Firestore as your DataStore, and Gemini as your LLM, you can do something like this:\n```\nfinal agent = Agent();\nconst apiKey = String.fromEnvironment('GEMINI_API_KEY');\nagent.init(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: apiKey, modelName: 'gemini-1.5-flash'),\n);\n```\n\nTo get the key run using:\n```\nflutter run -d chrome --dart-define=GEMINI_API_KEY=Your-Gemini-Key\n```\n\n### Generating Response\nTo get a response from the Agent, call the agent.generateResponse method.\n```\nfinal res = await Agent().generateResponse(\n convoId: '1',\n userMessage: userMessage,\n);\n```\n\n### Building a tool\nThe Agent will be capable enough to maintain context using previous messages in a conversation, understand and intelligently respond to user's prompt, but to perform any specific action, like hit an API endpoint, or run a database query, you will need to build and register tools.\n\nThere are 2 kinds of tools.\n1. Tools without parameters.\n2. Tools with parameters.\n\nYou can build them something like this. \n```\nclass NewsTool extends Tool {\n NewsTool({required super.name, required super.description});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'title': 'Breaking News',\n 'description': 'This is a sample news description.',\n }; \n }\n}\n```\n\nThe above tool uses no params, but to use a tool with params. Do something like this.\n```\nclass WeatherTool extends Tool {\n WeatherTool({required super.name, required super.description, required super.parameters});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'temperature': '25°C',\n 'condition': 'Sunny',\n 'location': params['location'],\n };\n }\n}\n```\n\nOnce you have defined yout tools, register them as follows:\n```\nToolRegistry().registerTool(\n NewsTool(\n name: 'news_tool',\n description:\n 'This tool should be used if the user asks for news of any sort.',\n ),\n );\nToolRegistry().registerTool(\n WeatherTool(\n name: 'weather_tool',\n description:\n 'This tool should be used if the user asks for the weather.',\n parameters: [\n ParamSpec(\n name: 'location',\n type: 'String',\n description: 'The location for which to get the weather.',\n required: true,\n ),\n ],\n ),\n);\n```\n\nOnce a tool is defined Agenix is capable enough to hit them when required, deduce the parameters from the input, or ask for the parameters if they are required. If your tool fails to perform the intended task, you can try adding a more defined description.\n\n## Examples\nTo view full feldg examples, please take a look inside the example directory!\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "0bbd6e34b862468c4dc3cae86c4aba3ff15e217c": {"t": 1785764725.2911863, "n": 5466, "h": "7e024ec3d6d01486", "c": "# Agenix\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n## Overview\n\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. It comes with various Datastores to store your messages, various LLMs to act as the base of your agentic app. Just define the data of your Agentic app, your tools and you are good to go!\n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n5. Tool Registry: Whatever tool you have, don't forget to add them to the registry!\n\n## How to Use?\n\n### Initialization\nAn agentic app runs using an AI Agent, your AI agent should have some background knowledge about your application and what job is it performing. To provide this knowledge add a file called **system_data.json**, in this file define the name of the agent, it's role in the app, it's personality and anything else you want to add. You can basically customize this file as per your wish.\nLocation of the file\n\n\n**assets/system_data.json**\n\n\nIn the main function or in your bloc or the point of contact to your agent, add the following lines to initialize the Agent.\n```\nfinal agent = Agent();\nagent.init(\n dataStore: YourDataStore(),\n llm: YourLLM(),\n);Ï\n```\n\nIf you want to use Firebase Firestore as your DataStore, and Gemini as your LLM, you can do something like this:\n```\nfinal agent = Agent();\nconst apiKey = String.fromEnvironment('GEMINI_API_KEY');\nagent.init(\n dataStore: DataStore.firestoreDataStore(),\n llm: LLM.geminiLLM(apiKey: apiKey, modelName: 'gemini-1.5-flash'),\n);\n```\n\nTo get the key run using:\n```\nflutter run -d chrome --dart-define=GEMINI_API_KEY=Your-Gemini-Key\n```\n\n### Generating Response\nTo get a response from the Agent, call the agent.generateResponse method.\n```\nfinal res = await Agent().generateResponse(\n convoId: '1',\n userMessage: userMessage,\n);\n```\n\n### Building a tool\nThe Agent will be capable enough to maintain context using previous messages in a conversation, understand and intelligently respond to user's prompt, but to perform any specific action, like hit an API endpoint, or run a database query, you will need to build and register tools.\n\nThere are 2 kinds of tools.\n1. Tools without parameters.\n2. Tools with parameters.\n\nYou can build them something like this. \n```\nclass NewsTool extends Tool {\n NewsTool({required super.name, required super.description});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'title': 'Breaking News',\n 'description': 'This is a sample news description.',\n }; \n }\n}\n```\n\nThe above tool uses no params, but to use a tool with params. Do something like this.\n```\nclass WeatherTool extends Tool {\n WeatherTool({required super.name, required super.description, required super.parameters});\n\n @override\n Future<Map<String, dynamic>?> run(Map<String, dynamic> params) async {\n // Simulate a network call\n await Future.delayed(const Duration(seconds: 2));\n return {\n 'temperature': '25°C',\n 'condition': 'Sunny',\n 'location': params['location'],\n };\n }\n}\n```\n\nOnce you have defined yout tools, register them as follows:\n```\nToolRegistry().registerTool(\n NewsTool(\n name: 'news_tool',\n description:\n 'This tool should be used if the user asks for news of any sort.',\n ),\n );\nToolRegistry().registerTool(\n WeatherTool(\n name: 'weather_tool',\n description:\n 'This tool should be used if the user asks for the weather.',\n parameters: [\n ParamSpec(\n name: 'location',\n type: 'String',\n description: 'The location for which to get the weather.',\n required: true,\n ),\n ],\n ),\n);\n```\n\nOnce a tool is defined Agenix is capable enough to hit them when required, deduce the parameters from the input, or ask for the parameters if they are required. If your tool fails to perform the intended task, you can try adding a more defined description.\n\n## Examples\nTo view full feldg examples, please take a look inside the example directory!\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "408d09b02d8f91e7442f3193802ff8bc8eda071a": {"t": 1785764726.9251382, "n": 1517, "h": "4621dba30de63172", "c": "# Agenix\n\n<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Flutter & Dart!\n\n---\n\n## Overview\n\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. \n\n## Components\nAgenix allows users to build agentic apps, there are some key components that users should be familiar with before using Agenix.\n1. Agent: Agent is the main component you will be dealing with in your flutter and dart code. It exposes you to the public facing API that allows users to generate response from the LLM. \n2. DataStore: This is how Agenix deals with the data, whether it is to save the data, get an ongoing conversation or to fetch all conversations with the agent. You can use a pre-built datastore like FirebaseDataStore, or you can create a custom implementation. \n3. LLM: A large language model to support the agent. You can use a pre-built model like Gemini or if you have a custom implementation running on the server, you can use that.\n4. Tools: Tools are elements that do the work for the agent, if you want the agent to fetch news? Make and register a tool to fetch news from the internet.\n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "d9be5551ffdc230214a42e67215e798b218cf26f": {"t": 1785764728.6717415, "n": 573, "h": "f8894bcb29f7fa10", "c": "<p align=\"center\">\n<a href=\"https://github.com/ahmadexe/agenix\"><img src=\"https://img.shields.io/github/stars/ahmadexe/agenix.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n</p>\n\n---\n\nA framework to build agentic apps using Dart!\n\n---\n\n\n## Overview\n\nAgenix aims at providing an easy interface to build Agentic apps using Flutter and Dart. \n\n## Maintainers\n\n- [Muhammad Ahmad](https://github.com/ahmadexe)\n"}, "5f3f7764f506b4251359cd9ab8d47a8dbf3136b4": {"t": 1785764730.3802636, "n": 1258, "h": "140e07ab1667ac2f", "c": "<!--\nThis README describes the package. If you publish this package to pub.dev,\nthis README's contents appear on the landing page for your package.\n\nFor information about how to write a good package README, see the guide for\n[writing package pages](https://dart.dev/tools/pub/writing-package-pages).\n\nFor general information about developing packages, see the Dart guide for\n[creating packages](https://dart.dev/guides/libraries/create-packages)\nand the Flutter guide for\n[developing packages and plugins](https://flutter.dev/to/develop-packages).\n-->\n\nTODO: Put a short description of the package here that helps potential users\nknow whether this package might be useful for them.\n\n## Features\n\nTODO: List what your package can do. Maybe include images, gifs, or videos.\n\n## Getting started\n\nTODO: List prerequisites and provide or point to information on how to\nstart using the package.\n\n## Usage\n\nTODO: Include short and useful examples for package users. Add longer examples\nto `/example` folder.\n\n```dart\nconst like = 'sample';\n```\n\n## Additional information\n\nTODO: Tell users more about the package: where to find more information, how to\ncontribute to the package, how to file issues, what response they can expect\nfrom the package authors, and more.\n"}}}}