edsl 0.1.59__py3-none-any.whl → 0.1.61__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.
edsl/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.59"
1
+ __version__ = "0.1.61"
edsl/agents/agent.py CHANGED
@@ -58,6 +58,7 @@ from typing import (
58
58
  TypeVar,
59
59
  Type,
60
60
  )
61
+ from collections.abc import MutableMapping
61
62
 
62
63
  if TYPE_CHECKING:
63
64
  from ..caching import Cache
@@ -106,24 +107,69 @@ class DirectAnswerMethod(Protocol):
106
107
  def __call__(self, self_: A, question: QuestionBase, scenario: Scenario) -> Any: ...
107
108
 
108
109
 
109
- class AgentTraits(Scenario):
110
- """A class representing the traits of an agent.
111
-
112
- AgentTraits inherits from Scenario to provide a structured way to store and
113
- access agent characteristics. This allows agent traits to be handled consistently
114
- throughout the EDSL framework, including for presentation in prompts.
115
-
116
- Attributes:
117
- data: Dictionary containing the agent's traits as key-value pairs
110
+ class AgentTraits(MutableMapping):
118
111
  """
112
+ A proxy around the real trait dict.
113
+ All writes go through _guard(), which delegates to the parent Agent
114
+ to enforce whatever rules it wants (no dynamic-traits override, etc.).
115
+ """
116
+ def __init__(self, data: dict, parent: "Agent"):
117
+ self._store = Scenario(data)
118
+ self._parent = parent
119
+
120
+ # ---- internal helper -------------------------------------------------
121
+ def _guard(self):
122
+ self._parent._check_before_modifying_traits() # raise if not allowed
123
+
124
+ # ---- MutableMapping interface ----------------------------------------
125
+ def __getitem__(self, key):
126
+ return self._store[key]
127
+
128
+ def __setitem__(self, key, value):
129
+ self._guard()
130
+ self._store[key] = value
131
+
132
+ def __delitem__(self, key):
133
+ self._guard()
134
+ del self._store[key]
119
135
 
136
+ def __iter__(self):
137
+ return iter(self._store)
138
+
139
+ def __len__(self):
140
+ return len(self._store)
141
+
142
+ # nice repr for debugging
120
143
  def __repr__(self):
121
- """Generate a string representation of the agent traits.
122
-
123
- Returns:
124
- str: String representation of the agent traits dictionary
144
+ return dict(self._store).__repr__()
145
+
146
+ # allow dict | union syntax to work like normal dicts
147
+ def __or__(self, other):
148
+ """Return a regular dictionary that is the union of this mapping and *other*.
149
+
150
+ Mirrors the behaviour of ``dict.__or__`` introduced in Python 3.9 so that
151
+ ``AgentTraits | AgentTraits`` (or ``|`` with any mapping) behaves the
152
+ same as with plain ``dict`` objects. The result is a **new** *dict*
153
+ (not an ``AgentTraits`` instance) which matches the semantics of the
154
+ built-in type.
125
155
  """
126
- return f"{self.data}"
156
+ if isinstance(other, MutableMapping):
157
+ return {**dict(self), **dict(other)}
158
+ return NotImplemented
159
+
160
+ # support reversed operand order (e.g. ``dict | AgentTraits``)
161
+ def __ror__(self, other):
162
+ if isinstance(other, MutableMapping):
163
+ return {**dict(other), **dict(self)}
164
+ return NotImplemented
165
+
166
+ # in-place union ``|=`` – delegates to __setitem__ so guards still fire
167
+ def __ior__(self, other):
168
+ if isinstance(other, MutableMapping):
169
+ for k, v in other.items():
170
+ self[k] = v # will trigger _guard()
171
+ return self
172
+ return NotImplemented
127
173
 
128
174
 
129
175
  class Agent(Base):
@@ -267,7 +313,7 @@ class Agent(Base):
267
313
  codebook: Dictionary mapping trait keys to descriptions
