fyodorov-llm-agents 0.3.8__py3-none-any.whl → 0.3.9__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.
@@ -0,0 +1,40 @@
1
+ from pydantic import BaseModel, HttpUrl, Field
2
+ import re
3
+
4
+ MAX_TITLE_LENGTH = 80
5
+ VALID_CHARACTERS_REGEX = r'^[a-zA-Z0-9\s.,!?:;\'"-]+$'
6
+
7
+ class InstanceModel(BaseModel):
8
+ id: str = Field(None, alias='id')
9
+ agent_id: str # Links to AgentModel.id
10
+ title: str = ""
11
+ chat_history: list[dict] = []
12
+
13
+ def validate(self) -> bool:
14
+ try:
15
+ InstanceModel.validate_title(self.title)
16
+ except ValueError as e:
17
+ print("Instance model validation error:", e)
18
+ return False
19
+ else:
20
+ return True
21
+
22
+ def to_dict(self) -> dict:
23
+ data = {
24
+ 'agent_id': str(self.agent_id),
25
+ 'title': self.title,
26
+ 'chat_history': self.chat_history,
27
+ }
28
+ if self.id is not None:
29
+ data['id'] = str(self.id)
30
+ return data
31
+
32
+ @staticmethod
33
+ def validate_title(title: str) -> str:
34
+ if not title:
35
+ raise ValueError('Title is required')
36
+ if len(title) > MAX_TITLE_LENGTH:
37
+ raise ValueError('Title exceeds maximum length')
38
+ if not re.match(VALID_CHARACTERS_REGEX, title):
39
+ raise ValueError('Title contains invalid characters')
40
+ return title
@@ -1,14 +1,13 @@
1
1
  from datetime import datetime
2
2
  from supabase import Client
3
3
  from fyodorov_utils.config.supabase import get_supabase
4
- from models.instance import InstanceModel
5
- from fyodorov_llm_agents.agents.agent import Agent as AgentModel
6
4
  from .agent import Agent
7
- from .provider import Provider
5
+ from .providers.provider_service import Provider
8
6
 
9
- from fyodorov_llm_agents.tools.mcp_tool_service import MCPTool as Tool
10
- from .model import LLM
11
- from models.model import LLMModel
7
+ from fyodorov_llm_agents.agents.agent import Agent as AgentModel
8
+ from fyodorov_llm_agents.models.llm_model import LLMModel
9
+ from fyodorov_llm_agents.models.llm_service import LLM
10
+ from fyodorov_llm_agents.instances.instance_model import InstanceModel
12
11
 
13
12
  supabase: Client = get_supabase()
14
13
 
