intentkit 0.7.4rc1__py3-none-any.whl → 0.7.4rc3__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/models/agent.py CHANGED
@@ -1,3 +1,4 @@
1
+ import hashlib
1
2
  import json
2
3
  import logging
3
4
  import re
@@ -14,10 +15,11 @@ from epyxid import XID
14
15
  from fastapi import HTTPException
15
16
  from intentkit.models.agent_data import AgentData
16
17
  from intentkit.models.base import Base
18
+ from intentkit.models.credit import CreditAccount
17
19
  from intentkit.models.db import get_session
18
20
  from intentkit.models.llm import LLMModelInfo, LLMModelInfoTable, LLMProvider
19
21
  from intentkit.models.skill import SkillTable
20
- from pydantic import BaseModel, ConfigDict, field_validator, model_validator
22
+ from pydantic import BaseModel, ConfigDict, field_validator
21
23
  from pydantic import Field as PydanticField
22
24
  from pydantic.json_schema import SkipJsonSchema
23
25
  from sqlalchemy import (
@@ -128,33 +130,6 @@ class AgentAutonomous(BaseModel):
128
130
  )
129
131
  return v
130
132
 
131
- @field_validator("name")
132
- @classmethod
133
- def validate_name(cls, v: Optional[str]) -> Optional[str]:
134
- if v is not None and len(v.encode()) > 50:
135
- raise ValueError("name must be at most 50 bytes")
136
- return v
137
-
138
- @field_validator("description")
139
- @classmethod
140
- def validate_description(cls, v: Optional[str]) -> Optional[str]:
141
- if v is not None and len(v.encode()) > 200:
142
- raise ValueError("description must be at most 200 bytes")
143
- return v
144
-
145
- @field_validator("prompt")
146
- @classmethod
147
- def validate_prompt(cls, v: Optional[str]) -> Optional[str]:
148
- if v is not None and len(v.encode()) > 20000:
149
- raise ValueError("prompt must be at most 20000 bytes")
150
- return v
151
-
152
- @model_validator(mode="after")
153
- def validate_schedule(self) -> "AgentAutonomous":
154
- # This validator is kept for backward compatibility
155
- # The actual validation now happens in AgentUpdate.validate_autonomous_schedule
156
- return self
157
-
158
133
 
159
134
  class AgentExample(BaseModel):
160
135
  """Agent example configuration."""
@@ -191,66 +166,22 @@ class AgentExample(BaseModel):
191
166
  ]
192
167
 
193
168
 
194
- class AgentTable(Base):
195
- """Agent table db model."""
169
+ class AgentUserInputColumns:
170
+ """Abstract base class containing columns that are common to AgentTable and other tables."""
196
171
 
197
- __tablename__ = "agents"
172
+ __abstract__ = True
198
173
 
199
- id = Column(
200
- String,
201
- primary_key=True,
202
- comment="Unique identifier for the agent. Must be URL-safe, containing only lowercase letters, numbers, and hyphens",
203
- )
174
+ # Basic information fields from AgentCore
204
175
  name = Column(
205
176
  String,
206
177
  nullable=True,
207
178
  comment="Display name of the agent",
208
179
  )
209
- slug = Column(
210
- String,
211
- nullable=True,
212
- comment="Slug of the agent, used for URL generation",
213
- )
214
- description = Column(
215
- String,
216
- nullable=True,
217
- comment="Description of the agent, for public view, not contained in prompt",
218
- )
219
- external_website = Column(
220
- String,
221
- nullable=True,
222
- comment="Link of external website of the agent, if you have one",
223
- )
224
180
  picture = Column(
225
181
  String,
226
182
  nullable=True,
227
183
  comment="Picture of the agent",
228
184
  )
229
- ticker = Column(
230
- String,
231
- nullable=True,
232
- comment="Ticker symbol of the agent",
233
- )
234
- token_address = Column(
235
- String,
236
- nullable=True,
237
- comment="Token address of the agent",
238
- )
239
- token_pool = Column(
240
- String,
241
- nullable=True,
242
- comment="Pool of the agent token",
243
- )
244
- mode = Column(
245
- String,
246
- nullable=True,
247
- comment="Mode of the agent, public or private",
248
- )
249
- fee_percentage = Column(
250
- Numeric(22, 4),
251
- nullable=True,
252
- comment="Fee percentage of the agent",
253
- )
254
185
  purpose = Column(
255
186
  String,
256
187
  nullable=True,
@@ -266,39 +197,8 @@ class AgentTable(Base):
266
197
  nullable=True,
267
198
  comment="Principles or values of the agent",
268
199
  )
269
- owner = Column(
270
- String,
271
- nullable=True,
272
- comment="Owner identifier of the agent, used for access control",
273
- )
274
- upstream_id = Column(
275
- String,
276
- index=True,
277
- nullable=True,
278
- comment="Upstream reference ID for idempotent operations",
279
- )
280
- upstream_extra = Column(
281
- JSON().with_variant(JSONB(), "postgresql"),
282
- nullable=True,
283
- comment="Additional data store for upstream use",
284
- )
285
- wallet_provider = Column(
286
- String,
287
- nullable=True,
288
- comment="Provider of the agent's wallet",
289
- )
290
- readonly_wallet_address = Column(
291
- String,
292
- nullable=True,
293
- comment="Readonly wallet address of the agent",
294
- )
295
- network_id = Column(
296
- String,
297
- nullable=True,
298
- default="base-mainnet",
299
- comment="Network identifier",
300
- )
301
- # AI part
200
+
201
+ # AI model configuration fields from AgentCore
302
202
  model = Column(
303
203
  String,
304
204
  nullable=True,
@@ -333,43 +233,44 @@ class AgentTable(Base):
333
233
  default=0.0,
334
234
  comment="Controls topic adherence (-2.0~2.0). Higher values allow more topic deviation, lower values enforce stricter topic adherence.",
335
235
  )
