oscura 0.5.1__py3-none-any.whl → 0.7.0__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.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +17 -102
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +183 -67
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +1 -1
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -291
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,7 +7,7 @@ analysis and assistance.
|
|
|
7
7
|
Examples:
|
|
8
8
|
Basic usage with auto-selection:
|
|
9
9
|
|
|
10
|
-
>>> from oscura.integrations import llm
|
|
10
|
+
>>> from oscura.api.integrations import llm
|
|
11
11
|
>>> client = llm.get_client() # Auto-selects available provider
|
|
12
12
|
>>> response = client.chat_completion("What is signal rise time?")
|
|
13
13
|
|
|
@@ -428,7 +428,7 @@ class LLMIntegration:
|
|
|
428
428
|
try:
|
|
429
429
|
provider_enum = LLMProvider(provider.lower())
|
|
430
430
|
except ValueError:
|
|
431
|
-
raise LLMError(f"Unknown provider: {provider}")
|
|
431
|
+
raise LLMError(f"Unknown provider: {provider}")
|
|
432
432
|
|
|
433
433
|
self.config = LLMConfig(
|
|
434
434
|
provider=provider_enum,
|
|
@@ -482,11 +482,9 @@ class LLMIntegration:
|
|
|
482
482
|
LLMError: If OpenAI package not available or configuration invalid
|
|
483
483
|
"""
|
|
484
484
|
try:
|
|
485
|
-
import openai # type: ignore[import-not-found]
|
|
485
|
+
import openai # type: ignore[import-not-found] # noqa: F401
|
|
486
486
|
except ImportError:
|
|
487
|
-
raise LLMError(
|
|
488
|
-
"OpenAI package not installed. Install with: pip install openai"
|
|
489
|
-
)
|
|
487
|
+
raise LLMError("OpenAI package not installed. Install with: pip install openai")
|
|
490
488
|
|
|
491
489
|
if not self.config.api_key:
|
|
492
490
|
raise LLMError("OpenAI API key required")
|
|
@@ -506,11 +504,9 @@ class LLMIntegration:
|
|
|
506
504
|
LLMError: If Anthropic package not available or configuration invalid
|
|
507
505
|
"""
|
|
508
506
|
try:
|
|
509
|
-
import anthropic # type: ignore[import-not-found]
|
|
507
|
+
import anthropic # type: ignore[import-not-found] # noqa: F401
|
|
510
508
|
except ImportError:
|
|
511
|
-
raise LLMError(
|
|
512
|
-
"Anthropic package not installed. Install with: pip install anthropic"
|
|
513
|
-
)
|
|
509
|
+
raise LLMError("Anthropic package not installed. Install with: pip install anthropic")
|
|
514
510
|
|
|
515
511
|
if not self.config.api_key:
|
|
516
512
|
raise LLMError("Anthropic API key required")
|
|
@@ -620,7 +616,7 @@ class LLMIntegration:
|
|
|
620
616
|
|
|
621
617
|
except Exception as e:
|
|
622
618
|
self.trigger_hook(AnalysisHook.ON_ERROR, trace, question, e)
|
|
623
|
-
raise LLMError(f"LLM analysis failed: {e}")
|
|
619
|
+
raise LLMError(f"LLM analysis failed: {e}")
|
|
624
620
|
|
|
625
621
|
def explain(self, measurement: Any) -> str:
|
|
626
622
|
"""Explain a measurement result.
|
|
@@ -663,13 +659,11 @@ class OpenAIClient:
|
|
|
663
659
|
|
|
664
660
|
# Import and initialize OpenAI client
|
|
665
661
|
try:
|
|
666
|
-
import openai
|
|
662
|
+
import openai
|
|
667
663
|
|
|
668
664
|
self._openai = openai
|
|
669
665
|
except ImportError:
|
|
670
|
-
raise LLMError(
|
|
671
|
-
"OpenAI package not installed. Install with: pip install openai"
|
|
672
|
-
)
|
|
666
|
+
raise LLMError("OpenAI package not installed. Install with: pip install openai")
|
|
673
667
|
|
|
674
668
|
# Get API key from config or environment
|
|
675
669
|
api_key = config.api_key or os.environ.get("OPENAI_API_KEY")
|
|
@@ -746,25 +740,25 @@ class OpenAIClient:
|
|
|
746
740
|
wait_time = 2**attempt
|
|
747
741
|
time.sleep(wait_time)
|
|
748
742
|
continue
|
|
749
|
-
raise LLMError(f"OpenAI rate limit exceeded: {e}")
|
|
743
|
+
raise LLMError(f"OpenAI rate limit exceeded: {e}")
|
|
750
744
|
|
|
751
745
|
except self._openai.APITimeoutError as e:
|
|
752
746
|
last_exception = e
|
|
753
747
|
if attempt < self.config.max_retries - 1:
|
|
754
748
|
time.sleep(1)
|
|
755
749
|
continue
|
|
756
|
-
raise LLMError(f"OpenAI request timeout: {e}")
|
|
750
|
+
raise LLMError(f"OpenAI request timeout: {e}")
|
|
757
751
|
|
|
758
752
|
except self._openai.APIError as e:
|
|
759
753
|
last_exception = e
|
|
760
754
|
if attempt < self.config.max_retries - 1:
|
|
761
755
|
time.sleep(1)
|
|
762
756
|
continue
|
|
763
|
-
raise LLMError(f"OpenAI API error: {e}")
|
|
757
|
+
raise LLMError(f"OpenAI API error: {e}")
|
|
764
758
|
|
|
765
759
|
except Exception as e:
|
|
766
760
|
last_exception = e
|
|
767
|
-
raise LLMError(f"OpenAI request failed: {e}")
|
|
761
|
+
raise LLMError(f"OpenAI request failed: {e}")
|
|
768
762
|
|
|
769
763
|
raise LLMError(
|
|
770
764
|
f"OpenAI request failed after {self.config.max_retries} retries: {last_exception}"
|
|
@@ -967,6 +961,77 @@ class OpenAIClient:
|
|
|
967
961
|
return response.answer
|
|
968
962
|
|
|
969
963
|
|
|
964
|
+
def _convert_anthropic_messages(
|
|
965
|
+
messages: list[dict[str, str]], system: str | None
|
|
966
|
+
) -> tuple[list[dict[str, str]], str | None]:
|
|
967
|
+
"""Convert messages to Anthropic format (separate system from user messages).
|
|
968
|
+
|
|
969
|
+
Args:
|
|
970
|
+
messages: Original messages with mixed roles.
|
|
971
|
+
system: Optional system prompt.
|
|
972
|
+
|
|
973
|
+
Returns:
|
|
974
|
+
Tuple of (user_messages, system_message).
|
|
975
|
+
"""
|
|
976
|
+
user_messages = []
|
|
977
|
+
system_message = system
|
|
978
|
+
|
|
979
|
+
for msg in messages:
|
|
980
|
+
if msg["role"] == "system" and not system_message:
|
|
981
|
+
system_message = msg["content"]
|
|
982
|
+
elif msg["role"] in ["user", "assistant"]:
|
|
983
|
+
user_messages.append(msg)
|
|
984
|
+
|
|
985
|
+
return (user_messages, system_message)
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def _extract_anthropic_answer(response: Any) -> str:
|
|
989
|
+
"""Extract text answer from Anthropic response.
|
|
990
|
+
|
|
991
|
+
Args:
|
|
992
|
+
response: Anthropic API response.
|
|
993
|
+
|
|
994
|
+
Returns:
|
|
995
|
+
Concatenated text from all content blocks.
|
|
996
|
+
"""
|
|
997
|
+
answer = ""
|
|
998
|
+
for block in response.content:
|
|
999
|
+
if hasattr(block, "text"):
|
|
1000
|
+
answer += block.text
|
|
1001
|
+
return answer
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
def _build_anthropic_response(response: Any, answer: str, estimated_cost: float) -> LLMResponse:
|
|
1005
|
+
"""Build LLMResponse from Anthropic API response.
|
|
1006
|
+
|
|
1007
|
+
Args:
|
|
1008
|
+
response: Anthropic API response.
|
|
1009
|
+
answer: Extracted answer text.
|
|
1010
|
+
estimated_cost: Estimated API cost.
|
|
1011
|
+
|
|
1012
|
+
Returns:
|
|
1013
|
+
LLMResponse with metadata.
|
|
1014
|
+
"""
|
|
1015
|
+
return LLMResponse(
|
|
1016
|
+
answer=answer,
|
|
1017
|
+
confidence=None, # Anthropic doesn't provide confidence scores
|
|
1018
|
+
suggested_commands=[],
|
|
1019
|
+
metadata={
|
|
1020
|
+
"model": response.model,
|
|
1021
|
+
"usage": {
|
|
1022
|
+
"input_tokens": response.usage.input_tokens,
|
|
1023
|
+
"output_tokens": response.usage.output_tokens,
|
|
1024
|
+
},
|
|
1025
|
+
"stop_reason": response.stop_reason,
|
|
1026
|
+
},
|
|
1027
|
+
raw_response={
|
|
1028
|
+
"id": response.id,
|
|
1029
|
+
"type": response.type,
|
|
1030
|
+
},
|
|
1031
|
+
estimated_cost=estimated_cost,
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
|
|
970
1035
|
class AnthropicClient:
|
|
971
1036
|
"""Anthropic client implementation.
|
|
972
1037
|
|
|
@@ -991,13 +1056,11 @@ class AnthropicClient:
|
|
|
991
1056
|
|
|
992
1057
|
# Import and initialize Anthropic client
|
|
993
1058
|
try:
|
|
994
|
-
import anthropic
|
|
1059
|
+
import anthropic
|
|
995
1060
|
|
|
996
1061
|
self._anthropic = anthropic
|
|
997
1062
|
except ImportError:
|
|
998
|
-
raise LLMError(
|
|
999
|
-
"Anthropic package not installed. Install with: pip install anthropic"
|
|
1000
|
-
)
|
|
1063
|
+
raise LLMError("Anthropic package not installed. Install with: pip install anthropic")
|
|
1001
1064
|
|
|
1002
1065
|
# Get API key from config or environment
|
|
1003
1066
|
api_key = config.api_key or os.environ.get("ANTHROPIC_API_KEY")
|
|
@@ -1030,100 +1093,138 @@ class AnthropicClient:
|
|
|
1030
1093
|
"""
|
|
1031
1094
|
self.rate_limiter.acquire()
|
|
1032
1095
|
|
|
1033
|
-
# Convert messages format
|
|
1034
|
-
user_messages =
|
|
1035
|
-
system_message = system
|
|
1036
|
-
for msg in messages:
|
|
1037
|
-
if msg["role"] == "system" and not system_message:
|
|
1038
|
-
system_message = msg["content"]
|
|
1039
|
-
elif msg["role"] in ["user", "assistant"]:
|
|
1040
|
-
user_messages.append(msg)
|
|
1096
|
+
# Convert messages format for Anthropic
|
|
1097
|
+
user_messages, system_message = _convert_anthropic_messages(messages, system)
|
|
1041
1098
|
|
|
1099
|
+
# Retry loop with exponential backoff
|
|
1042
1100
|
last_exception = None
|
|
1043
1101
|
for attempt in range(self.config.max_retries):
|
|
1044
1102
|
try:
|
|
1045
|
-
# Build request
|
|
1046
|
-
|
|
1047
|
-
"model": self.config.model,
|
|
1048
|
-
"messages": user_messages,
|
|
1049
|
-
"max_tokens": kwargs.get("max_tokens", 1024),
|
|
1050
|
-
}
|
|
1051
|
-
if system_message:
|
|
1052
|
-
request_params["system"] = system_message
|
|
1103
|
+
# Build and send request
|
|
1104
|
+
response = self._send_anthropic_request(user_messages, system_message, kwargs)
|
|
1053
1105
|
|
|
1054
|
-
#
|
|
1055
|
-
|
|
1056
|
-
if key in kwargs:
|
|
1057
|
-
request_params[key] = kwargs[key]
|
|
1106
|
+
# Extract answer from response
|
|
1107
|
+
answer = _extract_anthropic_answer(response)
|
|
1058
1108
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
# Extract response content
|
|
1062
|
-
answer = ""
|
|
1063
|
-
for block in response.content:
|
|
1064
|
-
if hasattr(block, "text"):
|
|
1065
|
-
answer += block.text
|
|
1066
|
-
|
|
1067
|
-
# Track costs./API-020
|
|
1068
|
-
input_tokens = response.usage.input_tokens
|
|
1069
|
-
output_tokens = response.usage.output_tokens
|
|
1070
|
-
estimated_cost = 0.0
|
|
1109
|
+
# Track token usage and costs
|
|
1110
|
+
estimated_cost = self._track_anthropic_costs(response)
|
|
1071
1111
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
response.model, input_tokens, output_tokens
|
|
1075
|
-
)
|
|
1076
|
-
|
|
1077
|
-
return LLMResponse(
|
|
1078
|
-
answer=answer,
|
|
1079
|
-
confidence=None, # Anthropic doesn't provide confidence scores
|
|
1080
|
-
suggested_commands=[],
|
|
1081
|
-
metadata={
|
|
1082
|
-
"model": response.model,
|
|
1083
|
-
"usage": {
|
|
1084
|
-
"input_tokens": input_tokens,
|
|
1085
|
-
"output_tokens": output_tokens,
|
|
1086
|
-
},
|
|
1087
|
-
"stop_reason": response.stop_reason,
|
|
1088
|
-
},
|
|
1089
|
-
raw_response={
|
|
1090
|
-
"id": response.id,
|
|
1091
|
-
"type": response.type,
|
|
1092
|
-
},
|
|
1093
|
-
estimated_cost=estimated_cost,
|
|
1094
|
-
)
|
|
1112
|
+
# Build and return LLM response
|
|
1113
|
+
return _build_anthropic_response(response, answer, estimated_cost)
|
|
1095
1114
|
|
|
1096
1115
|
except self._anthropic.RateLimitError as e:
|
|
1097
1116
|
last_exception = e
|
|
1098
|
-
if
|
|
1099
|
-
|
|
1100
|
-
wait_time = 2**attempt
|
|
1101
|
-
time.sleep(wait_time)
|
|
1102
|
-
continue
|
|
1103
|
-
raise LLMError(f"Anthropic rate limit exceeded: {e}") # noqa: B904
|
|
1117
|
+
if not self._handle_rate_limit_retry(attempt):
|
|
1118
|
+
raise LLMError(f"Anthropic rate limit exceeded: {e}")
|
|
1104
1119
|
|
|
1105
1120
|
except self._anthropic.APITimeoutError as e:
|
|
1106
1121
|
last_exception = e
|
|
1107
|
-
if
|
|
1108
|
-
|
|
1109
|
-
continue
|
|
1110
|
-
raise LLMError(f"Anthropic request timeout: {e}") # noqa: B904
|
|
1122
|
+
if not self._handle_timeout_retry(attempt):
|
|
1123
|
+
raise LLMError(f"Anthropic request timeout: {e}")
|
|
1111
1124
|
|
|
1112
1125
|
except self._anthropic.APIError as e:
|
|
1113
1126
|
last_exception = e
|
|
1114
|
-
if
|
|
1115
|
-
|
|
1116
|
-
continue
|
|
1117
|
-
raise LLMError(f"Anthropic API error: {e}") # noqa: B904
|
|
1127
|
+
if not self._handle_api_error_retry(attempt):
|
|
1128
|
+
raise LLMError(f"Anthropic API error: {e}")
|
|
1118
1129
|
|
|
1119
1130
|
except Exception as e:
|
|
1120
1131
|
last_exception = e
|
|
1121
|
-
raise LLMError(f"Anthropic request failed: {e}")
|
|
1132
|
+
raise LLMError(f"Anthropic request failed: {e}")
|
|
1122
1133
|
|
|
1123
1134
|
raise LLMError(
|
|
1124
1135
|
f"Anthropic request failed after {self.config.max_retries} retries: {last_exception}"
|
|
1125
1136
|
)
|
|
1126
1137
|
|
|
1138
|
+
def _send_anthropic_request(
|
|
1139
|
+
self,
|
|
1140
|
+
user_messages: list[dict[str, str]],
|
|
1141
|
+
system_message: str | None,
|
|
1142
|
+
kwargs: dict[str, Any],
|
|
1143
|
+
) -> Any:
|
|
1144
|
+
"""Send request to Anthropic API.
|
|
1145
|
+
|
|
1146
|
+
Args:
|
|
1147
|
+
user_messages: Filtered user/assistant messages.
|
|
1148
|
+
system_message: System prompt.
|
|
1149
|
+
kwargs: Additional API parameters.
|
|
1150
|
+
|
|
1151
|
+
Returns:
|
|
1152
|
+
Anthropic API response.
|
|
1153
|
+
"""
|
|
1154
|
+
request_params = {
|
|
1155
|
+
"model": self.config.model,
|
|
1156
|
+
"messages": user_messages,
|
|
1157
|
+
"max_tokens": kwargs.get("max_tokens", 1024),
|
|
1158
|
+
}
|
|
1159
|
+
if system_message:
|
|
1160
|
+
request_params["system"] = system_message
|
|
1161
|
+
|
|
1162
|
+
# Add optional parameters
|
|
1163
|
+
for key in ["temperature", "top_p", "top_k"]:
|
|
1164
|
+
if key in kwargs:
|
|
1165
|
+
request_params[key] = kwargs[key]
|
|
1166
|
+
|
|
1167
|
+
return self.client.messages.create(**request_params)
|
|
1168
|
+
|
|
1169
|
+
def _track_anthropic_costs(self, response: Any) -> float:
|
|
1170
|
+
"""Track token usage and return estimated cost.
|
|
1171
|
+
|
|
1172
|
+
Args:
|
|
1173
|
+
response: Anthropic API response.
|
|
1174
|
+
|
|
1175
|
+
Returns:
|
|
1176
|
+
Estimated cost in USD.
|
|
1177
|
+
"""
|
|
1178
|
+
input_tokens = response.usage.input_tokens
|
|
1179
|
+
output_tokens = response.usage.output_tokens
|
|
1180
|
+
|
|
1181
|
+
if self.config.track_costs:
|
|
1182
|
+
return _global_cost_tracker.record(response.model, input_tokens, output_tokens)
|
|
1183
|
+
return 0.0
|
|
1184
|
+
|
|
1185
|
+
def _handle_rate_limit_retry(self, attempt: int) -> bool:
|
|
1186
|
+
"""Handle rate limit error with exponential backoff.
|
|
1187
|
+
|
|
1188
|
+
Args:
|
|
1189
|
+
attempt: Current attempt number.
|
|
1190
|
+
|
|
1191
|
+
Returns:
|
|
1192
|
+
True if should continue retry, False if should raise.
|
|
1193
|
+
"""
|
|
1194
|
+
if attempt < self.config.max_retries - 1:
|
|
1195
|
+
wait_time = 2**attempt
|
|
1196
|
+
time.sleep(wait_time)
|
|
1197
|
+
return True
|
|
1198
|
+
return False
|
|
1199
|
+
|
|
1200
|
+
def _handle_timeout_retry(self, attempt: int) -> bool:
|
|
1201
|
+
"""Handle timeout error with retry.
|
|
1202
|
+
|
|
1203
|
+
Args:
|
|
1204
|
+
attempt: Current attempt number.
|
|
1205
|
+
|
|
1206
|
+
Returns:
|
|
1207
|
+
True if should continue retry, False if should raise.
|
|
1208
|
+
"""
|
|
1209
|
+
if attempt < self.config.max_retries - 1:
|
|
1210
|
+
time.sleep(1)
|
|
1211
|
+
return True
|
|
1212
|
+
return False
|
|
1213
|
+
|
|
1214
|
+
def _handle_api_error_retry(self, attempt: int) -> bool:
|
|
1215
|
+
"""Handle API error with retry.
|
|
1216
|
+
|
|
1217
|
+
Args:
|
|
1218
|
+
attempt: Current attempt number.
|
|
1219
|
+
|
|
1220
|
+
Returns:
|
|
1221
|
+
True if should continue retry, False if should raise.
|
|
1222
|
+
"""
|
|
1223
|
+
if attempt < self.config.max_retries - 1:
|
|
1224
|
+
time.sleep(1)
|
|
1225
|
+
return True
|
|
1226
|
+
return False
|
|
1227
|
+
|
|
1127
1228
|
def analyze_trace(self, trace: Any, question: str) -> LLMResponse:
|
|
1128
1229
|
"""Analyze trace with question.
|
|
1129
1230
|
|
|
@@ -1384,9 +1485,7 @@ def get_provider(name: str, **config_kwargs: Any) -> LLMClient:
|
|
|
1384
1485
|
try:
|
|
1385
1486
|
provider_enum = LLMProvider(name.lower())
|
|
1386
1487
|
except ValueError:
|
|
1387
|
-
raise LLMError(
|
|
1388
|
-
f"Unknown provider: {name}. Available: {[p.value for p in LLMProvider]}"
|
|
1389
|
-
)
|
|
1488
|
+
raise LLMError(f"Unknown provider: {name}. Available: {[p.value for p in LLMProvider]}")
|
|
1390
1489
|
|
|
1391
1490
|
# Build config with sensible defaults
|
|
1392
1491
|
config = LLMConfig(
|
|
@@ -1416,7 +1515,7 @@ def get_provider(name: str, **config_kwargs: Any) -> LLMClient:
|
|
|
1416
1515
|
)
|
|
1417
1516
|
except ImportError as e:
|
|
1418
1517
|
# .: Graceful degradation when API unavailable
|
|
1419
|
-
raise LLMError(
|
|
1518
|
+
raise LLMError(
|
|
1420
1519
|
f"Provider {name} unavailable: {e}. "
|
|
1421
1520
|
"Install the required package or use 'local' provider."
|
|
1422
1521
|
)
|
|
@@ -1668,7 +1767,7 @@ class FailoverLLMClient:
|
|
|
1668
1767
|
def operation(client: LLMClient) -> str:
|
|
1669
1768
|
if hasattr(client, "chat_completion"):
|
|
1670
1769
|
messages = [{"role": "user", "content": prompt}]
|
|
1671
|
-
response = client.chat_completion(messages, **kwargs)
|
|
1770
|
+
response = client.chat_completion(messages, **kwargs)
|
|
1672
1771
|
return response.answer # type: ignore[no-any-return]
|
|
1673
1772
|
else:
|
|
1674
1773
|
response = client.query(prompt, {})
|
|
@@ -1697,7 +1796,7 @@ class FailoverLLMClient:
|
|
|
1697
1796
|
trace = DictTrace(trace_data)
|
|
1698
1797
|
|
|
1699
1798
|
if hasattr(client, "analyze_trace"):
|
|
1700
|
-
response = client.analyze_trace(trace, "Analyze this signal")
|
|
1799
|
+
response = client.analyze_trace(trace, "Analyze this signal")
|
|
1701
1800
|
else:
|
|
1702
1801
|
response = client.analyze(trace, "Analyze this signal")
|
|
1703
1802
|
|
|
@@ -1729,7 +1828,7 @@ class FailoverLLMClient:
|
|
|
1729
1828
|
trace = CharTrace(signal_characteristics)
|
|
1730
1829
|
|
|
1731
1830
|
if hasattr(client, "suggest_measurements"):
|
|
1732
|
-
response = client.suggest_measurements(trace)
|
|
1831
|
+
response = client.suggest_measurements(trace)
|
|
1733
1832
|
else:
|
|
1734
1833
|
response = client.analyze(trace, "What measurements should I perform?")
|
|
1735
1834
|
|
|
@@ -1795,7 +1894,7 @@ def is_provider_available(provider: str) -> bool:
|
|
|
1795
1894
|
if not os.environ.get("OPENAI_API_KEY"):
|
|
1796
1895
|
return False
|
|
1797
1896
|
try:
|
|
1798
|
-
import openai #
|
|
1897
|
+
import openai # noqa: F401
|
|
1799
1898
|
|
|
1800
1899
|
return True
|
|
1801
1900
|
except ImportError:
|
|
@@ -1805,7 +1904,7 @@ def is_provider_available(provider: str) -> bool:
|
|
|
1805
1904
|
if not os.environ.get("ANTHROPIC_API_KEY"):
|
|
1806
1905
|
return False
|
|
1807
1906
|
try:
|
|
1808
|
-
import anthropic #
|
|
1907
|
+
import anthropic # noqa: F401
|
|
1809
1908
|
|
|
1810
1909
|
return True
|
|
1811
1910
|
except ImportError:
|
oscura/api/operators.py
CHANGED
|
@@ -46,7 +46,7 @@ class TimeIndex:
|
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
48
|
# Time unit multipliers to seconds
|
|
49
|
-
TIME_UNITS = {
|
|
49
|
+
TIME_UNITS = {
|
|
50
50
|
"s": 1.0,
|
|
51
51
|
"ms": 1e-3,
|
|
52
52
|
"us": 1e-6,
|
|
@@ -212,7 +212,7 @@ class UnitConverter:
|
|
|
212
212
|
"""
|
|
213
213
|
|
|
214
214
|
# SI prefixes
|
|
215
|
-
SI_PREFIXES = {
|
|
215
|
+
SI_PREFIXES = {
|
|
216
216
|
"P": 1e15, # peta
|
|
217
217
|
"T": 1e12, # tera
|
|
218
218
|
"G": 1e9, # giga
|
|
@@ -227,7 +227,7 @@ class UnitConverter:
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
# Base units
|
|
230
|
-
BASE_UNITS = {
|
|
230
|
+
BASE_UNITS = {
|
|
231
231
|
"V": "voltage",
|
|
232
232
|
"A": "current",
|
|
233
233
|
"W": "power",
|