@@ -0,0 +1,46 @@
1
+ from pydantic import BaseModel, HttpUrl
2
+ from typing import Literal
3
+
4
+ class LLMModel(BaseModel):
5
+ id: str | None = None
6
+ name: str
7
+ provider: str
8
+ params: dict | None = None
9
+ mode: str = 'chat'
10
+ base_model: str
11
+ input_cost_per_token: float | None = None
12
+ output_cost_per_token: float | None = None
13
+ max_tokens: int | None = None
14
+
15
+ def to_dict(self):
16
+ return {
17
+ 'name': self.name,
18
+ 'provider': self.provider,
19
+ 'params': self.params,
20
+ 'mode': self.mode,
21
+ 'base_model': self.base_model,
22
+ 'input_cost_per_token': self.input_cost_per_token,
23
+ 'output_cost_per_token': self.output_cost_per_token,
24
+ 'max_tokens': self.max_tokens
25
+ }
26
+
27
+ @staticmethod
28
+ def from_dict(data) -> 'LLMModel':
29
+ input_dict = {
30
+ "name": data['name'],
31
+ "provider": data['provider'],
32
+ }
33
+ if 'model_info' in data and 'base_model' in data['model_info']:
34
+ input_dict['base_model'] = data['model_info']['base_model']
35
+ if 'params' in data:
36
+ input_dict['params'] = data['params']
37
+ if 'mode' in data:
38
+ input_dict['mode'] = data['mode']
39
+ if 'input_cost_per_token' in data:
40
+ input_dict['input_cost_per_token'] = data['input_cost_per_token']
41
+ if 'output_cost_per_token' in data:
42
+ input_dict['output_cost_per_token'] = data['output_cost_per_token']
43
+ if 'max_tokens' in data:
44
+ input_dict['max_tokens'] = data['max_tokens']
45
+ print('Input dict for LLMModel:', input_dict)
46
+ return LLMModel(**input_dict)
@@ -0,0 +1,97 @@
1
+
2
+ from pydantic import BaseModel
3
+ import re
4
+ from datetime import datetime
5
+ from models.model import LLMModel
6
+ from .provider import Provider
7
+ from supabase import Client
8
+ from fyodorov_utils.config.supabase import get_supabase
9
+
10
+ supabase: Client = get_supabase()
11
+
12
+ class LLM(LLMModel):
13
+
14
+ @staticmethod
15
+ async def update_model_in_db(access_token: str, user_id: str, name: str, update: dict) -> dict:
16
+ if not user_id or not name:
17
+ raise ValueError('Model name and User ID is required')
18
+ try:
19
+ result = supabase.table('models').update(update).eq('user_id', user_id).eq('name', name).execute()
20
+ update = result.data[0]
21
+ print('Updated model:', update)
22
+ return update
23
+ except Exception as e:
24
+ print(f"Error updating model with user_id {user_id} and name {name} "
25
+ f"and update {update} ")
26
+ raise e
27
+
28
+ @staticmethod
29
+ async def save_model_in_db(access_token: str, user_id: str, model: LLMModel) -> dict:
30
+ try:
31
+ supabase = get_supabase(access_token)
32
+ provider = await Provider.get_or_create_provider(access_token, user_id, model.provider)
33
+ model_dict = model.to_dict()
34
+ model_dict['provider'] = provider.id
35
+ model_dict['user_id'] = user_id
36
+ result = supabase.table('models').upsert(model_dict).execute()
37
+ model_dict = result.data[0]
38
+ return model_dict
39
+ except Exception as e:
40
+ print('Error saving model', str(e))
41
+ raise e
42
+
43
+ @staticmethod
44
+ async def delete_model_in_db(access_token: str, user_id: str, name: str, id: str) -> bool:
45
+ try:
46
+ supabase = get_supabase(access_token)
47
+ if id:
48
+ result = supabase.table('models').delete().eq('id', id).execute()
49
+ elif user_id and name:
50
+ result = supabase.table('models').delete().eq('user_id', user_id).eq('name', name).execute()
51
+ else:
52
+ raise ValueError('Some form of id is required to delete a model')
53
+ print('Deleted model', result.data)
54
+ return True
55
+ except Exception as e:
56
+ print('Error deleting model', str(e))
57
+ raise e
58
+
59
+ @staticmethod
60
+ async def get_model(access_token: str, user_id: str = None, name: str = None, id: str = None) -> LLMModel:
61
+ try:
62
+ supabase = get_supabase(access_token)
63
+ if id:
64
+ print(f"Getting model with id: {id}")
65
+ result = supabase.table('models').select('*').eq('id', id).execute()
66
+ elif user_id and name:
67
+ print(f"Getting model with name: {name} and user_id: {user_id}")
68
+ result = supabase.table('models').select('*').eq('user_id', user_id).eq('name', name).execute()
69
+ else:
70
+ raise ValueError('Some form of id is required to retrieve a model')
71
+ print('Fetched model', result)
72
+ if not result or not result.data:
73
+ return None
74
+ model_dict = result.data[0]
75
+ model_dict['id'] = str(model_dict['id'])
76
+ model_dict['provider'] = str(model_dict['provider'])
77
+ model = LLMModel(**model_dict)
78
+ return model
79
+ except Exception as e:
80
+ print('Error fetching model from db', str(e))
81
+ raise e
82
+
83
+ @staticmethod
84
+ async def get_models(limit: int = 10, created_at_lt: datetime = datetime.now()) -> [dict]:
85
+ try:
86
+ result = supabase.table('models') \
87
+ .select('*') \
88
+ .order('created_at', desc=True) \
89
+ .limit(limit) \
90
+ .lt('created_at', created_at_lt) \
91
+ .execute()
92
+ data = result.data
93
+ print('Fetched models', data)
94
+ return data
95
+ except Exception as e:
96
+ print('Error fetching models', str(e))
97
+ raise e
@@ -0,0 +1,32 @@
1
+ from pydantic import BaseModel, HttpUrl
2
+ from typing import Literal
3
+
4
+ Provider = Literal['openai', 'mistral', 'ollama', 'openrouter']
5
+
6
+ class ProviderModel(BaseModel):
7
+ id: str | None = None
8
+ name: Provider
9
+ api_key: str | None = None
10
+ api_url: HttpUrl | None = None
11
+
12
+ def to_dict(self):
13
+ dict = {
14
+ 'name': self.name.lower(),
15
+ }
16
+ if self.api_url is not None:
17
+ dict['api_url'] = str(self.api_url)
18
+ if self.api_key is not None:
19
+ dict['api_key'] = self.api_key
20
+ if self.id is not None:
21
+ dict['id'] = self.id
22
+ return dict
23
+
24
+ def from_dict(data):
25
+ name = data['name'] if 'name' in data else None
26
+ api_url = data['api_url'] if 'api_url' in data else None
27
+ api_key = data['api_key'] if 'api_key' in data else None
28
+ return ProviderModel(
29
+ name=name.lower(),
30
+ api_key=api_key,
31
+ api_url=api_url,
32
+ )
@@ -0,0 +1,138 @@
1
+ from datetime import datetime
2
+ from .provider_model import ProviderModel
3
+ from supabase import Client
4
+ from fyodorov_utils.config.supabase import get_supabase
5
+
6
+ supabase: Client = get_supabase()
7
+
8
+
9
+ class Provider(ProviderModel):
10
+
11
+ @staticmethod
12
+ async def update_provider_in_db(id: str, update: dict) -> dict:
13
+ if not id:
14
+ raise ValueError('Provider ID is required')
15
+ try:
16
+ result = supabase.table('providers').update(update).eq('id', id).execute()
17
+ update = result.data[0]
18
+ print('Updated provider:', update)
19
+ return update
20
+ except Exception as e:
21
+ print(f"Error updating provider with id {id} "
22
+ f"and update {update} ")
23
+ raise e
24
+
25
+ @staticmethod
26
+ async def save_provider_in_db(access_token: str, provider: ProviderModel, user_id: str) -> dict:
27
+ try:
28
+ print('Access token for saving provider:', access_token)
29
+ supabase = get_supabase(access_token)
30
+ provider.name = provider.name.lower()
31
+ if not provider.api_url or provider.api_url == "":
32
+ if provider.name == "openai":
33
+ provider.api_url = "https://api.openai.com/v1"
34
+ elif provider.name == "mistral":
35
+ provider.api_url = "https://api.mistral.ai/v1"
36
+ elif provider.name == "ollama":
37
+ provider.api_url = "http://localhost:11434/v1"
38
+ elif provider.name == "openrouter":
39
+ provider.api_url = "https://openrouter.ai/api/v1"
40
+ else:
41
+ raise ValueError('No URL provided when creating a provider')
42
+ print('Setting provider api_url to', provider.api_url)
43
+ provider_dict = provider.to_dict()
44
+ provider_dict['user_id'] = user_id
45
+ print('Provider dict before merging existing row:', provider_dict)
46
+ # Check if the provider already exists based on name and user_id
47
+ existing_provider = await Provider.get_provider(access_token, user_id, provider.name)
48
+ if existing_provider:
49
+ tmp = {**existing_provider.to_dict(), **provider_dict}
50
+ provider_dict = tmp
51
+ print('Saving provider', provider_dict)
52
+ result = supabase.table('providers').upsert(provider_dict).execute()
53
+ provider = result.data[0]
54
+ print('Saved provider', provider)
55
+ return provider
56
+ except Exception as e:
57
+ print('Error saving provider', str(e))
58
+ if e.code == '23505':
59
+ print('Provider already exists')
60
+ return provider
61
+ raise e
62
+
63
+ @staticmethod
64
+ async def delete_provider_in_db(id) -> bool:
65
+ if not id:
66
+ raise ValueError('Provider ID is required')
67
+ try:
68
+ result = supabase.table('providers').delete().eq('id', id).execute()
69
+ return True
70
+ except Exception as e:
71
+ print('Error deleting provider', str(e))
72
+ raise e
73
+
74
+ @staticmethod
75
+ async def get_provider_by_id(access_token: str, id: str) -> ProviderModel:
76
+ if not id:
77
+ raise ValueError('Provider ID is required')
78
+ try:
79
+ supabase = get_supabase(access_token)
80
+ result = supabase.table('providers').select('*').eq('id', id).limit(1).execute()
81
+ provider_dict = result.data[0]
82
+ print('Fetched provider', provider_dict)
83
+ provider_dict['id'] = str(provider_dict['id'])
84
+ provider = ProviderModel(**provider_dict)
85
+ return provider
86
+ except Exception as e:
87
+ print('Error fetching provider', str(e))
88
+ raise e
89
+
90
+ @staticmethod
91
+ async def get_provider(access_token: str, user_id: str, name: str) -> ProviderModel:
92
+ print(f"Getting provider with name: {name} and user_id: {user_id}")
93
+ if not name:
94
+ raise ValueError('Provider name is required')
95
+ if not user_id:
96
+ raise ValueError('User ID is required')
97
+ try:
98
+ supabase = get_supabase(access_token)
99
+ print('Got access token for getting provider:', access_token)
100
+ result = supabase.table('providers').select('*')\
101
+ .eq('user_id', user_id)\
102
+ .eq('name', name.lower())\
103
+ .limit(1).execute()
104
+ print('Result of getting provider:', result)
105
+ provider_dict = result.data[0]
106
+ print('Fetched provider', provider_dict)
107
+ provider_dict['id'] = str(provider_dict['id'])
108
+ provider = ProviderModel(**provider_dict)
109
+ return provider
110
+ except Exception as e:
111
+ print('Error fetching provider', str(e))
112
+ raise e
113
+
114
+ @staticmethod
115
+ async def get_or_create_provider(access_token: str, user_id: str, name: str) -> ProviderModel:
116
+ try:
117
+ provider = await Provider.get_provider(access_token, user_id, name)
118
+ return provider
119
+ except Exception as e:
120
+ provider = ProviderModel(name=name.lower())
121
+ provider = await Provider.save_provider_in_db(access_token, provider, user_id)
122
+ return provider
123
+
124
+ @staticmethod
125
+ async def get_providers(limit: int = 10, created_at_lt: datetime = datetime.now()) -> [dict]:
126
+ try:
127
+ result = supabase.table('providers') \
128
+ .select('*') \
129
+ .order('created_at', desc=True) \
130
+ .limit(limit) \
131
+ .lt('created_at', created_at_lt) \
132
+ .execute()
133
+ data = result.data
134
+ print('Fetched providers', data)
135
+ return data
136
+ except Exception as e:
137
+ print('Error fetching providers', str(e))
138
+ raise e
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fyodorov_llm_agents
3
- Version: 0.3.8
3
+ Version: 0.3.9
4
4
  Summary: LLM agents for the Fyodorov AI suite