268
314
  """
269
315
  self.name = name
270
- self._traits = AgentTraits(traits or dict())
316
+ self._traits = AgentTraits(traits or {}, parent=self)
271
317
  self.codebook = codebook or dict()
272
318
 
273
319
  def _initialize_instruction(self, instruction) -> None:
@@ -711,7 +757,7 @@ class Agent(Base):
711
757
  return self.dynamic_traits_function()
712
758
  else:
713
759
  # Return the stored traits
714
- return dict(self._traits)
760
+ return self._traits
715
761
 
716
762
  @contextmanager
717
763
  def modify_traits_context(self):
@@ -719,7 +765,8 @@ class Agent(Base):
719
765
  try:
720
766
  yield
721
767
  finally:
722
- self._traits = AgentTraits(self._traits)
768
+ # re-wrap the possibly mutated mapping so future writes remain guarded
769
+ self._traits = AgentTraits(dict(self._traits), parent=self)
723
770
 
724
771
  def _check_before_modifying_traits(self):
725
772
  """Check before modifying traits."""
@@ -732,6 +779,7 @@ class Agent(Base):
732
779
  @traits.setter
733
780
  def traits(self, traits: dict[str, str]):
734
781
  with self.modify_traits_context():
782
+ # store raw dict temporarily – it will be wrapped by the context manager
735
783
  self._traits = traits
736
784
 
737
785
  def rename(
edsl/agents/agent_list.py CHANGED
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import csv
6
6
  import sys
7
7
  import random
8
+ import warnings
8
9
  import logging
9
10
  from collections import defaultdict
10
11
  from itertools import product
@@ -38,6 +39,9 @@ class EmptyAgentList:
38
39
  def __repr__(self):
39
40
  return "Empty AgentList"
40
41
 
42
+ def __len__(self):
43
+ return 0
44
+
41
45
 
42
46
  # ResultsExportMixin,
43
47
  class AgentList(UserList, Base, AgentListOperationsMixin):
@@ -49,7 +53,7 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
49
53
 
50
54
  >>> AgentList.example().to_scenario_list().drop('age')
51
55
  ScenarioList([Scenario({'hair': 'brown', 'height': 5.5}), Scenario({'hair': 'brown', 'height': 5.5})])
52
-
56
+
53
57
  >>> AgentList.example().to_dataset()
54
58
  Dataset([{'age': [22, 22]}, {'hair': ['brown', 'brown']}, {'height': [5.5, 5.5]}])
55
59
 
@@ -65,7 +69,11 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
65
69
  "https://docs.expectedparrot.com/en/latest/agents.html#agentlist-class"
66
70
  )
67
71
 
68
- def __init__(self, data: Optional[list["Agent"]] = None, codebook: Optional[dict[str, str]] = None):
72
+ def __init__(
73
+ self,
74
+ data: Optional[list["Agent"]] = None,
75
+ codebook: Optional[dict[str, str]] = None,
76
+ ):
69
77
  """Initialize a new AgentList.
70
78
 
71
79
  >>> from edsl import Agent
@@ -79,14 +87,14 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
79
87
 
80
88
  Args:
81
89
  data: A list of Agent objects. If None, creates an empty AgentList.
82
- codebook: Optional dictionary mapping trait names to descriptions.
90
+ codebook: Optional dictionary mapping trait names to descriptions.
83
91
  If provided, will be applied to all agents in the list.
84
92
  """
85
93
  if data is not None:
86
94
  super().__init__(data)
87
95
  else:
88
96
  super().__init__()
89
-
97
+
90
98
  # Apply codebook to all agents if provided
91
99
  if codebook is not None:
92
100
  self.set_codebook(codebook)
@@ -99,10 +107,12 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
99
107
  """
100
108
  for agent in self.data:
101
109
  agent.instruction = instruction
102
-
110
+
103
111
  return None
104
-
105
- def set_traits_presentation_template(self, traits_presentation_template: str) -> None:
112
+
113
+ def set_traits_presentation_template(
114
+ self, traits_presentation_template: str
115
+ ) -> None:
106
116
  """Set the traits presentation template for all agents in the list.
107
117
 
108
118
  Args:
@@ -110,7 +120,7 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
110
120
  """