336
- short_term_memory_strategy = Column(
236
+
237
+ # Wallet and network configuration fields from AgentCore
238
+ wallet_provider = Column(
337
239
  String,
338
240
  nullable=True,
339
- default="trim",
340
- comment="Strategy for managing short-term memory when context limit is reached. 'trim' removes oldest messages, 'summarize' creates summaries.",
341
- )
342
- # autonomous mode
343
- autonomous = Column(
344
- JSON().with_variant(JSONB(), "postgresql"),
345
- nullable=True,
346
- comment="Autonomous agent configurations",
241
+ comment="Provider of the agent's wallet",
347
242
  )
348
- # agent examples
349
- example_intro = Column(
243
+ readonly_wallet_address = Column(
350
244
  String,
351
245
  nullable=True,
352
- comment="Introduction for example interactions",
246
+ comment="Readonly wallet address of the agent",
353
247
  )
354
- examples = Column(
355
- JSON().with_variant(JSONB(), "postgresql"),
248
+ network_id = Column(
249
+ String,
356
250
  nullable=True,
357
- comment="List of example interactions for the agent",
251
+ default="base-mainnet",
252
+ comment="Network identifier",
358
253
  )
359
- # skills
254
+
255
+ # Skills configuration from AgentCore
360
256
  skills = Column(
361
257
  JSON().with_variant(JSONB(), "postgresql"),
362
258
  nullable=True,
363
259
  comment="Dict of skills and their corresponding configurations",
364
260
  )
365
261
 
366
- cdp_network_id = Column(
262
+ # Additional fields from AgentUserInput
263
+ short_term_memory_strategy = Column(
367
264
  String,
368
265
  nullable=True,
369
- default="base-mainnet",
370
- comment="Network identifier for CDP integration",
266
+ default="trim",
267
+ comment="Strategy for managing short-term memory when context limit is reached. 'trim' removes oldest messages, 'summarize' creates summaries.",
268
+ )
269
+ autonomous = Column(
270
+ JSON().with_variant(JSONB(), "postgresql"),
271
+ nullable=True,
272
+ comment="Autonomous agent configurations",
371
273
  )
372
- # if telegram_entrypoint_enabled, the telegram_entrypoint_enabled will be enabled, telegram_config will be checked
373
274
  telegram_entrypoint_enabled = Column(
374
275
  Boolean,
375
276
  nullable=True,
@@ -391,6 +292,107 @@ class AgentTable(Base):
391
292
  nullable=True,
392
293
  comment="Extra prompt for xmtp entrypoint",
393
294
  )
295
+
296
+
297
+ class AgentTable(Base, AgentUserInputColumns):
298
+ """Agent table db model."""
299
+
300
+ __tablename__ = "agents"
301
+
302
+ id = Column(
303
+ String,
304
+ primary_key=True,
305
+ comment="Unique identifier for the agent. Must be URL-safe, containing only lowercase letters, numbers, and hyphens",
306
+ )
307
+ slug = Column(
308
+ String,
309
+ nullable=True,
310
+ comment="Slug of the agent, used for URL generation",
311
+ )
312
+ owner = Column(
313
+ String,
314
+ nullable=True,
315
+ comment="Owner identifier of the agent, used for access control",
316
+ )
317
+ upstream_id = Column(
318
+ String,
319
+ index=True,
320
+ nullable=True,
321
+ comment="Upstream reference ID for idempotent operations",
322
+ )
323
+ upstream_extra = Column(
324
+ JSON().with_variant(JSONB(), "postgresql"),
325
+ nullable=True,
326
+ comment="Additional data store for upstream use",
327
+ )
328
+ version = Column(
329
+ String,
330
+ nullable=True,
331
+ comment="Version hash of the agent",
332
+ )
333
+ statistics = Column(
334
+ JSON().with_variant(JSONB(), "postgresql"),
335
+ nullable=True,
336
+ comment="Statistics of the agent, update every 1 hour for query",
337
+ )
338
+ assets = Column(
339
+ JSON().with_variant(JSONB(), "postgresql"),
340
+ nullable=True,
341
+ comment="Assets of the agent, update every 1 hour for query",
342
+ )
343
+ account_snapshot = Column(
344
+ JSON().with_variant(JSONB(), "postgresql"),
345
+ nullable=True,
346
+ comment="Account snapshot of the agent, update every 1 hour for query",
347
+ )
348
+ extra = Column(
349
+ JSON().with_variant(JSONB(), "postgresql"),
350
+ nullable=True,
351
+ comment="Other helper data fields for query, come from agent and agent data",
352
+ )
353
+
354
+ # Fields moved from AgentUserInputColumns that are no longer in AgentUserInput
355
+ description = Column(
356
+ String,
357
+ nullable=True,
358
+ comment="Description of the agent, for public view, not contained in prompt",
359
+ )
360
+ external_website = Column(
361
+ String,
362
+ nullable=True,
363
+ comment="Link of external website of the agent, if you have one",
364
+ )
365
+ ticker = Column(
366
+ String,
367
+ nullable=True,
368
+ comment="Ticker symbol of the agent",
369
+ )
370
+ token_address = Column(
371
+ String,
372
+ nullable=True,
373
+ comment="Token address of the agent",
374
+ )
375
+ token_pool = Column(
376
+ String,
377
+ nullable=True,
378
+ comment="Pool of the agent token",
379
+ )
380
+ fee_percentage = Column(
381
+ Numeric(22, 4),
382
+ nullable=True,
383
+ comment="Fee percentage of the agent",
384
+ )
385
+ example_intro = Column(
386
+ String,
387
+ nullable=True,
388
+ comment="Introduction for example interactions",
389
+ )
390
+ examples = Column(
391
+ JSON().with_variant(JSONB(), "postgresql"),
392
+ nullable=True,
393
+ comment="List of example interactions for the agent",
394
+ )
395
+
394
396
  # auto timestamp
395
397
  created_at = Column(
396
398
  DateTime(timezone=True),
@@ -407,16 +409,8 @@ class AgentTable(Base):
407
409
  )
408
410
 
409
411
 
410
- class AgentUpdate(BaseModel):
411
- """Agent update model."""
412
-
413
- model_config = ConfigDict(
414
- title="Agent",
415
- from_attributes=True,
416
- json_schema_extra={
417
- "required": ["name", "purpose", "personality", "principles"],
418
- },
419
- )
412
+ class AgentCore(BaseModel):
413
+ """Agent core model."""
420
414
 
421
415
  name: Annotated[
422
416
  Optional[str],
@@ -431,42 +425,6 @@ class AgentUpdate(BaseModel):
431
425
  },
432
426
  ),
433
427
  ]
434
- slug: Annotated[
435
- Optional[str],
436
- PydanticField(
437
- default=None,
438
- description="Slug of the agent, used for URL generation",
439
- max_length=30,
440
- min_length=2,
441
- json_schema_extra={
442
- "x-group": "internal",
443
- "readOnly": True,
444
- },
445
- ),
446
- ]
447
- description: Annotated[
448
- Optional[str],
449
- PydanticField(
450
- default=None,
451
- description="Description of the agent, for public view, not contained in prompt",
452
- json_schema_extra={
453
- "x-group": "basic",
454
- "x-placeholder": "Introduce your agent",
455
- },
456
- ),
457
- ]
458
- external_website: Annotated[
459
- Optional[str],
460
- PydanticField(
461
- default=None,
462
- description="Link of external website of the agent, if you have one",
463
- json_schema_extra={
464
- "x-group": "basic",
465
- "x-placeholder": "Enter agent external website url",
466
- "format": "uri",
467
- },
468
- ),
469
- ]
470
428
  picture: Annotated[
471
429
  Optional[str],
472
430
  PydanticField(
@@ -478,64 +436,6 @@ class AgentUpdate(BaseModel):
478
436
  },
479
437
  ),
480
438
  ]
481
- ticker: Annotated[
482
- Optional[str],
483
- PydanticField(
484
- default=None,
485
- description="Ticker symbol of the agent",
486
- max_length=10,
487
- min_length=1,
488
- json_schema_extra={
489
- "x-group": "basic",
490
- "x-placeholder": "If one day, your agent has it's own token, what will it be?",
491
- },
492
- ),
493
- ]
494
- token_address: Annotated[
495
- Optional[str],
496
- PydanticField(
497
- default=None,
498
- description="Token address of the agent",
499
- max_length=42,
500
- json_schema_extra={
501
- "x-group": "internal",
502
- "readOnly": True,
503
- },
504
- ),
505
- ]
506
- token_pool: Annotated[
507
- Optional[str],
508
- PydanticField(
509
- default=None,
510
- description="Pool of the agent token",
511
- max_length=42,
512
- json_schema_extra={
513
- "x-group": "internal",
514
- "readOnly": True,
515
- },
516
- ),
517
- ]
518
- mode: Annotated[
519
- Optional[Literal["public", "private"]],
520
- PydanticField(
521
- default=None,
522
- description="Mode of the agent, public or private",
523
- json_schema_extra={
524
- "x-group": "basic",
525
- },
526
- ),
527
- ]
528
- fee_percentage: Annotated[
529
- Optional[Decimal],
530
- PydanticField(
531
- default=None,
532
- description="Fee percentage of the agent",
533
- ge=Decimal("0.0"),
534
- json_schema_extra={
535
- "x-group": "basic",
536
- },
537
- ),
538
- ]
539
439
  purpose: Annotated[
540
440
  Optional[str],
541
441
  PydanticField(
@@ -584,38 +484,6 @@ class AgentUpdate(BaseModel):
584
484
  },
585
485
  ),
586
486
  ]
