solana-agent 11.3.0__py3-none-any.whl → 14.0.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.
Files changed (39) hide show
  1. solana_agent/__init__.py +33 -1
  2. solana_agent/adapters/__init__.py +6 -0
  3. solana_agent/adapters/llm_adapter.py +123 -0
  4. solana_agent/adapters/mongodb_adapter.py +69 -0
  5. solana_agent/client/__init__.py +0 -0
  6. solana_agent/client/solana_agent.py +75 -0
  7. solana_agent/domains/__init__.py +6 -0
  8. solana_agent/domains/agents.py +80 -0
  9. solana_agent/domains/routing.py +14 -0
  10. solana_agent/factories/__init__.py +0 -0
  11. solana_agent/factories/agent_factory.py +148 -0
  12. solana_agent/interfaces/__init__.py +11 -0
  13. solana_agent/interfaces/plugins/plugins.py +118 -0
  14. solana_agent/interfaces/providers/data_storage.py +53 -0
  15. solana_agent/interfaces/providers/llm.py +34 -0
  16. solana_agent/interfaces/providers/memory.py +38 -0
  17. solana_agent/interfaces/repositories/agent.py +33 -0
  18. solana_agent/interfaces/services/agent.py +41 -0
  19. solana_agent/interfaces/services/query.py +11 -0
  20. solana_agent/interfaces/services/routing.py +19 -0
  21. solana_agent/plugins/__init__.py +6 -0
  22. solana_agent/plugins/manager.py +147 -0
  23. solana_agent/plugins/registry.py +64 -0
  24. solana_agent/plugins/tools/__init__.py +10 -0
  25. solana_agent/plugins/tools/auto_tool.py +47 -0
  26. solana_agent/repositories/__init__.py +6 -0
  27. solana_agent/repositories/agent.py +99 -0
  28. solana_agent/repositories/memory.py +134 -0
  29. solana_agent/services/__init__.py +6 -0
  30. solana_agent/services/agent.py +320 -0
  31. solana_agent/services/query.py +223 -0
  32. solana_agent/services/routing.py +149 -0
  33. solana_agent-14.0.0.dist-info/METADATA +121 -0
  34. solana_agent-14.0.0.dist-info/RECORD +36 -0
  35. solana_agent/ai.py +0 -7590
  36. solana_agent-11.3.0.dist-info/METADATA +0 -443
  37. solana_agent-11.3.0.dist-info/RECORD +0 -6
  38. {solana_agent-11.3.0.dist-info → solana_agent-14.0.0.dist-info}/LICENSE +0 -0
  39. {solana_agent-11.3.0.dist-info → solana_agent-14.0.0.dist-info}/WHEEL +0 -0
