intentkit 0.7.5.dev17__py3-none-any.whl → 0.7.5.dev18__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.
Potentially problematic release.
This version of intentkit might be problematic. Click here for more details.
- intentkit/__init__.py +1 -1
- intentkit/config/config.py +0 -5
- intentkit/core/engine.py +61 -42
- intentkit/models/llm.py +8 -8
- intentkit/skills/enso/__init__.py +1 -1
- intentkit/skills/enso/base.py +44 -17
- intentkit/skills/enso/best_yield.py +11 -17
- intentkit/skills/enso/networks.py +3 -2
- intentkit/skills/enso/prices.py +9 -9
- intentkit/skills/enso/route.py +27 -23
- intentkit/skills/enso/tokens.py +24 -21
- intentkit/skills/enso/wallet.py +71 -186
- {intentkit-0.7.5.dev17.dist-info → intentkit-0.7.5.dev18.dist-info}/METADATA +1 -1
- {intentkit-0.7.5.dev17.dist-info → intentkit-0.7.5.dev18.dist-info}/RECORD +16 -16
- {intentkit-0.7.5.dev17.dist-info → intentkit-0.7.5.dev18.dist-info}/WHEEL +0 -0
- {intentkit-0.7.5.dev17.dist-info → intentkit-0.7.5.dev18.dist-info}/licenses/LICENSE +0 -0
intentkit/__init__.py
CHANGED
intentkit/config/config.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# app/config.py
|
|
2
|
-
import asyncio
|
|
3
1
|
import json
|
|
4
2
|
import logging
|
|
5
3
|
import os
|
|
@@ -11,9 +9,6 @@ from intentkit.utils.logging import setup_logging
|
|
|
11
9
|
from intentkit.utils.s3 import init_s3
|
|
12
10
|
from intentkit.utils.slack_alert import init_slack
|
|
13
11
|
|
|
14
|
-
# hack for cdp bug
|
|
15
|
-
asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
|
|
16
|
-
|
|
17
12
|
# Load environment variables from .env file
|
|
18
13
|
load_dotenv()
|
|
19
14
|
|
intentkit/core/engine.py
CHANGED
|
@@ -21,6 +21,7 @@ from typing import Optional, Tuple
|
|
|
21
21
|
|
|
22
22
|
import sqlalchemy
|
|
23
23
|
from epyxid import XID
|
|
24
|
+
from langchain_core.language_models import BaseChatModel
|
|
24
25
|
from langchain_core.messages import (
|
|
25
26
|
BaseMessage,
|
|
26
27
|
HumanMessage,
|
|
@@ -29,6 +30,7 @@ from langchain_core.tools import BaseTool
|
|
|
29
30
|
from langgraph.errors import GraphRecursionError
|
|
30
31
|
from langgraph.graph.state import CompiledStateGraph
|
|
31
32
|
from langgraph.prebuilt import create_react_agent
|
|
33
|
+
from langgraph.runtime import Runtime
|
|
32
34
|
from sqlalchemy import func, update
|
|
33
35
|
from sqlalchemy.exc import SQLAlchemyError
|
|
34
36
|
|
|
@@ -70,8 +72,8 @@ _agents_updated: dict[str, datetime] = {}
|
|
|
70
72
|
_private_agents_updated: dict[str, datetime] = {}
|
|
71
73
|
|
|
72
74
|
|
|
73
|
-
async def
|
|
74
|
-
"""
|
|
75
|
+
async def build_agent(agent: Agent, agent_data: AgentData) -> CompiledStateGraph:
|
|
76
|
+
"""Build an AI agent with specified configuration and tools.
|
|
75
77
|
|
|
76
78
|
This function:
|
|
77
79
|
1. Initializes LLM with specified model
|
|
@@ -81,13 +83,12 @@ async def create_agent(agent: Agent, is_private: bool = False) -> CompiledStateG
|
|
|
81
83
|
|
|
82
84
|
Args:
|
|
83
85
|
agent (Agent): Agent configuration object
|
|
86
|
+
agent_data (AgentData): Agent data object
|
|
84
87
|
is_private (bool, optional): Flag indicating whether the agent is private. Defaults to False.
|
|
85
|
-
has_search (bool, optional): Flag indicating whether to include search tools. Defaults to False.
|
|
86
88
|
|
|
87
89
|
Returns:
|
|
88
90
|
CompiledStateGraph: Initialized LangChain agent
|
|
89
91
|
"""
|
|
90
|
-
agent_data = await AgentData.get(agent.id)
|
|
91
92
|
|
|
92
93
|
# Create the LLM model instance
|
|
93
94
|
llm_model = await create_llm_model(
|
|
@@ -108,6 +109,7 @@ async def create_agent(agent: Agent, is_private: bool = False) -> CompiledStateG
|
|
|
108
109
|
|
|
109
110
|
# ==== Load skills
|
|
110
111
|
tools: list[BaseTool | dict] = []
|
|
112
|
+
private_tools: list[BaseTool | dict] = []
|
|
111
113
|
|
|
112
114
|
if agent.skills:
|
|
113
115
|
for k, v in agent.skills.items():
|
|
@@ -116,11 +118,18 @@ async def create_agent(agent: Agent, is_private: bool = False) -> CompiledStateG
|
|
|
116
118
|
try:
|
|
117
119
|
skill_module = importlib.import_module(f"intentkit.skills.{k}")
|
|
118
120
|
if hasattr(skill_module, "get_skills"):
|
|
121
|
+
# all
|
|
119
122
|
skill_tools = await skill_module.get_skills(
|
|
120
|
-
v,
|
|
123
|
+
v, False, agent_store, agent_id=agent.id
|
|
121
124
|
)
|
|
122
125
|
if skill_tools and len(skill_tools) > 0:
|
|
123
126
|
tools.extend(skill_tools)
|
|
127
|
+
# private
|
|
128
|
+
skill_private_tools = await skill_module.get_skills(
|
|
129
|
+
v, True, agent_store, agent_id=agent.id
|
|
130
|
+
)
|
|
131
|
+
if skill_private_tools and len(skill_private_tools) > 0:
|
|
132
|
+
private_tools.extend(skill_private_tools)
|
|
124
133
|
else:
|
|
125
134
|
logger.error(f"Skill {k} does not have get_skills function")
|
|
126
135
|
except ImportError as e:
|
|
@@ -128,23 +137,33 @@ async def create_agent(agent: Agent, is_private: bool = False) -> CompiledStateG
|
|
|
128
137
|
|
|
129
138
|
# filter the duplicate tools
|
|
130
139
|
tools = list({tool.name: tool for tool in tools}.values())
|
|
140
|
+
private_tools = list({tool.name: tool for tool in private_tools}.values())
|
|
131
141
|
|
|
132
142
|
# Add search tools if requested
|
|
133
143
|
if (
|
|
134
|
-
llm_model.info.provider == LLMProvider.OPENAI
|
|
135
|
-
and
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
) # tmp disable gpt-5 search since package bugs
|
|
144
|
+
llm_model.info.provider == LLMProvider.OPENAI and llm_model.info.supports_search
|
|
145
|
+
# and not agent.model.startswith(
|
|
146
|
+
# "gpt-5"
|
|
147
|
+
# ) # tmp disable gpt-5 search since package bugs
|
|
139
148
|
):
|
|
140
149
|
tools.append({"type": "web_search_preview"})
|
|
150
|
+
private_tools.append({"type": "web_search_preview"})
|
|
141
151
|
|
|
142
152
|
# Create the formatted_prompt function using the refactored prompt module
|
|
143
153
|
formatted_prompt = create_formatted_prompt_function(agent, agent_data)
|
|
144
154
|
|
|
155
|
+
# bind tools to llm
|
|
156
|
+
def select_model(
|
|
157
|
+
state: AgentState, runtime: Runtime[AgentContext]
|
|
158
|
+
) -> BaseChatModel:
|
|
159
|
+
context = runtime.context
|
|
160
|
+
if context.is_private:
|
|
161
|
+
return llm.bind_tools(private_tools)
|
|
162
|
+
return llm.bind_tools(tools)
|
|
163
|
+
|
|
145
164
|
for tool in tools:
|
|
146
165
|
logger.info(
|
|
147
|
-
f"[{agent.id}
|
|
166
|
+
f"[{agent.id}] loaded tool: {tool.name if isinstance(tool, BaseTool) else tool}"
|
|
148
167
|
)
|
|
149
168
|
|
|
150
169
|
# Pre model hook
|
|
@@ -157,7 +176,7 @@ async def create_agent(agent: Agent, is_private: bool = False) -> CompiledStateG
|
|
|
157
176
|
|
|
158
177
|
# Create ReAct Agent using the LLM and CDP Agentkit tools.
|
|
159
178
|
executor = create_react_agent(
|
|
160
|
-
model=
|
|
179
|
+
model=select_model,
|
|
161
180
|
tools=tools,
|
|
162
181
|
prompt=formatted_prompt,
|
|
163
182
|
pre_model_hook=pre_model_hook,
|
|
@@ -172,7 +191,23 @@ async def create_agent(agent: Agent, is_private: bool = False) -> CompiledStateG
|
|
|
172
191
|
return executor
|
|
173
192
|
|
|
174
193
|
|
|
175
|
-
async def
|
|
194
|
+
async def create_agent(agent: Agent) -> CompiledStateGraph:
|
|
195
|
+
"""Create an AI agent with specified configuration and tools.
|
|
196
|
+
|
|
197
|
+
This function maintains backward compatibility by calling build_agent internally.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
agent (Agent): Agent configuration object
|
|
201
|
+
is_private (bool, optional): Flag indicating whether the agent is private. Defaults to False.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
CompiledStateGraph: Initialized LangChain agent
|
|
205
|
+
"""
|
|
206
|
+
agent_data = await AgentData.get(agent.id)
|
|
207
|
+
return await build_agent(agent, agent_data)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def initialize_agent(aid):
|
|
176
211
|
"""Initialize an AI agent with specified configuration and tools.
|
|
177
212
|
|
|
178
213
|
This function:
|
|
@@ -198,47 +233,34 @@ async def initialize_agent(aid, is_private=False):
|
|
|
198
233
|
)
|
|
199
234
|
|
|
200
235
|
# Create the agent using the new create_agent function
|
|
201
|
-
executor = await create_agent(agent
|
|
236
|
+
executor = await create_agent(agent)
|
|
202
237
|
|
|
203
238
|
# Cache the agent executor
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
_private_agents_updated[aid] = agent.updated_at
|
|
207
|
-
else:
|
|
208
|
-
_agents[aid] = executor
|
|
209
|
-
_agents_updated[aid] = agent.updated_at
|
|
239
|
+
_agents[aid] = executor
|
|
240
|
+
_agents_updated[aid] = agent.deployed_at if agent.deployed_at else agent.updated_at
|
|
210
241
|
|
|
211
242
|
|
|
212
|
-
async def agent_executor(
|
|
213
|
-
agent_id: str, is_private: bool
|
|
214
|
-
) -> Tuple[CompiledStateGraph, float]:
|
|
243
|
+
async def agent_executor(agent_id: str) -> Tuple[CompiledStateGraph, float]:
|
|
215
244
|
start = time.perf_counter()
|
|
216
245
|
agent = await Agent.get(agent_id)
|
|
217
246
|
if not agent:
|
|
218
247
|
raise IntentKitAPIError(
|
|
219
248
|
status_code=404, key="AgentNotFound", message="Agent not found"
|
|
220
249
|
)
|
|
221
|
-
|
|
222
|
-
agents_updated = _private_agents_updated if is_private else _agents_updated
|
|
223
|
-
|
|
250
|
+
updated_at = agent.deployed_at if agent.deployed_at else agent.updated_at
|
|
224
251
|
# Check if agent needs reinitialization due to updates
|
|
225
252
|
needs_reinit = False
|
|
226
|
-
if agent_id in
|
|
227
|
-
if
|
|
228
|
-
agent_id not in agents_updated
|
|
229
|
-
or agent.updated_at != agents_updated[agent_id]
|
|
230
|
-
):
|
|
253
|
+
if agent_id in _agents:
|
|
254
|
+
if agent_id not in _agents_updated or updated_at != _agents_updated[agent_id]:
|
|
231
255
|
needs_reinit = True
|
|
232
|
-
logger.info(
|
|
233
|
-
f"Reinitializing agent {agent_id} due to updates, private mode: {is_private}"
|
|
234
|
-
)
|
|
256
|
+
logger.info(f"Reinitializing agent {agent_id} due to updates")
|
|
235
257
|
|
|
236
258
|
# cold start or needs reinitialization
|
|
237
259
|
cold_start_cost = 0.0
|
|
238
|
-
if (agent_id not in
|
|
239
|
-
await initialize_agent(agent_id
|
|
260
|
+
if (agent_id not in _agents) or needs_reinit:
|
|
261
|
+
await initialize_agent(agent_id)
|
|
240
262
|
cold_start_cost = time.perf_counter() - start
|
|
241
|
-
return
|
|
263
|
+
return _agents[agent_id], cold_start_cost
|
|
242
264
|
|
|
243
265
|
|
|
244
266
|
async def stream_agent(message: ChatMessageCreate):
|
|
@@ -355,7 +377,7 @@ async def stream_agent(message: ChatMessageCreate):
|
|
|
355
377
|
if input.user_id == agent.owner:
|
|
356
378
|
is_private = True
|
|
357
379
|
|
|
358
|
-
executor, cold_start_cost = await agent_executor(input.agent_id
|
|
380
|
+
executor, cold_start_cost = await agent_executor(input.agent_id)
|
|
359
381
|
last = start + cold_start_cost
|
|
360
382
|
|
|
361
383
|
# Extract images from attachments
|
|
@@ -889,10 +911,7 @@ async def clean_agent_memory(
|
|
|
889
911
|
async def thread_stats(agent_id: str, chat_id: str) -> list[BaseMessage]:
|
|
890
912
|
thread_id = f"{agent_id}-{chat_id}"
|
|
891
913
|
stream_config = {"configurable": {"thread_id": thread_id}}
|
|
892
|
-
|
|
893
|
-
if chat_id.startswith("owner") or chat_id.startswith("autonomous"):
|
|
894
|
-
is_private = True
|
|
895
|
-
executor, _ = await agent_executor(agent_id, is_private)
|
|
914
|
+
executor, _ = await agent_executor(agent_id)
|
|
896
915
|
snap = await executor.aget_state(stream_config)
|
|
897
916
|
if snap.values and "messages" in snap.values:
|
|
898
917
|
return snap.values["messages"]
|
intentkit/models/llm.py
CHANGED
|
@@ -12,7 +12,7 @@ from intentkit.models.base import Base
|
|
|
12
12
|
from intentkit.models.db import get_session
|
|
13
13
|
from intentkit.models.redis import get_redis
|
|
14
14
|
from intentkit.utils.error import IntentKitLookUpError
|
|
15
|
-
from
|
|
15
|
+
from langchain.chat_models.base import BaseChatModel
|
|
16
16
|
from pydantic import BaseModel, ConfigDict, Field
|
|
17
17
|
from sqlalchemy import Boolean, Column, DateTime, Integer, Numeric, String, func, select
|
|
18
18
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
@@ -344,7 +344,7 @@ class LLMModel(BaseModel):
|
|
|
344
344
|
return model_info
|
|
345
345
|
|
|
346
346
|
# This will be implemented by subclasses to return the appropriate LLM instance
|
|
347
|
-
async def create_instance(self, config: Any) ->
|
|
347
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
348
348
|
"""Create and return the LLM instance based on the configuration."""
|
|
349
349
|
raise NotImplementedError("Subclasses must implement create_instance")
|
|
350
350
|
|
|
@@ -362,7 +362,7 @@ class LLMModel(BaseModel):
|
|
|
362
362
|
class OpenAILLM(LLMModel):
|
|
363
363
|
"""OpenAI LLM configuration."""
|
|
364
364
|
|
|
365
|
-
async def create_instance(self, config: Any) ->
|
|
365
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
366
366
|
"""Create and return a ChatOpenAI instance."""
|
|
367
367
|
from langchain_openai import ChatOpenAI
|
|
368
368
|
|
|
@@ -398,7 +398,7 @@ class OpenAILLM(LLMModel):
|
|
|
398
398
|
class DeepseekLLM(LLMModel):
|
|
399
399
|
"""Deepseek LLM configuration."""
|
|
400
400
|
|
|
401
|
-
async def create_instance(self, config: Any) ->
|
|
401
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
402
402
|
"""Create and return a ChatDeepseek instance."""
|
|
403
403
|
|
|
404
404
|
from langchain_deepseek import ChatDeepSeek
|
|
@@ -431,7 +431,7 @@ class DeepseekLLM(LLMModel):
|
|
|
431
431
|
class XAILLM(LLMModel):
|
|
432
432
|
"""XAI (Grok) LLM configuration."""
|
|
433
433
|
|
|
434
|
-
async def create_instance(self, config: Any) ->
|
|
434
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
435
435
|
"""Create and return a ChatXAI instance."""
|
|
436
436
|
|
|
437
437
|
from langchain_xai import ChatXAI
|
|
@@ -463,7 +463,7 @@ class XAILLM(LLMModel):
|
|
|
463
463
|
class EternalLLM(LLMModel):
|
|
464
464
|
"""Eternal AI LLM configuration."""
|
|
465
465
|
|
|
466
|
-
async def create_instance(self, config: Any) ->
|
|
466
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
467
467
|
"""Create and return a ChatOpenAI instance configured for Eternal AI."""
|
|
468
468
|
from langchain_openai import ChatOpenAI
|
|
469
469
|
|
|
@@ -495,7 +495,7 @@ class EternalLLM(LLMModel):
|
|
|
495
495
|
class ReigentLLM(LLMModel):
|
|
496
496
|
"""Reigent LLM configuration."""
|
|
497
497
|
|
|
498
|
-
async def create_instance(self, config: Any) ->
|
|
498
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
499
499
|
"""Create and return a ChatOpenAI instance configured for Reigent."""
|
|
500
500
|
from langchain_openai import ChatOpenAI
|
|
501
501
|
|
|
@@ -517,7 +517,7 @@ class ReigentLLM(LLMModel):
|
|
|
517
517
|
class VeniceLLM(LLMModel):
|
|
518
518
|
"""Venice LLM configuration."""
|
|
519
519
|
|
|
520
|
-
async def create_instance(self, config: Any) ->
|
|
520
|
+
async def create_instance(self, config: Any) -> BaseChatModel:
|
|
521
521
|
"""Create and return a ChatOpenAI instance configured for Venice."""
|
|
522
522
|
from langchain_openai import ChatOpenAI
|
|
523
523
|
|
intentkit/skills/enso/base.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
1
2
|
from typing import Optional, Type
|
|
2
3
|
|
|
3
|
-
from cdp import EvmServerAccount
|
|
4
4
|
from coinbase_agentkit import CdpEvmWalletProvider
|
|
5
5
|
from langchain.tools.base import ToolException
|
|
6
6
|
from pydantic import BaseModel, Field
|
|
@@ -9,10 +9,9 @@ from intentkit.abstracts.graph import AgentContext
|
|
|
9
9
|
from intentkit.abstracts.skill import SkillStoreABC
|
|
10
10
|
from intentkit.clients import CdpClient, get_cdp_client
|
|
11
11
|
from intentkit.skills.base import IntentKitSkill
|
|
12
|
-
from intentkit.utils.chain import ChainProvider,
|
|
12
|
+
from intentkit.utils.chain import ChainProvider, Network, network_to_id
|
|
13
13
|
|
|
14
14
|
base_url = "https://api.enso.finance"
|
|
15
|
-
default_chain_id = int(NetworkId.BaseMainnet)
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class EnsoBaseTool(IntentKitSkill):
|
|
@@ -25,18 +24,6 @@ class EnsoBaseTool(IntentKitSkill):
|
|
|
25
24
|
description="The skill store for persisting data"
|
|
26
25
|
)
|
|
27
26
|
|
|
28
|
-
async def get_account(self, context: AgentContext) -> Optional[EvmServerAccount]:
|
|
29
|
-
"""Get the account object from the CDP client.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
context: The skill context containing agent information.
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
Optional[EvmServerAccount]: The account object if available.
|
|
36
|
-
"""
|
|
37
|
-
client: CdpClient = await get_cdp_client(context.agent.id, self.skill_store)
|
|
38
|
-
return await client.get_account()
|
|
39
|
-
|
|
40
27
|
async def get_wallet_provider(
|
|
41
28
|
self, context: AgentContext
|
|
42
29
|
) -> Optional[CdpEvmWalletProvider]:
|
|
@@ -51,6 +38,13 @@ class EnsoBaseTool(IntentKitSkill):
|
|
|
51
38
|
client: CdpClient = await get_cdp_client(context.agent.id, self.skill_store)
|
|
52
39
|
return await client.get_wallet_provider()
|
|
53
40
|
|
|
41
|
+
async def get_wallet_address(self, context: AgentContext) -> str:
|
|
42
|
+
client: CdpClient = await get_cdp_client(context.agent.id, self.skill_store)
|
|
43
|
+
provider_config = await client.get_provider_config()
|
|
44
|
+
if not provider_config.address:
|
|
45
|
+
raise ToolException("wallet address not found for agent")
|
|
46
|
+
return provider_config.address
|
|
47
|
+
|
|
54
48
|
def get_chain_provider(self, context: AgentContext) -> Optional[ChainProvider]:
|
|
55
49
|
return self.skill_store.get_system_config("chain_provider")
|
|
56
50
|
|
|
@@ -60,8 +54,7 @@ class EnsoBaseTool(IntentKitSkill):
|
|
|
60
54
|
return skill_config["main_tokens"]
|
|
61
55
|
return []
|
|
62
56
|
|
|
63
|
-
def
|
|
64
|
-
context = self.get_context()
|
|
57
|
+
def get_api_token(self, context: AgentContext) -> str:
|
|
65
58
|
skill_config = context.agent.skill_config(self.category)
|
|
66
59
|
api_key_provider = skill_config.get("api_key_provider")
|
|
67
60
|
if api_key_provider == "platform":
|
|
@@ -74,6 +67,40 @@ class EnsoBaseTool(IntentKitSkill):
|
|
|
74
67
|
f"Invalid API key provider: {api_key_provider}, or no api_token in config"
|
|
75
68
|
)
|
|
76
69
|
|
|
70
|
+
def resolve_chain_id(
|
|
71
|
+
self, context: AgentContext, chain_id: Optional[int] = None
|
|
72
|
+
) -> int:
|
|
73
|
+
if chain_id:
|
|
74
|
+
return chain_id
|
|
75
|
+
|
|
76
|
+
agent = context.agent
|
|
77
|
+
try:
|
|
78
|
+
network = Network(agent.network_id)
|
|
79
|
+
except ValueError as exc: # pragma: no cover - defensive
|
|
80
|
+
raise ToolException(
|
|
81
|
+
f"Unsupported network configured for agent: {agent.network_id}"
|
|
82
|
+
) from exc
|
|
83
|
+
|
|
84
|
+
network_id = network_to_id.get(network)
|
|
85
|
+
if network_id is None:
|
|
86
|
+
raise ToolException(
|
|
87
|
+
f"Unable to determine chain id for network: {agent.network_id}"
|
|
88
|
+
)
|
|
89
|
+
return int(network_id)
|
|
90
|
+
|
|
77
91
|
@property
|
|
78
92
|
def category(self) -> str:
|
|
79
93
|
return "enso"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def format_amount_with_decimals(
|
|
97
|
+
amount: object, decimals: Optional[int]
|
|
98
|
+
) -> Optional[str]:
|
|
99
|
+
if amount is None or decimals is None:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
value = Decimal(str(amount)) / (Decimal(10) ** decimals)
|
|
104
|
+
return format(value, "f")
|
|
105
|
+
except Exception: # pragma: no cover - defensive
|
|
106
|
+
return None
|
|
@@ -4,14 +4,7 @@ import httpx
|
|
|
4
4
|
from langchain.tools.base import ToolException
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
|
-
from intentkit.skills.enso.base import
|
|
8
|
-
EnsoBaseTool,
|
|
9
|
-
base_url,
|
|
10
|
-
)
|
|
11
|
-
from intentkit.utils.chain import NetworkId
|
|
12
|
-
|
|
13
|
-
# Chain ID for Base Mainnet
|
|
14
|
-
BASE_CHAIN_ID = int(NetworkId.BaseMainnet)
|
|
7
|
+
from intentkit.skills.enso.base import EnsoBaseTool, base_url
|
|
15
8
|
|
|
16
9
|
|
|
17
10
|
class EnsoGetBestYieldInput(BaseModel):
|
|
@@ -21,9 +14,9 @@ class EnsoGetBestYieldInput(BaseModel):
|
|
|
21
14
|
"USDC",
|
|
22
15
|
description="Symbol of the token to find the best yield for (e.g., 'USDC', 'ETH', 'USDT')",
|
|
23
16
|
)
|
|
24
|
-
chain_id: int = Field(
|
|
25
|
-
|
|
26
|
-
description="The blockchain chain ID.
|
|
17
|
+
chain_id: int | None = Field(
|
|
18
|
+
None,
|
|
19
|
+
description="The blockchain chain ID. Defaults to the agent's configured network.",
|
|
27
20
|
)
|
|
28
21
|
top_n: int = Field(
|
|
29
22
|
5,
|
|
@@ -80,7 +73,7 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
80
73
|
async def _arun(
|
|
81
74
|
self,
|
|
82
75
|
token_symbol: str = "USDC",
|
|
83
|
-
chain_id: int =
|
|
76
|
+
chain_id: int | None = None,
|
|
84
77
|
top_n: int = 5,
|
|
85
78
|
**kwargs,
|
|
86
79
|
) -> EnsoGetBestYieldOutput:
|
|
@@ -89,7 +82,7 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
89
82
|
|
|
90
83
|
Args:
|
|
91
84
|
token_symbol (str): Symbol of the token to find the best yield for (default: USDC)
|
|
92
|
-
chain_id (int): The chain id of the network
|
|
85
|
+
chain_id (int | None): The chain id of the network. Defaults to the agent's configured network.
|
|
93
86
|
top_n (int): Number of top yield options to return
|
|
94
87
|
|
|
95
88
|
Returns:
|
|
@@ -99,16 +92,17 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
99
92
|
ToolException: If there's an error accessing the Enso API.
|
|
100
93
|
"""
|
|
101
94
|
context = self.get_context()
|
|
95
|
+
resolved_chain_id = self.resolve_chain_id(context, chain_id)
|
|
102
96
|
api_token = self.get_api_token(context)
|
|
103
97
|
|
|
104
98
|
if not api_token:
|
|
105
99
|
raise ToolException("No API token found for Enso Finance")
|
|
106
100
|
|
|
107
101
|
# Get the chain name for the given chain ID
|
|
108
|
-
chain_name = await self._get_chain_name(api_token,
|
|
102
|
+
chain_name = await self._get_chain_name(api_token, resolved_chain_id)
|
|
109
103
|
|
|
110
104
|
# Get all protocols on the specified chain
|
|
111
|
-
protocols = await self._get_protocols(api_token,
|
|
105
|
+
protocols = await self._get_protocols(api_token, resolved_chain_id)
|
|
112
106
|
|
|
113
107
|
# Collect all yield options from all protocols
|
|
114
108
|
all_yield_options = []
|
|
@@ -119,7 +113,7 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
119
113
|
|
|
120
114
|
# Get yield-bearing tokens for this protocol
|
|
121
115
|
tokens = await self._get_protocol_tokens(
|
|
122
|
-
api_token,
|
|
116
|
+
api_token, resolved_chain_id, protocol_slug, token_symbol
|
|
123
117
|
)
|
|
124
118
|
|
|
125
119
|
# Process tokens to extract yield options
|
|
@@ -170,7 +164,7 @@ class EnsoGetBestYield(EnsoBaseTool):
|
|
|
170
164
|
return EnsoGetBestYieldOutput(
|
|
171
165
|
best_options=top_options,
|
|
172
166
|
token_symbol=token_symbol,
|
|
173
|
-
chain_id=
|
|
167
|
+
chain_id=resolved_chain_id,
|
|
174
168
|
chain_name=chain_name,
|
|
175
169
|
)
|
|
176
170
|
|
|
@@ -7,8 +7,6 @@ from pydantic import BaseModel, Field
|
|
|
7
7
|
|
|
8
8
|
from .base import EnsoBaseTool, base_url
|
|
9
9
|
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
11
|
-
|
|
12
10
|
|
|
13
11
|
class EnsoGetNetworksInput(BaseModel):
|
|
14
12
|
"""
|
|
@@ -38,6 +36,9 @@ class EnsoGetNetworksOutput(BaseModel):
|
|
|
38
36
|
)
|
|
39
37
|
|
|
40
38
|
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
41
42
|
class EnsoGetNetworks(EnsoBaseTool):
|
|
42
43
|
"""
|
|
43
44
|
Tool for retrieving networks and their corresponding chainId, the output should be kept.
|
intentkit/skills/enso/prices.py
CHANGED
|
@@ -4,13 +4,11 @@ import httpx
|
|
|
4
4
|
from langchain.tools.base import ToolException
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
|
-
from .base import EnsoBaseTool, base_url
|
|
7
|
+
from .base import EnsoBaseTool, base_url
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class EnsoGetPricesInput(BaseModel):
|
|
11
|
-
chainId: int = Field(
|
|
12
|
-
default_chain_id, description="Blockchain chain ID of the token"
|
|
13
|
-
)
|
|
11
|
+
chainId: int | None = Field(None, description="Blockchain chain ID of the token")
|
|
14
12
|
address: str = Field(
|
|
15
13
|
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
16
14
|
description="Contract address of the token",
|
|
@@ -43,22 +41,21 @@ class EnsoGetPrices(EnsoBaseTool):
|
|
|
43
41
|
async def _arun(
|
|
44
42
|
self,
|
|
45
43
|
address: str,
|
|
46
|
-
chainId: int =
|
|
44
|
+
chainId: int | None = None,
|
|
47
45
|
**kwargs,
|
|
48
46
|
) -> EnsoGetPricesOutput:
|
|
49
47
|
"""
|
|
50
48
|
Asynchronous function to request the token price from the API.
|
|
51
49
|
|
|
52
50
|
Args:
|
|
53
|
-
chainId (int): The blockchain's chain ID.
|
|
51
|
+
chainId (int | None): The blockchain's chain ID. Defaults to the agent's configured network.
|
|
54
52
|
address (str): Contract address of the token.
|
|
55
53
|
|
|
56
54
|
Returns:
|
|
57
55
|
EnsoGetPricesOutput: Token price response or error message.
|
|
58
56
|
"""
|
|
59
|
-
url = f"{base_url}/api/v1/prices/{str(chainId)}/{address}"
|
|
60
|
-
|
|
61
57
|
context = self.get_context()
|
|
58
|
+
resolved_chain_id = self.resolve_chain_id(context, chainId)
|
|
62
59
|
api_token = self.get_api_token(context)
|
|
63
60
|
|
|
64
61
|
headers = {
|
|
@@ -68,7 +65,10 @@ class EnsoGetPrices(EnsoBaseTool):
|
|
|
68
65
|
|
|
69
66
|
async with httpx.AsyncClient() as client:
|
|
70
67
|
try:
|
|
71
|
-
response = await client.get(
|
|
68
|
+
response = await client.get(
|
|
69
|
+
f"{base_url}/api/v1/prices/{str(resolved_chain_id)}/{address}",
|
|
70
|
+
headers=headers,
|
|
71
|
+
)
|
|
72
72
|
response.raise_for_status()
|
|
73
73
|
json_dict = response.json()
|
|
74
74
|
|