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 +1 -1
- edsl/agents/agent.py +65 -17
- edsl/agents/agent_list.py +117 -33
- edsl/base/base_class.py +80 -11
- edsl/base/data_transfer_models.py +5 -0
- edsl/base/enums.py +7 -2
- edsl/config/config_class.py +7 -2
- edsl/coop/coop.py +1295 -85
- edsl/coop/coop_prolific_filters.py +171 -0
- edsl/dataset/dataset_operations_mixin.py +2 -2
- edsl/dataset/display/table_display.py +40 -7
- edsl/db_list/sqlite_list.py +102 -3
- edsl/inference_services/services/__init__.py +3 -1
- edsl/inference_services/services/open_ai_service_v2.py +243 -0
- edsl/jobs/data_structures.py +48 -30
- edsl/jobs/jobs.py +73 -2
- edsl/jobs/remote_inference.py +49 -15
- edsl/key_management/key_lookup_builder.py +25 -3
- edsl/language_models/language_model.py +2 -1
- edsl/language_models/raw_response_handler.py +126 -7
- edsl/questions/loop_processor.py +289 -10
- edsl/questions/templates/dict/answering_instructions.jinja +0 -1
- edsl/results/result.py +37 -0
- edsl/results/results.py +1 -0
- edsl/scenarios/scenario_list.py +31 -1
- edsl/scenarios/scenario_source.py +606 -498
- edsl/surveys/survey.py +198 -163
- {edsl-0.1.59.dist-info → edsl-0.1.61.dist-info}/METADATA +4 -4
- {edsl-0.1.59.dist-info → edsl-0.1.61.dist-info}/RECORD +32 -30
- {edsl-0.1.59.dist-info → edsl-0.1.61.dist-info}/LICENSE +0 -0
- {edsl-0.1.59.dist-info → edsl-0.1.61.dist-info}/WHEEL +0 -0
- {edsl-0.1.59.dist-info → edsl-0.1.61.dist-info}/entry_points.txt +0 -0
edsl/__version__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
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(
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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__(
|
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(
|
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(
|
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(
|
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
|
-
{
|
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(
|
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(
|
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(
|
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(
|
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
|
-
"""
|
181
|
+
) -> dict:
|
182
|
+
"""
|
183
|
+
Get a signed URL for directly uploading an object to Google Cloud Storage.
|
157
184
|
|
158
|
-
This method
|
159
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
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
|
+
)
|