langwatch 0.2.19__py3-none-any.whl → 0.3.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.
- langwatch/__init__.py +5 -5
- langwatch/__version__.py +1 -1
- langwatch/generated/langwatch_rest_api_client/models/__init__.py +2 -18
- langwatch/generated/langwatch_rest_api_client/models/post_api_scenario_events_body_type_2.py +7 -115
- langwatch/generated/langwatch_rest_api_client/models/{post_api_scenario_events_body_type_2_messages_item_type_2_tool_calls_item_function.py → post_api_scenario_events_body_type_2_messages_item.py} +23 -22
- langwatch/prompts/__init__.py +2 -2
- langwatch/prompts/decorators/prompt_service_tracing.py +6 -4
- langwatch/prompts/decorators/prompt_tracing.py +13 -7
- langwatch/prompts/local_loader.py +170 -0
- langwatch/prompts/prompt.py +41 -43
- langwatch/prompts/{service.py → prompt_api_service.py} +23 -33
- langwatch/prompts/prompt_facade.py +139 -0
- langwatch/prompts/types/__init__.py +27 -0
- langwatch/prompts/types/prompt_data.py +93 -0
- langwatch/prompts/types/structures.py +37 -0
- langwatch/prompts/types.py +16 -24
- langwatch/utils/transformation.py +16 -5
- {langwatch-0.2.19.dist-info → langwatch-0.3.1.dist-info}/METADATA +1 -1
- {langwatch-0.2.19.dist-info → langwatch-0.3.1.dist-info}/RECORD +20 -22
- langwatch/generated/langwatch_rest_api_client/models/post_api_scenario_events_body_type_2_messages_item_type_0.py +0 -88
- langwatch/generated/langwatch_rest_api_client/models/post_api_scenario_events_body_type_2_messages_item_type_1.py +0 -88
- langwatch/generated/langwatch_rest_api_client/models/post_api_scenario_events_body_type_2_messages_item_type_2.py +0 -120
- langwatch/generated/langwatch_rest_api_client/models/post_api_scenario_events_body_type_2_messages_item_type_2_tool_calls_item.py +0 -87
- langwatch/generated/langwatch_rest_api_client/models/post_api_scenario_events_body_type_2_messages_item_type_3.py +0 -88
- langwatch/generated/langwatch_rest_api_client/models/post_api_scenario_events_body_type_2_messages_item_type_4.py +0 -85
- langwatch/prompts/formatter.py +0 -31
- {langwatch-0.2.19.dist-info → langwatch-0.3.1.dist-info}/WHEEL +0 -0
langwatch/__init__.py
CHANGED
|
@@ -19,11 +19,11 @@ if TYPE_CHECKING:
|
|
|
19
19
|
import langwatch.dataset as dataset
|
|
20
20
|
import langwatch.dspy as dspy
|
|
21
21
|
import langwatch.langchain as langchain
|
|
22
|
-
from .prompts.
|
|
22
|
+
from .prompts.prompt_facade import PromptsFacade
|
|
23
23
|
|
|
24
24
|
# Type hint for the prompts service specifically
|
|
25
25
|
# required to get the instance typing correct
|
|
26
|
-
prompts:
|
|
26
|
+
prompts: PromptsFacade
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@module_property
|
|
@@ -61,10 +61,10 @@ def __getattr__(name: str):
|
|
|
61
61
|
globals()[name] = module
|
|
62
62
|
return module
|
|
63
63
|
elif name == "prompts":
|
|
64
|
-
# Special-case: expose a
|
|
65
|
-
from .prompts.
|
|
64
|
+
# Special-case: expose a PromptsFacade instance at langwatch.prompts
|
|
65
|
+
from .prompts.prompt_facade import PromptsFacade
|
|
66
66
|
|
|
67
|
-
svc =
|
|
67
|
+
svc = PromptsFacade.from_global()
|
|
68
68
|
globals()[name] = svc # Cache for subsequent access
|
|
69
69
|
return svc
|
|
70
70
|
|
langwatch/__version__.py
CHANGED
|
@@ -486,17 +486,7 @@ from .post_api_scenario_events_body_type_1_results_type_0_verdict import (
|
|
|
486
486
|
)
|
|
487
487
|
from .post_api_scenario_events_body_type_1_status import PostApiScenarioEventsBodyType1Status
|
|
488
488
|
from .post_api_scenario_events_body_type_2 import PostApiScenarioEventsBodyType2
|
|
489
|
-
from .
|
|
490
|
-
from .post_api_scenario_events_body_type_2_messages_item_type_1 import PostApiScenarioEventsBodyType2MessagesItemType1
|
|
491
|
-
from .post_api_scenario_events_body_type_2_messages_item_type_2 import PostApiScenarioEventsBodyType2MessagesItemType2
|
|
492
|
-
from .post_api_scenario_events_body_type_2_messages_item_type_2_tool_calls_item import (
|
|
493
|
-
PostApiScenarioEventsBodyType2MessagesItemType2ToolCallsItem,
|
|
494
|
-
)
|
|
495
|
-
from .post_api_scenario_events_body_type_2_messages_item_type_2_tool_calls_item_function import (
|
|
496
|
-
PostApiScenarioEventsBodyType2MessagesItemType2ToolCallsItemFunction,
|
|
497
|
-
)
|
|
498
|
-
from .post_api_scenario_events_body_type_2_messages_item_type_3 import PostApiScenarioEventsBodyType2MessagesItemType3
|
|
499
|
-
from .post_api_scenario_events_body_type_2_messages_item_type_4 import PostApiScenarioEventsBodyType2MessagesItemType4
|
|
489
|
+
from .post_api_scenario_events_body_type_2_messages_item import PostApiScenarioEventsBodyType2MessagesItem
|
|
500
490
|
from .post_api_scenario_events_response_201 import PostApiScenarioEventsResponse201
|
|
501
491
|
from .post_api_scenario_events_response_400 import PostApiScenarioEventsResponse400
|
|
502
492
|
from .post_api_scenario_events_response_401 import PostApiScenarioEventsResponse401
|
|
@@ -853,13 +843,7 @@ __all__ = (
|
|
|
853
843
|
"PostApiScenarioEventsBodyType1ResultsType0Verdict",
|
|
854
844
|
"PostApiScenarioEventsBodyType1Status",
|
|
855
845
|
"PostApiScenarioEventsBodyType2",
|
|
856
|
-
"
|
|
857
|
-
"PostApiScenarioEventsBodyType2MessagesItemType1",
|
|
858
|
-
"PostApiScenarioEventsBodyType2MessagesItemType2",
|
|
859
|
-
"PostApiScenarioEventsBodyType2MessagesItemType2ToolCallsItem",
|
|
860
|
-
"PostApiScenarioEventsBodyType2MessagesItemType2ToolCallsItemFunction",
|
|
861
|
-
"PostApiScenarioEventsBodyType2MessagesItemType3",
|
|
862
|
-
"PostApiScenarioEventsBodyType2MessagesItemType4",
|
|
846
|
+
"PostApiScenarioEventsBodyType2MessagesItem",
|
|
863
847
|
"PostApiScenarioEventsResponse201",
|
|
864
848
|
"PostApiScenarioEventsResponse400",
|
|
865
849
|
"PostApiScenarioEventsResponse401",
|
langwatch/generated/langwatch_rest_api_client/models/post_api_scenario_events_body_type_2.py
CHANGED
|
@@ -7,21 +7,7 @@ from attrs import field as _attrs_field
|
|
|
7
7
|
from ..types import UNSET, Unset
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
-
from ..models.
|
|
11
|
-
PostApiScenarioEventsBodyType2MessagesItemType0,
|
|
12
|
-
)
|
|
13
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_1 import (
|
|
14
|
-
PostApiScenarioEventsBodyType2MessagesItemType1,
|
|
15
|
-
)
|
|
16
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_2 import (
|
|
17
|
-
PostApiScenarioEventsBodyType2MessagesItemType2,
|
|
18
|
-
)
|
|
19
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_3 import (
|
|
20
|
-
PostApiScenarioEventsBodyType2MessagesItemType3,
|
|
21
|
-
)
|
|
22
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_4 import (
|
|
23
|
-
PostApiScenarioEventsBodyType2MessagesItemType4,
|
|
24
|
-
)
|
|
10
|
+
from ..models.post_api_scenario_events_body_type_2_messages_item import PostApiScenarioEventsBodyType2MessagesItem
|
|
25
11
|
|
|
26
12
|
|
|
27
13
|
T = TypeVar("T", bound="PostApiScenarioEventsBodyType2")
|
|
@@ -33,9 +19,7 @@ class PostApiScenarioEventsBodyType2:
|
|
|
33
19
|
Attributes:
|
|
34
20
|
type_ (Literal['SCENARIO_MESSAGE_SNAPSHOT']):
|
|
35
21
|
timestamp (float):
|
|
36
|
-
messages (list[
|
|
37
|
-
'PostApiScenarioEventsBodyType2MessagesItemType1', 'PostApiScenarioEventsBodyType2MessagesItemType2',
|
|
38
|
-
'PostApiScenarioEventsBodyType2MessagesItemType3', 'PostApiScenarioEventsBodyType2MessagesItemType4']]):
|
|
22
|
+
messages (list['PostApiScenarioEventsBodyType2MessagesItem']):
|
|
39
23
|
batch_run_id (str):
|
|
40
24
|
scenario_id (str):
|
|
41
25
|
scenario_run_id (str):
|
|
@@ -45,15 +29,7 @@ class PostApiScenarioEventsBodyType2:
|
|
|
45
29
|
|
|
46
30
|
type_: Literal["SCENARIO_MESSAGE_SNAPSHOT"]
|
|
47
31
|
timestamp: float
|
|
48
|
-
messages: list[
|
|
49
|
-
Union[
|
|
50
|
-
"PostApiScenarioEventsBodyType2MessagesItemType0",
|
|
51
|
-
"PostApiScenarioEventsBodyType2MessagesItemType1",
|
|
52
|
-
"PostApiScenarioEventsBodyType2MessagesItemType2",
|
|
53
|
-
"PostApiScenarioEventsBodyType2MessagesItemType3",
|
|
54
|
-
"PostApiScenarioEventsBodyType2MessagesItemType4",
|
|
55
|
-
]
|
|
56
|
-
]
|
|
32
|
+
messages: list["PostApiScenarioEventsBodyType2MessagesItem"]
|
|
57
33
|
batch_run_id: str
|
|
58
34
|
scenario_id: str
|
|
59
35
|
scenario_run_id: str
|
|
@@ -62,37 +38,13 @@ class PostApiScenarioEventsBodyType2:
|
|
|
62
38
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
63
39
|
|
|
64
40
|
def to_dict(self) -> dict[str, Any]:
|
|
65
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_0 import (
|
|
66
|
-
PostApiScenarioEventsBodyType2MessagesItemType0,
|
|
67
|
-
)
|
|
68
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_1 import (
|
|
69
|
-
PostApiScenarioEventsBodyType2MessagesItemType1,
|
|
70
|
-
)
|
|
71
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_2 import (
|
|
72
|
-
PostApiScenarioEventsBodyType2MessagesItemType2,
|
|
73
|
-
)
|
|
74
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_3 import (
|
|
75
|
-
PostApiScenarioEventsBodyType2MessagesItemType3,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
41
|
type_ = self.type_
|
|
79
42
|
|
|
80
43
|
timestamp = self.timestamp
|
|
81
44
|
|
|
82
45
|
messages = []
|
|
83
46
|
for messages_item_data in self.messages:
|
|
84
|
-
messages_item
|
|
85
|
-
if isinstance(messages_item_data, PostApiScenarioEventsBodyType2MessagesItemType0):
|
|
86
|
-
messages_item = messages_item_data.to_dict()
|
|
87
|
-
elif isinstance(messages_item_data, PostApiScenarioEventsBodyType2MessagesItemType1):
|
|
88
|
-
messages_item = messages_item_data.to_dict()
|
|
89
|
-
elif isinstance(messages_item_data, PostApiScenarioEventsBodyType2MessagesItemType2):
|
|
90
|
-
messages_item = messages_item_data.to_dict()
|
|
91
|
-
elif isinstance(messages_item_data, PostApiScenarioEventsBodyType2MessagesItemType3):
|
|
92
|
-
messages_item = messages_item_data.to_dict()
|
|
93
|
-
else:
|
|
94
|
-
messages_item = messages_item_data.to_dict()
|
|
95
|
-
|
|
47
|
+
messages_item = messages_item_data.to_dict()
|
|
96
48
|
messages.append(messages_item)
|
|
97
49
|
|
|
98
50
|
batch_run_id = self.batch_run_id
|
|
@@ -126,20 +78,8 @@ class PostApiScenarioEventsBodyType2:
|
|
|
126
78
|
|
|
127
79
|
@classmethod
|
|
128
80
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
129
|
-
from ..models.
|
|
130
|
-
|
|
131
|
-
)
|
|
132
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_1 import (
|
|
133
|
-
PostApiScenarioEventsBodyType2MessagesItemType1,
|
|
134
|
-
)
|
|
135
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_2 import (
|
|
136
|
-
PostApiScenarioEventsBodyType2MessagesItemType2,
|
|
137
|
-
)
|
|
138
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_3 import (
|
|
139
|
-
PostApiScenarioEventsBodyType2MessagesItemType3,
|
|
140
|
-
)
|
|
141
|
-
from ..models.post_api_scenario_events_body_type_2_messages_item_type_4 import (
|
|
142
|
-
PostApiScenarioEventsBodyType2MessagesItemType4,
|
|
81
|
+
from ..models.post_api_scenario_events_body_type_2_messages_item import (
|
|
82
|
+
PostApiScenarioEventsBodyType2MessagesItem,
|
|
143
83
|
)
|
|
144
84
|
|
|
145
85
|
d = dict(src_dict)
|
|
@@ -152,55 +92,7 @@ class PostApiScenarioEventsBodyType2:
|
|
|
152
92
|
messages = []
|
|
153
93
|
_messages = d.pop("messages")
|
|
154
94
|
for messages_item_data in _messages:
|
|
155
|
-
|
|
156
|
-
def _parse_messages_item(
|
|
157
|
-
data: object,
|
|
158
|
-
) -> Union[
|
|
159
|
-
"PostApiScenarioEventsBodyType2MessagesItemType0",
|
|
160
|
-
"PostApiScenarioEventsBodyType2MessagesItemType1",
|
|
161
|
-
"PostApiScenarioEventsBodyType2MessagesItemType2",
|
|
162
|
-
"PostApiScenarioEventsBodyType2MessagesItemType3",
|
|
163
|
-
"PostApiScenarioEventsBodyType2MessagesItemType4",
|
|
164
|
-
]:
|
|
165
|
-
try:
|
|
166
|
-
if not isinstance(data, dict):
|
|
167
|
-
raise TypeError()
|
|
168
|
-
messages_item_type_0 = PostApiScenarioEventsBodyType2MessagesItemType0.from_dict(data)
|
|
169
|
-
|
|
170
|
-
return messages_item_type_0
|
|
171
|
-
except: # noqa: E722
|
|
172
|
-
pass
|
|
173
|
-
try:
|
|
174
|
-
if not isinstance(data, dict):
|
|
175
|
-
raise TypeError()
|
|
176
|
-
messages_item_type_1 = PostApiScenarioEventsBodyType2MessagesItemType1.from_dict(data)
|
|
177
|
-
|
|
178
|
-
return messages_item_type_1
|
|
179
|
-
except: # noqa: E722
|
|
180
|
-
pass
|
|
181
|
-
try:
|
|
182
|
-
if not isinstance(data, dict):
|
|
183
|
-
raise TypeError()
|
|
184
|
-
messages_item_type_2 = PostApiScenarioEventsBodyType2MessagesItemType2.from_dict(data)
|
|
185
|
-
|
|
186
|
-
return messages_item_type_2
|
|
187
|
-
except: # noqa: E722
|
|
188
|
-
pass
|
|
189
|
-
try:
|
|
190
|
-
if not isinstance(data, dict):
|
|
191
|
-
raise TypeError()
|
|
192
|
-
messages_item_type_3 = PostApiScenarioEventsBodyType2MessagesItemType3.from_dict(data)
|
|
193
|
-
|
|
194
|
-
return messages_item_type_3
|
|
195
|
-
except: # noqa: E722
|
|
196
|
-
pass
|
|
197
|
-
if not isinstance(data, dict):
|
|
198
|
-
raise TypeError()
|
|
199
|
-
messages_item_type_4 = PostApiScenarioEventsBodyType2MessagesItemType4.from_dict(data)
|
|
200
|
-
|
|
201
|
-
return messages_item_type_4
|
|
202
|
-
|
|
203
|
-
messages_item = _parse_messages_item(messages_item_data)
|
|
95
|
+
messages_item = PostApiScenarioEventsBodyType2MessagesItem.from_dict(messages_item_data)
|
|
204
96
|
|
|
205
97
|
messages.append(messages_item)
|
|
206
98
|
|
|
@@ -1,54 +1,55 @@
|
|
|
1
1
|
from collections.abc import Mapping
|
|
2
|
-
from typing import Any, TypeVar
|
|
2
|
+
from typing import Any, TypeVar, Union
|
|
3
3
|
|
|
4
4
|
from attrs import define as _attrs_define
|
|
5
5
|
from attrs import field as _attrs_field
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from ..types import UNSET, Unset
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T", bound="PostApiScenarioEventsBodyType2MessagesItem")
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
@_attrs_define
|
|
11
|
-
class
|
|
13
|
+
class PostApiScenarioEventsBodyType2MessagesItem:
|
|
12
14
|
"""
|
|
13
15
|
Attributes:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
id (Union[Unset, str]):
|
|
17
|
+
trace_id (Union[Unset, str]):
|
|
16
18
|
"""
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
id: Union[Unset, str] = UNSET
|
|
21
|
+
trace_id: Union[Unset, str] = UNSET
|
|
20
22
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
21
23
|
|
|
22
24
|
def to_dict(self) -> dict[str, Any]:
|
|
23
|
-
|
|
25
|
+
id = self.id
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
trace_id = self.trace_id
|
|
26
28
|
|
|
27
29
|
field_dict: dict[str, Any] = {}
|
|
28
30
|
field_dict.update(self.additional_properties)
|
|
29
|
-
field_dict.update(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
31
|
+
field_dict.update({})
|
|
32
|
+
if id is not UNSET:
|
|
33
|
+
field_dict["id"] = id
|
|
34
|
+
if trace_id is not UNSET:
|
|
35
|
+
field_dict["trace_id"] = trace_id
|
|
35
36
|
|
|
36
37
|
return field_dict
|
|
37
38
|
|
|
38
39
|
@classmethod
|
|
39
40
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
40
41
|
d = dict(src_dict)
|
|
41
|
-
|
|
42
|
+
id = d.pop("id", UNSET)
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
trace_id = d.pop("trace_id", UNSET)
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
post_api_scenario_events_body_type_2_messages_item = cls(
|
|
47
|
+
id=id,
|
|
48
|
+
trace_id=trace_id,
|
|
48
49
|
)
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
return
|
|
51
|
+
post_api_scenario_events_body_type_2_messages_item.additional_properties = d
|
|
52
|
+
return post_api_scenario_events_body_type_2_messages_item
|
|
52
53
|
|
|
53
54
|
@property
|
|
54
55
|
def additional_keys(self) -> list[str]:
|
langwatch/prompts/__init__.py
CHANGED
|
@@ -48,9 +48,11 @@ class PromptServiceTracing:
|
|
|
48
48
|
|
|
49
49
|
span.set_attributes(
|
|
50
50
|
{
|
|
51
|
-
AttributeKey.LangWatchPromptId: result.id,
|
|
52
|
-
AttributeKey.LangWatchPromptVersionId: result.
|
|
53
|
-
|
|
51
|
+
AttributeKey.LangWatchPromptId: result.get("id"),
|
|
52
|
+
AttributeKey.LangWatchPromptVersionId: result.get(
|
|
53
|
+
"version_id"
|
|
54
|
+
),
|
|
55
|
+
AttributeKey.LangWatchPromptHandle: result.get("handle"),
|
|
54
56
|
}
|
|
55
57
|
)
|
|
56
58
|
return result
|
|
@@ -63,7 +65,7 @@ class PromptServiceTracing:
|
|
|
63
65
|
@staticmethod
|
|
64
66
|
def _create_span_name(span_name: str) -> str:
|
|
65
67
|
"""Create a span name for the prompt"""
|
|
66
|
-
return "
|
|
68
|
+
return "PromptApiService" + "." + span_name
|
|
67
69
|
|
|
68
70
|
|
|
69
71
|
prompt_service_tracing = PromptServiceTracing()
|
|
@@ -21,7 +21,7 @@ class PromptTracing:
|
|
|
21
21
|
"""Internal method to create compile decorators with specified span name"""
|
|
22
22
|
|
|
23
23
|
def decorator(
|
|
24
|
-
func: Callable[..., "CompiledPrompt"]
|
|
24
|
+
func: Callable[..., "CompiledPrompt"],
|
|
25
25
|
) -> Callable[..., "CompiledPrompt"]:
|
|
26
26
|
@wraps(func)
|
|
27
27
|
def wrapper(self: "Prompt", *args: Any, **kwargs: Any) -> "CompiledPrompt":
|
|
@@ -31,10 +31,16 @@ class PromptTracing:
|
|
|
31
31
|
# Set base prompt
|
|
32
32
|
span.set_attributes(
|
|
33
33
|
{
|
|
34
|
-
AttributeKey.LangWatchPromptId: self
|
|
35
|
-
AttributeKey.LangWatchPromptHandle:
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
AttributeKey.LangWatchPromptId: getattr(self, "id", None),
|
|
35
|
+
AttributeKey.LangWatchPromptHandle: getattr(
|
|
36
|
+
self, "handle", None
|
|
37
|
+
),
|
|
38
|
+
AttributeKey.LangWatchPromptVersionId: getattr(
|
|
39
|
+
self, "version_id", None
|
|
40
|
+
),
|
|
41
|
+
AttributeKey.LangWatchPromptVersionNumber: getattr(
|
|
42
|
+
self, "version", None
|
|
43
|
+
),
|
|
38
44
|
}
|
|
39
45
|
)
|
|
40
46
|
|
|
@@ -67,14 +73,14 @@ class PromptTracing:
|
|
|
67
73
|
|
|
68
74
|
@staticmethod
|
|
69
75
|
def compile(
|
|
70
|
-
func: Callable[..., "CompiledPrompt"]
|
|
76
|
+
func: Callable[..., "CompiledPrompt"],
|
|
71
77
|
) -> Callable[..., "CompiledPrompt"]:
|
|
72
78
|
"""Decorator for Prompt.compile method with OpenTelemetry tracing"""
|
|
73
79
|
return PromptTracing._create_compile_decorator("compile")(func)
|
|
74
80
|
|
|
75
81
|
@staticmethod
|
|
76
82
|
def compile_strict(
|
|
77
|
-
func: Callable[..., "CompiledPrompt"]
|
|
83
|
+
func: Callable[..., "CompiledPrompt"],
|
|
78
84
|
) -> Callable[..., "CompiledPrompt"]:
|
|
79
85
|
"""Decorator for Prompt.compile_strict method with OpenTelemetry tracing"""
|
|
80
86
|
return PromptTracing._create_compile_decorator("compile_strict")(func)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Local prompt file loader for LangWatch Python SDK.
|
|
3
|
+
|
|
4
|
+
Reads prompts from local files in the CLI format:
|
|
5
|
+
- prompts.json: Configuration file
|
|
6
|
+
- prompts-lock.json: Lock file with materialized paths
|
|
7
|
+
- *.prompt.yaml: Individual prompt files
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional, Dict, Any
|
|
15
|
+
import warnings
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
from .types import PromptData, MessageDict, ResponseFormatDict
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LocalPromptLoader:
|
|
25
|
+
"""Loads prompts from local files in CLI format."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, base_path: Optional[Path] = None):
|
|
28
|
+
"""Initialize with base path (defaults to current working directory)."""
|
|
29
|
+
self.base_path = base_path or Path.cwd()
|
|
30
|
+
|
|
31
|
+
def load_prompt(self, prompt_id: str) -> Optional[PromptData]:
|
|
32
|
+
"""
|
|
33
|
+
Load a prompt from local files.
|
|
34
|
+
|
|
35
|
+
Returns None if prompt not found locally.
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
# Check if prompts.json exists
|
|
39
|
+
prompts_json_path = self.base_path / "prompts.json"
|
|
40
|
+
if not prompts_json_path.exists():
|
|
41
|
+
logger.debug(
|
|
42
|
+
f"No prompts.json found at {prompts_json_path}, falling back to API"
|
|
43
|
+
)
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
# Load prompts.json
|
|
47
|
+
try:
|
|
48
|
+
with open(prompts_json_path, "r") as f:
|
|
49
|
+
prompts_config = json.load(f)
|
|
50
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
51
|
+
warnings.warn(
|
|
52
|
+
f"Failed to read prompts.json at {prompts_json_path}: {e}. "
|
|
53
|
+
f"Falling back to API for prompt '{prompt_id}'.",
|
|
54
|
+
UserWarning,
|
|
55
|
+
)
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
# Check if prompt exists in config
|
|
59
|
+
if prompt_id not in prompts_config.get("prompts", {}):
|
|
60
|
+
logger.debug(
|
|
61
|
+
f"Prompt '{prompt_id}' not found in prompts.json, falling back to API"
|
|
62
|
+
)
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
# Load prompts-lock.json to get materialized path
|
|
66
|
+
prompts_lock_path = self.base_path / "prompts-lock.json"
|
|
67
|
+
if not prompts_lock_path.exists():
|
|
68
|
+
warnings.warn(
|
|
69
|
+
f"prompts.json exists but prompts-lock.json not found at {prompts_lock_path}. "
|
|
70
|
+
f"Run 'langwatch prompts pull' to sync local prompts. "
|
|
71
|
+
f"Falling back to API for prompt '{prompt_id}'.",
|
|
72
|
+
UserWarning,
|
|
73
|
+
)
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
with open(prompts_lock_path, "r") as f:
|
|
78
|
+
prompts_lock = json.load(f)
|
|
79
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
80
|
+
warnings.warn(
|
|
81
|
+
f"Failed to read prompts-lock.json at {prompts_lock_path}: {e}. "
|
|
82
|
+
f"Falling back to API for prompt '{prompt_id}'.",
|
|
83
|
+
UserWarning,
|
|
84
|
+
)
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
# Get materialized path
|
|
88
|
+
prompt_info = prompts_lock.get("prompts", {}).get(prompt_id)
|
|
89
|
+
if not prompt_info:
|
|
90
|
+
warnings.warn(
|
|
91
|
+
f"Prompt '{prompt_id}' found in prompts.json but not in prompts-lock.json. "
|
|
92
|
+
f"Run 'langwatch prompts pull' to sync local prompts. "
|
|
93
|
+
f"Falling back to API for prompt '{prompt_id}'.",
|
|
94
|
+
UserWarning,
|
|
95
|
+
)
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
materialized_path = prompt_info.get("materialized")
|
|
99
|
+
if not materialized_path:
|
|
100
|
+
warnings.warn(
|
|
101
|
+
f"Prompt '{prompt_id}' in prompts-lock.json has no materialized path. "
|
|
102
|
+
f"Run 'langwatch prompts pull' to sync local prompts. "
|
|
103
|
+
f"Falling back to API for prompt '{prompt_id}'.",
|
|
104
|
+
UserWarning,
|
|
105
|
+
)
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
# Load the actual prompt file
|
|
109
|
+
prompt_file_path = self.base_path / materialized_path
|
|
110
|
+
if not prompt_file_path.exists():
|
|
111
|
+
warnings.warn(
|
|
112
|
+
f"Prompt file not found at {prompt_file_path}. "
|
|
113
|
+
f"Run 'langwatch prompts pull' to sync local prompts. "
|
|
114
|
+
f"Falling back to API for prompt '{prompt_id}'.",
|
|
115
|
+
UserWarning,
|
|
116
|
+
)
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
with open(prompt_file_path, "r") as f:
|
|
121
|
+
prompt_data = yaml.safe_load(f)
|
|
122
|
+
except (yaml.YAMLError, OSError) as e:
|
|
123
|
+
warnings.warn(
|
|
124
|
+
f"Failed to parse prompt file at {prompt_file_path}: {e}. "
|
|
125
|
+
f"Falling back to API for prompt '{prompt_id}'.",
|
|
126
|
+
UserWarning,
|
|
127
|
+
)
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
# Build PromptData directly
|
|
131
|
+
logger.info(
|
|
132
|
+
f"Successfully loaded prompt '{prompt_id}' from local file: {prompt_file_path}"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Convert messages
|
|
136
|
+
messages = []
|
|
137
|
+
if "messages" in prompt_data:
|
|
138
|
+
messages = [
|
|
139
|
+
MessageDict(role=msg["role"], content=msg["content"])
|
|
140
|
+
for msg in prompt_data["messages"]
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
# Convert response format if present
|
|
144
|
+
response_format = None
|
|
145
|
+
if "response_format" in prompt_data and prompt_data["response_format"]:
|
|
146
|
+
response_format = ResponseFormatDict(
|
|
147
|
+
type="json_schema", json_schema=prompt_data["response_format"]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return PromptData(
|
|
151
|
+
handle=prompt_id, # The prompt_id parameter is the handle
|
|
152
|
+
model=prompt_data["model"], # Required field - let it fail if missing
|
|
153
|
+
messages=messages,
|
|
154
|
+
prompt=prompt_data.get("prompt"),
|
|
155
|
+
temperature=prompt_data.get("temperature"),
|
|
156
|
+
max_tokens=prompt_data.get("max_tokens"),
|
|
157
|
+
response_format=response_format,
|
|
158
|
+
version=prompt_info.get("version"),
|
|
159
|
+
version_id=prompt_info.get("versionId"),
|
|
160
|
+
# id and scope are not available in local files
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
# If any unexpected error occurs, warn and fall back to API
|
|
165
|
+
warnings.warn(
|
|
166
|
+
f"Unexpected error loading prompt '{prompt_id}' from local files: {e}. "
|
|
167
|
+
f"Falling back to API.",
|
|
168
|
+
UserWarning,
|
|
169
|
+
)
|
|
170
|
+
return None
|