111
121
  for agent in self.data:
112
122
  agent.traits_presentation_template = traits_presentation_template
113
-
123
+
114
124
  return None
115
125
 
116
126
  def shuffle(self, seed: Optional[str] = None) -> AgentList:
@@ -141,7 +151,7 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
141
151
  if seed:
142
152
  random.seed(seed)
143
153
  return AgentList(random.sample(self.data, n))
144
-
154
+
145
155
  def drop(self, field_name: str) -> AgentList:
146
156
  """Drop a field from the AgentList.
147
157
 
@@ -166,6 +176,34 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
166
176
  """
167
177
  return AgentList([a.duplicate() for a in self.data])
168
178
 
179
+ def collapse(self, warn_about_none_name: bool = True) -> "AgentList":
180
+ """All agents with the same name have their traits combined.
181
+
182
+ >>> al = AgentList([Agent(name = 'steve'), Agent(name = 'roxanne')])
183
+ >>> al.collapse()
184
+ AgentList([Agent(name = \"\"\"steve\"\"\", traits = {}), Agent(name = \"\"\"roxanne\"\"\", traits = {})])
185
+ >>> al = AgentList([Agent(name = 'steve', traits = {'age': 22}), Agent(name = 'steve', traits = {'hair': 'brown'})])
186
+ >>> al.collapse()
187
+ AgentList([Agent(name = \"\"\"steve\"\"\", traits = {'age': 22, 'hair': 'brown'})])
188
+ >>> AgentList.example().collapse(warn_about_none_name = False)
189
+ AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})])
190
+ """
191
+ new_agent_list = AgentList()
192
+ warned_about_none_name = False
193
+ d = {}
194
+ for agent in self:
195
+ if agent.name is None:
196
+ if not warned_about_none_name and warn_about_none_name:
197
+ warnings.warn("Agent has no name, so it will be ignored.")
198
+ warned_about_none_name = True
199
+ if agent.name not in d:
200
+ d[agent.name] = agent
201
+ else:
202
+ d[agent.name].traits.update(agent.traits)
203
+ for name, agent in d.items():
204
+ new_agent_list.append(agent)
205
+ return new_agent_list
206
+
169
207
  def rename(self, old_name: str, new_name: str) -> AgentList:
170
208
  """Rename a trait across all agents in the list.
171
209
 
@@ -212,7 +250,20 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
212
250
  traits_to_select = list(traits)
213
251
 
214
252
  return AgentList([agent.select(*traits_to_select) for agent in self.data])
215
-
253
+
254
+ # @classmethod
255
+ # def from_results(self, results: "Results") -> "AgentList":
256
+ # """Create an AgentList from a Results object.
257
+
258
+ # >>> from edsl import Results
259
+ # >>> results = Results.example()
260
+ # >>> al = AgentList.from_results(results)
261
+ # >>> al
262
+ # AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})])
263
+ # """
264
+ # al = results.agent_list
265
+ # return al
266
+
216
267
  def filter(self, expression: str) -> AgentList:
217
268
  """Filter agents based on a boolean expression.
218
269
 
@@ -229,11 +280,23 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
229
280
  ... Agent(traits = {'a': 1, 'b': 2})])
230
281
  >>> al.filter("b == 2")
231
282
  AgentList([Agent(traits = {'a': 1, 'b': 2})])
283
+ >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}, name = 'steve'),
284
+ ... Agent(traits = {'a': 1, 'b': 2}, name = 'roxanne')])
285
+ >>> len(al.filter("name == 'steve'"))
286
+ 1
287
+ >>> len(al.filter("name == 'roxanne'"))
288
+ 1
289
+ >>> len(al.filter("name == 'steve' and a == 1"))
290
+ 1
291
+ >>> len(al.filter("name == 'steve' and a == 2"))
292
+ 0
293
+ >>> len(al.filter("name == 'steve' and a == 1 and b == 2"))
294
+ 0
232
295
  """
233
296
 
234
297
  def create_evaluator(agent: "Agent"):
235
298
  """Create an evaluator for the given agent."""