587
- owner: Annotated[
588
- Optional[str],
589
- PydanticField(
590
- default=None,
591
- description="Owner identifier of the agent, used for access control",
592
- max_length=50,
593
- json_schema_extra={
594
- "x-group": "internal",
595
- },
596
- ),
597
- ]
598
- upstream_id: Annotated[
599
- Optional[str],
600
- PydanticField(
601
- default=None,
602
- description="External reference ID for idempotent operations",
603
- max_length=100,
604
- json_schema_extra={
605
- "x-group": "internal",
606
- },
607
- ),
608
- ]
609
- upstream_extra: Annotated[
610
- Optional[Dict[str, Any]],
611
- PydanticField(
612
- default=None,
613
- description="Additional data store for upstream use",
614
- json_schema_extra={
615
- "x-group": "internal",
616
- },
617
- ),
618
- ]
619
487
  # AI part
620
488
  model: Annotated[
621
489
  str,
@@ -675,91 +543,21 @@ class AgentUpdate(BaseModel):
675
543
  default=0.0,
676
544
  description="The frequency penalty is a measure of how much the AI is allowed to repeat itself. A lower value means the AI is more likely to repeat previous responses, while a higher value means the AI is more likely to generate new content. For creative tasks, you can adjust it to 1 or a bit higher. (-2.0~2.0)",
677
545
  ge=-2.0,
678
- le=2.0,
679
- json_schema_extra={
680
- "x-group": "ai",
681
- },
682
- ),
683
- ]
684
- presence_penalty: Annotated[
685
- Optional[float],
686
- PydanticField(
687
- default=0.0,
688
- description="The presence penalty is a measure of how much the AI is allowed to deviate from the topic. A higher value means the AI is more likely to deviate from the topic, while a lower value means the AI is more likely to follow the topic. For creative tasks, you can adjust it to 1 or a bit higher. (-2.0~2.0)",
689
- ge=-2.0,
690
- le=2.0,
691
- json_schema_extra={
692
- "x-group": "ai",
693
- },
694
- ),
695
- ]
696
- short_term_memory_strategy: Annotated[
697
- Optional[Literal["trim", "summarize"]],
698
- PydanticField(
699
- default="trim",
700
- description="Strategy for managing short-term memory when context limit is reached. 'trim' removes oldest messages, 'summarize' creates summaries.",
701
- json_schema_extra={
702
- "x-group": "ai",
703
- },
704
- ),
705
- ]
706
- # autonomous mode
707
- autonomous: Annotated[
708
- Optional[List[AgentAutonomous]],
709
- PydanticField(
710
- default=None,
711
- description=(
712
- "Autonomous agent configurations.\n"
713
- "autonomous:\n"
714
- " - id: a\n"
715
- " name: TestA\n"
716
- " minutes: 1\n"
717
- " prompt: |-\n"
718
- " Say hello [sequence], use number for sequence.\n"
719
- " - id: b\n"
720
- " name: TestB\n"
721
- ' cron: "0/3 * * * *"\n'
722
- " prompt: |-\n"
723
- " Say hi [sequence], use number for sequence.\n"
724
- ),
725
- json_schema_extra={
726
- "x-group": "autonomous",
727
- "x-inline": True,
728
- },
729
- ),
730
- ]
731
- example_intro: Annotated[
732
- Optional[str],
733
- PydanticField(
734
- default=None,
735
- description="Introduction of the example",
736
- max_length=2000,
737
- json_schema_extra={
738
- "x-group": "examples",
739
- },
740
- ),
741
- ]
742
- examples: Annotated[
743
- Optional[List[AgentExample]],
744
- PydanticField(
745
- default=None,
746
- description="List of example prompts for the agent",
747
- max_length=6,
546
+ le=2.0,
748
547
  json_schema_extra={
749
- "x-group": "examples",
750
- "x-inline": True,
548
+ "x-group": "ai",
751
549
  },
752
550
  ),
753
551
  ]
