axel-protocol 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- axel/__init__.py +80 -0
- axel/adapters/__init__.py +148 -0
- axel/adapters/bedrock.py +153 -0
- axel/adapters/cohere.py +121 -0
- axel/adapters/gemini.py +119 -0
- axel/adapters/groq.py +122 -0
- axel/adapters/litellm.py +148 -0
- axel/adapters/mistral.py +117 -0
- axel/adapters/together.py +125 -0
- axel/cli.py +532 -0
- axel/client.py +461 -0
- axel/core.py +674 -0
- axel/learning.py +687 -0
- axel/persistence.py +376 -0
- axel/server.py +1072 -0
- axel/static/monitor.html +595 -0
- axel/testing.py +273 -0
- axel/universal.py +1378 -0
- axel_protocol-2.1.0.dist-info/METADATA +523 -0
- axel_protocol-2.1.0.dist-info/RECORD +23 -0
- axel_protocol-2.1.0.dist-info/WHEEL +4 -0
- axel_protocol-2.1.0.dist-info/entry_points.txt +4 -0
- axel_protocol-2.1.0.dist-info/licenses/LICENSE +21 -0
axel/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
axel — Agent eXchange Language
|
|
3
|
+
================================
|
|
4
|
+
A universal protocol for multi-LLM networks.
|
|
5
|
+
|
|
6
|
+
Quick start:
|
|
7
|
+
from axel import AXELClient, AXELAgent, AXELServer
|
|
8
|
+
|
|
9
|
+
# Start the hub (with optional auth + persistence)
|
|
10
|
+
server = AXELServer(key="secret", db="axel.db")
|
|
11
|
+
server.start_background()
|
|
12
|
+
|
|
13
|
+
# Connect an agent
|
|
14
|
+
client = AXELClient("http://localhost:7331", "my_agent",
|
|
15
|
+
model="claude-3-5-haiku", provider="anthropic",
|
|
16
|
+
caps=["research", "summarize"],
|
|
17
|
+
key="secret")
|
|
18
|
+
|
|
19
|
+
# Execute a task on another agent
|
|
20
|
+
result = client.execute("writer", "draft_content", {"topic": "AI"})
|
|
21
|
+
|
|
22
|
+
Testing:
|
|
23
|
+
from axel.testing import MockServer, MessageCapture
|
|
24
|
+
|
|
25
|
+
with MockServer() as url:
|
|
26
|
+
c = AXELClient(url, "tester", model="x", provider="mock", caps=[])
|
|
27
|
+
...
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from axel.core import AXELBuilder, AXELMessage
|
|
31
|
+
from axel.client import AXELClient, AXELAgent
|
|
32
|
+
from axel.server import AXELServer
|
|
33
|
+
from axel.universal import SmartMemory
|
|
34
|
+
from axel.adapters import (
|
|
35
|
+
# bundled
|
|
36
|
+
MockAdapter, AnthropicAdapter, OpenAIAdapter, OllamaAdapter, OpenRouterAdapter,
|
|
37
|
+
# optional (lazy-loaded)
|
|
38
|
+
GeminiAdapter, MistralAdapter, GroqAdapter,
|
|
39
|
+
TogetherAdapter, CohereAdapter, LiteLLMAdapter, BedrockAdapter,
|
|
40
|
+
# factory
|
|
41
|
+
make_adapter, get_adapter,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
__version__ = "2.1.0"
|
|
45
|
+
__author__ = "Sector X"
|
|
46
|
+
__license__ = "MIT"
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
# Core message types
|
|
50
|
+
"AXELMessage",
|
|
51
|
+
"AXELBuilder",
|
|
52
|
+
# Client SDK
|
|
53
|
+
"AXELClient",
|
|
54
|
+
"AXELAgent",
|
|
55
|
+
# Server
|
|
56
|
+
"AXELServer",
|
|
57
|
+
# Memory
|
|
58
|
+
"SmartMemory",
|
|
59
|
+
# Adapters — bundled
|
|
60
|
+
"MockAdapter",
|
|
61
|
+
"AnthropicAdapter",
|
|
62
|
+
"OpenAIAdapter",
|
|
63
|
+
"OllamaAdapter",
|
|
64
|
+
"OpenRouterAdapter",
|
|
65
|
+
# Adapters — optional providers
|
|
66
|
+
"GeminiAdapter",
|
|
67
|
+
"MistralAdapter",
|
|
68
|
+
"GroqAdapter",
|
|
69
|
+
"TogetherAdapter",
|
|
70
|
+
"CohereAdapter",
|
|
71
|
+
"LiteLLMAdapter",
|
|
72
|
+
"BedrockAdapter",
|
|
73
|
+
# Factory helpers
|
|
74
|
+
"make_adapter",
|
|
75
|
+
"get_adapter",
|
|
76
|
+
# Testing utilities
|
|
77
|
+
"testing",
|
|
78
|
+
# Persistence
|
|
79
|
+
"persistence",
|
|
80
|
+
]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
axel.adapters — LLM provider adapters for the AXEL network
|
|
3
|
+
===========================================================
|
|
4
|
+
|
|
5
|
+
All adapters share the same interface: instantiate with an agent_id + model,
|
|
6
|
+
then call adapter.execute(task_msg) to get back an AXELMessage.
|
|
7
|
+
|
|
8
|
+
Quick reference
|
|
9
|
+
---------------
|
|
10
|
+
Provider Class Install Env var
|
|
11
|
+
────────────── ──────────────── ──────────────────────── ────────────────────────
|
|
12
|
+
OpenRouter OpenRouterAdapter pip install openai OPENROUTER_API_KEY ← start here
|
|
13
|
+
Anthropic AnthropicAdapter (bundled) ANTHROPIC_API_KEY
|
|
14
|
+
OpenAI OpenAIAdapter (bundled) OPENAI_API_KEY
|
|
15
|
+
Ollama (local) OllamaAdapter (bundled) —
|
|
16
|
+
Google Gemini GeminiAdapter google-generativeai GOOGLE_API_KEY
|
|
17
|
+
Mistral AI MistralAdapter mistralai MISTRAL_API_KEY
|
|
18
|
+
Groq GroqAdapter groq GROQ_API_KEY
|
|
19
|
+
Together AI TogetherAdapter together TOGETHER_API_KEY
|
|
20
|
+
Cohere CohereAdapter cohere COHERE_API_KEY
|
|
21
|
+
AWS Bedrock BedrockAdapter boto3 AWS_* credentials
|
|
22
|
+
Any (LiteLLM) LiteLLMAdapter litellm provider-specific
|
|
23
|
+
Mock (testing) MockAdapter (bundled) —
|
|
24
|
+
|
|
25
|
+
Usage
|
|
26
|
+
-----
|
|
27
|
+
from axel.adapters import GeminiAdapter, GroqAdapter, LiteLLMAdapter
|
|
28
|
+
|
|
29
|
+
# Google Gemini researcher
|
|
30
|
+
researcher = GeminiAdapter("researcher",
|
|
31
|
+
model="gemini-1.5-flash",
|
|
32
|
+
capabilities=["research", "summarize"])
|
|
33
|
+
|
|
34
|
+
# Groq fast reviewer
|
|
35
|
+
reviewer = GroqAdapter("reviewer",
|
|
36
|
+
model="llama-3.1-8b-instant",
|
|
37
|
+
capabilities=["review", "score"])
|
|
38
|
+
|
|
39
|
+
# LiteLLM — any model, one adapter
|
|
40
|
+
writer = LiteLLMAdapter("writer",
|
|
41
|
+
model="groq/llama-3.3-70b-versatile",
|
|
42
|
+
capabilities=["write", "edit"])
|
|
43
|
+
|
|
44
|
+
# Register with the AXEL server bridge
|
|
45
|
+
bridge.add_adapter("researcher", researcher)
|
|
46
|
+
bridge.add_adapter("reviewer", reviewer)
|
|
47
|
+
bridge.add_adapter("writer", writer)
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# ── Bundled adapters (no extra install) ───────────────────────────
|
|
51
|
+
from axel.universal import (
|
|
52
|
+
AnthropicAdapter,
|
|
53
|
+
OpenAIAdapter,
|
|
54
|
+
OllamaAdapter,
|
|
55
|
+
OpenRouterAdapter,
|
|
56
|
+
MockAdapter,
|
|
57
|
+
BaseAdapter,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# ── Optional provider adapters ────────────────────────────────────
|
|
61
|
+
# All use lazy imports internally — importing these classes is free.
|
|
62
|
+
# The provider SDK is only loaded when execute() is first called.
|
|
63
|
+
|
|
64
|
+
from axel.adapters.gemini import GeminiAdapter
|
|
65
|
+
from axel.adapters.mistral import MistralAdapter
|
|
66
|
+
from axel.adapters.groq import GroqAdapter
|
|
67
|
+
from axel.adapters.together import TogetherAdapter
|
|
68
|
+
from axel.adapters.cohere import CohereAdapter
|
|
69
|
+
from axel.adapters.litellm import LiteLLMAdapter
|
|
70
|
+
from axel.adapters.bedrock import BedrockAdapter
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
# Bundled
|
|
74
|
+
"BaseAdapter",
|
|
75
|
+
"MockAdapter",
|
|
76
|
+
"AnthropicAdapter",
|
|
77
|
+
"OpenAIAdapter",
|
|
78
|
+
"OllamaAdapter",
|
|
79
|
+
"OpenRouterAdapter",
|
|
80
|
+
# Optional
|
|
81
|
+
"GeminiAdapter",
|
|
82
|
+
"MistralAdapter",
|
|
83
|
+
"GroqAdapter",
|
|
84
|
+
"TogetherAdapter",
|
|
85
|
+
"CohereAdapter",
|
|
86
|
+
"LiteLLMAdapter",
|
|
87
|
+
"BedrockAdapter",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
# ── Provider registry — useful for dynamic instantiation ──────────
|
|
91
|
+
PROVIDERS: dict[str, type] = {
|
|
92
|
+
"anthropic": AnthropicAdapter,
|
|
93
|
+
"openai": OpenAIAdapter,
|
|
94
|
+
"ollama": OllamaAdapter,
|
|
95
|
+
"google": GeminiAdapter,
|
|
96
|
+
"gemini": GeminiAdapter,
|
|
97
|
+
"mistral": MistralAdapter,
|
|
98
|
+
"groq": GroqAdapter,
|
|
99
|
+
"together": TogetherAdapter,
|
|
100
|
+
"cohere": CohereAdapter,
|
|
101
|
+
"litellm": LiteLLMAdapter,
|
|
102
|
+
"bedrock": BedrockAdapter,
|
|
103
|
+
"aws": BedrockAdapter,
|
|
104
|
+
"openrouter": OpenRouterAdapter,
|
|
105
|
+
"mock": MockAdapter,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_adapter(provider: str) -> type:
|
|
110
|
+
"""
|
|
111
|
+
Lookup an adapter class by provider name (case-insensitive).
|
|
112
|
+
|
|
113
|
+
Example::
|
|
114
|
+
|
|
115
|
+
AdapterClass = get_adapter("groq")
|
|
116
|
+
adapter = AdapterClass("my_agent", model="llama-3.1-8b-instant")
|
|
117
|
+
"""
|
|
118
|
+
key = provider.lower()
|
|
119
|
+
if key not in PROVIDERS:
|
|
120
|
+
available = ", ".join(sorted(PROVIDERS))
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"Unknown provider '{provider}'. Available: {available}"
|
|
123
|
+
)
|
|
124
|
+
return PROVIDERS[key]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def make_adapter(
|
|
128
|
+
agent_id: str,
|
|
129
|
+
provider: str,
|
|
130
|
+
model: str,
|
|
131
|
+
capabilities: list[str] | None = None,
|
|
132
|
+
**kwargs,
|
|
133
|
+
) -> BaseAdapter:
|
|
134
|
+
"""
|
|
135
|
+
Factory: create any adapter by provider name.
|
|
136
|
+
|
|
137
|
+
Example::
|
|
138
|
+
|
|
139
|
+
adapter = make_adapter("researcher", "groq",
|
|
140
|
+
"llama-3.3-70b-versatile",
|
|
141
|
+
capabilities=["research"])
|
|
142
|
+
result = adapter.execute(task_msg)
|
|
143
|
+
"""
|
|
144
|
+
AdapterClass = get_adapter(provider)
|
|
145
|
+
# MockAdapter has a simplified signature
|
|
146
|
+
if AdapterClass is MockAdapter:
|
|
147
|
+
return AdapterClass(agent_id)
|
|
148
|
+
return AdapterClass(agent_id, model=model, capabilities=capabilities, **kwargs)
|
axel/adapters/bedrock.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
axel/adapters/bedrock.py — AWS Bedrock adapter for AXEL
|
|
3
|
+
|
|
4
|
+
Bedrock gives access to frontier models (Claude, Llama, Mistral, Titan)
|
|
5
|
+
inside your AWS VPC — no data leaves your cloud environment.
|
|
6
|
+
Ideal for enterprise deployments with strict data residency requirements.
|
|
7
|
+
|
|
8
|
+
Supported model IDs (as of 2024):
|
|
9
|
+
anthropic.claude-3-5-haiku-20241022-v1:0 Claude 3.5 Haiku
|
|
10
|
+
anthropic.claude-3-5-sonnet-20241022-v2:0 Claude 3.5 Sonnet
|
|
11
|
+
anthropic.claude-3-opus-20240229-v1:0 Claude 3 Opus
|
|
12
|
+
meta.llama3-1-70b-instruct-v1:0 Llama 3.1 70B
|
|
13
|
+
meta.llama3-1-8b-instruct-v1:0 Llama 3.1 8B
|
|
14
|
+
mistral.mistral-large-2402-v1:0 Mistral Large
|
|
15
|
+
amazon.titan-text-premier-v1:0 Amazon Titan
|
|
16
|
+
|
|
17
|
+
Install:
|
|
18
|
+
pip install boto3
|
|
19
|
+
|
|
20
|
+
Auth:
|
|
21
|
+
AWS credentials via env vars, ~/.aws/credentials, or IAM role:
|
|
22
|
+
export AWS_ACCESS_KEY_ID=...
|
|
23
|
+
export AWS_SECRET_ACCESS_KEY=...
|
|
24
|
+
export AWS_DEFAULT_REGION=us-east-1
|
|
25
|
+
|
|
26
|
+
Permissions needed:
|
|
27
|
+
bedrock:InvokeModel on arn:aws:bedrock:*::foundation-model/*
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import json
|
|
33
|
+
import os
|
|
34
|
+
import re
|
|
35
|
+
from typing import List, Optional
|
|
36
|
+
|
|
37
|
+
from axel.universal import BaseAdapter, make_system_prompt, AXELMessage
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BedrockAdapter(BaseAdapter):
|
|
41
|
+
"""
|
|
42
|
+
Execute AXEL TASK messages using AWS Bedrock.
|
|
43
|
+
|
|
44
|
+
Bedrock uses the Converse API (unified chat interface across all models),
|
|
45
|
+
so this adapter works consistently regardless of which model is selected.
|
|
46
|
+
|
|
47
|
+
Example::
|
|
48
|
+
|
|
49
|
+
adapter = BedrockAdapter(
|
|
50
|
+
"secure_researcher",
|
|
51
|
+
model_id="anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
52
|
+
region="us-east-1",
|
|
53
|
+
capabilities=["research", "summarize"],
|
|
54
|
+
)
|
|
55
|
+
result = adapter.execute(task_msg)
|
|
56
|
+
|
|
57
|
+
Notes:
|
|
58
|
+
- Model access must be enabled in the Bedrock console for your region
|
|
59
|
+
- Cross-region inference profiles are supported (prefix with region code)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
PROVIDER = "bedrock"
|
|
63
|
+
DEFAULT_MODEL = "anthropic.claude-3-5-haiku-20241022-v1:0"
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
agent_id: str,
|
|
68
|
+
model_id: str = DEFAULT_MODEL,
|
|
69
|
+
capabilities: Optional[List[str]] = None,
|
|
70
|
+
region: Optional[str] = None,
|
|
71
|
+
aws_access_key_id: Optional[str] = None,
|
|
72
|
+
aws_secret_access_key: Optional[str] = None,
|
|
73
|
+
aws_session_token: Optional[str] = None,
|
|
74
|
+
max_tokens: int = 2048,
|
|
75
|
+
temperature: float = 0.7,
|
|
76
|
+
):
|
|
77
|
+
system = make_system_prompt(agent_id, capabilities or [], f"bedrock/{model_id}")
|
|
78
|
+
super().__init__(agent_id, system)
|
|
79
|
+
self.model_id = model_id
|
|
80
|
+
self.max_tokens = max_tokens
|
|
81
|
+
self.temperature = temperature
|
|
82
|
+
self._region = region or os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
|
|
83
|
+
self._key_id = aws_access_key_id
|
|
84
|
+
self._secret = aws_secret_access_key
|
|
85
|
+
self._token = aws_session_token
|
|
86
|
+
self._client = None
|
|
87
|
+
|
|
88
|
+
def _get_client(self):
|
|
89
|
+
if self._client is None:
|
|
90
|
+
try:
|
|
91
|
+
import boto3
|
|
92
|
+
except ImportError:
|
|
93
|
+
raise RuntimeError(
|
|
94
|
+
"boto3 not installed.\n"
|
|
95
|
+
"Run: pip install boto3"
|
|
96
|
+
)
|
|
97
|
+
session_kwargs = {}
|
|
98
|
+
if self._key_id:
|
|
99
|
+
session_kwargs["aws_access_key_id"] = self._key_id
|
|
100
|
+
if self._secret:
|
|
101
|
+
session_kwargs["aws_secret_access_key"] = self._secret
|
|
102
|
+
if self._token:
|
|
103
|
+
session_kwargs["aws_session_token"] = self._token
|
|
104
|
+
|
|
105
|
+
session = boto3.Session(**session_kwargs) if session_kwargs else boto3.Session()
|
|
106
|
+
self._client = session.client("bedrock-runtime", region_name=self._region)
|
|
107
|
+
return self._client
|
|
108
|
+
|
|
109
|
+
def execute(self, task_msg: AXELMessage) -> AXELMessage:
|
|
110
|
+
action = task_msg.body.get("act", "unknown")
|
|
111
|
+
args = task_msg.body.get("args", {})
|
|
112
|
+
ctx = task_msg.body.get("ctx", {})
|
|
113
|
+
|
|
114
|
+
user_content = (
|
|
115
|
+
f"AXEL TASK:\n{task_msg.to_json(compact=False)}\n\n"
|
|
116
|
+
f"Execute '{action}' with args: {json.dumps(args)}\n"
|
|
117
|
+
f"Context: {json.dumps(ctx)}\n\n"
|
|
118
|
+
f"Respond with a single compact AXEL OK or ER JSON message."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
client = self._get_client()
|
|
123
|
+
|
|
124
|
+
# Bedrock Converse API — unified interface across all models
|
|
125
|
+
resp = client.converse(
|
|
126
|
+
modelId=self.model_id,
|
|
127
|
+
system=[{"text": self.system_prompt}],
|
|
128
|
+
messages=[{"role": "user", "content": [{"text": user_content}]}],
|
|
129
|
+
inferenceConfig={
|
|
130
|
+
"maxTokens": self.max_tokens,
|
|
131
|
+
"temperature": self.temperature,
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
raw = resp["output"]["message"]["content"][0]["text"].strip()
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
match = re.search(r"\{.*\}", raw, re.DOTALL)
|
|
138
|
+
if match:
|
|
139
|
+
parsed = json.loads(match.group())
|
|
140
|
+
return AXELMessage.from_dict({
|
|
141
|
+
**parsed,
|
|
142
|
+
"fr": self.agent_id,
|
|
143
|
+
"to": task_msg.fr,
|
|
144
|
+
"cid": task_msg.cid,
|
|
145
|
+
"re": task_msg.id,
|
|
146
|
+
})
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
return self._ok(task_msg, {"text": raw})
|
|
151
|
+
|
|
152
|
+
except Exception as exc:
|
|
153
|
+
return self._err(task_msg, "INTERNAL", str(exc), retry=True)
|
axel/adapters/cohere.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
axel/adapters/cohere.py — Cohere adapter for AXEL
|
|
3
|
+
|
|
4
|
+
Cohere specialises in enterprise NLP: RAG, classification, reranking, and embeddings.
|
|
5
|
+
Their Command R+ model is particularly strong for tool-use and multi-step reasoning.
|
|
6
|
+
|
|
7
|
+
Supported models (as of 2024):
|
|
8
|
+
command-r-plus best quality, tool-use, 128k context
|
|
9
|
+
command-r balanced speed/quality, 128k context
|
|
10
|
+
command-light fast and cheap
|
|
11
|
+
command production-ready general model
|
|
12
|
+
|
|
13
|
+
Install:
|
|
14
|
+
pip install cohere
|
|
15
|
+
|
|
16
|
+
Auth:
|
|
17
|
+
export COHERE_API_KEY=your_key
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
from typing import List, Optional
|
|
26
|
+
|
|
27
|
+
from axel.universal import BaseAdapter, make_system_prompt, AXELMessage
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CohereAdapter(BaseAdapter):
|
|
31
|
+
"""
|
|
32
|
+
Execute AXEL TASK messages using Cohere.
|
|
33
|
+
|
|
34
|
+
Cohere's Command R+ is excellent for RAG-augmented tasks and tool use,
|
|
35
|
+
making it well-suited for research and retrieval agent roles in a network.
|
|
36
|
+
|
|
37
|
+
Example::
|
|
38
|
+
|
|
39
|
+
adapter = CohereAdapter(
|
|
40
|
+
"rag_researcher",
|
|
41
|
+
model="command-r-plus",
|
|
42
|
+
capabilities=["research", "rag_query", "rerank"],
|
|
43
|
+
)
|
|
44
|
+
result = adapter.execute(task_msg)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
PROVIDER = "cohere"
|
|
48
|
+
DEFAULT_MODEL = "command-r-plus"
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
agent_id: str,
|
|
53
|
+
model: str = DEFAULT_MODEL,
|
|
54
|
+
capabilities: Optional[List[str]] = None,
|
|
55
|
+
api_key: Optional[str] = None,
|
|
56
|
+
temperature: float = 0.7,
|
|
57
|
+
max_tokens: int = 2048,
|
|
58
|
+
):
|
|
59
|
+
system = make_system_prompt(agent_id, capabilities or [], f"cohere/{model}")
|
|
60
|
+
super().__init__(agent_id, system)
|
|
61
|
+
self.model = model
|
|
62
|
+
self.temperature = temperature
|
|
63
|
+
self.max_tokens = max_tokens
|
|
64
|
+
self._api_key = api_key or os.environ.get("COHERE_API_KEY")
|
|
65
|
+
self._client = None
|
|
66
|
+
|
|
67
|
+
def _get_client(self):
|
|
68
|
+
if self._client is None:
|
|
69
|
+
try:
|
|
70
|
+
import cohere
|
|
71
|
+
except ImportError:
|
|
72
|
+
raise RuntimeError(
|
|
73
|
+
"Cohere SDK not installed.\n"
|
|
74
|
+
"Run: pip install cohere"
|
|
75
|
+
)
|
|
76
|
+
self._client = cohere.ClientV2(api_key=self._api_key)
|
|
77
|
+
return self._client
|
|
78
|
+
|
|
79
|
+
def execute(self, task_msg: AXELMessage) -> AXELMessage:
|
|
80
|
+
action = task_msg.body.get("act", "unknown")
|
|
81
|
+
args = task_msg.body.get("args", {})
|
|
82
|
+
ctx = task_msg.body.get("ctx", {})
|
|
83
|
+
|
|
84
|
+
user_content = (
|
|
85
|
+
f"AXEL TASK:\n{task_msg.to_json(compact=False)}\n\n"
|
|
86
|
+
f"Execute '{action}' with args: {json.dumps(args)}\n"
|
|
87
|
+
f"Context: {json.dumps(ctx)}\n\n"
|
|
88
|
+
f"Respond with a single compact AXEL OK or ER JSON message."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
client = self._get_client()
|
|
93
|
+
resp = client.chat(
|
|
94
|
+
model=self.model,
|
|
95
|
+
messages=[
|
|
96
|
+
{"role": "system", "content": self.system_prompt},
|
|
97
|
+
{"role": "user", "content": user_content},
|
|
98
|
+
],
|
|
99
|
+
temperature=self.temperature,
|
|
100
|
+
max_tokens=self.max_tokens,
|
|
101
|
+
)
|
|
102
|
+
raw = resp.message.content[0].text.strip()
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
match = re.search(r"\{.*\}", raw, re.DOTALL)
|
|
106
|
+
if match:
|
|
107
|
+
parsed = json.loads(match.group())
|
|
108
|
+
return AXELMessage.from_dict({
|
|
109
|
+
**parsed,
|
|
110
|
+
"fr": self.agent_id,
|
|
111
|
+
"to": task_msg.fr,
|
|
112
|
+
"cid": task_msg.cid,
|
|
113
|
+
"re": task_msg.id,
|
|
114
|
+
})
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
return self._ok(task_msg, {"text": raw})
|
|
119
|
+
|
|
120
|
+
except Exception as exc:
|
|
121
|
+
return self._err(task_msg, "INTERNAL", str(exc), retry=True)
|
axel/adapters/gemini.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
axel/adapters/gemini.py — Google Gemini adapter for AXEL
|
|
3
|
+
|
|
4
|
+
Supported models (as of 2024):
|
|
5
|
+
gemini-1.5-flash fast, cheap, 1M context
|
|
6
|
+
gemini-1.5-pro flagship, multimodal, 2M context
|
|
7
|
+
gemini-2.0-flash next-gen fast model
|
|
8
|
+
|
|
9
|
+
Install:
|
|
10
|
+
pip install google-generativeai
|
|
11
|
+
|
|
12
|
+
Auth:
|
|
13
|
+
export GOOGLE_API_KEY=your_key
|
|
14
|
+
# OR pass api_key= directly
|
|
15
|
+
# OR use Application Default Credentials (ADC) on GCP/Vertex
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
from typing import List, Optional
|
|
24
|
+
|
|
25
|
+
from axel.universal import BaseAdapter, make_system_prompt, AXELMessage
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GeminiAdapter(BaseAdapter):
|
|
29
|
+
"""
|
|
30
|
+
Execute AXEL TASK messages using Google Gemini.
|
|
31
|
+
|
|
32
|
+
Example::
|
|
33
|
+
|
|
34
|
+
adapter = GeminiAdapter(
|
|
35
|
+
"researcher",
|
|
36
|
+
model="gemini-1.5-flash",
|
|
37
|
+
capabilities=["research", "summarize"],
|
|
38
|
+
)
|
|
39
|
+
result = adapter.execute(task_msg)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
PROVIDER = "google"
|
|
43
|
+
DEFAULT_MODEL = "gemini-1.5-flash"
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
agent_id: str,
|
|
48
|
+
model: str = DEFAULT_MODEL,
|
|
49
|
+
capabilities: Optional[List[str]] = None,
|
|
50
|
+
api_key: Optional[str] = None,
|
|
51
|
+
temperature: float = 0.7,
|
|
52
|
+
max_tokens: int = 2048,
|
|
53
|
+
):
|
|
54
|
+
system = make_system_prompt(agent_id, capabilities or [], f"gemini/{model}")
|
|
55
|
+
super().__init__(agent_id, system)
|
|
56
|
+
self.model = model
|
|
57
|
+
self.temperature = temperature
|
|
58
|
+
self.max_tokens = max_tokens
|
|
59
|
+
self._api_key = api_key or os.environ.get("GOOGLE_API_KEY")
|
|
60
|
+
self._client = None
|
|
61
|
+
|
|
62
|
+
def _get_client(self):
|
|
63
|
+
if self._client is None:
|
|
64
|
+
try:
|
|
65
|
+
import google.generativeai as genai
|
|
66
|
+
except ImportError:
|
|
67
|
+
raise RuntimeError(
|
|
68
|
+
"Google Generative AI SDK not installed.\n"
|
|
69
|
+
"Run: pip install google-generativeai"
|
|
70
|
+
)
|
|
71
|
+
if self._api_key:
|
|
72
|
+
genai.configure(api_key=self._api_key)
|
|
73
|
+
# else relies on GOOGLE_API_KEY env var or ADC
|
|
74
|
+
self._client = genai.GenerativeModel(
|
|
75
|
+
model_name=self.model,
|
|
76
|
+
system_instruction=self.system_prompt,
|
|
77
|
+
generation_config={
|
|
78
|
+
"temperature": self.temperature,
|
|
79
|
+
"max_output_tokens": self.max_tokens,
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
return self._client
|
|
83
|
+
|
|
84
|
+
def execute(self, task_msg: AXELMessage) -> AXELMessage:
|
|
85
|
+
action = task_msg.body.get("act", "unknown")
|
|
86
|
+
args = task_msg.body.get("args", {})
|
|
87
|
+
ctx = task_msg.body.get("ctx", {})
|
|
88
|
+
|
|
89
|
+
prompt = (
|
|
90
|
+
f"AXEL TASK received:\n"
|
|
91
|
+
f"{task_msg.to_json(compact=False)}\n\n"
|
|
92
|
+
f"Execute action '{action}' with args: {json.dumps(args)}\n"
|
|
93
|
+
f"Context: {json.dumps(ctx)}\n\n"
|
|
94
|
+
f"Respond with a single compact AXEL OK or ER JSON message."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
model = self._get_client()
|
|
99
|
+
resp = model.generate_content(prompt)
|
|
100
|
+
raw = resp.text.strip()
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
match = re.search(r"\{.*\}", raw, re.DOTALL)
|
|
104
|
+
if match:
|
|
105
|
+
parsed = json.loads(match.group())
|
|
106
|
+
return AXELMessage.from_dict({
|
|
107
|
+
**parsed,
|
|
108
|
+
"fr": self.agent_id,
|
|
109
|
+
"to": task_msg.fr,
|
|
110
|
+
"cid": task_msg.cid,
|
|
111
|
+
"re": task_msg.id,
|
|
112
|
+
})
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
return self._ok(task_msg, {"text": raw})
|
|
117
|
+
|
|
118
|
+
except Exception as exc:
|
|
119
|
+
return self._err(task_msg, "INTERNAL", str(exc), retry=True)
|