236
- return EvalWithCompoundTypes(names=agent.traits)
299
+ return EvalWithCompoundTypes(names={**agent.traits, "name": agent.name})
237
300
 
238
301
  try:
239
302
  new_data = [
@@ -269,7 +332,12 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
269
332
  return list(d.keys())
270
333
 
271
334
  @classmethod
272
- def from_csv(cls, file_path: str, name_field: Optional[str] = None, codebook: Optional[dict[str, str]] = None):
335
+ def from_csv(
336
+ cls,
337
+ file_path: str,
338
+ name_field: Optional[str] = None,
339
+ codebook: Optional[dict[str, str]] = None,
340
+ ):
273
341
  """Load AgentList from a CSV file.
274
342
 
275
343
  >>> import csv
@@ -306,7 +374,9 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
306
374
  name_field = "name"
307
375
  if name_field is not None:
308
376
  agent_name = row.pop(name_field)
309
- agent_list.append(Agent(traits=row, name=agent_name, codebook=codebook))
377
+ agent_list.append(
378
+ Agent(traits=row, name=agent_name, codebook=codebook)
379
+ )
310
380
  else:
311
381
  agent_list.append(Agent(traits=row, codebook=codebook))
312
382
  return cls(agent_list)
@@ -420,19 +490,19 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
420
490
  agent.to_dict(add_edsl_version=add_edsl_version) for agent in data
421
491
  ]
422
492
  }
423
-
493
+
424
494
  # Add codebook if all agents have the same codebook
425
495
  if len(self.data) > 0:
426
496
  # Get the first agent's codebook
427
497
  first_codebook = self.data[0].codebook
428
-
498
+
429
499
  # Check if all agents have the same codebook
430
500
  all_same = all(agent.codebook == first_codebook for agent in self.data)
431
-
501
+
432
502
  # Only include codebook if it's non-empty and consistent across all agents
433
503
  if all_same and first_codebook:
434
504
  d["codebook"] = first_codebook
435
-
505
+
436
506
  if add_edsl_version:
437
507
  from edsl import __version__
438
508
 
@@ -453,7 +523,7 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
453
523
  return {
454
524
  "agents": len(self),
455
525
  }
456
-
526
+
457
527
  def set_codebook(self, codebook: dict[str, str]) -> AgentList:
458
528
  """Set the codebook for the AgentList.
459
529
 
@@ -472,7 +542,6 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
472
542
 
473
543
  return self
474
544
 
475
-
476
545
  def table(
477
546
  self,
478
547
  *fields,
@@ -525,7 +594,11 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
525
594
  data[trait_key].append(agent.traits.get(trait_key, None))
526
595
  if not traits_only:
527
596
  data["agent_parameters"].append(
528
- {"instruction": agent.instruction, "agent_name": agent.name, "traits_presentation_template": agent.traits_presentation_template}
597
+ {
598
+ "instruction": agent.instruction,
599
+ "agent_name": agent.name,
600
+ "traits_presentation_template": agent.traits_presentation_template,
601
+ }
529
602
  )
530
603
  return Dataset([{key: entry} for key, entry in data.items()])
531
604
 
@@ -551,21 +624,23 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
551
624
 
552
625
  agents = [Agent.from_dict(agent_dict) for agent_dict in data["agent_list"]]
553
626
  agent_list = cls(agents)
554
-
627
+
555
628
  # Apply codebook if present in the dictionary
556
629
  if "codebook" in data and data["codebook"]:
557
630
  agent_list.set_codebook(data["codebook"])
558
-
631
+
559
632
  return agent_list
560
633
 
561
634
  @classmethod
562
- def example(cls, randomize: bool = False, codebook: Optional[dict[str, str]] = None) -> AgentList:
635
+ def example(
636
+ cls, randomize: bool = False, codebook: Optional[dict[str, str]] = None
637
+ ) -> AgentList:
563
638
  """
564
639
  Returns an example AgentList instance.
565
640
 
566
641
  :param randomize: If True, uses Agent's randomize method.
567
642
  :param codebook: Optional dictionary mapping trait names to descriptions.
568
-
643
+
569
644
  >>> al = AgentList.example()
570
645
  >>> al
571
646
  AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})])
@@ -576,14 +651,19 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
576
651
  from .agent import Agent
577
652
 
578
653
  agent_list = cls([Agent.example(randomize), Agent.example(randomize)])
579
-
654
+
580
655
  if codebook:
581
656
  agent_list.set_codebook(codebook)
582
-
657
+
583
658
  return agent_list
584
659
 
585
660
  @classmethod
586
- def from_list(self, trait_name: str, values: List[Any], codebook: Optional[dict[str, str]] = None) -> "AgentList":
661
+ def from_list(
662
+ self,
663
+ trait_name: str,
664
+ values: List[Any],
665
+ codebook: Optional[dict[str, str]] = None,
666
+ ) -> "AgentList":
587
667
  """Create an AgentList from a list of values.
588
668
 
589
669
  :param trait_name: The name of the trait.
@@ -599,10 +679,10 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
599
679
  from .agent import Agent
600
680
 
601
681
  agent_list = AgentList([Agent({trait_name: value}) for value in values])
602
-
682
+
603
683
  if codebook:
604
684
  agent_list.set_codebook(codebook)
605
-
685
+
606
686
  return agent_list
607
687
 
608
688
  def __mul__(self, other: AgentList) -> AgentList:
@@ -649,7 +729,7 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
649
729
  AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})])