754
- # skills
755
- skills: Annotated[
756
- Optional[Dict[str, Any]],
552
+ presence_penalty: Annotated[
553
+ Optional[float],
757
554
  PydanticField(
758
- default=None,
759
- description="Dict of skills and their corresponding configurations",
555
+ default=0.0,
556
+ description="The presence penalty is a measure of how much the AI is allowed to deviate from the topic. A higher value means the AI is more likely to deviate from the topic, while a lower value means the AI is more likely to follow the topic. For creative tasks, you can adjust it to 1 or a bit higher. (-2.0~2.0)",
557
+ ge=-2.0,
558
+ le=2.0,
760
559
  json_schema_extra={
761
- "x-group": "skills",
762
- "x-inline": True,
560
+ "x-group": "ai",
763
561
  },
764
562
  ),
765
563
  ]
@@ -804,26 +602,80 @@ class AgentUpdate(BaseModel):
804
602
  },
805
603
  ),
806
604
  ]
807
- cdp_network_id: Annotated[
808
- Optional[
809
- Literal[
810
- "ethereum-mainnet",
811
- "ethereum-sepolia",
812
- "polygon-mainnet",
813
- "polygon-mumbai",
814
- "base-mainnet",
815
- "base-sepolia",
816
- "arbitrum-mainnet",
817
- "arbitrum-sepolia",
818
- "optimism-mainnet",
819
- "optimism-sepolia",
820
- ]
821
- ],
605
+ skills: Annotated[
606
+ Optional[Dict[str, Any]],
822
607
  PydanticField(
823
- default="base-mainnet",
824
- description="Network identifier for CDP integration",
608
+ default=None,
609
+ description="Dict of skills and their corresponding configurations",
610
+ json_schema_extra={
611
+ "x-group": "skills",
612
+ "x-inline": True,
613
+ },
614
+ ),
615
+ ]
616
+
617
+ def hash(self) -> str:
618
+ """
619
+ Generate a fixed-length hash based on the agent's content.
620
+
621
+ The hash remains unchanged if the content is the same and changes if the content changes.
622
+ This method serializes only AgentCore fields to JSON and generates a SHA-256 hash.
623
+ When called from subclasses, it will only use AgentCore fields, not subclass fields.
624
+
625
+ Returns:
626
+ str: A 64-character hexadecimal hash string
627
+ """
628
+ # Create a dictionary with only AgentCore fields for hashing
629
+ hash_data = {}
630
+
631
+ # Get only AgentCore field values, excluding None values for consistency
632
+ for field_name in AgentCore.model_fields:
633
+ value = getattr(self, field_name)
634
+ if value is not None:
635
+ hash_data[field_name] = value
636
+
637
+ # Convert to JSON string with sorted keys for consistent ordering
638
+ json_str = json.dumps(hash_data, sort_keys=True, default=str, ensure_ascii=True)
639
+
640
+ # Generate SHA-256 hash
641
+ return hashlib.sha256(json_str.encode("utf-8")).hexdigest()
642
+
643
+
644
+ class AgentUserInput(AgentCore):
645
+ """Agent update model."""
646
+
647
+ short_term_memory_strategy: Annotated[
648
+ Optional[Literal["trim", "summarize"]],
649
+ PydanticField(
650
+ default="trim",
651
+ description="Strategy for managing short-term memory when context limit is reached. 'trim' removes oldest messages, 'summarize' creates summaries.",
652
+ json_schema_extra={
653
+ "x-group": "ai",
654
+ },
655
+ ),
656
+ ]
657
+ # autonomous mode
658
+ autonomous: Annotated[
659
+ Optional[List[AgentAutonomous]],
660
+ PydanticField(
661
+ default=None,
662
+ description=(
663
+ "Autonomous agent configurations.\n"
664
+ "autonomous:\n"
665
+ " - id: a\n"
666
+ " name: TestA\n"
667
+ " minutes: 1\n"
668
+ " prompt: |-\n"
669
+ " Say hello [sequence], use number for sequence.\n"
670
+ " - id: b\n"
671
+ " name: TestB\n"
672
+ ' cron: "0/3 * * * *"\n'
673
+ " prompt: |-\n"
674
+ " Say hi [sequence], use number for sequence.\n"
675
+ ),
825
676
  json_schema_extra={
826
- "x-group": "deprecated",
677
+ "x-group": "autonomous",
678
+ "x-inline": True,
827
679
  },
828
680
  ),
829
681
  ]
@@ -871,6 +723,37 @@ class AgentUpdate(BaseModel):
871
723
  ),
