sentienceapi 0.90.16__py3-none-any.whl → 0.92.2__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.
Potentially problematic release.
This version of sentienceapi might be problematic. Click here for more details.
- sentience/__init__.py +14 -5
- sentience/action_executor.py +215 -0
- sentience/actions.py +408 -25
- sentience/agent.py +802 -293
- sentience/agent_config.py +3 -0
- sentience/async_api.py +83 -1142
- sentience/base_agent.py +95 -0
- sentience/browser.py +484 -1
- sentience/browser_evaluator.py +299 -0
- sentience/cloud_tracing.py +457 -33
- sentience/conversational_agent.py +77 -43
- sentience/element_filter.py +136 -0
- sentience/expect.py +98 -2
- sentience/extension/background.js +56 -185
- sentience/extension/content.js +117 -289
- sentience/extension/injected_api.js +799 -1374
- sentience/extension/manifest.json +1 -1
- sentience/extension/pkg/sentience_core.js +190 -396
- sentience/extension/pkg/sentience_core_bg.wasm +0 -0
- sentience/extension/release.json +47 -47
- sentience/formatting.py +9 -53
- sentience/inspector.py +183 -1
- sentience/llm_interaction_handler.py +191 -0
- sentience/llm_provider.py +74 -52
- sentience/llm_provider_utils.py +120 -0
- sentience/llm_response_builder.py +153 -0
- sentience/models.py +60 -1
- sentience/overlay.py +109 -2
- sentience/protocols.py +228 -0
- sentience/query.py +1 -1
- sentience/read.py +95 -3
- sentience/recorder.py +223 -3
- sentience/schemas/trace_v1.json +102 -9
- sentience/screenshot.py +48 -2
- sentience/sentience_methods.py +86 -0
- sentience/snapshot.py +291 -38
- sentience/snapshot_diff.py +141 -0
- sentience/text_search.py +119 -5
- sentience/trace_event_builder.py +129 -0
- sentience/trace_file_manager.py +197 -0
- sentience/trace_indexing/index_schema.py +95 -7
- sentience/trace_indexing/indexer.py +117 -14
- sentience/tracer_factory.py +119 -6
- sentience/tracing.py +172 -8
- sentience/utils/__init__.py +40 -0
- sentience/utils/browser.py +46 -0
- sentience/utils/element.py +257 -0
- sentience/utils/formatting.py +59 -0
- sentience/utils.py +1 -1
- sentience/visual_agent.py +2056 -0
- sentience/wait.py +68 -2
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/METADATA +2 -1
- sentienceapi-0.92.2.dist-info/RECORD +65 -0
- sentience/extension/test-content.js +0 -4
- sentienceapi-0.90.16.dist-info/RECORD +0 -50
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/WHEEL +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/entry_points.txt +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/licenses/LICENSE +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/licenses/LICENSE-APACHE +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/licenses/LICENSE-MIT +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/top_level.txt +0 -0
sentience/llm_provider.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
LLM Provider abstraction layer for Sentience SDK
|
|
3
5
|
Enables "Bring Your Own Brain" (BYOB) pattern - plug in any LLM provider
|
|
@@ -6,6 +8,9 @@ Enables "Bring Your Own Brain" (BYOB) pattern - plug in any LLM provider
|
|
|
6
8
|
from abc import ABC, abstractmethod
|
|
7
9
|
from dataclasses import dataclass
|
|
8
10
|
|
|
11
|
+
from .llm_provider_utils import get_api_key_from_env, handle_provider_error, require_package
|
|
12
|
+
from .llm_response_builder import LLMResponseBuilder
|
|
13
|
+
|
|
9
14
|
|
|
10
15
|
@dataclass
|
|
11
16
|
class LLMResponse:
|
|
@@ -31,6 +36,15 @@ class LLMProvider(ABC):
|
|
|
31
36
|
- Any other completion API
|
|
32
37
|
"""
|
|
33
38
|
|
|
39
|
+
def __init__(self, model: str):
|
|
40
|
+
"""
|
|
41
|
+
Initialize LLM provider with model name.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
model: Model identifier (e.g., "gpt-4o", "claude-3-sonnet")
|
|
45
|
+
"""
|
|
46
|
+
self._model_name = model
|
|
47
|
+
|
|
34
48
|
@abstractmethod
|
|
35
49
|
def generate(self, system_prompt: str, user_prompt: str, **kwargs) -> LLMResponse:
|
|
36
50
|
"""
|
|
@@ -95,13 +109,16 @@ class OpenAIProvider(LLMProvider):
|
|
|
95
109
|
base_url: Custom API base URL (for compatible APIs)
|
|
96
110
|
organization: OpenAI organization ID
|
|
97
111
|
"""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
super().__init__(model) # Initialize base class with model name
|
|
113
|
+
|
|
114
|
+
OpenAI = require_package(
|
|
115
|
+
"openai",
|
|
116
|
+
"openai",
|
|
117
|
+
"OpenAI",
|
|
118
|
+
"pip install openai",
|
|
119
|
+
)
|
|
102
120
|
|
|
103
121
|
self.client = OpenAI(api_key=api_key, base_url=base_url, organization=organization)
|
|
104
|
-
self._model_name = model
|
|
105
122
|
|
|
106
123
|
def generate(
|
|
107
124
|
self,
|
|
@@ -148,12 +165,15 @@ class OpenAIProvider(LLMProvider):
|
|
|
148
165
|
api_params.update(kwargs)
|
|
149
166
|
|
|
150
167
|
# Call OpenAI API
|
|
151
|
-
|
|
168
|
+
try:
|
|
169
|
+
response = self.client.chat.completions.create(**api_params)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
handle_provider_error(e, "OpenAI", "generate response")
|
|
152
172
|
|
|
153
173
|
choice = response.choices[0]
|
|
154
174
|
usage = response.usage
|
|
155
175
|
|
|
156
|
-
return
|
|
176
|
+
return LLMResponseBuilder.from_openai_format(
|
|
157
177
|
content=choice.message.content,
|
|
158
178
|
prompt_tokens=usage.prompt_tokens if usage else None,
|
|
159
179
|
completion_tokens=usage.completion_tokens if usage else None,
|
|
@@ -191,15 +211,16 @@ class AnthropicProvider(LLMProvider):
|
|
|
191
211
|
api_key: Anthropic API key (or set ANTHROPIC_API_KEY env var)
|
|
192
212
|
model: Model name (claude-3-opus, claude-3-sonnet, claude-3-haiku, etc.)
|
|
193
213
|
"""
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
214
|
+
super().__init__(model) # Initialize base class with model name
|
|
215
|
+
|
|
216
|
+
Anthropic = require_package(
|
|
217
|
+
"anthropic",
|
|
218
|
+
"anthropic",
|
|
219
|
+
"Anthropic",
|
|
220
|
+
"pip install anthropic",
|
|
221
|
+
)
|
|
200
222
|
|
|
201
223
|
self.client = Anthropic(api_key=api_key)
|
|
202
|
-
self._model_name = model
|
|
203
224
|
|
|
204
225
|
def generate(
|
|
205
226
|
self,
|
|
@@ -237,21 +258,19 @@ class AnthropicProvider(LLMProvider):
|
|
|
237
258
|
api_params.update(kwargs)
|
|
238
259
|
|
|
239
260
|
# Call Anthropic API
|
|
240
|
-
|
|
261
|
+
try:
|
|
262
|
+
response = self.client.messages.create(**api_params)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
handle_provider_error(e, "Anthropic", "generate response")
|
|
241
265
|
|
|
242
266
|
content = response.content[0].text if response.content else ""
|
|
243
267
|
|
|
244
|
-
return
|
|
268
|
+
return LLMResponseBuilder.from_anthropic_format(
|
|
245
269
|
content=content,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
total_tokens=(
|
|
249
|
-
(response.usage.input_tokens + response.usage.output_tokens)
|
|
250
|
-
if hasattr(response, "usage")
|
|
251
|
-
else None
|
|
252
|
-
),
|
|
270
|
+
input_tokens=response.usage.input_tokens if hasattr(response, "usage") else None,
|
|
271
|
+
output_tokens=response.usage.output_tokens if hasattr(response, "usage") else None,
|
|
253
272
|
model_name=response.model,
|
|
254
|
-
|
|
273
|
+
stop_reason=response.stop_reason,
|
|
255
274
|
)
|
|
256
275
|
|
|
257
276
|
def supports_json_mode(self) -> bool:
|
|
@@ -285,13 +304,16 @@ class GLMProvider(LLMProvider):
|
|
|
285
304
|
api_key: Zhipu AI API key (or set GLM_API_KEY env var)
|
|
286
305
|
model: Model name (glm-4-plus, glm-4, glm-4-air, glm-4-flash, etc.)
|
|
287
306
|
"""
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
307
|
+
super().__init__(model) # Initialize base class with model name
|
|
308
|
+
|
|
309
|
+
ZhipuAI = require_package(
|
|
310
|
+
"zhipuai",
|
|
311
|
+
"zhipuai",
|
|
312
|
+
"ZhipuAI",
|
|
313
|
+
"pip install zhipuai",
|
|
314
|
+
)
|
|
292
315
|
|
|
293
316
|
self.client = ZhipuAI(api_key=api_key)
|
|
294
|
-
self._model_name = model
|
|
295
317
|
|
|
296
318
|
def generate(
|
|
297
319
|
self,
|
|
@@ -333,12 +355,15 @@ class GLMProvider(LLMProvider):
|
|
|
333
355
|
api_params.update(kwargs)
|
|
334
356
|
|
|
335
357
|
# Call GLM API
|
|
336
|
-
|
|
358
|
+
try:
|
|
359
|
+
response = self.client.chat.completions.create(**api_params)
|
|
360
|
+
except Exception as e:
|
|
361
|
+
handle_provider_error(e, "GLM", "generate response")
|
|
337
362
|
|
|
338
363
|
choice = response.choices[0]
|
|
339
364
|
usage = response.usage
|
|
340
365
|
|
|
341
|
-
return
|
|
366
|
+
return LLMResponseBuilder.from_openai_format(
|
|
342
367
|
content=choice.message.content,
|
|
343
368
|
prompt_tokens=usage.prompt_tokens if usage else None,
|
|
344
369
|
completion_tokens=usage.completion_tokens if usage else None,
|
|
@@ -378,25 +403,20 @@ class GeminiProvider(LLMProvider):
|
|
|
378
403
|
api_key: Google API key (or set GEMINI_API_KEY or GOOGLE_API_KEY env var)
|
|
379
404
|
model: Model name (gemini-2.0-flash-exp, gemini-1.5-pro, gemini-1.5-flash, etc.)
|
|
380
405
|
"""
|
|
381
|
-
|
|
382
|
-
import google.generativeai as genai
|
|
383
|
-
except ImportError:
|
|
384
|
-
raise ImportError(
|
|
385
|
-
"Google Generative AI package not installed. Install with: pip install google-generativeai"
|
|
386
|
-
)
|
|
406
|
+
super().__init__(model) # Initialize base class with model name
|
|
387
407
|
|
|
388
|
-
|
|
408
|
+
genai = require_package(
|
|
409
|
+
"google-generativeai",
|
|
410
|
+
"google.generativeai",
|
|
411
|
+
install_command="pip install google-generativeai",
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# Configure API key (check parameter first, then environment variables)
|
|
415
|
+
api_key = get_api_key_from_env(["GEMINI_API_KEY", "GOOGLE_API_KEY"], api_key)
|
|
389
416
|
if api_key:
|
|
390
417
|
genai.configure(api_key=api_key)
|
|
391
|
-
else:
|
|
392
|
-
import os
|
|
393
|
-
|
|
394
|
-
api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
|
|
395
|
-
if api_key:
|
|
396
|
-
genai.configure(api_key=api_key)
|
|
397
418
|
|
|
398
419
|
self.genai = genai
|
|
399
|
-
self._model_name = model
|
|
400
420
|
self.model = genai.GenerativeModel(model)
|
|
401
421
|
|
|
402
422
|
def generate(
|
|
@@ -435,7 +455,10 @@ class GeminiProvider(LLMProvider):
|
|
|
435
455
|
generation_config.update(kwargs)
|
|
436
456
|
|
|
437
457
|
# Call Gemini API
|
|
438
|
-
|
|
458
|
+
try:
|
|
459
|
+
response = self.model.generate_content(full_prompt, generation_config=generation_config)
|
|
460
|
+
except Exception as e:
|
|
461
|
+
handle_provider_error(e, "Gemini", "generate response")
|
|
439
462
|
|
|
440
463
|
# Extract content
|
|
441
464
|
content = response.text if response.text else ""
|
|
@@ -450,13 +473,12 @@ class GeminiProvider(LLMProvider):
|
|
|
450
473
|
completion_tokens = response.usage_metadata.candidates_token_count
|
|
451
474
|
total_tokens = response.usage_metadata.total_token_count
|
|
452
475
|
|
|
453
|
-
return
|
|
476
|
+
return LLMResponseBuilder.from_gemini_format(
|
|
454
477
|
content=content,
|
|
455
478
|
prompt_tokens=prompt_tokens,
|
|
456
479
|
completion_tokens=completion_tokens,
|
|
457
480
|
total_tokens=total_tokens,
|
|
458
481
|
model_name=self._model_name,
|
|
459
|
-
finish_reason=None, # Gemini uses different finish reason format
|
|
460
482
|
)
|
|
461
483
|
|
|
462
484
|
def supports_json_mode(self) -> bool:
|
|
@@ -503,6 +525,9 @@ class LocalLLMProvider(LLMProvider):
|
|
|
503
525
|
load_in_8bit: Use 8-bit quantization (saves 50% memory)
|
|
504
526
|
torch_dtype: Data type ("auto", "float16", "bfloat16", "float32")
|
|
505
527
|
"""
|
|
528
|
+
super().__init__(model_name) # Initialize base class with model name
|
|
529
|
+
|
|
530
|
+
# Import required packages with consistent error handling
|
|
506
531
|
try:
|
|
507
532
|
import torch
|
|
508
533
|
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
|
|
@@ -512,8 +537,6 @@ class LocalLLMProvider(LLMProvider):
|
|
|
512
537
|
"Install with: pip install transformers torch"
|
|
513
538
|
)
|
|
514
539
|
|
|
515
|
-
self._model_name = model_name
|
|
516
|
-
|
|
517
540
|
# Load tokenizer
|
|
518
541
|
self.tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
|
|
519
542
|
|
|
@@ -620,11 +643,10 @@ class LocalLLMProvider(LLMProvider):
|
|
|
620
643
|
generated_tokens = outputs[0][input_length:]
|
|
621
644
|
response_text = self.tokenizer.decode(generated_tokens, skip_special_tokens=True).strip()
|
|
622
645
|
|
|
623
|
-
return
|
|
646
|
+
return LLMResponseBuilder.from_local_format(
|
|
624
647
|
content=response_text,
|
|
625
648
|
prompt_tokens=input_length,
|
|
626
649
|
completion_tokens=len(generated_tokens),
|
|
627
|
-
total_tokens=input_length + len(generated_tokens),
|
|
628
650
|
model_name=self._model_name,
|
|
629
651
|
)
|
|
630
652
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM Provider utility functions for common initialization and error handling.
|
|
3
|
+
|
|
4
|
+
This module provides helper functions to reduce duplication across LLM provider implementations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from typing import Any, Optional, TypeVar
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def require_package(
|
|
15
|
+
package_name: str,
|
|
16
|
+
module_name: str,
|
|
17
|
+
class_name: str | None = None,
|
|
18
|
+
install_command: str | None = None,
|
|
19
|
+
) -> Any:
|
|
20
|
+
"""
|
|
21
|
+
Import a package with consistent error handling.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
package_name: Name of the package (for error messages)
|
|
25
|
+
module_name: Module name to import (e.g., "openai", "google.generativeai")
|
|
26
|
+
class_name: Optional class name to import from module (e.g., "OpenAI")
|
|
27
|
+
install_command: Installation command (defaults to "pip install {package_name}")
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Imported module or class
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ImportError: If package is not installed, with helpful message
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> OpenAI = require_package("openai", "openai", "OpenAI", "pip install openai")
|
|
37
|
+
>>> genai = require_package("google-generativeai", "google.generativeai", install_command="pip install google-generativeai")
|
|
38
|
+
"""
|
|
39
|
+
if install_command is None:
|
|
40
|
+
install_command = f"pip install {package_name}"
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
if class_name:
|
|
44
|
+
# Import specific class: from module import class
|
|
45
|
+
module = __import__(module_name, fromlist=[class_name])
|
|
46
|
+
return getattr(module, class_name)
|
|
47
|
+
else:
|
|
48
|
+
# Import entire module
|
|
49
|
+
return __import__(module_name)
|
|
50
|
+
except ImportError:
|
|
51
|
+
raise ImportError(f"{package_name} package not installed. Install with: {install_command}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_api_key_from_env(
|
|
55
|
+
env_vars: list[str],
|
|
56
|
+
api_key: str | None = None,
|
|
57
|
+
) -> str | None:
|
|
58
|
+
"""
|
|
59
|
+
Get API key from parameter or environment variables.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
env_vars: List of environment variable names to check (in order)
|
|
63
|
+
api_key: Optional API key parameter (takes precedence)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
API key string or None if not found
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
>>> key = get_api_key_from_env(["OPENAI_API_KEY"], api_key="sk-...")
|
|
70
|
+
>>> # Returns "sk-..." if provided, otherwise checks OPENAI_API_KEY env var
|
|
71
|
+
"""
|
|
72
|
+
if api_key:
|
|
73
|
+
return api_key
|
|
74
|
+
|
|
75
|
+
for env_var in env_vars:
|
|
76
|
+
value = os.getenv(env_var)
|
|
77
|
+
if value:
|
|
78
|
+
return value
|
|
79
|
+
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def handle_provider_error(
|
|
84
|
+
error: Exception,
|
|
85
|
+
provider_name: str,
|
|
86
|
+
operation: str = "operation",
|
|
87
|
+
) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Standardize error handling for LLM provider operations.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
error: Exception that occurred
|
|
93
|
+
provider_name: Name of the provider (e.g., "OpenAI", "Anthropic")
|
|
94
|
+
operation: Description of the operation that failed
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
RuntimeError: With standardized error message
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
>>> try:
|
|
101
|
+
... response = client.chat.completions.create(...)
|
|
102
|
+
... except Exception as e:
|
|
103
|
+
... handle_provider_error(e, "OpenAI", "generate response")
|
|
104
|
+
"""
|
|
105
|
+
error_msg = str(error)
|
|
106
|
+
if "api key" in error_msg.lower() or "authentication" in error_msg.lower():
|
|
107
|
+
raise RuntimeError(
|
|
108
|
+
f"{provider_name} API key is invalid or missing. "
|
|
109
|
+
f"Please check your API key configuration."
|
|
110
|
+
) from error
|
|
111
|
+
elif "rate limit" in error_msg.lower() or "429" in error_msg:
|
|
112
|
+
raise RuntimeError(
|
|
113
|
+
f"{provider_name} rate limit exceeded. Please try again later."
|
|
114
|
+
) from error
|
|
115
|
+
elif "model" in error_msg.lower() and "not found" in error_msg.lower():
|
|
116
|
+
raise RuntimeError(
|
|
117
|
+
f"{provider_name} model not found. Please check the model name."
|
|
118
|
+
) from error
|
|
119
|
+
else:
|
|
120
|
+
raise RuntimeError(f"{provider_name} {operation} failed: {error_msg}") from error
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM Response building utilities for consistent response construction.
|
|
3
|
+
|
|
4
|
+
This module provides helper functions for building LLMResponse objects
|
|
5
|
+
from various provider API responses.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
# Import LLMResponse here to avoid circular dependency
|
|
11
|
+
# We import it inside functions to break the cycle
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LLMResponseBuilder:
|
|
15
|
+
"""
|
|
16
|
+
Helper for building LLMResponse objects with consistent structure.
|
|
17
|
+
|
|
18
|
+
Provides static methods for building responses from different provider formats.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def from_openai_format(
|
|
23
|
+
content: str,
|
|
24
|
+
prompt_tokens: int | None = None,
|
|
25
|
+
completion_tokens: int | None = None,
|
|
26
|
+
total_tokens: int | None = None,
|
|
27
|
+
model_name: str | None = None,
|
|
28
|
+
finish_reason: str | None = None,
|
|
29
|
+
) -> "LLMResponse":
|
|
30
|
+
"""
|
|
31
|
+
Build LLMResponse from OpenAI-style API response.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
content: Response text content
|
|
35
|
+
prompt_tokens: Number of prompt tokens
|
|
36
|
+
completion_tokens: Number of completion tokens
|
|
37
|
+
total_tokens: Total tokens (or sum of prompt + completion)
|
|
38
|
+
model_name: Model identifier
|
|
39
|
+
finish_reason: Finish reason (stop, length, etc.)
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
LLMResponse object
|
|
43
|
+
"""
|
|
44
|
+
from .llm_provider import LLMResponse # Import here to avoid circular dependency
|
|
45
|
+
|
|
46
|
+
return LLMResponse(
|
|
47
|
+
content=content,
|
|
48
|
+
prompt_tokens=prompt_tokens,
|
|
49
|
+
completion_tokens=completion_tokens,
|
|
50
|
+
total_tokens=total_tokens
|
|
51
|
+
or (
|
|
52
|
+
(prompt_tokens + completion_tokens) if prompt_tokens and completion_tokens else None
|
|
53
|
+
),
|
|
54
|
+
model_name=model_name,
|
|
55
|
+
finish_reason=finish_reason,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def from_anthropic_format(
|
|
60
|
+
content: str,
|
|
61
|
+
input_tokens: int | None = None,
|
|
62
|
+
output_tokens: int | None = None,
|
|
63
|
+
model_name: str | None = None,
|
|
64
|
+
stop_reason: str | None = None,
|
|
65
|
+
) -> "LLMResponse":
|
|
66
|
+
"""
|
|
67
|
+
Build LLMResponse from Anthropic-style API response.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
content: Response text content
|
|
71
|
+
input_tokens: Number of input tokens
|
|
72
|
+
output_tokens: Number of output tokens
|
|
73
|
+
model_name: Model identifier
|
|
74
|
+
stop_reason: Stop reason (end_turn, max_tokens, etc.)
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
LLMResponse object
|
|
78
|
+
"""
|
|
79
|
+
from .llm_provider import LLMResponse # Import here to avoid circular dependency
|
|
80
|
+
|
|
81
|
+
return LLMResponse(
|
|
82
|
+
content=content,
|
|
83
|
+
prompt_tokens=input_tokens,
|
|
84
|
+
completion_tokens=output_tokens,
|
|
85
|
+
total_tokens=(input_tokens + output_tokens) if input_tokens and output_tokens else None,
|
|
86
|
+
model_name=model_name,
|
|
87
|
+
finish_reason=stop_reason,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def from_gemini_format(
|
|
92
|
+
content: str,
|
|
93
|
+
prompt_tokens: int | None = None,
|
|
94
|
+
completion_tokens: int | None = None,
|
|
95
|
+
total_tokens: int | None = None,
|
|
96
|
+
model_name: str | None = None,
|
|
97
|
+
) -> "LLMResponse":
|
|
98
|
+
"""
|
|
99
|
+
Build LLMResponse from Gemini-style API response.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
content: Response text content
|
|
103
|
+
prompt_tokens: Number of prompt tokens
|
|
104
|
+
completion_tokens: Number of completion tokens
|
|
105
|
+
total_tokens: Total tokens
|
|
106
|
+
model_name: Model identifier
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
LLMResponse object
|
|
110
|
+
"""
|
|
111
|
+
from .llm_provider import LLMResponse # Import here to avoid circular dependency
|
|
112
|
+
|
|
113
|
+
return LLMResponse(
|
|
114
|
+
content=content,
|
|
115
|
+
prompt_tokens=prompt_tokens,
|
|
116
|
+
completion_tokens=completion_tokens,
|
|
117
|
+
total_tokens=total_tokens
|
|
118
|
+
or (
|
|
119
|
+
(prompt_tokens + completion_tokens) if prompt_tokens and completion_tokens else None
|
|
120
|
+
),
|
|
121
|
+
model_name=model_name,
|
|
122
|
+
finish_reason=None, # Gemini uses different finish reason format
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def from_local_format(
|
|
127
|
+
content: str,
|
|
128
|
+
prompt_tokens: int,
|
|
129
|
+
completion_tokens: int,
|
|
130
|
+
model_name: str,
|
|
131
|
+
) -> "LLMResponse":
|
|
132
|
+
"""
|
|
133
|
+
Build LLMResponse from local model generation.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
content: Response text content
|
|
137
|
+
prompt_tokens: Number of prompt tokens
|
|
138
|
+
completion_tokens: Number of completion tokens
|
|
139
|
+
model_name: Model identifier
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
LLMResponse object
|
|
143
|
+
"""
|
|
144
|
+
from .llm_provider import LLMResponse # Import here to avoid circular dependency
|
|
145
|
+
|
|
146
|
+
return LLMResponse(
|
|
147
|
+
content=content,
|
|
148
|
+
prompt_tokens=prompt_tokens,
|
|
149
|
+
completion_tokens=completion_tokens,
|
|
150
|
+
total_tokens=prompt_tokens + completion_tokens,
|
|
151
|
+
model_name=model_name,
|
|
152
|
+
finish_reason=None,
|
|
153
|
+
)
|
sentience/models.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Pydantic models for Sentience SDK - matches spec/snapshot.schema.json
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Literal, Optional
|
|
6
7
|
|
|
7
8
|
from pydantic import BaseModel, Field
|
|
8
9
|
|
|
@@ -50,6 +51,9 @@ class Element(BaseModel):
|
|
|
50
51
|
ml_probability: float | None = None # Confidence score from ONNX model (0.0 - 1.0)
|
|
51
52
|
ml_score: float | None = None # Raw logit score (optional, for debugging)
|
|
52
53
|
|
|
54
|
+
# Diff status for frontend Diff Overlay feature
|
|
55
|
+
diff_status: Literal["ADDED", "REMOVED", "MODIFIED", "MOVED"] | None = None
|
|
56
|
+
|
|
53
57
|
|
|
54
58
|
class Snapshot(BaseModel):
|
|
55
59
|
"""Snapshot response from extension"""
|
|
@@ -410,3 +414,58 @@ class TextRectSearchResult(BaseModel):
|
|
|
410
414
|
)
|
|
411
415
|
viewport: Viewport | None = Field(None, description="Current viewport dimensions")
|
|
412
416
|
error: str | None = Field(None, description="Error message if status is 'error'")
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class ReadResult(BaseModel):
|
|
420
|
+
"""Result of read() or read_async() operation"""
|
|
421
|
+
|
|
422
|
+
status: Literal["success", "error"]
|
|
423
|
+
url: str
|
|
424
|
+
format: Literal["raw", "text", "markdown"]
|
|
425
|
+
content: str
|
|
426
|
+
length: int
|
|
427
|
+
error: str | None = None
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class TraceStats(BaseModel):
|
|
431
|
+
"""Execution statistics for trace completion"""
|
|
432
|
+
|
|
433
|
+
total_steps: int
|
|
434
|
+
total_events: int
|
|
435
|
+
duration_ms: int | None = None
|
|
436
|
+
final_status: Literal["success", "failure", "partial", "unknown"]
|
|
437
|
+
started_at: str | None = None
|
|
438
|
+
ended_at: str | None = None
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class StepExecutionResult(BaseModel):
|
|
442
|
+
"""Result of executing a single step in ConversationalAgent"""
|
|
443
|
+
|
|
444
|
+
success: bool
|
|
445
|
+
action: str
|
|
446
|
+
data: dict[str, Any] # Flexible data field for step-specific results
|
|
447
|
+
error: str | None = None
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
class ExtractionResult(BaseModel):
|
|
451
|
+
"""Result of extracting information from a page"""
|
|
452
|
+
|
|
453
|
+
found: bool
|
|
454
|
+
data: dict[str, Any] # Extracted data fields
|
|
455
|
+
summary: str # Brief description of what was found
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
@dataclass
|
|
459
|
+
class ScreenshotMetadata:
|
|
460
|
+
"""
|
|
461
|
+
Metadata for a stored screenshot.
|
|
462
|
+
|
|
463
|
+
Used by CloudTraceSink to track screenshots before upload.
|
|
464
|
+
All fields are required for type safety.
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
sequence: int
|
|
468
|
+
format: Literal["png", "jpeg"]
|
|
469
|
+
size_bytes: int
|
|
470
|
+
step_id: str | None
|
|
471
|
+
filepath: str
|