650
730
  """
651
731
  from .agent import Agent # Use direct relative import
652
-
732
+
653
733
  agents = []
654
734
  for scenario in scenario_list:
655
735
  # Simple implementation to handle the basic test case
@@ -659,13 +739,17 @@ class AgentList(UserList, Base, AgentListOperationsMixin):
659
739
 
660
740
  # Add a debug check to verify we've processed the scenarios correctly
661
741
  if len(agents) != len(scenario_list):
662
- raise ValueError(f"Expected {len(scenario_list)} agents, but created {len(agents)}")
742
+ raise ValueError(
743
+ f"Expected {len(scenario_list)} agents, but created {len(agents)}"
744
+ )
663
745
 
664
746
  return cls(agents)
665
747
 
666
748
 
667
749
  if __name__ == "__main__":
668
750
  import doctest
669
-
751
+
670
752
  # Just run the standard doctests with verbose flag
671
- doctest.testmod(verbose=True, optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)
753
+ doctest.testmod(
754
+ verbose=True, optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE
755
+ )
edsl/base/base_class.py CHANGED
@@ -146,31 +146,62 @@ class PersistenceMixin:
146
146
  """
147
147
  print(cls.__doc__)
148
148
 
149
+ # def push(
150
+ # self,
151
+ # description: Optional[str] = None,
152
+ # alias: Optional[str] = None,
153
+ # visibility: Optional[str] = "unlisted",
154
+ # expected_parrot_url: Optional[str] = None,
155
+ # ):
156
+ # """Upload this object to the EDSL cooperative platform.
157
+
158
+ # This method serializes the object and posts it to the EDSL coop service,
159
+ # making it accessible to others or for your own use across sessions.
160
+
161
+ # Args:
162
+ # description: Optional text description of the object
163
+ # alias: Optional human-readable identifier for the object
164
+ # visibility: Access level setting ("private", "unlisted", or "public")
165
+ # expected_parrot_url: Optional custom URL for the coop service
166
+
167
+ # Returns:
168
+ # The response from the coop service containing the object's unique identifier
169
+ # """
170
+ # from edsl.coop import Coop
171
+
172
+ # c = Coop(url=expected_parrot_url)
173
+ # return c.create(self, description, alias, visibility)
174
+
149
175
  def push(
150
176
  self,
151
177
  description: Optional[str] = None,
152
178
  alias: Optional[str] = None,
153
179
  visibility: Optional[str] = "unlisted",
154
180
  expected_parrot_url: Optional[str] = None,
155
- ):
156
- """Upload this object to the EDSL cooperative platform.
181
+ ) -> dict:
182
+ """
183
+ Get a signed URL for directly uploading an object to Google Cloud Storage.
157
184
 