872
724
  ]
873
725
 
726
+
727
+ class AgentUpdate(AgentUserInput):
728
+ """Agent update model."""
729
+
730
+ model_config = ConfigDict(
731
+ title="Agent",
732
+ from_attributes=True,
733
+ json_schema_extra={
734
+ "required": ["name"],
735
+ },
736
+ )
737
+
738
+ upstream_id: Annotated[
739
+ Optional[str],
740
+ PydanticField(
741
+ default=None,
742
+ description="External reference ID for idempotent operations",
743
+ max_length=100,
744
+ ),
745
+ ]
746
+ upstream_extra: Annotated[
747
+ Optional[Dict[str, Any]],
748
+ PydanticField(
749
+ default=None,
750
+ description="Additional data store for upstream use",
751
+ json_schema_extra={
752
+ "x-group": "internal",
753
+ },
754
+ ),
755
+ ]
756
+
874
757
  @field_validator("purpose", "personality", "principles", "prompt", "prompt_append")
875
758
  @classmethod
876
759
  def validate_no_level1_level2_headings(cls, v: Optional[str]) -> Optional[str]:
@@ -960,6 +843,7 @@ class AgentUpdate(BaseModel):
960
843
  detail="The shortest execution interval is 5 minutes",
961
844
  )
962
845
 
846
+ # deprecated, use override instead
963
847
  async def update(self, id: str) -> "Agent":
964
848
  # Validate autonomous schedule settings if present
965
849
  if "autonomous" in self.model_dump(exclude_unset=True):
@@ -969,12 +853,6 @@ class AgentUpdate(BaseModel):
969
853
  db_agent = await db.get(AgentTable, id)
970
854
  if not db_agent:
971
855
  raise HTTPException(status_code=404, detail="Agent not found")
972
- # check owner
973
- if self.owner and db_agent.owner != self.owner:
974
- raise HTTPException(
975
- status_code=403,
976
- detail="You do not have permission to update this agent",
977
- )
978
856
  # update
979
857
  for key, value in self.model_dump(exclude_unset=True).items():
980
858
  setattr(db_agent, key, value)
@@ -991,15 +869,11 @@ class AgentUpdate(BaseModel):
991
869
  db_agent = await db.get(AgentTable, id)
992
870
  if not db_agent:
993
871
  raise HTTPException(status_code=404, detail="Agent not found")
994
- # check owner
995
- if db_agent.owner and db_agent.owner != self.owner:
996
- raise HTTPException(
997
- status_code=403,
998
- detail="You do not have permission to update this agent",
999
- )
1000
872
  # update
1001
873
  for key, value in self.model_dump().items():
1002
874
  setattr(db_agent, key, value)
875
+ # version
876
+ db_agent.version = self.hash()
1003
877
  await db.commit()
1004
878
  await db.refresh(db_agent)
1005
879
  return Agent.model_validate(db_agent)
@@ -1018,77 +892,195 @@ class AgentCreate(AgentUpdate):
1018
892
  max_length=67,