solana_agent/__init__.py CHANGED
@@ -1 +1,33 @@
1
- from .ai import *
1
+ """
2
+ Solana Agent - An AI agent framework with routing, memory, and specialized agents.
3
+
4
+ This package provides a modular framework for building AI agent systems with
5
+ multiple specialized agents, memory management, and conversation routing.
6
+ """
7
+
8
+ __version__ = "14.0.0" # Update with your actual version
9
+
10
+ # Client interface (main entry point)
11
+ from solana_agent.client.solana_agent import SolanaAgent
12
+
13
+ # Factory for creating agent systems
14
+ from solana_agent.factories.agent_factory import SolanaAgentFactory
15
+
16
+ # Useful tools and utilities
17
+ from solana_agent.plugins.manager import PluginManager
18
+ from solana_agent.plugins.registry import ToolRegistry
19
+ from solana_agent.plugins.tools import AutoTool
20
+
21
+ # Package metadata
22
+ __all__ = [
23
+ # Main client interfaces
24
+ "SolanaAgent",
25
+
26
+ # Factories
27
+ "SolanaAgentFactory",
28
+
29
+ # Tools
30
+ "PluginManager",
31
+ "ToolRegistry",
32
+ "AutoTool",
33
+ ]
@@ -0,0 +1,6 @@
1
+ """
2
+ Adapters for external systems and services.
3
+
4
+ These adapters implement the interfaces defined in solana_agent.interfaces
5
+ and provide concrete implementations for interacting with external systems.
6
+ """
@@ -0,0 +1,123 @@
1
+ """
2
+ LLM provider adapters for the Solana Agent system.
3
+
4
+ These adapters implement the LLMProvider interface for different LLM services.
5
+ """
6
+ from typing import AsyncGenerator, List, Type, TypeVar
7
+
8
+ from openai import OpenAI
9
+ from pydantic import BaseModel
10
+
11
+ from solana_agent.interfaces.providers.llm import LLMProvider
12
+
13
+ T = TypeVar('T', bound=BaseModel)
14
+
15
+
16
+ class OpenAIAdapter(LLMProvider):
17
+ """OpenAI implementation of LLMProvider with web search capabilities."""
18
+
19
+ def __init__(self, api_key: str, model: str = "gpt-4o-mini"):
20
+ self.client = OpenAI(api_key=api_key)
21
+ self.model = model
22
+ # Add search-enabled model variants
23
+ self.search_models = {
24
+ "gpt-4o": "gpt-4o-search-preview",
25
+ "gpt-4o-mini": "gpt-4o-mini-search-preview"
26
+ }
27
+
28
+ async def generate_text(
29
+ self,
30
+ prompt: str,
31
+ system_prompt: str = "",
32
+ needs_search: bool = False,
33
+ **kwargs,
34
+ ) -> AsyncGenerator[str, None]:
35
+ """Generate text from OpenAI models with web search capability."""
36
+ messages = []
37
+
38
+ if system_prompt:
39
+ messages.append({"role": "system", "content": system_prompt})
40
+
41
+ messages.append({"role": "user", "content": prompt})
42
+
43
+ # Prepare request parameters
44
+ request_params = {
45
+ "messages": messages,
46
+ "stream": kwargs.get("stream", True),
47
+ "response_format": kwargs.get("response_format", None),
48
+ }
49
+
50
+ # If search is needed, update model and add search options
51
+ if needs_search:
52
+ base_model = kwargs.get("model", self.model)
53
+ request_params["model"] = self.search_models.get(
54
+ base_model, "gpt-4o-mini-search-preview")
55
+ request_params["web_search_options"] = {
56
+ "search_context_size": "medium",
57
+ "user_location": {
58
+ "type": "approximate",
59
+ "approximate": {
60
+ "country": "US",
61
+ "timezone": "America/Los_Angeles"
62
+ }
63
+ }
64
+ }
65
+ else:
66
+ request_params["model"] = kwargs.get("model", self.model)
67
+
68
+ try:
69
+ response = self.client.chat.completions.create(**request_params)
70
+ current_text = ""
71
+
72
+ for chunk in response:
73
+ if chunk.choices:
74
+ # Handle content
75
+ if chunk.choices[0].delta.content:
76
+ text = chunk.choices[0].delta.content
77
+ current_text += text
78
+ yield text
79
+
80
+ except Exception as e:
81
+ print(f"Error in generate_text: {str(e)}")
82
+ import traceback
83
+ print(traceback.format_exc())
84
+ yield f"I apologize, but I encountered an error: {str(e)}"
85
+
86
+ def generate_embedding(self, text: str) -> List[float]:
87
+ """Generate embeddings for a given text using OpenAI's embedding model."""
88
+ try:
89
+ response = self.client.embeddings.create(
90
+ model="text-embedding-3-small",
91
+ input=text
92
+ )
93
+ return response.data[0].embedding
94
+ except Exception as e:
95
+ print(f"Error generating embedding: {e}")
96
+ # Return a zero vector as fallback (not ideal but prevents crashing)
97
+ return [0.0] * 1536 # Standard size for text-embedding-3-small
98
+
99
+ async def parse_structured_output(
100
+ self,
101
+ prompt: str,
102
+ system_prompt: str,
103
+ model_class: Type[T],
104
+ **kwargs
105
+ ) -> T:
106
+ """Generate structured output using Pydantic model parsing."""
107
+ messages = []
108
+ if system_prompt:
109
+ messages.append({"role": "system", "content": system_prompt})
110
+
111
+ messages.append({"role": "user", "content": prompt})
112
+
113
+ try:
114
+ # First try the beta parsing API
115
+ completion = self.client.beta.chat.completions.parse(
116
+ model=kwargs.get("model", self.model),
117
+ messages=messages,
118
+ response_format=model_class,
119
+ temperature=kwargs.get("temperature", 0.2),
120
+ )
121
+ return completion.choices[0].message.parsed
122
+ except Exception as e:
123
+ print(f"Error with beta.parse method: {e}")
@@ -0,0 +1,69 @@
1
+ """
2
+ MongoDB adapter for the Solana Agent system.
3
+
4
+ This adapter implements the DataStorageProvider interface for MongoDB.
5
+ """
6
+ import uuid
7
+ from typing import Dict, List, Tuple, Optional
8
+
9
+ from pymongo import MongoClient
10
+
11
+ from solana_agent.interfaces.providers.data_storage import DataStorageProvider
12
+
13
+
14
+ class MongoDBAdapter(DataStorageProvider):
15
+ """MongoDB implementation of DataStorageProvider."""
16
+
17
+ def __init__(self, connection_string: str, database_name: str):
18
+ self.client = MongoClient(connection_string)
19
+ self.db = self.client[database_name]
20
+
21
+ def create_collection(self, name: str) -> None:
22
+ if name not in self.db.list_collection_names():
23
+ self.db.create_collection(name)
24
+
25
+ def collection_exists(self, name: str) -> bool:
26
+ return name in self.db.list_collection_names()
27
+
28
+ def insert_one(self, collection: str, document: Dict) -> str:
29
+ if "_id" not in document:
30
+ document["_id"] = str(uuid.uuid4())
31
+ self.db[collection].insert_one(document)
32
+ return document["_id"]
33
+
34
+ def find_one(self, collection: str, query: Dict) -> Optional[Dict]:
35
+ return self.db[collection].find_one(query)
36
+
37
+ def find(
38
+ self,
39
+ collection: str,
40
+ query: Dict,
41
+ sort: Optional[List[Tuple]] = None,
42
+ limit: int = 0,
43
+ skip: int = 0
44
+ ) -> List[Dict]:
45
+ cursor = self.db[collection].find(query)
46
+ if sort:
47
+ cursor = cursor.sort(sort)
48
+ if limit > 0:
49
+ cursor = cursor.limit(limit)
50
+ if skip > 0:
51
+ cursor = cursor.skip(skip)
52
+ return list(cursor)
53
+
54
+ def update_one(self, collection: str, query: Dict, update: Dict, upsert: bool = False) -> bool:
55
+ result = self.db[collection].update_one(query, update, upsert=upsert)
56
+ return result.modified_count > 0 or (upsert and result.upserted_id is not None)
57
+
58
+ def delete_one(self, collection: str, query: Dict) -> bool:
59
+ result = self.db[collection].delete_one(query)
60
+ return result.deleted_count > 0
61
+
62
+ def count_documents(self, collection: str, query: Dict) -> int:
63
+ return self.db[collection].count_documents(query)
64
+
65
+ def aggregate(self, collection: str, pipeline: List[Dict]) -> List[Dict]:
66
+ return list(self.db[collection].aggregate(pipeline))
67
+
68
+ def create_index(self, collection: str, keys: List[Tuple], **kwargs) -> None:
69
+ self.db[collection].create_index(keys, **kwargs)
File without changes
@@ -0,0 +1,75 @@
1
+ """
2
+ Simplified client interface for interacting with the Solana Agent system.
3
+
4
+ This module provides a clean API for end users to interact with
5
+ the agent system without dealing with internal implementation details.
6
+ """
7
+ import json
8
+ import importlib.util
9
+ from typing import AsyncGenerator, Dict, Any
10
+
11
+ from solana_agent.factories.agent_factory import SolanaAgentFactory
12
+
13
+
14
+ class SolanaAgent:
15
+ """Simplified client interface for interacting with the agent system."""
16
+
17
+ def __init__(self, config_path: str = None, config: Dict[str, Any] = None):
18
+ """Initialize the agent system from config file or dictionary.
19
+
20
+ Args:
21
+ config_path: Path to configuration file (JSON or Python)
22
+ config: Configuration dictionary
23
+ """
24
+ if not config and not config_path:
25
+ raise ValueError("Either config or config_path must be provided")
26
+
27
+ if config_path:
28
+ with open(config_path, "r") as f:
29
+ if config_path.endswith(".json"):
30
+ config = json.load(f)
31
+ else:
32
+ # Assume it's a Python file
33
+ spec = importlib.util.spec_from_file_location(
34
+ "config", config_path)
35
+ config_module = importlib.util.module_from_spec(spec)
36
+ spec.loader.exec_module(config_module)
37
+ config = config_module.config
38
+
39
+ self.query_service = SolanaAgentFactory.create_from_config(config)
40
+
41
+ async def process(self, user_id: str, message: str) -> AsyncGenerator[str, None]:
42
+ """Process a user message and return the response stream.
43
+
44
+ Args:
45
+ user_id: User ID
46
+ message: User message
47
+
48
+ Returns:
49
+ Async generator yielding response chunks
50
+ """
51
+ async for chunk in self.query_service.process(user_id, message):
52
+ yield chunk
53
+
54
+ async def get_user_history(
55
+ self,
56
+ user_id: str,
57
+ page_num: int = 1,
58
+ page_size: int = 20,
59
+ sort_order: str = "asc" # "asc" for oldest-first, "desc" for newest-first
60
+ ) -> Dict[str, Any]:
61
+ """
62
+ Get paginated message history for a user.
63
+
64
+ Args:
65
+ user_id: User ID
66
+ page_num: Page number (starting from 1)
67
+ page_size: Number of messages per page
68
+ sort_order: Sort order ("asc" or "desc")
69
+
70
+ Returns:
71
+ Dictionary with paginated results and metadata
72
+ """
73
+ return await self.query_service.get_user_history(
74
+ user_id, page_num, page_size, sort_order
75
+ )
@@ -0,0 +1,6 @@
1
+ """
2
+ Domain models for the Solana Agent system.
3
+
4
+ This package contains all the core domain models that represent the
5
+ business objects and value types in the system.
6
+ """
@@ -0,0 +1,80 @@
1
+ """
2
+ Domain models for AI and human agents.
3
+
4
+ This module defines the core domain models for representing
5
+ AI agents, human agents, and organization mission/values.
6
+ """
7
+ from typing import List, Optional, Dict, Any, Union
8
+ # Import the class directly, not the module
9
+ from datetime import datetime
10
+ from pydantic import BaseModel, Field, field_validator
11
+
12
+
13
+ class OrganizationMission(BaseModel):
14
+ """Organization mission and values to guide agent behavior."""
15
+
16
+ mission_statement: str = Field(...,
17
+ description="Organization mission statement")
18
+ values: List[Dict[str, str]] = Field(
19
+ default_factory=list,
20
+ description="Organization values as name-description pairs"
21
+ )
22
+ goals: List[str] = Field(
23
+ default_factory=list,
24
+ description="Organization goals"
25
+ )
26
+ guidance: Optional[str] = Field(
27
+ None, description="Additional guidance for agents")
28
+
29
+ @field_validator("mission_statement")
30
+ @classmethod
31
+ def mission_statement_not_empty(cls, v: str) -> str:
32
+ """Validate that mission statement is not empty."""
33
+ if not v.strip():
34
+ raise ValueError("Mission statement cannot be empty")
35
+ return v
36
+
37
+ @field_validator("values")
38
+ @classmethod
39
+ def validate_values(cls, values: List[Dict[str, str]]) -> List[Dict[str, str]]:
40
+ """Validate that values have proper format."""
41
+ for value in values:
42
+ if "name" not in value or "description" not in value:
43
+ raise ValueError("Each value must have a name and description")
44
+ return values
45
+
46
+
47
+ class AIAgent(BaseModel):
48
+ """AI agent with specialized capabilities."""
49
+
50
+ model_config = {"arbitrary_types_allowed": True}
51
+
52
+ name: str = Field(..., description="Unique agent identifier name")
53
+ instructions: str = Field(...,
54
+ description="Base instructions for the agent")
55
+ specialization: str = Field(..., description="Agent's specialized domain")
56
+ model: str = Field("gpt-4o-mini", description="Language model to use")
57
+ created_at: datetime = Field(
58
+ default_factory=datetime.now, description="Creation timestamp")
59
+ updated_at: datetime = Field(
60
+ default_factory=datetime.now, description="Last update timestamp")
61
+ description: Optional[str] = Field(
62
+ None, description="Agent description or summary")
63
+
64
+ @field_validator("name", "specialization")
65
+ @classmethod
66
+ def not_empty(cls, v: str) -> str:
67
+ """Validate that string fields are not empty."""
68
+ if not v.strip():
69
+ raise ValueError("Field cannot be empty")
70
+ return v
71
+
72
+ @field_validator("instructions")
73
+ @classmethod
74
+ def instructions_not_empty(cls, v: str) -> str:
75
+ """Validate that instructions are not empty."""
76
+ if not v.strip():
77
+ raise ValueError("Instructions cannot be empty")
78
+ if len(v) < 10:
79
+ raise ValueError("Instructions must be at least 10 characters")
80
+ return v
@@ -0,0 +1,14 @@
1
+ from typing import List
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class QueryAnalysis(BaseModel):
6
+ """Analysis of a user query for routing purposes."""
7
+ primary_specialization: str = Field(...,
8
+ description="Main specialization needed")
9
+ secondary_specializations: List[str] = Field(
10
+ ..., description="Other helpful specializations")
11
+ complexity_level: int = Field(...,
12
+ description="Complexity level (1-5)")
13
+ topics: List[str] = Field(..., description="Key topics in the query")
14
+ confidence: float = Field(..., description="Confidence in the analysis")
File without changes
@@ -0,0 +1,148 @@
1
+ """
2
+ Factory for creating and wiring components of the Solana Agent system.
3
+
4
+ This module handles the creation and dependency injection for all
5
+ services and components used in the system.
6
+ """
7
+ from typing import Dict, Any
8
+
9
+ # Service imports
10
+ from solana_agent.services.query import QueryService
11
+ from solana_agent.services.agent import AgentService
12
+ from solana_agent.services.routing import RoutingService
13
+
14
+ # Repository imports
15
+ from solana_agent.repositories.memory import MemoryRepository
16
+ from solana_agent.repositories.agent import MongoAgentRepository
17
+
18
+ # Adapter imports
19
+ from solana_agent.adapters.llm_adapter import OpenAIAdapter
20
+ from solana_agent.adapters.mongodb_adapter import MongoDBAdapter
21
+
22
+ # Domain and plugin imports
23
+ from solana_agent.domains.agents import OrganizationMission
24
+ from solana_agent.plugins.manager import PluginManager
25
+
26
+
27
+ class SolanaAgentFactory:
28
+ """Factory for creating and wiring components of the Solana Agent system."""
29
+
30
+ @staticmethod
31
+ def create_from_config(config: Dict[str, Any]) -> QueryService:
32
+ """Create the agent system from configuration.
33
+
34
+ Args:
35
+ config: Configuration dictionary
36
+
37
+ Returns:
38
+ Configured QueryService instance
39
+ """
40
+ # Create adapters
41
+ db_adapter = MongoDBAdapter(
42
+ connection_string=config["mongo"]["connection_string"],
43
+ database_name=config["mongo"]["database"],
44
+ )
45
+
46
+ llm_adapter = OpenAIAdapter(
47
+ api_key=config["openai"]["api_key"],
48
+ model=config.get("openai", {}).get("default_model", "gpt-4o-mini"),
49
+ )
50
+
51
+ # Create organization mission if specified in config
52
+ organization_mission = None
53
+ if "organization" in config:
54
+ org_config = config["organization"]
55
+ organization_mission = OrganizationMission(
56
+ mission_statement=org_config.get("mission_statement", ""),
57
+ values=[{"name": k, "description": v}
58
+ for k, v in org_config.get("values", {}).items()],
59
+ goals=org_config.get("goals", []),
60
+ guidance=org_config.get("guidance", "")
61
+ )
62
+
63
+ # Create repositories
64
+ memory_provider = MemoryRepository(
65
+ db_adapter, config["zep"].get("api_key"), config["zep"].get("base_url"))
66
+ agent_repo = MongoAgentRepository(db_adapter)
67
+
68
+ # Create primary services
69
+ agent_service = AgentService(
70
+ agent_repository=agent_repo,
71
+ llm_provider=llm_adapter,
72
+ organization_mission=organization_mission,
73
+ )
74
+
75
+ # Debug the agent service tool registry
76
+ print(
77
+ f"Agent service tools after initialization: {agent_service.tool_registry.list_all_tools()}")
78
+
79
+ # Create routing service
80
+ routing_service = RoutingService(
81
+ llm_provider=llm_adapter,
82
+ agent_service=agent_service,
83
+ )
84
+
85
+ # Initialize plugin system
86
+ agent_service.plugin_manager = PluginManager(
87
+ config=config,
88
+ tool_registry=agent_service.tool_registry
89
+ )
90
+ loaded_plugins = agent_service.plugin_manager.load_plugins()
91
+ print(f"Loaded {loaded_plugins} plugins")
92
+
93
+ # Sync MongoDB with config-defined agents
94
+ config_defined_agents = [agent["name"]
95
+ for agent in config.get("agents", [])]
96
+ all_db_agents = agent_repo.get_all_ai_agents()
97
+ db_agent_names = [agent.name for agent in all_db_agents]
98
+
99
+ # Delete agents not in config
100
+ agents_to_delete = [
101
+ name for name in db_agent_names if name not in config_defined_agents]
102
+ for agent_name in agents_to_delete:
103
+ print(
104
+ f"Deleting agent '{agent_name}' from MongoDB - no longer defined in config")
105
+ agent_repo.delete_ai_agent(agent_name)
106
+
107
+ # Register predefined agents
108
+ for agent_config in config.get("ai_agents", []):
109
+ agent_service.register_ai_agent(
110
+ name=agent_config["name"],
111
+ instructions=agent_config["instructions"],
112
+ specialization=agent_config["specialization"],
113
+ model=agent_config.get("model", "gpt-4o-mini"),
114
+ )
115
+
116
+ # Register tools for this agent
117
+ if "tools" in agent_config:
118
+ for tool_name in agent_config["tools"]:
119
+ print(
120
+ f"Available tools before registering {tool_name}: {agent_service.tool_registry.list_all_tools()}")
121
+ try:
122
+ agent_service.assign_tool_for_agent(
123
+ agent_config["name"], tool_name
124
+ )
125
+ print(
126
+ f"Successfully registered {tool_name} for agent {agent_config['name']}")
127
+ except ValueError as e:
128
+ print(
129
+ f"Error registering tool {tool_name} for agent {agent_config['name']}: {e}")
130
+
131
+ # Global tool registrations
132
+ if "agent_tools" in config:
133
+ for agent_name, tools in config["agent_tools"].items():
134
+ for tool_name in tools:
135
+ try:
136
+ agent_service.assign_tool_for_agent(
137
+ agent_name, tool_name)
138
+ except ValueError as e:
139
+ print(f"Error registering tool: {e}")
140
+
141
+ # Create and return the query service
142
+ query_service = QueryService(
143
+ agent_service=agent_service,
144
+ routing_service=routing_service,
145
+ memory_provider=memory_provider,
146
+ )
147
+
148
+ return query_service
@@ -0,0 +1,11 @@
1
+ """
2
+ Abstract interfaces for the Solana Agent system.
3
+
4
+ These interfaces define the contracts that concrete implementations
5
+ must adhere to, following the Dependency Inversion Principle.
6
+
7
+ This package contains:
8
+ - Repository interfaces for data access
9
+ - Provider interfaces for external service adapters
10
+ - Service interfaces for business logic components
11
+ """