158
- This method serializes the object and posts it to the EDSL coop service,
159
- making it accessible to others or for your own use across sessions.
185
+ This method provides a more efficient way to upload objects compared to the push() method,
186
+ especially for large files, by generating a direct signed URL to the storage bucket.
160
187
 
161
188
  Args:
162
- description: Optional text description of the object
163
- alias: Optional human-readable identifier for the object
164
- visibility: Access level setting ("private", "unlisted", or "public")
165
- expected_parrot_url: Optional custom URL for the coop service
189
+ expected_parrot_url (str, optional): Optional custom URL for the coop service
166
190
 
167
191
  Returns:
168
- The response from the coop service containing the object's unique identifier
192
+ dict: A response containing the signed_url for direct upload and optionally a job_id
193
+
194
+ Example:
195
+ >>> from edsl.surveys import Survey
196
+ >>> survey = Survey(...)
197
+ >>> response = survey.push()
198
+ >>> print(f"Upload URL: {response['signed_url']}")
199
+ >>> # Use the signed_url to upload the object directly
169
200
  """
170
201
  from edsl.coop import Coop
171
202
 
172
203
  c = Coop(url=expected_parrot_url)
173
- return c.create(self, description, alias, visibility)
204
+ return c.push(self, description, alias, visibility)
174
205
 
175
206
  def to_yaml(self, add_edsl_version=False, filename: str = None) -> Union[str, None]:
176
207
  """Convert the object to YAML format.
@@ -243,7 +274,7 @@ class PersistenceMixin:
243
274
  return fs.create_link()
244
275
 
245
276
  @classmethod