1019
893
  ),
1020
894
  ]
895
+ owner: Annotated[
896
+ Optional[str],
897
+ PydanticField(
898
+ default=None,
899
+ description="Owner identifier of the agent, used for access control",
900
+ max_length=50,
901
+ ),
902
+ ]
903
+
904
+ async def check_upstream_id(self) -> None:
905
+ if not self.upstream_id:
906
+ return None
907
+ async with get_session() as db:
908
+ existing = await db.scalar(
909
+ select(AgentTable).where(AgentTable.upstream_id == self.upstream_id)
910
+ )
911
+ if existing:
912
+ raise HTTPException(
913
+ status_code=400,
914
+ detail="Upstream id already in use",
915
+ )
916
+
917
+ async def get_by_upstream_id(self) -> Optional["Agent"]:
918
+ if not self.upstream_id:
919
+ return None
920
+ async with get_session() as db:
921
+ existing = await db.scalar(
922
+ select(AgentTable).where(AgentTable.upstream_id == self.upstream_id)
923
+ )
924
+ if existing:
925
+ return Agent.model_validate(existing)
926
+ return None
927
+
928
+ async def create(self) -> "Agent":
929
+ # Validate autonomous schedule settings if present
930
+ if self.autonomous:
931
+ self.validate_autonomous_schedule()
932
+
933
+ async with get_session() as db:
934
+ db_agent = AgentTable(**self.model_dump())
935
+ db_agent.version = self.hash()
936
+ db.add(db_agent)
937
+ await db.commit()
938
+ await db.refresh(db_agent)
939
+ return Agent.model_validate(db_agent)
940
+
941
+
942
+ class AgentPublicInfo(BaseModel):
943
+ """Public information of the agent."""
944
+
945
+ description: Annotated[
946
+ Optional[str],
947
+ PydanticField(
948
+ default=None,
949
+ description="Description of the agent, for public view, not contained in prompt",
950
+ json_schema_extra={
951
+ "x-group": "basic",
952
+ "x-placeholder": "Introduce your agent",
953
+ },
954
+ ),
955
+ ]
956
+ external_website: Annotated[
957
+ Optional[str],
958
+ PydanticField(
959
+ default=None,
960
+ description="Link of external website of the agent, if you have one",
961
+ json_schema_extra={
962
+ "x-group": "basic",
963
+ "x-placeholder": "Enter agent external website url",
964
+ "format": "uri",
965
+ },
966
+ ),
967
+ ]
968
+ ticker: Annotated[
969
+ Optional[str],
970
+ PydanticField(
971
+ default=None,
972
+ description="Ticker symbol of the agent",
973
+ max_length=10,
974
+ min_length=1,
975
+ json_schema_extra={
976
+ "x-group": "basic",
977
+ "x-placeholder": "If one day, your agent has it's own token, what will it be?",
978
+ },
979
+ ),
980
+ ]
981
+ token_address: Annotated[
982
+ Optional[str],
983
+ PydanticField(
984
+ default=None,
985
+ description="Token address of the agent",
986
+ max_length=42,
987
+ json_schema_extra={
988
+ "x-group": "internal",
989
+ "readOnly": True,
990
+ },
991
+ ),
992
+ ]
993
+ token_pool: Annotated[
994
+ Optional[str],
995
+ PydanticField(
996
+ default=None,
997
+ description="Pool of the agent token",
998
+ max_length=42,
999
+ json_schema_extra={
1000
+ "x-group": "internal",
1001
+ "readOnly": True,
1002
+ },
1003
+ ),
1004
+ ]
1005
+ fee_percentage: Annotated[
1006
+ Optional[Decimal],
1007
+ PydanticField(
1008
+ default=None,
1009
+ description="Fee percentage of the agent",
1010
+ ge=Decimal("0.0"),
1011
+ json_schema_extra={
1012
+ "x-group": "basic",
1013
+ },
1014
+ ),
1015
+ ]
1016
+ example_intro: Annotated[
1017
+ Optional[str],
1018
+ PydanticField(
1019
+ default=None,
1020
+ description="Introduction of the example",
1021
+ max_length=2000,
1022
+ json_schema_extra={
1023
+ "x-group": "examples",
1024
+ },
1025
+ ),
1026
+ ]
1027
+ examples: Annotated[
1028
+ Optional[List[AgentExample]],
1029
+ PydanticField(
1030
+ default=None,
1031
+ description="List of example prompts for the agent",
1032
+ max_length=6,
1033
+ json_schema_extra={
1034
+ "x-group": "examples",
1035
+ "x-inline": True,
1036
+ },
1037
+ ),
1038
+ ]
1021
1039
 
