intentkit 0.6.10.dev7__py3-none-any.whl → 0.6.11__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/clients/cdp.py +21 -7
- intentkit/config/config.py +43 -16
- intentkit/core/credit.py +29 -0
- intentkit/core/engine.py +10 -138
- intentkit/core/prompt.py +423 -42
- intentkit/models/llm.py +6 -5
- intentkit/skills/xmtp/__init__.py +16 -0
- intentkit/skills/xmtp/price.py +72 -0
- intentkit/skills/xmtp/schema.json +32 -0
- intentkit/skills/xmtp/swap.py +213 -0
- intentkit/skills/xmtp/transfer.py +16 -5
- {intentkit-0.6.10.dev7.dist-info → intentkit-0.6.11.dist-info}/METADATA +2 -1
- {intentkit-0.6.10.dev7.dist-info → intentkit-0.6.11.dist-info}/RECORD +16 -14
- {intentkit-0.6.10.dev7.dist-info → intentkit-0.6.11.dist-info}/WHEEL +0 -0
- {intentkit-0.6.10.dev7.dist-info → intentkit-0.6.11.dist-info}/licenses/LICENSE +0 -0
intentkit/core/prompt.py
CHANGED
|
@@ -1,57 +1,438 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Callable, Optional
|
|
3
|
+
|
|
4
|
+
from eth_utils import is_address
|
|
5
|
+
from langchain_core.messages import BaseMessage
|
|
6
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
7
|
+
from langgraph.runtime import Runtime
|
|
8
|
+
|
|
9
|
+
from intentkit.abstracts.graph import AgentContext, AgentState
|
|
1
10
|
from intentkit.config.config import config
|
|
2
11
|
from intentkit.models.agent import Agent
|
|
3
12
|
from intentkit.models.agent_data import AgentData
|
|
13
|
+
from intentkit.models.chat import AuthorType
|
|
14
|
+
from intentkit.models.skill import Skill
|
|
4
15
|
|
|
16
|
+
# ============================================================================
|
|
17
|
+
# CONSTANTS AND CONFIGURATION
|
|
18
|
+
# ============================================================================
|
|
5
19
|
|
|
6
|
-
|
|
20
|
+
# Base system prompt components
|
|
21
|
+
BASE_SYSTEM_PROMPT = """You are an AI agent built using IntentKit.
|
|
22
|
+
Your tools are called 'skills'.
|
|
23
|
+
If your skill fails to execute due to a technical error ask the user to try again later, don't retry by yourself. If someone asks you to do something you can't do with your currently available skills, you must say so, recommend them to submit their feedback to the IntentKit team at https://github.com/crestalnetwork/intentkit. Be concise and helpful with your responses."""
|
|
24
|
+
|
|
25
|
+
ENSO_SKILLS_GUIDE = """## ENSO Skills Guide
|
|
26
|
+
|
|
27
|
+
You are integrated with the Enso API. You can use enso_get_tokens to retrieve token information,
|
|
28
|
+
including APY, Protocol Slug, Symbol, Address, Decimals, and underlying tokens. When interacting with token amounts,
|
|
29
|
+
ensure to multiply input amounts by the token's decimal places and divide output amounts by the token's decimals.
|
|
30
|
+
Utilize enso_route_shortcut to find the best swap or deposit route. Set broadcast_request to True only when the
|
|
31
|
+
user explicitly requests a transaction broadcast. Insufficient funds or insufficient spending approval can cause
|
|
32
|
+
Route Shortcut broadcasts to fail. To avoid this, use the enso_broadcast_wallet_approve tool that requires explicit
|
|
33
|
+
user confirmation before broadcasting any approval transactions for security reasons.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ============================================================================
|
|
39
|
+
# CORE PROMPT BUILDING FUNCTIONS
|
|
40
|
+
# ============================================================================
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _build_system_header() -> str:
|
|
44
|
+
"""Build the system prompt header."""
|
|
7
45
|
prompt = "# SYSTEM PROMPT\n\n"
|
|
8
46
|
if config.system_prompt:
|
|
9
47
|
prompt += config.system_prompt + "\n\n"
|
|
10
|
-
prompt +=
|
|
11
|
-
prompt
|
|
12
|
-
|
|
48
|
+
prompt += BASE_SYSTEM_PROMPT + "\n"
|
|
49
|
+
return prompt
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _build_agent_identity_section(agent: Agent) -> str:
|
|
53
|
+
"""Build agent identity information section."""
|
|
54
|
+
identity_parts = []
|
|
55
|
+
|
|
13
56
|
if agent.name:
|
|
14
|
-
|
|
57
|
+
identity_parts.append(f"Your name is {agent.name}.")
|
|
15
58
|
if agent.ticker:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
identity_parts.append(f"Your ticker symbol is {agent.ticker}.")
|
|
60
|
+
|
|
61
|
+
return "\n".join(identity_parts) + ("\n" if identity_parts else "")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _build_social_accounts_section(agent_data: AgentData) -> str:
|
|
65
|
+
"""Build social accounts information section."""
|
|
66
|
+
if not agent_data:
|
|
67
|
+
return ""
|
|
68
|
+
|
|
69
|
+
social_parts = []
|
|
70
|
+
|
|
71
|
+
# Twitter info
|
|
72
|
+
if agent_data.twitter_id:
|
|
73
|
+
social_parts.append(
|
|
74
|
+
f"Your twitter id is {agent_data.twitter_id}, never reply or retweet yourself."
|
|
75
|
+
)
|
|
76
|
+
if agent_data.twitter_username:
|
|
77
|
+
social_parts.append(f"Your twitter username is {agent_data.twitter_username}.")
|
|
78
|
+
if agent_data.twitter_name:
|
|
79
|
+
social_parts.append(f"Your twitter name is {agent_data.twitter_name}.")
|
|
80
|
+
|
|
81
|
+
# Twitter verification status
|
|
82
|
+
if agent_data.twitter_is_verified:
|
|
83
|
+
social_parts.append("Your twitter account is verified.")
|
|
84
|
+
else:
|
|
85
|
+
social_parts.append("Your twitter account is not verified.")
|
|
86
|
+
|
|
87
|
+
# Telegram info
|
|
88
|
+
if agent_data.telegram_id:
|
|
89
|
+
social_parts.append(f"Your telegram bot id is {agent_data.telegram_id}.")
|
|
90
|
+
if agent_data.telegram_username:
|
|
91
|
+
social_parts.append(
|
|
92
|
+
f"Your telegram bot username is {agent_data.telegram_username}."
|
|
93
|
+
)
|
|
94
|
+
if agent_data.telegram_name:
|
|
95
|
+
social_parts.append(f"Your telegram bot name is {agent_data.telegram_name}.")
|
|
96
|
+
|
|
97
|
+
return "\n".join(social_parts) + ("\n" if social_parts else "")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _build_wallet_section(agent: Agent, agent_data: AgentData) -> str:
|
|
101
|
+
"""Build wallet information section."""
|
|
102
|
+
if not agent_data:
|
|
103
|
+
return ""
|
|
104
|
+
|
|
105
|
+
wallet_parts = []
|
|
106
|
+
network_id = agent.network_id or agent.cdp_network_id
|
|
107
|
+
|
|
108
|
+
if agent_data.evm_wallet_address and network_id != "solana":
|
|
109
|
+
wallet_parts.append(
|
|
110
|
+
f"Your wallet address in {network_id} is {agent_data.evm_wallet_address}."
|
|
111
|
+
)
|
|
112
|
+
if agent_data.solana_wallet_address and network_id == "solana":
|
|
113
|
+
wallet_parts.append(
|
|
114
|
+
f"Your wallet address in {network_id} is {agent_data.solana_wallet_address}."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return "\n".join(wallet_parts) + ("\n" if wallet_parts else "")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _build_user_info_section(context: AgentContext) -> str:
|
|
121
|
+
"""Build user information section when user_id is a valid EVM wallet address."""
|
|
122
|
+
if not context.user_id:
|
|
123
|
+
return ""
|
|
124
|
+
|
|
125
|
+
# Check if user_id is a valid EVM wallet address
|
|
126
|
+
try:
|
|
127
|
+
if is_address(context.user_id):
|
|
128
|
+
return f"## User Info\n\nThe person you are talking to has wallet address: {context.user_id}\n\n"
|
|
129
|
+
except Exception:
|
|
130
|
+
# If validation fails, don't include the section
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
return ""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _build_agent_characteristics_section(agent: Agent) -> str:
|
|
137
|
+
"""Build agent characteristics section (purpose, personality, principles, etc.)."""
|
|
138
|
+
sections = []
|
|
139
|
+
|
|
41
140
|
if agent.purpose:
|
|
42
|
-
|
|
141
|
+
sections.append(f"## Purpose\n\n{agent.purpose}")
|
|
43
142
|
if agent.personality:
|
|
44
|
-
|
|
143
|
+
sections.append(f"## Personality\n\n{agent.personality}")
|
|
45
144
|
if agent.principles:
|
|
46
|
-
|
|
145
|
+
sections.append(f"## Principles\n\n{agent.principles}")
|
|
47
146
|
if agent.prompt:
|
|
48
|
-
|
|
147
|
+
sections.append(f"## Initial Rules\n\n{agent.prompt}")
|
|
148
|
+
|
|
149
|
+
return "\n\n".join(sections) + ("\n\n" if sections else "")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _build_skills_guides_section(agent: Agent) -> str:
|
|
153
|
+
"""Build skills-specific guides section."""
|
|
154
|
+
guides = []
|
|
155
|
+
|
|
156
|
+
# ENSO skills guide
|
|
49
157
|
if agent.skills and "enso" in agent.skills and agent.skills["enso"].get("enabled"):
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
158
|
+
guides.append(ENSO_SKILLS_GUIDE)
|
|
159
|
+
|
|
160
|
+
return "".join(guides)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def build_agent_prompt(agent: Agent, agent_data: AgentData) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Build the complete agent system prompt.
|
|
166
|
+
|
|
167
|
+
This function orchestrates the building of different prompt sections:
|
|
168
|
+
- System header and base prompt
|
|
169
|
+
- Agent identity (name, ticker)
|
|
170
|
+
- Social accounts (Twitter, Telegram)
|
|
171
|
+
- Wallet information
|
|
172
|
+
- Agent characteristics (purpose, personality, principles)
|
|
173
|
+
- Skills-specific guides
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
agent: The agent configuration
|
|
177
|
+
agent_data: The agent's runtime data
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
str: The complete system prompt
|
|
181
|
+
"""
|
|
182
|
+
prompt_sections = [
|
|
183
|
+
_build_system_header(),
|
|
184
|
+
_build_agent_identity_section(agent),
|
|
185
|
+
_build_social_accounts_section(agent_data),
|
|
186
|
+
_build_wallet_section(agent, agent_data),
|
|
187
|
+
"\n", # Add spacing before characteristics
|
|
188
|
+
_build_agent_characteristics_section(agent),
|
|
189
|
+
_build_skills_guides_section(agent),
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
return "".join(section for section in prompt_sections if section)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# Legacy function name for backward compatibility
|
|
196
|
+
def agent_prompt(agent: Agent, agent_data: AgentData) -> str:
|
|
197
|
+
"""Legacy function name. Use build_agent_prompt instead."""
|
|
198
|
+
return build_agent_prompt(agent, agent_data)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
async def explain_prompt(message: str) -> str:
|
|
202
|
+
"""
|
|
203
|
+
Process message to replace @skill:*:* patterns with (call skill xxxxx) format.
|
|
204
|
+
This function is used when admin_llm_skill_control is enabled.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
message (str): The input message to process
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
str: The processed message with @skill patterns replaced
|
|
211
|
+
"""
|
|
212
|
+
# Pattern to match @skill:category:config_name with word boundaries
|
|
213
|
+
pattern = r"\b@skill:([^:]+):([^\s]+)\b"
|
|
214
|
+
|
|
215
|
+
async def replace_skill_pattern(match):
|
|
216
|
+
category = match.group(1)
|
|
217
|
+
config_name = match.group(2)
|
|
218
|
+
|
|
219
|
+
# Get skill by category and config_name
|
|
220
|
+
skill = await Skill.get_by_config_name(category, config_name)
|
|
221
|
+
|
|
222
|
+
if skill:
|
|
223
|
+
return f"(call skill {skill.name})"
|
|
224
|
+
else:
|
|
225
|
+
# If skill not found, keep original pattern
|
|
226
|
+
return match.group(0)
|
|
227
|
+
|
|
228
|
+
# Find all matches
|
|
229
|
+
matches = list(re.finditer(pattern, message))
|
|
230
|
+
|
|
231
|
+
# Process matches in reverse order to maintain string positions
|
|
232
|
+
result = message
|
|
233
|
+
for match in reversed(matches):
|
|
234
|
+
replacement = await replace_skill_pattern(match)
|
|
235
|
+
result = result[: match.start()] + replacement + result[match.end() :]
|
|
236
|
+
|
|
237
|
+
return result
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# ============================================================================
|
|
241
|
+
# UTILITY FUNCTIONS
|
|
242
|
+
# ============================================================================
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def escape_prompt(prompt: str) -> str:
|
|
246
|
+
"""Escape curly braces in the prompt for template processing."""
|
|
247
|
+
return prompt.replace("{", "{{").replace("}", "}}")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# ============================================================================
|
|
251
|
+
# ENTRYPOINT PROCESSING FUNCTIONS
|
|
252
|
+
# ============================================================================
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _build_social_entrypoint_prompt(agent: Agent, entrypoint: str) -> Optional[str]:
|
|
256
|
+
"""Build prompt for social media entrypoints (Twitter, Telegram)."""
|
|
257
|
+
if (
|
|
258
|
+
agent.twitter_entrypoint_enabled
|
|
259
|
+
and agent.twitter_entrypoint_prompt
|
|
260
|
+
and entrypoint == AuthorType.TWITTER.value
|
|
261
|
+
):
|
|
262
|
+
return agent.twitter_entrypoint_prompt
|
|
263
|
+
elif (
|
|
264
|
+
agent.telegram_entrypoint_enabled
|
|
265
|
+
and agent.telegram_entrypoint_prompt
|
|
266
|
+
and entrypoint == AuthorType.TELEGRAM.value
|
|
267
|
+
):
|
|
268
|
+
return agent.telegram_entrypoint_prompt
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _build_autonomous_task_prompt(agent: Agent, context: AgentContext) -> str:
|
|
273
|
+
"""Build prompt for autonomous task entrypoint."""
|
|
274
|
+
task_id = context.chat_id.removeprefix("autonomous-")
|
|
275
|
+
|
|
276
|
+
# Find the autonomous task by task_id
|
|
277
|
+
autonomous_task = None
|
|
278
|
+
if agent.autonomous:
|
|
279
|
+
for task in agent.autonomous:
|
|
280
|
+
if task.id == task_id:
|
|
281
|
+
autonomous_task = task
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
if not autonomous_task:
|
|
285
|
+
# Fallback if task not found
|
|
286
|
+
return f"You are running an autonomous task. The task id is {task_id}. "
|
|
287
|
+
|
|
288
|
+
# Build detailed task info - always include task_id
|
|
289
|
+
if autonomous_task.name:
|
|
290
|
+
task_info = f"You are running an autonomous task '{autonomous_task.name}' (ID: {task_id})"
|
|
291
|
+
else:
|
|
292
|
+
task_info = f"You are running an autonomous task (ID: {task_id})"
|
|
293
|
+
|
|
294
|
+
# Add description if available
|
|
295
|
+
if autonomous_task.description:
|
|
296
|
+
task_info += f": {autonomous_task.description}"
|
|
297
|
+
|
|
298
|
+
# Add cycle info
|
|
299
|
+
if autonomous_task.minutes:
|
|
300
|
+
task_info += f". This task runs every {autonomous_task.minutes} minute(s)"
|
|
301
|
+
elif autonomous_task.cron:
|
|
302
|
+
task_info += f". This task runs on schedule: {autonomous_task.cron}"
|
|
303
|
+
|
|
304
|
+
return f"{task_info}. "
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
async def build_entrypoint_prompt(agent: Agent, context: AgentContext) -> Optional[str]:
|
|
308
|
+
"""
|
|
309
|
+
Build entrypoint-specific prompt based on context.
|
|
310
|
+
|
|
311
|
+
Supports different entrypoint types:
|
|
312
|
+
- Twitter: Uses agent.twitter_entrypoint_prompt
|
|
313
|
+
- Telegram: Uses agent.telegram_entrypoint_prompt
|
|
314
|
+
- Autonomous tasks: Builds task-specific prompt with scheduling info
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
agent: The agent configuration
|
|
318
|
+
context: The agent context containing entrypoint information
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
Optional[str]: The entrypoint-specific prompt, or None if no entrypoint
|
|
322
|
+
"""
|
|
323
|
+
if not context.entrypoint:
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
entrypoint = context.entrypoint
|
|
327
|
+
entrypoint_prompt = None
|
|
328
|
+
|
|
329
|
+
# Handle social media entrypoints
|
|
330
|
+
entrypoint_prompt = _build_social_entrypoint_prompt(agent, entrypoint)
|
|
331
|
+
|
|
332
|
+
# Handle autonomous task entrypoint
|
|
333
|
+
if not entrypoint_prompt and entrypoint == AuthorType.TRIGGER.value:
|
|
334
|
+
entrypoint_prompt = _build_autonomous_task_prompt(agent, context)
|
|
335
|
+
|
|
336
|
+
# Process with admin LLM skill control if enabled
|
|
337
|
+
if entrypoint_prompt and config.admin_llm_skill_control:
|
|
338
|
+
entrypoint_prompt = await explain_prompt(entrypoint_prompt)
|
|
339
|
+
|
|
340
|
+
return entrypoint_prompt
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def build_internal_info_prompt(context: AgentContext) -> str:
|
|
344
|
+
"""Build internal info prompt with context information."""
|
|
345
|
+
internal_info = "## Internal Info\n\n"
|
|
346
|
+
internal_info += "These are for your internal use. You can use them when querying or storing data, "
|
|
347
|
+
internal_info += "but please do not directly share this information with users.\n\n"
|
|
348
|
+
internal_info += f"chat_id: {context.chat_id}\n\n"
|
|
349
|
+
if context.user_id:
|
|
350
|
+
internal_info += f"user_id: {context.user_id}\n\n"
|
|
351
|
+
return internal_info
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# ============================================================================
|
|
355
|
+
# MAIN PROMPT FACTORY FUNCTION
|
|
356
|
+
# ============================================================================
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def create_formatted_prompt_function(agent: Agent, agent_data: AgentData) -> Callable:
|
|
360
|
+
"""
|
|
361
|
+
Create the formatted_prompt function with agent-specific configuration.
|
|
362
|
+
|
|
363
|
+
This is the main factory function that creates a prompt formatting function
|
|
364
|
+
tailored to a specific agent. The returned function will be used by the
|
|
365
|
+
agent's runtime to format prompts for each conversation.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
agent: The agent configuration
|
|
369
|
+
agent_data: The agent's runtime data
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Callable: An async function that formats prompts based on agent state and context
|
|
373
|
+
"""
|
|
374
|
+
# Build base prompt using the new function name
|
|
375
|
+
prompt = build_agent_prompt(agent, agent_data)
|
|
376
|
+
escaped_prompt = escape_prompt(prompt)
|
|
377
|
+
|
|
378
|
+
# Process with admin LLM skill control if enabled
|
|
379
|
+
async def get_base_prompt():
|
|
380
|
+
if config.admin_llm_skill_control:
|
|
381
|
+
return await explain_prompt(escaped_prompt)
|
|
382
|
+
return escaped_prompt
|
|
383
|
+
|
|
384
|
+
# Build prompt array
|
|
385
|
+
prompt_array = [
|
|
386
|
+
("placeholder", "{system_prompt}"),
|
|
387
|
+
("placeholder", "{messages}"),
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
if agent.prompt_append:
|
|
391
|
+
# Escape any curly braces in prompt_append
|
|
392
|
+
escaped_append = escape_prompt(agent.prompt_append)
|
|
393
|
+
prompt_array.append(("system", escaped_append))
|
|
394
|
+
|
|
395
|
+
prompt_temp = ChatPromptTemplate.from_messages(prompt_array)
|
|
396
|
+
|
|
397
|
+
async def formatted_prompt(
|
|
398
|
+
state: AgentState, runtime: Runtime[AgentContext]
|
|
399
|
+
) -> list[BaseMessage]:
|
|
400
|
+
# Get base prompt (with potential admin LLM skill control processing)
|
|
401
|
+
final_system_prompt = await get_base_prompt()
|
|
402
|
+
|
|
403
|
+
context = runtime.context
|
|
404
|
+
|
|
405
|
+
# Add entrypoint prompt if applicable
|
|
406
|
+
entrypoint_prompt = await build_entrypoint_prompt(agent, context)
|
|
407
|
+
if entrypoint_prompt:
|
|
408
|
+
final_system_prompt = (
|
|
409
|
+
f"{final_system_prompt}## Entrypoint rules\n\n{entrypoint_prompt}\n\n"
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Add user info if user_id is a valid EVM wallet address
|
|
413
|
+
user_info = _build_user_info_section(context)
|
|
414
|
+
if user_info:
|
|
415
|
+
final_system_prompt = f"{final_system_prompt}{user_info}"
|
|
416
|
+
|
|
417
|
+
# Add internal info
|
|
418
|
+
internal_info = build_internal_info_prompt(context)
|
|
419
|
+
final_system_prompt = f"{final_system_prompt}{internal_info}"
|
|
420
|
+
|
|
421
|
+
# Process prompt_append with admin LLM skill control if needed
|
|
422
|
+
if agent.prompt_append and config.admin_llm_skill_control:
|
|
423
|
+
# Find the system message in prompt_array and process it
|
|
424
|
+
for i, (role, content) in enumerate(prompt_array):
|
|
425
|
+
if role == "system":
|
|
426
|
+
processed_append = await explain_prompt(content)
|
|
427
|
+
prompt_array[i] = ("system", processed_append)
|
|
428
|
+
break
|
|
429
|
+
|
|
430
|
+
system_prompt = [("system", final_system_prompt)]
|
|
431
|
+
return prompt_temp.invoke(
|
|
432
|
+
{
|
|
433
|
+
"messages": state["messages"],
|
|
434
|
+
"system_prompt": system_prompt,
|
|
435
|
+
}
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
return formatted_prompt
|
intentkit/models/llm.py
CHANGED
|
@@ -623,14 +623,15 @@ class DeepseekLLM(LLMModel):
|
|
|
623
623
|
async def create_instance(self, config: Any) -> LanguageModelLike:
|
|
624
624
|
"""Create and return a ChatDeepseek instance."""
|
|
625
625
|
|
|
626
|
-
from
|
|
626
|
+
from langchain_deepseek import ChatDeepSeek
|
|
627
627
|
|
|
628
628
|
info = await self.model_info()
|
|
629
629
|
|
|
630
630
|
kwargs = {
|
|
631
|
-
"
|
|
632
|
-
"
|
|
631
|
+
"model": self.model_name,
|
|
632
|
+
"api_key": config.deepseek_api_key,
|
|
633
633
|
"timeout": info.timeout,
|
|
634
|
+
"max_retries": 3,
|
|
634
635
|
}
|
|
635
636
|
|
|
636
637
|
# Add optional parameters based on model support
|
|
@@ -644,9 +645,9 @@ class DeepseekLLM(LLMModel):
|
|
|
644
645
|
kwargs["presence_penalty"] = self.presence_penalty
|
|
645
646
|
|
|
646
647
|
if info.api_base:
|
|
647
|
-
kwargs["
|
|
648
|
+
kwargs["api_base"] = info.api_base
|
|
648
649
|
|
|
649
|
-
return
|
|
650
|
+
return ChatDeepSeek(**kwargs)
|
|
650
651
|
|
|
651
652
|
|
|
652
653
|
class XAILLM(LLMModel):
|
|
@@ -6,6 +6,8 @@ from typing import TypedDict
|
|
|
6
6
|
from intentkit.abstracts.skill import SkillStoreABC
|
|
7
7
|
from intentkit.skills.base import SkillConfig, SkillState
|
|
8
8
|
from intentkit.skills.xmtp.base import XmtpBaseTool
|
|
9
|
+
from intentkit.skills.xmtp.price import XmtpGetSwapPrice
|
|
10
|
+
from intentkit.skills.xmtp.swap import XmtpSwap
|
|
9
11
|
from intentkit.skills.xmtp.transfer import XmtpTransfer
|
|
10
12
|
|
|
11
13
|
# Cache skills at the module level, because they are stateless
|
|
@@ -16,6 +18,8 @@ logger = logging.getLogger(__name__)
|
|
|
16
18
|
|
|
17
19
|
class SkillStates(TypedDict):
|
|
18
20
|
xmtp_transfer: SkillState
|
|
21
|
+
xmtp_swap: SkillState
|
|
22
|
+
xmtp_get_swap_price: SkillState
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
class Config(SkillConfig):
|
|
@@ -77,6 +81,18 @@ def get_xmtp_skill(
|
|
|
77
81
|
skill_store=store,
|
|
78
82
|
)
|
|
79
83
|
return _cache[name]
|
|
84
|
+
elif name == "xmtp_swap":
|
|
85
|
+
if name not in _cache:
|
|
86
|
+
_cache[name] = XmtpSwap(
|
|
87
|
+
skill_store=store,
|
|
88
|
+
)
|
|
89
|
+
return _cache[name]
|
|
90
|
+
elif name == "xmtp_get_swap_price":
|
|
91
|
+
if name not in _cache:
|
|
92
|
+
_cache[name] = XmtpGetSwapPrice(
|
|
93
|
+
skill_store=store,
|
|
94
|
+
)
|
|
95
|
+
return _cache[name]
|
|
80
96
|
else:
|
|
81
97
|
logger.warning(f"Unknown XMTP skill: {name}")
|
|
82
98
|
return None
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from typing import Literal, Type
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from intentkit.clients.cdp import get_origin_cdp_client
|
|
6
|
+
from intentkit.skills.xmtp.base import XmtpBaseTool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SwapPriceInput(BaseModel):
|
|
10
|
+
"""Input for querying swap price via CDP."""
|
|
11
|
+
|
|
12
|
+
from_token: str = Field(description="The contract address to swap from")
|
|
13
|
+
to_token: str = Field(description="The contract address to swap to")
|
|
14
|
+
from_amount: str = Field(description="Input amount in smallest units (as string)")
|
|
15
|
+
from_address: str = Field(
|
|
16
|
+
description="The address where the from_token balance is located"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class XmtpGetSwapPrice(XmtpBaseTool):
|
|
21
|
+
"""Skill for fetching indicative swap price using CDP SDK."""
|
|
22
|
+
|
|
23
|
+
name: str = "xmtp_get_swap_price"
|
|
24
|
+
description: str = "Get an indicative swap price/quote for token pair and amount on Base networks using CDP."
|
|
25
|
+
response_format: Literal["content", "content_and_artifact"] = "content"
|
|
26
|
+
args_schema: Type[BaseModel] = SwapPriceInput
|
|
27
|
+
|
|
28
|
+
async def _arun(
|
|
29
|
+
self,
|
|
30
|
+
from_token: str,
|
|
31
|
+
to_token: str,
|
|
32
|
+
from_amount: str,
|
|
33
|
+
from_address: str,
|
|
34
|
+
) -> str:
|
|
35
|
+
context = self.get_context()
|
|
36
|
+
agent = context.agent
|
|
37
|
+
|
|
38
|
+
if agent.network_id not in ("base-mainnet", "base-sepolia"):
|
|
39
|
+
raise ValueError(
|
|
40
|
+
f"Swap price only supported on base-mainnet or base-sepolia. Current: {agent.network_id}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
network_for_cdp = {
|
|
44
|
+
"base-mainnet": "base",
|
|
45
|
+
"base-sepolia": "base-sepolia",
|
|
46
|
+
}[agent.network_id]
|
|
47
|
+
|
|
48
|
+
cdp_client = get_origin_cdp_client(self.skill_store)
|
|
49
|
+
# Note: Don't use async with context manager as get_origin_cdp_client returns a managed global client
|
|
50
|
+
price = await cdp_client.evm.get_swap_price(
|
|
51
|
+
from_token=from_token,
|
|
52
|
+
to_token=to_token,
|
|
53
|
+
from_amount=str(from_amount),
|
|
54
|
+
network=network_for_cdp,
|
|
55
|
+
taker=from_address,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Try to format a readable message from typical fields
|
|
59
|
+
try:
|
|
60
|
+
amount_out = getattr(price, "to_amount", None) or (
|
|
61
|
+
price.get("to_amount") if isinstance(price, dict) else None
|
|
62
|
+
)
|
|
63
|
+
route = getattr(price, "route", None) or (
|
|
64
|
+
price.get("route") if isinstance(price, dict) else None
|
|
65
|
+
)
|
|
66
|
+
route_str = f" via {route}" if route else ""
|
|
67
|
+
if amount_out:
|
|
68
|
+
return f"Estimated output: {amount_out} units of {to_token}{route_str} on {agent.network_id}."
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
return f"Swap price result (raw): {price}"
|
|
@@ -36,6 +36,38 @@
|
|
|
36
36
|
],
|
|
37
37
|
"description": "Create XMTP transaction requests for transferring ETH or ERC20 tokens on Base mainnet. Supports both native ETH transfers and ERC20 token transfers. Generates wallet_sendCalls transaction data that users can sign.",
|
|
38
38
|
"default": "disabled"
|
|
39
|
+
},
|
|
40
|
+
"xmtp_swap": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"title": "XMTP Swap",
|
|
43
|
+
"enum": [
|
|
44
|
+
"disabled",
|
|
45
|
+
"public",
|
|
46
|
+
"private"
|
|
47
|
+
],
|
|
48
|
+
"x-enum-title": [
|
|
49
|
+
"Disabled",
|
|
50
|
+
"Agent Owner + All Users",
|
|
51
|
+
"Agent Owner Only"
|
|
52
|
+
],
|
|
53
|
+
"description": "Create XMTP transaction requests for swapping tokens on Base using CDP swap quote. Returns a wallet_sendCalls payload that can include an optional approval call and the swap call. Only supports base-mainnet and base-sepolia.",
|
|
54
|
+
"default": "disabled"
|
|
55
|
+
},
|
|
56
|
+
"xmtp_get_swap_price": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"title": "XMTP Get Swap Price",
|
|
59
|
+
"enum": [
|
|
60
|
+
"disabled",
|
|
61
|
+
"public",
|
|
62
|
+
"private"
|
|
63
|
+
],
|
|
64
|
+
"x-enum-title": [
|
|
65
|
+
"Disabled",
|
|
66
|
+
"Agent Owner + All Users",
|
|
67
|
+
"Agent Owner Only"
|
|
68
|
+
],
|
|
69
|
+
"description": "Get an indicative swap price/quote for token pair and amount on Base networks using CDP. Provides estimated output amounts for token swaps without creating transactions.",
|
|
70
|
+
"default": "disabled"
|
|
39
71
|
}
|
|
40
72
|
}
|
|
41
73
|
}
|