econagents 0.0.1__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.
@@ -0,0 +1,222 @@
1
+ from typing import Any, Callable, Optional, Protocol, Type, TypeVar
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+ from econagents.core.events import Message
6
+ from econagents.core.state.fields import EventField
7
+
8
+ EventHandler = Callable[[str, dict[str, Any]], None]
9
+ T = TypeVar("T", bound="GameState")
10
+
11
+
12
+ class PropertyMapping(BaseModel):
13
+ """Mapping between event data and state properties
14
+
15
+ Args:
16
+ event_key: Key in the event data
17
+ state_key: Key in the state object
18
+ state_type: Whether to update private or public information ("private" or "public")
19
+ phases: Optional list of phases where this mapping should be applied. If None, applies to all phases.
20
+ exclude_phases: Optional list of phases where this mapping should not be applied.
21
+ Cannot be used together with phases.
22
+ """
23
+
24
+ event_key: str
25
+ state_key: str
26
+ state_type: str = "private"
27
+ events: list[str] | None = None
28
+ exclude_events: list[str] | None = None
29
+
30
+ def model_post_init(self, __context: Any) -> None:
31
+ """Validate that events and exclude_events are not both specified"""
32
+ if self.events is not None and self.exclude_events is not None:
33
+ raise ValueError("Cannot specify both events and exclude_events")
34
+
35
+ def should_apply_in_event(self, current_event: str) -> bool:
36
+ """Determine if this mapping should be applied in the current event"""
37
+ if self.events is not None:
38
+ return current_event in self.events
39
+ if self.exclude_events is not None:
40
+ return current_event not in self.exclude_events
41
+ return True
42
+
43
+
44
+ class PrivateInformation(BaseModel):
45
+ """Private information for each agent in the game"""
46
+
47
+ model_config = ConfigDict(extra="allow", arbitrary_types_allowed=False)
48
+
49
+
50
+ class PublicInformation(BaseModel):
51
+ """Public information for the game"""
52
+
53
+ model_config = ConfigDict(extra="allow", arbitrary_types_allowed=False)
54
+
55
+
56
+ class MetaInformation(BaseModel):
57
+ """Meta information for the game"""
58
+
59
+ model_config = ConfigDict(extra="allow", arbitrary_types_allowed=False)
60
+
61
+ game_id: int = EventField(default=0)
62
+ """ID of the game"""
63
+ player_name: Optional[str] = EventField(default=None)
64
+ """Name of the player"""
65
+ player_number: Optional[int] = EventField(default=None)
66
+ """Number of the player"""
67
+ players: list[dict[str, Any]] = EventField(default_factory=list)
68
+ """List of players in the game"""
69
+ phase: int = EventField(default=0)
70
+ """Current phase of the game"""
71
+
72
+
73
+ class GameStateProtocol(Protocol):
74
+ meta: MetaInformation
75
+ private_information: PrivateInformation
76
+ public_information: PublicInformation
77
+
78
+ def model_dump(self) -> dict[str, Any]: ...
79
+
80
+ def model_dump_json(self) -> str: ...
81
+
82
+
83
+ class GameState(BaseModel):
84
+ """Game state for a given game"""
85
+
86
+ meta: MetaInformation = EventField(default_factory=MetaInformation)
87
+ """Meta information for the game"""
88
+ private_information: PrivateInformation = EventField(default_factory=PrivateInformation)
89
+ """Private information for each agent in the game"""
90
+ public_information: PublicInformation = EventField(default_factory=PublicInformation)
91
+ """Public information for the game"""
92
+
93
+ def __init__(self, **kwargs: Any):
94
+ super().__init__(**kwargs)
95
+ self._property_mappings = self._get_property_mappings()
96
+
97
+ def update(self, event: Message) -> None:
98
+ """
99
+ Generic state update method that handles both property mappings and custom event handlers.
100
+
101
+ Args:
102
+ event (Message): The event message containing event_type and data
103
+
104
+ This method will:
105
+ 1. Check for custom event handlers first
106
+ 2. Fall back to property mappings if no custom handler exists
107
+ 3. Update state based on property mappings, considering phase restrictions
108
+ """
109
+ # Get custom event handlers from child class
110
+ custom_handlers = self.get_custom_handlers()
111
+
112
+ # Check if there's a custom handler for this event type
113
+ if event.event_type in custom_handlers:
114
+ custom_handlers[event.event_type](event.event_type, event.data)
115
+ return
116
+
117
+ # Update state based on mappings
118
+ for mapping in self._property_mappings:
119
+ # Skip if mapping shouldn't be applied in current event
120
+ if not mapping.should_apply_in_event(event.event_type):
121
+ continue
122
+
123
+ # Skip if the event key isn't in the event data
124
+ if mapping.event_key not in event.data:
125
+ continue
126
+
127
+ value = event.data[mapping.event_key]
128
+
129
+ # Update the appropriate state object based on state_type
130
+ if mapping.state_type == "meta":
131
+ setattr(self.meta, mapping.state_key, value)
132
+ elif mapping.state_type == "private":
133
+ setattr(self.private_information, mapping.state_key, value)
134
+ elif mapping.state_type == "public":
135
+ setattr(self.public_information, mapping.state_key, value)
136
+
137
+ def _get_property_mappings(self) -> list[PropertyMapping]:
138
+ """
139
+ Default implementation that generates property mappings from EventField metadata.
140
+
141
+ Returns:
142
+ list[PropertyMapping]: List of PropertyMapping objects generated from field metadata.
143
+ """
144
+ mappings = []
145
+
146
+ # Generate mappings from meta information fields
147
+ mappings.extend(self._generate_mappings_from_model(self.meta.__class__, "meta"))
148
+
149
+ # Generate mappings from private information fields
150
+ mappings.extend(self._generate_mappings_from_model(self.private_information.__class__, "private"))
151
+
152
+ # Generate mappings from public information fields
153
+ mappings.extend(self._generate_mappings_from_model(self.public_information.__class__, "public"))
154
+
155
+ return mappings
156
+
157
+ def _generate_mappings_from_model(self, model_class: Type, state_type: str) -> list[PropertyMapping]:
158
+ """
159
+ Generate property mappings from a Pydantic model class.
160
+
161
+ Args:
162
+ model_class (Type): The Pydantic model class to inspect
163
+ state_type (str): The state type ("meta", "private", or "public")
164
+
165
+ Returns:
166
+ list[PropertyMapping]: List of PropertyMapping objects
167
+ """
168
+ mappings = []
169
+
170
+ for field_name, field_info in model_class.model_fields.items():
171
+ # Skip fields with exclude_from_mapping=True
172
+ exclude_from_mapping = False
173
+ if hasattr(field_info, "json_schema_extra") and "event_metadata" in field_info.json_schema_extra:
174
+ exclude_from_mapping = field_info.json_schema_extra["event_metadata"]["exclude_from_mapping"]
175
+ elif hasattr(field_info, "exclude_from_mapping"): # For backward compatibility
176
+ exclude_from_mapping = field_info.exclude_from_mapping
177
+
178
+ if exclude_from_mapping:
179
+ continue
180
+
181
+ # Get event key from event_key if provided, otherwise use field name
182
+ event_key = None
183
+ if hasattr(field_info, "json_schema_extra") and "event_metadata" in field_info.json_schema_extra:
184
+ event_key = field_info.json_schema_extra["event_metadata"]["event_key"]
185
+ elif hasattr(field_info, "event_key"): # For backward compatibility
186
+ event_key = field_info.event_key
187
+
188
+ if event_key is None:
189
+ event_key = field_name
190
+
191
+ # Get events and exclude_events if provided
192
+ events = None
193
+ exclude_events = None
194
+
195
+ if hasattr(field_info, "json_schema_extra") and "event_metadata" in field_info.json_schema_extra:
196
+ events = field_info.json_schema_extra["event_metadata"]["events"]
197
+ exclude_events = field_info.json_schema_extra["event_metadata"]["exclude_events"]
198
+ else:
199
+ # For backward compatibility
200
+ events = getattr(field_info, "events", None)
201
+ exclude_events = getattr(field_info, "exclude_events", None)
202
+
203
+ mappings.append(
204
+ PropertyMapping(
205
+ event_key=event_key,
206
+ state_key=field_name,
207
+ state_type=state_type,
208
+ events=events,
209
+ exclude_events=exclude_events,
210
+ )
211
+ )
212
+
213
+ return mappings
214
+
215
+ def get_custom_handlers(self) -> dict[str, EventHandler]:
216
+ """
217
+ Override this method to provide custom event handlers.
218
+
219
+ Returns:
220
+ dict[str, EventHandler]: A mapping of event types to handler functions.
221
+ """
222
+ return {}
@@ -0,0 +1,124 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel, Field, computed_field
4
+
5
+
6
+ class Order(BaseModel):
7
+ id: int
8
+ sender: int
9
+ price: float
10
+ quantity: float
11
+ type: str
12
+ condition: int
13
+ now: bool = False
14
+
15
+
16
+ class Trade(BaseModel):
17
+ from_id: int
18
+ to_id: int
19
+ price: float
20
+ quantity: float
21
+ condition: int
22
+ median: Optional[float] = None
23
+
24
+
25
+ class MarketState(BaseModel):
26
+ """
27
+ Represents the current state of the market:
28
+ - Active orders in an order book
29
+ - History of recent trades
30
+ """
31
+
32
+ orders: dict[int, Order] = Field(default_factory=dict)
33
+ trades: list[Trade] = Field(default_factory=list)
34
+
35
+ @computed_field
36
+ def order_book(self) -> str:
37
+ asks = sorted(
38
+ [order for order in self.orders.values() if order.type == "ask"],
39
+ key=lambda x: x.price,
40
+ reverse=True,
41
+ )
42
+ bids = sorted(
43
+ [order for order in self.orders.values() if order.type == "bid"],
44
+ key=lambda x: x.price,
45
+ reverse=True,
46
+ )
47
+ sorted_orders = asks + bids
48
+ return "\n".join([str(order) for order in sorted_orders])
49
+
50
+ def process_event(self, event_type: str, data: dict):
51
+ """
52
+ Update the MarketState based on the eventType and
53
+ event data from the server.
54
+ """
55
+ if event_type == "add-order":
56
+ self._on_add_order(data["order"])
57
+
58
+ elif event_type == "update-order":
59
+ self._on_update_order(data["order"])
60
+
61
+ elif event_type == "delete-order":
62
+ self._on_delete_order(data["order"])
63
+
64
+ elif event_type == "contract-fulfilled":
65
+ self._on_contract_fulfilled(data)
66
+
67
+ def get_orders_from_player(self, player_id: int) -> list[Order]:
68
+ """Get all orders from a specific player."""
69
+ return [order for order in self.orders.values() if order.sender == player_id]
70
+
71
+ def _on_add_order(self, order_data: dict):
72
+ """
73
+ The server is telling us a new order has been added.
74
+ We'll store it in self.orders by ID.
75
+ """
76
+ order_id = order_data["id"]
77
+ new_order = Order(
78
+ id=order_id,
79
+ sender=order_data["sender"],
80
+ price=order_data["price"],
81
+ quantity=order_data["quantity"],
82
+ type=order_data["type"],
83
+ condition=order_data["condition"],
84
+ now=order_data.get("now", False),
85
+ )
86
+ self.orders[order_id] = new_order
87
+
88
+ def _on_update_order(self, order_data: dict):
89
+ """
90
+ The server is telling us the order's quantity or other fields
91
+ have changed (often due to partial fills).
92
+ """
93
+ order_id = order_data["id"]
94
+ if order_id in self.orders:
95
+ existing = self.orders[order_id]
96
+ existing.quantity = order_data.get("quantity", existing.quantity)
97
+ self.orders[order_id] = existing
98
+
99
+ def _on_delete_order(self, order_data: dict):
100
+ """
101
+ The server is telling us this order is removed
102
+ from the order book (fully filled or canceled).
103
+ """
104
+ order_id = order_data["id"]
105
+ if order_id in self.orders:
106
+ del self.orders[order_id]
107
+
108
+ def _on_contract_fulfilled(self, data: dict):
109
+ """
110
+ This indicates a trade has happened between 'from' and 'to'.
111
+ The server might also send update-order or delete-order events
112
+ to reflect the fill on the order book.
113
+ We track the trade in self.trades, but we typically rely
114
+ on update-order or delete-order to fix the order's quantity.
115
+ """
116
+ new_trade = Trade(
117
+ from_id=data["from"],
118
+ to_id=data["to"],
119
+ price=data["price"],
120
+ quantity=data.get("quantity", 1.0),
121
+ condition=data["condition"],
122
+ median=data.get("median"),
123
+ )
124
+ self.trades.append(new_trade)
@@ -0,0 +1,132 @@
1
+ from abc import ABC, abstractmethod
2
+ import asyncio
3
+ import json
4
+ import logging
5
+ from typing import Any, Callable, Optional
6
+
7
+ import websockets
8
+ from websockets.asyncio.client import ClientConnection
9
+ from websockets.exceptions import ConnectionClosed
10
+
11
+ from econagents.core.logging_mixin import LoggerMixin
12
+
13
+
14
+ class AuthenticationMechanism(ABC):
15
+ """Abstract base class for authentication mechanisms."""
16
+
17
+ @abstractmethod
18
+ async def authenticate(self, transport: "WebSocketTransport", **kwargs) -> bool:
19
+ """Authenticate the transport."""
20
+ pass
21
+
22
+ @classmethod
23
+ def __get_pydantic_core_schema__(cls, _source_type, _handler):
24
+ from pydantic_core import core_schema
25
+
26
+ return core_schema.is_instance_schema(AuthenticationMechanism)
27
+
28
+
29
+ class SimpleLoginPayloadAuth(AuthenticationMechanism):
30
+ """Authentication mechanism that sends a login payload as the first message."""
31
+
32
+ async def authenticate(self, transport: "WebSocketTransport", **kwargs) -> bool:
33
+ """Send the login payload as a JSON message."""
34
+ initial_message = json.dumps(kwargs)
35
+ await transport.send(initial_message)
36
+ return True
37
+
38
+
39
+ class WebSocketTransport(LoggerMixin):
40
+ """
41
+ Responsible for connecting to a WebSocket, sending/receiving messages,
42
+ and reporting received messages to a callback function.
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ url: str,
48
+ logger: Optional[logging.Logger] = None,
49
+ auth_mechanism: Optional[AuthenticationMechanism] = None,
50
+ auth_mechanism_kwargs: Optional[dict[str, Any]] = None,
51
+ on_message_callback: Optional[Callable[[str], Any]] = None,
52
+ ):
53
+ """
54
+ Initialize the WebSocket transport.
55
+
56
+ Args:
57
+ url: WebSocket server URL
58
+ logger: (Optional) Logger instance
59
+ auth_mechanism: (Optional) Authentication mechanism
60
+ auth_mechanism_kwargs: (Optional) Keyword arguments to pass to auth_mechanism during authentication
61
+ on_message_callback: Callback function that receives raw message strings.
62
+ Can be synchronous or asynchronous.
63
+ """
64
+ self.url = url
65
+ self.auth_mechanism = auth_mechanism
66
+ self.auth_mechanism_kwargs = auth_mechanism_kwargs
67
+ if logger:
68
+ self.logger = logger
69
+ self.on_message_callback = on_message_callback
70
+ self.ws: Optional[ClientConnection] = None
71
+ self._running = False
72
+
73
+ async def connect(self) -> bool:
74
+ """Establish the WebSocket connection and authenticate."""
75
+ try:
76
+ self.ws = await websockets.connect(self.url, ping_interval=30, ping_timeout=10)
77
+ self.logger.info("WebSocketTransport: connection opened.")
78
+
79
+ # Perform authentication using the callback
80
+ if self.auth_mechanism:
81
+ if not self.auth_mechanism_kwargs:
82
+ self.auth_mechanism_kwargs = {}
83
+ auth_success = await self.auth_mechanism.authenticate(self, **self.auth_mechanism_kwargs)
84
+ if not auth_success:
85
+ self.logger.error("Authentication failed")
86
+ await self.stop()
87
+ self.ws = None # Ensure ws is set to None after stopping
88
+ return False
89
+
90
+ except Exception as e:
91
+ self.logger.exception(f"Transport connection error: {e}")
92
+ return False
93
+ else:
94
+ return True
95
+
96
+ async def start_listening(self):
97
+ """Begin receiving messages in a loop."""
98
+ self._running = True
99
+ while self._running and self.ws:
100
+ try:
101
+ message_str = await self.ws.recv()
102
+ if self.on_message_callback:
103
+ # Call the callback, supporting both sync and async functions
104
+ self.logger.debug(f"<-- Transport received: {message_str}")
105
+ result = self.on_message_callback(message_str)
106
+ # If the callback is a coroutine function, await it
107
+ if asyncio.iscoroutine(result):
108
+ asyncio.create_task(result)
109
+ except ConnectionClosed:
110
+ self.logger.info("WebSocket connection closed by remote.")
111
+ break
112
+ except Exception:
113
+ self.logger.exception("Error in receive loop.")
114
+ break
115
+ self._running = False
116
+
117
+ async def send(self, message: str):
118
+ """Send a raw string message to the WebSocket."""
119
+ if self.ws:
120
+ try:
121
+ self.logger.debug(f"--> Transport sending: {message}")
122
+ await self.ws.send(message)
123
+ except Exception:
124
+ self.logger.exception("Error sending message.")
125
+
126
+ async def stop(self):
127
+ """Gracefully close the WebSocket connection."""
128
+ self._running = False
129
+ if self.ws:
130
+ await self.ws.close()
131
+ self.logger.info("WebSocketTransport: connection closed.")
132
+ self.ws = None # Set ws to None after closing
@@ -0,0 +1,3 @@
1
+ from econagents.llm.openai import ChatOpenAI
2
+
3
+ __all__: list[str] = ["ChatOpenAI"]
@@ -0,0 +1,61 @@
1
+ from typing import Any, Optional
2
+
3
+ from langsmith import traceable
4
+ from langsmith.wrappers import wrap_openai
5
+ from openai import AsyncOpenAI
6
+
7
+
8
+ class ChatOpenAI:
9
+ """
10
+ A simple wrapper for LLM queries, e.g. using OpenAI and LangSmith.
11
+ """
12
+
13
+ def __init__(
14
+ self,
15
+ model_name: str = "gpt-4o",
16
+ api_key: Optional[str] = None,
17
+ ) -> None:
18
+ """Initialize the LLM interface."""
19
+ self.model_name = model_name
20
+ self.api_key = api_key
21
+
22
+ def build_messages(self, system_prompt: str, user_prompt: str):
23
+ """Build messages for the LLM.
24
+
25
+ Args:
26
+ system_prompt (str): The system prompt for the LLM.
27
+ user_prompt (str): The user prompt for the LLM.
28
+
29
+ Returns:
30
+ list[dict[str, Any]]: The messages for the LLM.
31
+ """
32
+ return [
33
+ {"role": "system", "content": system_prompt},
34
+ {"role": "user", "content": user_prompt},
35
+ ]
36
+
37
+ @traceable
38
+ async def get_response(
39
+ self,
40
+ messages: list[dict[str, Any]],
41
+ tracing_extra: dict[str, Any],
42
+ **kwargs: Any,
43
+ ):
44
+ """Get a response from the LLM.
45
+
46
+ Args:
47
+ messages (list[dict[str, Any]]): The messages for the LLM.
48
+ tracing_extra (dict[str, Any]): The extra tracing information.
49
+
50
+ Returns:
51
+ str: The response from the LLM.
52
+ """
53
+ client = wrap_openai(AsyncOpenAI(api_key=self.api_key))
54
+ response = await client.chat.completions.create(
55
+ messages=messages, # type: ignore
56
+ model=self.model_name,
57
+ response_format={"type": "json_object"},
58
+ langsmith_extra=tracing_extra,
59
+ **kwargs,
60
+ )
61
+ return response.choices[0].message.content
econagents/py.typed ADDED
File without changes
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.3
2
+ Name: econagents
3
+ Version: 0.0.1
4
+ Summary:
5
+ License: Apache-2.0
6
+ Author: Dylan
7
+ Requires-Python: >=3.10,<3.13
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: langsmith (>=0.3.13,<0.4.0)
14
+ Requires-Dist: numpy (>=2.2.3,<3.0.0)
15
+ Requires-Dist: openai (>=1.65.5,<2.0.0)
16
+ Requires-Dist: pydantic (>=2.10.6,<3.0.0)
17
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
18
+ Requires-Dist: typing-extensions (>=4.12.2,<5.0.0)
19
+ Requires-Dist: websockets (>=15.0,<16.0)
20
+ Project-URL: Homepage, https://github.com/iwanalabs/econagents
21
+ Project-URL: Repository, https://github.com/iwanalabs/econagents
22
+ Description-Content-Type: text/markdown
23
+
24
+ <div align="center">
25
+ <img src="https://raw.githubusercontent.com/iwanalabs/econagents/main/assets/logo_200w.png">
26
+ </div>
27
+
28
+ <div align="center">
29
+
30
+ ![Python compat](https://img.shields.io/badge/%3E=python-3.10-blue.svg)
31
+ [![PyPi](https://img.shields.io/pypi/v/econagents.svg)](https://pypi.python.org/pypi/econagents)
32
+ [![GHA Status](https://github.com/iwanalabs/econagents/actions/workflows/tests.yaml/badge.svg?branch=main)](https://github.com/iwanalabs/econagents/actions?query=workflow%3Atests)
33
+ [![Documentation Status](https://readthedocs.org/projects/econagents/badge/?version=latest)](https://econagents.readthedocs.io/en/latest/?badge=latest)
34
+
35
+ </div>
36
+
37
+ ---
38
+
39
+ # econagents
40
+
41
+ econagents is a Python library that lets you use LLM agents in economic experiments. The framework connects LLM agents to game servers through WebSockets and provides a flexible architecture for designing, customizing, and running economic simulations.
42
+
43
+ ## Installation
44
+
45
+ ```shell
46
+ # Install from PyPI
47
+ pip install econagents
48
+
49
+ # Or install directly from GitHub
50
+ pip install git+https://github.com/iwanalabs/econagents.git
51
+ ```
52
+
53
+ ## Framework Components
54
+
55
+ econagents consists of four key components:
56
+
57
+ 1. **Agent Roles**: Define player roles with customizable behaviors using a flexible prompt system.
58
+ 2. **Game State**: Hierarchical state management with automatic event-driven updates.
59
+ 3. **Agent Managers**: Manage agent connections to game servers and handle event processing.
60
+ 4. **Game Runner**: Orchestrates experiments by gluing together the other components.
61
+
62
+ ## Example Experiments
63
+
64
+ The repository includes two example experiments:
65
+
66
+ 1. **`prisoner`**: An iterated Prisoner's Dilemma game with 5 rounds and 2 LLM agents.
67
+ 2. **`tudeflt/harberger`**: A Harberger Tax simulation with LLM agents.
68
+ 3. **`tudeflt/futarchy`**: A Futarchy simulation with LLM agents.
69
+
70
+ ### Running the Prisoner's Dilemma Experiment
71
+
72
+ ```shell
73
+ # Run the server
74
+ python examples/server/prisoner/server.py
75
+
76
+ # Run the experiment (in a separate terminal)
77
+ python examples/prisoner/run_game.py
78
+ ```
79
+
80
+ ## Key Features
81
+
82
+ - **Flexible Agent Customization**: Customize agent behavior with Jinja templates or custom Python methods
83
+ - **Event-Driven State Management**: Automatically update game state based on server events
84
+ - **Turn-Based and Continuous Action Support**: Handle both turn-based games and continuous action phases
85
+ - **LangChain Integration**: Built-in support for LangChain's agent capabilities
86
+
87
+ ## Documentation
88
+
89
+ For detailed guides and API reference, visit [the documentation](https://econagents.readthedocs.io/en/latest/).
90
+
@@ -0,0 +1,21 @@
1
+ econagents/__init__.py,sha256=7oAI7W8akjmKDPArgmCjA7SDpWqvI7eLPbGeBRTiEzc,1050
2
+ econagents/_c_extension.pyi,sha256=evVvDNUCGqyMPrNViPF7QXfGUNNIMbUdY5HemRNQ1_o,113
3
+ econagents/core/__init__.py,sha256=QZoOp6n5CX1j-Ob6PZgyCNY78vi2kWmd_LVLrJUj1TU,393
4
+ econagents/core/agent_role.py,sha256=viT7V6U9AmwDplgr4xGIVQvm3vLMtIFpGaMcGKM4oWE,15216
5
+ econagents/core/events.py,sha256=hx-Ru_NoSISuN--7ZFC3CIql5hry3AATSnHZJJv3Kds,294
6
+ econagents/core/game_runner.py,sha256=CToHQWIWQ1dwFLWRBxgFGlNecOjtlQUx3YbuS4cfOFQ,12869
7
+ econagents/core/logging_mixin.py,sha256=tYsRc5ngW-hzfElrb838KO-9-BGOPyUv2v5LLuJToBE,1421
8
+ econagents/core/manager/__init__.py,sha256=bDpCQlFcw_E-js575X3Xl6iwZ1uILC18An1vt6oE7S4,284
9
+ econagents/core/manager/base.py,sha256=IMGkyCrghHlkJnkQLGUVfUSSr7sqZsKHYsmgf4UtGlI,16186
10
+ econagents/core/manager/phase.py,sha256=M7s7jyA99BESXC1V9VNBuAOcvVncGoN5lF1nK8dh0Eo,19632
11
+ econagents/core/state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ econagents/core/state/fields.py,sha256=YxVOqdriaHRHoyeXsIB8ZDHygneMJD1OikOyeILK_oA,1854
13
+ econagents/core/state/game.py,sha256=Ux0s7WhOxu0aFvwgX_LM0Aiho-aa1N3yh1ManwWBRP4,8681
14
+ econagents/core/state/market.py,sha256=Jg-X9mYH6B3cYOwxzjFDV5PDbCIYxipx2UN4ecfyyDE,3909
15
+ econagents/core/transport.py,sha256=7eq31nb2KY67RuL5i2kxJrcGtwfcVm5qy0eVj4_xWQw,5063
16
+ econagents/llm/__init__.py,sha256=-tgv6qf77EdceWENIX6pDWXxu2AumhuUCjLiv4FmGKk,82
17
+ econagents/llm/openai.py,sha256=1w8nHr8Ge2aEzO8lEsxKO3tUgLPEaGhYLwYu12653GY,1782
18
+ econagents/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ econagents-0.0.1.dist-info/METADATA,sha256=N9v2yhoCGJ_2JdYq-lZQzoE7QKtMHqDamEDVAQVjAno,3431
20
+ econagents-0.0.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
21
+ econagents-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any