1022
- async def check_upstream_id(self) -> None:
1023
- if not self.upstream_id:
1024
- return None
1025
- async with get_session() as db:
1026
- existing = await db.scalar(
1027
- select(AgentTable).where(AgentTable.upstream_id == self.upstream_id)
1028
- )
1029
- if existing:
1030
- raise HTTPException(
1031
- status_code=400,
1032
- detail="Upstream id already in use",
1033
- )
1034
-
1035
- async def get_by_upstream_id(self) -> Optional["Agent"]:
1036
- if not self.upstream_id:
1037
- return None
1038
- async with get_session() as db:
1039
- existing = await db.scalar(
1040
- select(AgentTable).where(AgentTable.upstream_id == self.upstream_id)
1041
- )
1042
- if existing:
1043
- return Agent.model_validate(existing)
1044
- return None
1045
-
1046
- async def create(self) -> "Agent":
1047
- # Validate autonomous schedule settings if present
1048
- if self.autonomous:
1049
- self.validate_autonomous_schedule()
1050
-
1051
- async with get_session() as db:
1052
- db_agent = AgentTable(**self.model_dump())
1053
- db.add(db_agent)
1054
- await db.commit()
1055
- await db.refresh(db_agent)
1056
- return Agent.model_validate(db_agent)
1057
-
1058
- async def create_or_update(self) -> ("Agent", bool):
1059
- # Validation is now handled by field validators
1060
- await self.check_upstream_id()
1061
-
1062
- # Validate autonomous schedule settings if present
1063
- if self.autonomous:
1064
- self.validate_autonomous_schedule()
1065
-
1066
- is_new = False
1067
- async with get_session() as db:
1068
- db_agent = await db.get(AgentTable, self.id)
1069
- if not db_agent:
1070
- db_agent = AgentTable(**self.model_dump())
1071
- db.add(db_agent)
1072
- is_new = True
1073
- else:
1074
- # check owner
1075
- if self.owner and db_agent.owner != self.owner:
1076
- raise HTTPException(
1077
- status_code=403,
1078
- detail="You do not have permission to update this agent",
1079
- )
1080
- for key, value in self.model_dump(exclude_unset=True).items():
1081
- setattr(db_agent, key, value)
1082
- await db.commit()
1083
- await db.refresh(db_agent)
1084
- return Agent.model_validate(db_agent), is_new
1085
-
1086
1040
 
1087
- class Agent(AgentCreate):
1041
+ class Agent(AgentCreate, AgentPublicInfo):
1088
1042
  """Agent model."""
1089
1043
 
1090
1044
  model_config = ConfigDict(from_attributes=True)
1091
1045
 
1046
+ slug: Annotated[
1047
+ Optional[str],
1048
+ PydanticField(
1049
+ default=None,
1050
+ description="Slug of the agent, used for URL generation",
1051
+ max_length=20,
1052
+ min_length=2,
1053
+ ),
1054
+ ]
1055
+ version: Annotated[
1056
+ Optional[str],
1057
+ PydanticField(
1058
+ default=None,
1059
+ description="Version hash of the agent",
1060
+ ),
1061
+ ]
1062
+ statistics: Annotated[
1063
+ Optional[Dict[str, Any]],
1064
+ PydanticField(
1065
+ description="Statistics of the agent, update every 1 hour for query"
1066
+ ),
1067
+ ]
1068
+ assets: Annotated[
1069
+ Optional[Dict[str, Any]],
1070
+ PydanticField(description="Assets of the agent, update every 1 hour for query"),
1071
+ ]
1072
+ account_snapshot: Annotated[
1073
+ Optional[CreditAccount],
1074
+ PydanticField(
1075
+ description="Account snapshot of the agent, update every 1 hour for query"
1076
+ ),
1077
+ ]
1078
+ extra: Annotated[
1079
+ Optional[Dict[str, Any]],
1080
+ PydanticField(
1081
+ description="Other helper data fields for query, come from agent and agent data"
1082
+ ),
1083
+ ]
1092
1084
  # auto timestamp
1093
1085
  created_at: Annotated[
1094
1086
  datetime,
@@ -1469,7 +1461,7 @@ class Agent(AgentCreate):
1469
1461
  return schema
1470
1462
 
1471
1463
 
1472
- class AgentResponse(BaseModel):
1464
+ class AgentResponse(Agent):
1473
1465
  """Response model for Agent API."""
1474
1466
 
1475
1467
  model_config = ConfigDict(
@@ -1479,193 +1471,6 @@ class AgentResponse(BaseModel):
1479
1471
  },
1480
1472
  )
1481
1473
 