246
- def pull(
277
+ def old_pull(
247
278
  cls,
248
279
  url_or_uuid: Optional[Union[str, UUID]] = None,
249
280
  ):
@@ -269,6 +300,44 @@ class PersistenceMixin:
269
300
 
270
301
  return coop.get(url_or_uuid, expected_object_type=object_type)
271
302
 
303
+ @classmethod
304
+ def pull(
305
+ cls,
306
+ url_or_uuid: Optional[Union[str, UUID]] = None,
307
+ expected_parrot_url: Optional[str] = None,
308
+ ) -> dict:
309
+ """
310
+ Get a signed URL for directly downloading an object from Google Cloud Storage.
311
+
312
+ This method provides a more efficient way to download objects compared to the old pull() method,
313
+ especially for large files, by generating a direct signed URL to the storage bucket.
314
+
315
+ Args:
316
+ url_or_uuid (Union[str, UUID], optional): Identifier for the object to retrieve.
317
+ Can be one of:
318
+ - UUID string (e.g., "123e4567-e89b-12d3-a456-426614174000")
319
+ - Full URL (e.g., "https://expectedparrot.com/content/123e4567...")
320
+ - Alias URL (e.g., "https://expectedparrot.com/content/username/my-survey")
321
+ expected_parrot_url (str, optional): Optional custom URL for the coop service
322
+
323
+ Returns:
324
+ dict: A response containing the signed_url for direct download
325
+
326
+ Example:
327
+ >>> response = SurveyClass.pull("123e4567-e89b-12d3-a456-426614174000")
328
+ >>> response = SurveyClass.pull("https://expectedparrot.com/content/username/my-survey")
329
+ >>> print(f"Download URL: {response['signed_url']}")
330
+ >>> # Use the signed_url to download the object directly
331
+ """
332
+ from edsl.coop import Coop
333
+ from edsl.coop import ObjectRegistry
334
+
335
+ coop = Coop(url=expected_parrot_url)
336
+
337
+ object_type = ObjectRegistry.get_object_type_by_edsl_class(cls)
338
+
339
+ return coop.pull(url_or_uuid, object_type)
340
+
272
341
  @classmethod
273
342
  def list(
274
343
  cls,
@@ -17,6 +17,7 @@ class EDSLOutput(NamedTuple):
17
17
  answer: Any
18
18
  generated_tokens: str
19
19
  comment: Optional[str] = None
20
+ reasoning_summary: Optional[Any] = None
20
21
 
21
22
 
22
23
  class ModelResponse(NamedTuple):
@@ -49,6 +50,7 @@ class EDSLResultObjectInput(NamedTuple):
49
50
  cache_key: str
50
51
  answer: Any
51
52
  comment: str
53
+ reasoning_summary: Optional[Any] = None
52
54
  validated: bool = False
53
55
  exception_occurred: Exception = None
54
56
  input_tokens: Optional[int] = None
@@ -96,12 +98,15 @@ class Answers(UserDict):
96
98
  answer = response.answer
97
99
  comment = response.comment
98
100
  generated_tokens = response.generated_tokens
101
+ reasoning_summary = response.reasoning_summary
99
102
  # record the answer
100
103
  if generated_tokens:
101
104
  self[question.question_name + "_generated_tokens"] = generated_tokens
102
105
  self[question.question_name] = answer
103
106
  if comment:
104
107
  self[question.question_name + "_comment"] = comment
108
+ if reasoning_summary:
109
+ self[question.question_name + "_reasoning_summary"] = reasoning_summary
105
110
 
106
111
  def replace_missing_answers_with_none(self, survey: "Survey") -> None:
107
112
  """Replace missing answers with None. Answers can be missing if the agent skips a question."""
edsl/base/enums.py CHANGED
@@ -57,6 +57,7 @@ class InferenceServiceType(EnumWithChecks):
57
57
  DEEP_INFRA = "deep_infra"
58
58
  REPLICATE = "replicate"
59
59
  OPENAI = "openai"
60
+ OPENAI_V2 = "openai_v2"
60
61
  GOOGLE = "google"
61
62
  TEST = "test"
62
63
  ANTHROPIC = "anthropic"
@@ -77,6 +78,7 @@ InferenceServiceLiteral = Literal[
77
78
  "deep_infra",
78
79
  "replicate",
79
80
  "openai",
81
+ "openai_v2",
80
82
  "google",
81
83
  "test",
82
84
  "anthropic",
@@ -93,6 +95,7 @@ InferenceServiceLiteral = Literal[
93
95
  available_models_urls = {
94
96
  "anthropic": "https://docs.anthropic.com/en/docs/about-claude/models",
95
97
  "openai": "https://platform.openai.com/docs/models/gp",
98
+ "openai_v2": "https://platform.openai.com/docs/models/gp",
96
99
  "groq": "https://console.groq.com/docs/models",
97
100
  "google": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models",
98
101
  }
@@ -102,6 +105,7 @@ service_to_api_keyname = {
102
105
  InferenceServiceType.DEEP_INFRA.value: "DEEP_INFRA_API_KEY",
103
106
  InferenceServiceType.REPLICATE.value: "TBD",
104
107
  InferenceServiceType.OPENAI.value: "OPENAI_API_KEY",
108
+ InferenceServiceType.OPENAI_V2.value: "OPENAI_API_KEY",
105
109
  InferenceServiceType.GOOGLE.value: "GOOGLE_API_KEY",
106
110
  InferenceServiceType.TEST.value: "TBD",
107
111
  InferenceServiceType.ANTHROPIC.value: "ANTHROPIC_API_KEY",
@@ -135,7 +139,7 @@ class TokenPricing:
135
139
  and self.prompt_token_price == other.prompt_token_price
136
140
  and self.completion_token_price == other.completion_token_price
137
141
  )
138
-
142
+
139
143
  @classmethod
140
144
  def example(cls) -> "TokenPricing":
141
145
  """Return an example TokenPricing object."""
@@ -145,6 +149,7 @@ class TokenPricing:
145
149
  completion_token_price_per_k=0.03,
146
150
  )
147
151
 
152
+
148
153
  pricing = {
149
154
  "dbrx-instruct": TokenPricing(
150
155
  model_name="dbrx-instruct",
@@ -212,4 +217,4 @@ def get_token_pricing(model_name):
212
217
  model_name=model_name,
213
218
  prompt_token_price_per_k=0.0,
214
219
  completion_token_price_per_k=0.0,
215
- )
220
+ )