construct-labs-crm-env 0.1.2__py3-none-any.whl → 0.1.3__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.
@@ -35,6 +35,7 @@ from .models import (
35
35
  CrmAgentState,
36
36
  )
37
37
  from .protocol import ParsedAction
38
+ from .tools import DEFAULT_TOOLS
38
39
 
39
40
  # Type alias for JSON-serializable dictionaries
40
41
  JsonDict = dict[str, Any]
@@ -105,7 +106,7 @@ class CrmAgentEnv(EnvClient[CrmAgentAction, CrmAgentObservation, CrmAgentState])
105
106
  raise ValueError(
106
107
  "API key is required. Pass api_key parameter or set "
107
108
  "CRM_AGENT_API_KEY environment variable. "
108
- "Get your API key by contacting hello@construct-labs.com"
109
+ "Contact hello@construct-labs.com to obtain an API key."
109
110
  )
110
111
 
111
112
  self._api_key = resolved_api_key
@@ -162,7 +163,7 @@ class CrmAgentEnv(EnvClient[CrmAgentAction, CrmAgentObservation, CrmAgentState])
162
163
  if "401" in error_msg or "403" in error_msg or "4001" in error_msg:
163
164
  raise ConnectionError(
164
165
  "Authentication failed. Please verify your API key. "
165
- "Get a valid key at https://construct-labs.com/api-keys"
166
+ "Contact hello@construct-labs.com if you need assistance."
166
167
  ) from e
167
168
  raise ConnectionError(f"Failed to connect to {self._ws_url}: {e}") from e
168
169
  finally:
@@ -194,12 +195,6 @@ class CrmAgentEnv(EnvClient[CrmAgentAction, CrmAgentObservation, CrmAgentState])
194
195
  """Parse server response into CrmAgentState."""
195
196
  return CrmAgentState.model_validate(payload)
196
197
 
197
- def _reset_payload(self, seed: int | None = None) -> JsonDict:
198
- """Create payload for reset request."""
199
- if seed is not None:
200
- return {"seed": seed}
201
- return {}
202
-
203
198
  # =========================================================================
204
199
  # Extensible Properties - Override these in subclasses
205
200
  # =========================================================================
@@ -270,11 +265,12 @@ IMPORTANT: Output ONLY a tool_call, no other text."""
270
265
  def tools(self) -> list[JsonDict]:
271
266
  """Tool definitions for the CRM environment.
272
267
 
273
- Override this property in a subclass to customize available tools.
274
- You can filter, extend, or replace the default tool set.
268
+ Returns tool definitions formatted by `format_tools()`. Override
269
+ `format_tools()` to transform the tool schema for different providers
270
+ (e.g., Anthropic, Google).
275
271
 
276
272
  Returns:
277
- List of tool definitions in OpenAI function calling format.
273
+ List of tool definitions (OpenAI format by default).
278
274
 
279
275
  Example:
280
276
  >>> class ReadOnlyAgent(CrmAgentEnv):
@@ -285,7 +281,34 @@ IMPORTANT: Output ONLY a tool_call, no other text."""
285
281
  ... return [t for t in self._default_tools()
286
282
  ... if any(op in t['function']['name'] for op in read_ops)]
287
283
  """
288
- return self._default_tools()
284
+ return self.format_tools(self._default_tools())
285
+
286
+ def format_tools(self, tools: list[JsonDict]) -> list[JsonDict]:
287
+ """Format tool definitions for the target LLM provider.
288
+
289
+ Override this method to transform tool schemas for different providers.
290
+ The default implementation returns OpenAI-compatible format unchanged.
291
+
292
+ Args:
293
+ tools: List of tool definitions in OpenAI format.
294
+
295
+ Returns:
296
+ Formatted tool definitions for your target provider.
297
+
298
+ Example (Anthropic format):
299
+ >>> class AnthropicCrmAgent(CrmAgentEnv):
300
+ ... def format_tools(self, tools):
301
+ ... # Convert OpenAI format to Anthropic format
302
+ ... return [
303
+ ... {
304
+ ... "name": t["function"]["name"],
305
+ ... "description": t["function"]["description"],
306
+ ... "input_schema": t["function"]["parameters"],
307
+ ... }
308
+ ... for t in tools
309
+ ... ]
310
+ """
311
+ return tools
289
312
 