1482
- id: Annotated[
1483
- str,
1484
- PydanticField(
1485
- description="Unique identifier for the agent. Must be URL-safe, containing only lowercase letters, numbers, and hyphens",
1486
- ),
1487
- ]
1488
- # auto timestamp
1489
- created_at: Annotated[
1490
- datetime,
1491
- PydanticField(
1492
- description="Timestamp when the agent was created, will ignore when importing"
1493
- ),
1494
- ]
1495
- updated_at: Annotated[
1496
- datetime,
1497
- PydanticField(
1498
- description="Timestamp when the agent was last updated, will ignore when importing"
1499
- ),
1500
- ]
1501
- # Agent part
1502
- name: Annotated[
1503
- Optional[str],
1504
- PydanticField(
1505
- default=None,
1506
- description="Display name of the agent",
1507
- ),
1508
- ]
1509
- slug: Annotated[
1510
- Optional[str],
1511
- PydanticField(
1512
- default=None,
1513
- description="Slug of the agent, used for URL generation",
1514
- ),
1515
- ]
1516
- description: Annotated[
1517
- Optional[str],
1518
- PydanticField(
1519
- default=None,
1520
- description="Description of the agent, for public view, not contained in prompt",
1521
- ),
1522
- ]
1523
- external_website: Annotated[
1524
- Optional[str],
1525
- PydanticField(
1526
- default=None,
1527
- description="Link of external website of the agent, if you have one",
1528
- ),
1529
- ]
1530
- picture: Annotated[
1531
- Optional[str],
1532
- PydanticField(
1533
- default=None,
1534
- description="Picture of the agent",
1535
- ),
1536
- ]
1537
- ticker: Annotated[
1538
- Optional[str],
1539
- PydanticField(
1540
- default=None,
1541
- description="Ticker symbol of the agent",
1542
- ),
1543
- ]
1544
- token_address: Annotated[
1545
- Optional[str],
1546
- PydanticField(
1547
- default=None,
1548
- description="Token address of the agent",
1549
- ),
1550
- ]
1551
- token_pool: Annotated[
1552
- Optional[str],
1553
- PydanticField(
1554
- default=None,
1555
- description="Pool of the agent token",
1556
- ),
1557
- ]
1558
- mode: Annotated[
1559
- Optional[Literal["public", "private"]],
1560
- PydanticField(
1561
- default=None,
1562
- description="Mode of the agent, public or private",
1563
- ),
1564
- ]
1565
- fee_percentage: Annotated[
1566
- Optional[Decimal],
1567
- PydanticField(
1568
- default=None,
1569
- description="Fee percentage of the agent",
1570
- ),
1571
- ]
1572
- owner: Annotated[
1573
- Optional[str],
1574
- PydanticField(
1575
- default=None,
1576
- description="Owner identifier of the agent, used for access control",
1577
- max_length=50,
1578
- json_schema_extra={
1579
- "x-group": "internal",
1580
- },
1581
- ),
1582
- ]
1583
- upstream_id: Annotated[
1584
- Optional[str],
1585
- PydanticField(
1586
- default=None,
1587
- description="External reference ID for idempotent operations",
1588
- max_length=100,
1589
- json_schema_extra={
1590
- "x-group": "internal",
1591
- },
1592
- ),
1593
- ]
1594
- upstream_extra: Annotated[
1595
- Optional[Dict[str, Any]],
1596
- PydanticField(
1597
- default=None,
1598
- description="Additional data store for upstream use",
1599
- ),
1600
- ]
1601
- # AI part
1602
- model: Annotated[
1603
- str,
1604
- PydanticField(
1605
- description="AI model identifier to be used by this agent for processing requests. Available models: gpt-4o, gpt-4o-mini, deepseek-chat, deepseek-reasoner, grok-2, eternalai, reigent",
1606
- ),
1607
- ]
1608
- # autonomous mode
1609
- autonomous: Annotated[
1610
- Optional[List[Dict[str, Any]]],
1611
- PydanticField(
1612
- default=None,
1613
- description=("Autonomous agent configurations."),
1614
- ),
1615
- ]
1616
- # agent examples
1617
- example_intro: Annotated[
1618
- Optional[str],
1619
- PydanticField(
1620
- default=None,
1621
- description="Introduction for example interactions",
1622
- ),
1623
- ]
1624
- examples: Annotated[
1625
- Optional[List[AgentExample]],
1626
- PydanticField(
1627
- default=None,
1628
- description="List of example prompts for the agent",
1629
- ),
1630
- ]
1631
- # skills
1632
- skills: Annotated[
1633
- Optional[Dict[str, Any]],
1634
- PydanticField(
1635
- default=None,
1636
- description="Dict of skills and their corresponding configurations",
1637
- ),
1638
- ]
1639
- wallet_provider: Annotated[
1640
- Optional[Literal["cdp", "readonly"]],
1641
- PydanticField(
1642
- default="cdp",
1643
- description="Provider of the agent's wallet",
1644
- ),
1645
- ]
1646
- network_id: Annotated[
1647
- Optional[str],
1648
- PydanticField(
1649
- default="base-mainnet",
1650
- description="Network identifier",
1651
- ),
1652
- ]
1653
- cdp_network_id: Annotated[
1654
- Optional[str],
1655
- PydanticField(
1656
- default="base-mainnet",
1657
- description="Network identifier for CDP integration",
1658
- ),
1659
- ]
1660
- # telegram entrypoint
1661
- telegram_entrypoint_enabled: Annotated[
1662
- Optional[bool],
1663
- PydanticField(
1664
- default=False,
1665
- description="Whether the agent can play telegram bot",
1666
- ),
1667
- ]
1668
-
1669
1474
  # data part
1670
1475
  cdp_wallet_address: Annotated[
1671
1476
  Optional[str], PydanticField(description="CDP wallet address for the agent")
@@ -1753,6 +1558,18 @@ class AgentResponse(BaseModel):
1753
1558
  # Get base data from agent
1754
1559
  data = agent.model_dump()
1755
1560
 
1561
+ # Hide the sensitive fields
1562
+ data.pop("purpose", None)
1563
+ data.pop("personality", None)
1564
+ data.pop("principles", None)
1565
+ data.pop("prompt", None)
1566
+ data.pop("prompt_append", None)
1567
+ data.pop("temperature", None)
1568
+ data.pop("frequency_penalty", None)
1569
+ data.pop("telegram_entrypoint_prompt", None)
1570
+ data.pop("telegram_config", None)
1571
+ data.pop("xmtp_entrypoint_prompt", None)
1572
+
1756
1573
  # Filter sensitive fields from autonomous list
1757
1574
  if data.get("autonomous"):
1758
1575
  filtered_autonomous = []
@@ -1852,4 +1669,4 @@ class AgentResponse(BaseModel):
1852
1669
  }
1853
1670
  )
1854
1671
 
1855
- return cls.model_validate(data)
1672
+ return cls.model_construct(**data)