-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.py
More file actions
174 lines (148 loc) · 6.99 KB
/
Copy pathmodels.py
File metadata and controls
174 lines (148 loc) · 6.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
"""Pydantic data models for the Price Negotiation Environment.
This module defines the three core data-contract types shared between the
server (``PriceNegotiationEnvironment``) and the client (``PriceNegotiationEnv``):
- ``PriceNegotiationAction`` — what the buyer sends each turn
- ``PriceNegotiationObservation`` — what the buyer receives after each turn
- ``PriceNegotiationState`` — the full internal episode state (exposed
via ``GET /state``)
All three extend the corresponding OpenEnv base types (``Action``,
``Observation``, ``State``) so they are automatically compatible with the
OpenEnv HTTP/WebSocket server and client infrastructure.
The agent always plays the **buyer** role. Each action is the buyer's next
natural-language negotiation move (which must include one action tag), and
each observation is the seller's response together with the updated
negotiation metadata visible to the buyer.
"""
from typing import Literal
from openenv.core.env_server.types import Action, Observation, State
from pydantic import Field
class PriceNegotiationAction(Action):
"""A single buyer turn in the negotiation.
The buyer's response must be natural language that ends with exactly one
of the following action tags so the environment can parse the intent:
- ``<action>OFFER $X</action>`` — make or counter with a specific price
- ``<action>ACCEPT</action>`` — accept the seller's current offer
- ``<action>WALK</action>`` — walk away from the negotiation
The text before the tag may include reasoning, counter-arguments, or
market references. The environment uses a substring check for the tag,
so the tag must appear verbatim (case-insensitive for OFFER/ACCEPT/WALK).
Example::
PriceNegotiationAction(
buyer_response=(
"That price is too high given current market comps. "
"I can do $450. <action>OFFER $450</action>"
)
)
"""
buyer_response: str = Field(
...,
description=(
"The buyer's natural-language response to the seller, including "
"exactly one action tag: <action>OFFER $X</action>, "
"<action>ACCEPT</action>, or <action>WALK</action>."
),
)
class PriceNegotiationObservation(Observation):
"""What the buyer agent observes after each step.
Returned by ``env.step()`` and embedded in ``StepResult``. Inherits
``done`` and ``reward`` from the OpenEnv ``Observation`` base class.
The three fields below capture the negotiation-specific metadata that
the buyer needs to decide its next move. The seller's actual reply text
is not included here — it is appended to ``buyer_messages`` in the
environment state and can be retrieved via ``env.state()``.
"""
next_turn: Literal["BUYER", "SELLER"] = Field(
default="BUYER",
description=(
"Whose turn it is next. Always ``'BUYER'`` when ``done=False`` "
"(the agent must act again). Set to ``'SELLER'`` on terminal "
"steps for logging purposes."
),
)
negotiation_round: int = Field(
default=0,
description=(
"The step number at which this observation was produced, "
"starting from 1 after the first ``step()`` call. Useful for "
"enforcing turn budgets on the client side."
),
)
deal_status: Literal["ONGOING", "ACCEPTED", "WALKED_AWAY"] = Field(
default="ONGOING",
description=(
"Current outcome of the negotiation. ``'ONGOING'`` while the "
"episode is still active; ``'ACCEPTED'`` when either side "
"accepted an offer; ``'WALKED_AWAY'`` when either side walked."
),
)
reward_breakdown: dict[str, float] | None = Field(
default=None,
description=(
"Raw reward component scores for the current trajectory. Present "
"when the web server computes trajectory reward details."
),
)
reward_weights: dict[str, float] | None = Field(
default=None,
description=(
"Aggregation weight for each reward component. Present alongside "
"``reward_breakdown`` when reward details are available."
),
)
reward_components: dict[str, dict[str, float]] | None = Field(
default=None,
description=(
"Per-component reward details, including raw score, aggregate-scale "
"score, aggregation weight, and weighted contribution."
),
)
class PriceNegotiationState(State):
"""Full internal state of a negotiation episode.
Returned by ``env.state()`` (``GET /state``). Inherits ``episode_id``
and ``step_count`` from the OpenEnv ``State`` base class.
This object gives the buyer agent (and reward functions) access to the
complete conversation histories and the sampled product scenario,
including private valuation data that is not visible in the observation.
Note:
``buyer_messages`` and ``seller_messages`` are in OpenAI chat format
(list of ``{"role": ..., "content": ...}`` dicts). The buyer history
uses ``"assistant"`` for buyer turns and ``"user"`` for seller turns;
the seller history uses the opposite convention.
"""
product_info: dict = Field(
default_factory=dict,
description=(
"The full scenario dict sampled from ``dataset.json`` for this "
"episode. Contains ``product``, ``valuations`` (including "
"``buyer_true_value``, ``seller_reserve_price``, ``zopa_width``, "
"``deal_possible``), ``metadata`` (``max_turns``, currency, "
"behavioral descriptors), and the raw ``buyer_prompt`` / "
"``seller_prompt`` strings."
),
)
buyer_messages: list[dict[str, str]] = Field(
default_factory=list,
description=(
"OpenAI-format chat history from the buyer's perspective. "
"Starts with a ``system`` message (the buyer prompt) and grows "
"by two messages per round: an ``assistant`` turn (buyer's "
"response) followed by a ``user`` turn (seller's reply). "
"Pass this list directly to an OpenAI-compatible API to generate "
"the next buyer turn."
),
)
seller_messages: list[dict[str, str]] = Field(
default_factory=list,
description=(
"OpenAI-format chat history from the seller's perspective. "
"Starts with a ``system`` message (the seller prompt) and grows "
"by two messages per round: a ``user`` turn (buyer's message) "
"followed by an ``assistant`` turn (seller's reply). "
"Used internally by the environment to generate seller responses."
),
)