290
313
  def _default_tools(self) -> list[JsonDict]:
291
314
  """Return the default tool definitions.
@@ -293,548 +316,9 @@ IMPORTANT: Output ONLY a tool_call, no other text."""
293
316
  Subclasses can call this to get all default tools and filter/extend them.
294
317
 
295
318
  Returns:
296
- Complete list of CRM tool definitions.
319
+ Complete list of CRM tool definitions in OpenAI format.
297
320
  """
298
- return [
299
- # =================================================================
300
- # Company Tools
301
- # =================================================================
302
- {
303
- "type": "function",
304
- "function": {
305
- "name": "list_companies",
306
- "description": "List all companies in the CRM",
307
- "parameters": {
308
- "type": "object",
309
- "properties": {
310
- "limit": {
311
- "type": "integer",
312
- "default": 60,
313
- "description": "Maximum number of companies to return (max 200)",
314
- },
315
- "starting_after": {
316
- "type": "string",
317
- "description": "Cursor for pagination - returns objects after this ID",
318
- },
319
- "ending_before": {
320
- "type": "string",
321
- "description": "Cursor for pagination - returns objects before this ID",
322
- },
323
- "order_by": {
324
- "type": "string",
325
- "description": "Order by: field_name[ASC|DESC]",
326
- },
327
- "filter": {
328
- "type": "string",
329
- "description": "Filter: field[eq|gt|lt|contains]:value",
330
- },
331
- "depth": {
332
- "type": "integer",
333
- "default": 1,
334
- "description": "Relation depth: 0=primary only, 1=include relations",
335
- },
336
- },
337
- "required": [],
338
- },
339
- },
340
- },
341
- {
342
- "type": "function",
343
- "function": {
344
- "name": "get_company",
345
- "description": "Get details of a specific company",
346
- "parameters": {
347
- "type": "object",
348
- "properties": {
349
- "record_id": {
350
- "type": "string",
351
- "description": "ID of the company to retrieve",
352
- },
353
- "depth": {
354
- "type": "integer",
355
- "default": 1,
356
- "description": "Relation depth: 0=primary only, 1=include relations",
357
- },
358
- },
359
- "required": ["record_id"],
360
- },
361
- },
362
- },
363
- {
364
- "type": "function",
365
- "function": {
366
- "name": "create_company",
367
- "description": "Create a new company in the CRM",
368
- "parameters": {
369
- "type": "object",
370
- "properties": {
371
- "company_name": {
372
- "type": "string",
373
- "description": "Name of the company",
374
- },
375
- "company_domain": {
376
- "type": "string",
377
- "description": "Domain/website of the company",
378
- },
379
- "company_address": {
380
- "type": "string",
381
- "description": "Address of the company",
382
- },
383
- "company_employees": {
384
- "type": "integer",
385
- "description": "Number of employees",
386
- },
387
- },
388
- "required": ["company_name"],
389
- },
390
- },
391
- },
392
- {
393
- "type": "function",
394
- "function": {
395
- "name": "update_company",
396
- "description": "Update an existing company",
397
- "parameters": {
398
- "type": "object",
399
- "properties": {
400
- "record_id": {
401
- "type": "string",
402
- "description": "ID of the company to update",
403
- },
404
- "company_name": {"type": "string"},
405
- "company_domain": {"type": "string"},
406
- "company_address": {"type": "string"},
407
- "company_employees": {"type": "integer"},
408
- },
409
- "required": ["record_id"],
410
- },
411
- },
412
- },
413
- {
414
- "type": "function",
415
- "function": {
416
- "name": "delete_company",
417
- "description": "Delete a company from the CRM",
418
- "parameters": {
419
- "type": "object",
420
- "properties": {
421
- "record_id": {
422
- "type": "string",
423
- "description": "ID of the company to delete",
424
- },
425
- },
426
- "required": ["record_id"],
427
- },
428
- },
429
- },
430
- # =================================================================
431
- # Person/Contact Tools
432
- # =================================================================
433
- {
434
- "type": "function",
435
- "function": {
436
- "name": "list_people",
437
- "description": "List all contacts/people in the CRM",
438
- "parameters": {
439
- "type": "object",
440
- "properties": {
441
- "limit": {
442
- "type": "integer",
443
- "default": 60,
444
- "description": "Maximum number of contacts to return (max 200)",
445
- },
446
- "starting_after": {"type": "string"},
447
- "ending_before": {"type": "string"},
448
- "order_by": {"type": "string"},
449
- "filter": {"type": "string"},
450
- "depth": {"type": "integer", "default": 1},
451
- },
452
- "required": [],
453
- },
454
- },
455
- },
456
- {
457
- "type": "function",
458
- "function": {
459
- "name": "get_person",
460
- "description": "Get details of a specific contact",
461
- "parameters": {
462
- "type": "object",
463
- "properties": {
464
- "record_id": {
465
- "type": "string",
466
- "description": "ID of the contact to retrieve",
467
- },
468
- "depth": {"type": "integer", "default": 1},
469
- },
470
- "required": ["record_id"],
471
- },
472
- },
473
- },
474
- {
475
- "type": "function",
476
- "function": {
477
- "name": "create_person",
478
- "description": "Create a new contact/person in the CRM",
479
- "parameters": {
480
- "type": "object",
481
- "properties": {
482
- "person_first_name": {
483
- "type": "string",
484
- "description": "First name",
485
- },
486
- "person_last_name": {
487
- "type": "string",
488
- "description": "Last name",
489
- },
490
- "person_email": {
491
- "type": "string",
492
- "description": "Email address",
493
- },
494
- "person_phone": {
495
- "type": "string",
496
- "description": "Phone number",
497
- },
498
- "person_company_id": {
499
- "type": "string",
500
- "description": "ID of associated company",
501
- },
502
- "person_job_title": {
503
- "type": "string",
504
- "description": "Job title",
505
- },
506
- },
507
- "required": ["person_first_name", "person_last_name"],
508
- },
509
- },
510
- },
511
- {
512
- "type": "function",
513
- "function": {
514
- "name": "update_person",
515
- "description": "Update an existing contact",
516
- "parameters": {
517
- "type": "object",
518
- "properties": {
519
- "record_id": {
520
- "type": "string",
521
- "description": "ID of the contact to update",
522
- },
523
- "person_first_name": {"type": "string"},
524
- "person_last_name": {"type": "string"},
525
- "person_email": {"type": "string"},
526
- "person_phone": {"type": "string"},
527
- "person_job_title": {"type": "string"},
528
- },
529
- "required": ["record_id"],
530
- },
531
- },
532
- },
533
- {
534
- "type": "function",
535
- "function": {
536
- "name": "delete_person",
537
- "description": "Delete a contact from the CRM",
538
- "parameters": {
539
- "type": "object",
540
- "properties": {
541
- "record_id": {
542
- "type": "string",
543
- "description": "ID of the contact to delete",
544
- },
545
- },
546
- "required": ["record_id"],
547
- },
548
- },
549
- },
550
- # =================================================================
551
- # Opportunity Tools
552
- # =================================================================
553
- {
554
- "type": "function",
555
- "function": {
556
- "name": "list_opportunities",
557
- "description": "List all opportunities/deals in the CRM",
558
- "parameters": {
559
- "type": "object",
560
- "properties": {
561
- "limit": {
562
- "type": "integer",
563
- "default": 60,
564
- "description": "Maximum number to return (max 200)",
565
- },
566
- "starting_after": {"type": "string"},
567
- "ending_before": {"type": "string"},
568
- "order_by": {"type": "string"},
569
- "filter": {"type": "string"},
570
- "depth": {"type": "integer", "default": 1},
571
- },
572
- "required": [],
573
- },
574
- },
575
- },
576
- {
577
- "type": "function",
578
- "function": {
579
- "name": "get_opportunity",
580
- "description": "Get details of a specific opportunity",
581
- "parameters": {
582
- "type": "object",
583
- "properties": {
584
- "record_id": {
585
- "type": "string",
586
- "description": "ID of the opportunity",
587
- },
588
- "depth": {"type": "integer", "default": 1},
589
- },
590
- "required": ["record_id"],
591
- },
592
- },
593
- },
594
- {
595
- "type": "function",
596
- "function": {
597
- "name": "create_opportunity",
598
- "description": "Create a new opportunity/deal",
599
- "parameters": {
600
- "type": "object",
601
- "properties": {
602
- "opportunity_name": {
603
- "type": "string",
604
- "description": "Name of the opportunity",
605
- },
606
- "opportunity_amount": {
607
- "type": "number",
608
- "description": "Deal value",
609
- },
610
- "opportunity_stage": {
611
- "type": "string",
612
- "enum": ["NEW", "MEETING", "PROPOSAL", "WON", "LOST"],
613
- "description": "Sales stage",
614
- },
615
- "opportunity_close_date": {
616
- "type": "string",
617
- "description": "Expected close date (ISO format)",
618
- },
619
- "opportunity_company_id": {
620
- "type": "string",
621
- "description": "Associated company ID",
622
- },
623
- "opportunity_person_id": {
624
- "type": "string",
625
- "description": "Point of contact ID",
626
- },
627
- },
628
- "required": ["opportunity_name"],
629
- },
630
- },
631
- },
632
- {
633
- "type": "function",
634
- "function": {
635
- "name": "update_opportunity",
636
- "description": "Update an existing opportunity",
637
- "parameters": {
638
- "type": "object",
639
- "properties": {
640
- "record_id": {
641
- "type": "string",
642
- "description": "ID of the opportunity to update",
643
- },
644
- "opportunity_name": {"type": "string"},
645
- "opportunity_amount": {"type": "number"},
646
- "opportunity_stage": {
647
- "type": "string",
648
- "enum": ["NEW", "MEETING", "PROPOSAL", "WON", "LOST"],
649
- },
650
- "opportunity_close_date": {"type": "string"},
651
- },
652
- "required": ["record_id"],
653
- },
654
- },
655
- },
656
- {
657
- "type": "function",
658
- "function": {
659
- "name": "delete_opportunity",
660
- "description": "Delete an opportunity",
661
- "parameters": {
662
- "type": "object",
663
- "properties": {
664
- "record_id": {
665
- "type": "string",
666
- "description": "ID of the opportunity to delete",
667
- },
668
- },
669
- "required": ["record_id"],
670
- },
671
- },
672
- },
673
- # =================================================================
674
- # Note Tools
675
- # =================================================================
676
- {
677
- "type": "function",
678
- "function": {
679
- "name": "list_notes",
680
- "description": "List all notes in the CRM",
681
- "parameters": {
682
- "type": "object",
683
- "properties": {
684
- "limit": {
685
- "type": "integer",
686
- "default": 10,
687
- "description": "Maximum number of notes to return",
688
- },
689
- },
690
- "required": [],
691
- },
692
- },
693
- },
694
- {
695
- "type": "function",
696
- "function": {
697
- "name": "create_note",
698
- "description": "Create a note attached to a record",
699
- "parameters": {
700
- "type": "object",
701
- "properties": {
702
- "note_body": {
703
- "type": "string",
704
- "description": "Content of the note",
705
- },
706
- "note_target_id": {
707
- "type": "string",
708
- "description": "ID of record to attach note to",
709
- },
710
- "note_target_type": {
711
- "type": "string",
712
- "enum": ["company", "person", "opportunity"],
713
- "description": "Type of record",
714
- },
715
- },
716
- "required": ["note_body"],
717
- },
718
- },
719
- },
720
- # =================================================================
721
- # Task Tools
722
- # =================================================================
723
- {
724
- "type": "function",
725
- "function": {
726
- "name": "list_tasks",
727
- "description": "List all tasks in the CRM",
728
- "parameters": {
729
- "type": "object",
730
- "properties": {
731
- "limit": {
732
- "type": "integer",
733
- "default": 10,
734
- "description": "Maximum number of tasks to return",
735
- },
736
- },
737
- "required": [],
738
- },
739
- },
740
- },
741
- {
742
- "type": "function",
743
- "function": {
744
- "name": "create_task",
745
- "description": "Create a task, optionally linked to a record",
746
- "parameters": {
747
- "type": "object",
748
- "properties": {
749
- "task_title": {
750
- "type": "string",
751
- "description": "Title of the task",
752
- },
753
- "task_body": {
754
- "type": "string",
755
- "description": "Description",
756
- },
757
- "task_due_date": {
758
- "type": "string",
759
- "description": "Due date (ISO format)",
760
- },
761
- "task_status": {
762
- "type": "string",
763
- "enum": ["TODO", "IN_PROGRESS", "DONE"],
764
- "description": "Status",
765
- },
766
- "task_target_id": {
767
- "type": "string",
768
- "description": "ID of record to link task to",
769
- },
770
- "task_target_type": {
771
- "type": "string",
772
- "enum": ["company", "person", "opportunity"],
773
- "description": "Type of record",
774
- },
775
- },
776
- "required": ["task_title"],
777
- },
778
- },
779
- },
780
- {
781
- "type": "function",
782
- "function": {
783
- "name": "update_task",
784
- "description": "Update an existing task",
785
- "parameters": {
786
- "type": "object",
787
- "properties": {
788
- "record_id": {
789
- "type": "string",
790
- "description": "ID of the task to update",
791
- },
792
- "task_title": {"type": "string"},
793
- "task_body": {"type": "string"},
794
- "task_due_date": {"type": "string"},
795
- },
796
- "required": ["record_id"],
797
- },
798
- },
799
- },
800
- {
801
- "type": "function",
802
- "function": {
803
- "name": "complete_task",
804
- "description": "Mark a task as complete",
805
- "parameters": {
806
- "type": "object",
807
- "properties": {
808
- "record_id": {
809
- "type": "string",
810
- "description": "ID of the task to complete",
811
- },
812
- },
813
- "required": ["record_id"],
814
- },
815
- },
816
- },
817
- # =================================================================
818
- # Submit Answer Tool
819
- # =================================================================
820
- {
821
- "type": "function",
822
- "function": {
823
- "name": "submit_answer",
824
- "description": "Submit final answer and end the session",
825
- "parameters": {
826
- "type": "object",
827
- "properties": {
828
- "answer": {
829
- "type": "string",
830
- "description": "The final answer based on CRM data",
831
- },
832
- },
833
- "required": ["answer"],
834
- },
835
- },
836
- },
837
- ]
321
+ return list(DEFAULT_TOOLS)
838
322
 
839
323
  # =========================================================================
840
324
  # Tool Parsing and Observation Formatting
@@ -967,6 +451,32 @@ IMPORTANT: Output ONLY a tool_call, no other text."""
967
451
  if field in arguments and arguments[field] is not None:
968
452
  action_kwargs[field] = arguments[field]
969
453
 
454
+ # Convert generic note_target_id/type to specific fields
455
+ # Tool schema uses: note_target_id + note_target_type
456
+ # Server expects: note_target_person_id, note_target_company_id, etc.
457
+ note_target_id = action_kwargs.pop("note_target_id", None)
458
+ note_target_type = action_kwargs.pop("note_target_type", None)
459
+ if note_target_id and note_target_type:
460
+ target_type = str(note_target_type).lower()
461
+ if target_type == "person":
462
+ action_kwargs["note_target_person_id"] = note_target_id
463
+ elif target_type == "company":
464
+ action_kwargs["note_target_company_id"] = note_target_id
465
+ elif target_type == "opportunity":
466
+ action_kwargs["note_target_opportunity_id"] = note_target_id
467
+
468
+ # Same conversion for tasks
469
+ task_target_id = action_kwargs.pop("task_target_id", None)
470
+ task_target_type = action_kwargs.pop("task_target_type", None)
471
+ if task_target_id and task_target_type:
472
+ target_type = str(task_target_type).lower()
473
+ if target_type == "person":
474
+ action_kwargs["task_target_person_id"] = task_target_id
475
+ elif target_type == "company":
476
+ action_kwargs["task_target_company_id"] = task_target_id
477
+ elif target_type == "opportunity":
478
+ action_kwargs["task_target_opportunity_id"] = task_target_id
479
+
970
480
  try:
971
481
  action = CrmAgentAction(**action_kwargs)
972
482
  return ParsedAction(action=action, is_valid=True)