5
5
  Author-email: Daniel Ransom <02masseur.alibis@icloud.com>
6
6
  Project-URL: Homepage, https://github.com/FyodorovAI/fyodorov-llm-agents
@@ -0,0 +1,15 @@
1
+ fyodorov_llm_agents/agents/agent.py,sha256=vVBCEBpG11m4ae11Mr94rF5TJpuBluI_loQPDTw734w,5044
2
+ fyodorov_llm_agents/agents/openai.py,sha256=FA5RS7yn3JwvFA8PXju60XSYC_2oUZFNgBUzeIYtGv0,1154
3
+ fyodorov_llm_agents/instances/instance_model.py,sha256=PQaoVSH9H4qp_wcLvyT_QgvNtwf9oehOxZaGI6mv1bA,1206
4
+ fyodorov_llm_agents/instances/instance_service.py,sha256=5HCgnaE9cd2HR1HqZFADKozVHR5SRvGHvXTRSB5Qiq4,8250
5
+ fyodorov_llm_agents/models/llm_model.py,sha256=aQtXtB7kRpnVdbPu-nmTGAaflbtKz3DPkgcckf1srsg,1645
6
+ fyodorov_llm_agents/models/llm_service.py,sha256=613PLLGiDzkCfkVbt0XDlRwTQRL06QPyr9bfsbwQtPk,3989
7
+ fyodorov_llm_agents/providers/provider_model.py,sha256=OyCK6WMRhyElsp88gILg0wso-OPHI7f55gEeypsJ7O0,957
8
+ fyodorov_llm_agents/providers/provider_service.py,sha256=GST-NLV8aLPsvapQEvgT_qHGYu7IpS5Xsut60XFmD-g,5865
9
+ fyodorov_llm_agents/tools/mcp_tool_model.py,sha256=i7oFZReVGZsHN2K8T7mCAGF4t92wMMIR9mCkRHUgKsI,4413
10
+ fyodorov_llm_agents/tools/mcp_tool_service.py,sha256=9tDvSdKpEAOKKU5MHImUqTOc6MZyk1hEjs3GpzgQUsk,5542
11
+ fyodorov_llm_agents/tools/tool.py,sha256=HyOk0X_3XE23sa8J-8UZx657tJ0sxwZWMbA4OPxXU6E,7940
12
+ fyodorov_llm_agents-0.3.9.dist-info/METADATA,sha256=5dPcGRcYhgr6oKGL1AopHd2kPYLooD5oGM8O7HD2jps,550
13
+ fyodorov_llm_agents-0.3.9.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
14
+ fyodorov_llm_agents-0.3.9.dist-info/top_level.txt,sha256=4QOslsBp8Gh7ng25DceA7fHp4KguTIdAxwURz97gH-g,20
15
+ fyodorov_llm_agents-0.3.9.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- fyodorov_llm_agents/agents/agent.py,sha256=vVBCEBpG11m4ae11Mr94rF5TJpuBluI_loQPDTw734w,5044
2
- fyodorov_llm_agents/agents/openai.py,sha256=FA5RS7yn3JwvFA8PXju60XSYC_2oUZFNgBUzeIYtGv0,1154
3
- fyodorov_llm_agents/instances/instance.py,sha256=edPOcn6qIB6Gnbr5d2_4HBaCk3sa8XYxfJPSHrcDLHE,8218
4
- fyodorov_llm_agents/tools/mcp_tool_model.py,sha256=i7oFZReVGZsHN2K8T7mCAGF4t92wMMIR9mCkRHUgKsI,4413
5
- fyodorov_llm_agents/tools/mcp_tool_service.py,sha256=9tDvSdKpEAOKKU5MHImUqTOc6MZyk1hEjs3GpzgQUsk,5542
6
- fyodorov_llm_agents/tools/tool.py,sha256=HyOk0X_3XE23sa8J-8UZx657tJ0sxwZWMbA4OPxXU6E,7940
7
- fyodorov_llm_agents-0.3.8.dist-info/METADATA,sha256=u-fvGhE1XF17eJcWQQNNbYSKq8sL1BV1yV9VFyMb878,550
8
- fyodorov_llm_agents-0.3.8.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
9
- fyodorov_llm_agents-0.3.8.dist-info/top_level.txt,sha256=4QOslsBp8Gh7ng25DceA7fHp4KguTIdAxwURz97gH-g,20
10
- fyodorov_llm_agents-0.3.8.dist-info/RECORD,,