lex-llm 0.4.18 → 0.5.1
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -2
- data/B1b-conformance-kit.md +79 -0
- data/CHANGELOG.md +27 -0
- data/lex-llm.gemspec +2 -3
- data/lib/legion/extensions/llm/attachment.rb +1 -1
- data/lib/legion/extensions/llm/canonical/chunk.rb +184 -0
- data/lib/legion/extensions/llm/canonical/content_block.rb +126 -0
- data/lib/legion/extensions/llm/canonical/message.rb +138 -0
- data/lib/legion/extensions/llm/canonical/params.rb +61 -0
- data/lib/legion/extensions/llm/canonical/request.rb +117 -0
- data/lib/legion/extensions/llm/canonical/response.rb +124 -0
- data/lib/legion/extensions/llm/canonical/thinking.rb +81 -0
- data/lib/legion/extensions/llm/canonical/tool_call.rb +134 -0
- data/lib/legion/extensions/llm/canonical/tool_definition.rb +98 -0
- data/lib/legion/extensions/llm/canonical/tool_schema.rb +46 -0
- data/lib/legion/extensions/llm/canonical/usage.rb +74 -0
- data/lib/legion/extensions/llm/canonical.rb +50 -0
- data/lib/legion/extensions/llm/chat.rb +3 -5
- data/lib/legion/extensions/llm/connection.rb +5 -1
- data/lib/legion/extensions/llm/error.rb +5 -7
- data/lib/legion/extensions/llm/fleet/envelope_validation.rb +1 -3
- data/lib/legion/extensions/llm/fleet/provider_responder.rb +1 -3
- data/lib/legion/extensions/llm/fleet/token_validator.rb +1 -3
- data/lib/legion/extensions/llm/model/info.rb +4 -6
- data/lib/legion/extensions/llm/models.rb +3 -3
- data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +9 -4
- data/lib/legion/extensions/llm/provider.rb +21 -4
- data/lib/legion/extensions/llm/provider_contract.rb +10 -1
- data/lib/legion/extensions/llm/routing/lane_key.rb +1 -3
- data/lib/legion/extensions/llm/stream_accumulator.rb +40 -1
- data/lib/legion/extensions/llm/streaming.rb +13 -5
- data/lib/legion/extensions/llm/tool.rb +1 -3
- data/lib/legion/extensions/llm/version.rb +1 -1
- data/lib/legion/extensions/llm.rb +118 -35
- data/spec/fixtures/ruby.mp3 +0 -0
- data/spec/fixtures/ruby.mp4 +0 -0
- data/spec/fixtures/ruby.png +0 -0
- data/spec/fixtures/ruby.txt +1 -0
- data/spec/fixtures/ruby.wav +0 -0
- data/spec/fixtures/ruby.xml +1 -0
- data/spec/fixtures/sample.pdf +0 -0
- data/spec/legion/extensions/llm/agent_spec.rb +179 -0
- data/spec/legion/extensions/llm/attachment_spec.rb +25 -0
- data/spec/legion/extensions/llm/auto_registration_spec.rb +38 -0
- data/spec/legion/extensions/llm/canonical/chunk_spec.rb +285 -0
- data/spec/legion/extensions/llm/canonical/content_block_spec.rb +179 -0
- data/spec/legion/extensions/llm/canonical/message_spec.rb +203 -0
- data/spec/legion/extensions/llm/canonical/params_spec.rb +159 -0
- data/spec/legion/extensions/llm/canonical/request_spec.rb +174 -0
- data/spec/legion/extensions/llm/canonical/response_spec.rb +234 -0
- data/spec/legion/extensions/llm/canonical/thinking_spec.rb +151 -0
- data/spec/legion/extensions/llm/canonical/tool_call_spec.rb +191 -0
- data/spec/legion/extensions/llm/canonical/tool_definition_spec.rb +221 -0
- data/spec/legion/extensions/llm/canonical/tool_schema_spec.rb +83 -0
- data/spec/legion/extensions/llm/canonical/usage_spec.rb +178 -0
- data/spec/legion/extensions/llm/configuration_spec.rb +38 -0
- data/spec/legion/extensions/llm/conformance/client_translator_examples.rb +432 -0
- data/spec/legion/extensions/llm/conformance/conformance.rb +51 -0
- data/spec/legion/extensions/llm/conformance/echo_translator.rb +56 -0
- data/spec/legion/extensions/llm/conformance/echo_translator_spec.rb +13 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_empty_response.json +13 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_error_response.json +19 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_fleet_round_trip.json +81 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_metering_audit_events.json +101 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_params_mapping_request.json +21 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_server_tool_continuation_request.json +43 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_server_tool_use_response.json +29 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_simple_text_request.json +13 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_simple_text_response.json +13 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_stop_reason_matrix.json +36 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_streaming_accumulated_response.json +20 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_streaming_error_chunks.json +26 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_streaming_server_tool_chunks.json +52 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_streaming_text_chunks.json +33 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_streaming_thinking_chunks.json +42 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_streaming_tool_call_chunks.json +41 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_system_prompt_request.json +14 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_thinking_request.json +18 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_thinking_response.json +17 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_tool_results_continuation_request.json +75 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_tool_use_response.json +25 -0
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_tools_request.json +34 -0
- data/spec/legion/extensions/llm/conformance/provider_tool_rendering_examples.rb +77 -0
- data/spec/legion/extensions/llm/conformance/provider_translator_examples.rb +390 -0
- data/spec/legion/extensions/llm/connection_logging_spec.rb +53 -0
- data/spec/legion/extensions/llm/connection_retry_spec.rb +36 -0
- data/spec/legion/extensions/llm/context_spec.rb +127 -0
- data/spec/legion/extensions/llm/credential_sources_spec.rb +468 -0
- data/spec/legion/extensions/llm/error_middleware_spec.rb +102 -0
- data/spec/legion/extensions/llm/error_spec.rb +87 -0
- data/spec/legion/extensions/llm/fleet/provider_responder_spec.rb +120 -0
- data/spec/legion/extensions/llm/fleet/token_validator_spec.rb +163 -0
- data/spec/legion/extensions/llm/fleet/worker_execution_spec.rb +128 -0
- data/spec/legion/extensions/llm/fleet_messages_spec.rb +402 -0
- data/spec/legion/extensions/llm/gemspec_spec.rb +25 -0
- data/spec/legion/extensions/llm/message_spec.rb +64 -0
- data/spec/legion/extensions/llm/model/info_spec.rb +222 -0
- data/spec/legion/extensions/llm/models_spec.rb +104 -0
- data/spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb +203 -0
- data/spec/legion/extensions/llm/provider/open_ai_compatible_tool_calls_array_spec.rb +68 -0
- data/spec/legion/extensions/llm/provider_contract_spec.rb +60 -0
- data/spec/legion/extensions/llm/provider_settings_spec.rb +76 -0
- data/spec/legion/extensions/llm/provider_spec.rb +613 -0
- data/spec/legion/extensions/llm/registry_event_builder_spec.rb +68 -0
- data/spec/legion/extensions/llm/registry_publisher_spec.rb +22 -0
- data/spec/legion/extensions/llm/responses/response_objects_spec.rb +75 -0
- data/spec/legion/extensions/llm/responses/thinking_extractor_spec.rb +75 -0
- data/spec/legion/extensions/llm/routing/model_offering_spec.rb +222 -0
- data/spec/legion/extensions/llm/routing/offering_registry_spec.rb +50 -0
- data/spec/legion/extensions/llm/routing/registry_event_spec.rb +120 -0
- data/spec/legion/extensions/llm/stream_accumulator_spec.rb +155 -0
- data/spec/legion/extensions/llm/streaming_spec.rb +108 -0
- data/spec/legion/extensions/llm/tool_spec.rb +94 -0
- data/spec/legion/extensions/llm/transport/fleet_lane_spec.rb +60 -0
- data/spec/legion/extensions/llm/utils_spec.rb +113 -0
- data/spec/legion/extensions/llm_base_contract_spec.rb +110 -0
- data/spec/legion/extensions/llm_extension_spec.rb +78 -0
- data/spec/legion/extensions/llm_root_spec.rb +51 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/fake_llm_provider.rb +148 -0
- data/spec/support/llm_configuration.rb +21 -0
- data/spec/support/rspec_configuration.rb +19 -0
- data/spec/support/simplecov_configuration.rb +20 -0
- metadata +103 -15
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Trivial echo translator for conformance kit self-testing.
|
|
4
|
+
# Passes canonical types through unchanged, proving the shared example groups work.
|
|
5
|
+
|
|
6
|
+
module Canonical
|
|
7
|
+
module Conformance
|
|
8
|
+
# Echo translator: identity transform for both provider and client sides.
|
|
9
|
+
# Used exclusively as a self-test to verify the conformance kit works.
|
|
10
|
+
class EchoTranslator
|
|
11
|
+
def capabilities
|
|
12
|
+
{ provider: 'echo', thinking: true, streaming: true, tool_calls: true }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Provider translator interface
|
|
16
|
+
def render_request(canonical_request)
|
|
17
|
+
canonical_request.to_h
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse_response(wire_hash)
|
|
21
|
+
canonical::Response.from_hash(wire_hash)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parse_chunk(raw_chunk)
|
|
25
|
+
canonical::Chunk.from_hash(raw_chunk)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Client translator interface
|
|
29
|
+
def format_request(canonical_request)
|
|
30
|
+
canonical_request.to_h
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parse_request(body, _env = {})
|
|
34
|
+
canonical::Request.from_hash(body)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def format_response(canonical_response)
|
|
38
|
+
canonical_response.to_h
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def format_chunk(canonical_chunk)
|
|
42
|
+
canonical_chunk.to_h
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def format_error(error, status)
|
|
46
|
+
[status, { error: error.message, type: error.class.name }]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def canonical
|
|
52
|
+
Legion::Extensions::Llm::Canonical
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require_relative 'conformance'
|
|
5
|
+
require_relative 'echo_translator'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Canonical::Conformance::EchoTranslator do
|
|
8
|
+
# Self-test: the echo translator passes both conformance groups,
|
|
9
|
+
# proving the shared example groups work correctly.
|
|
10
|
+
|
|
11
|
+
it_behaves_like 'a canonical provider translator', described_class
|
|
12
|
+
it_behaves_like 'a canonical client translator', described_class
|
|
13
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text": "",
|
|
3
|
+
"thinking": null,
|
|
4
|
+
"tool_calls": [],
|
|
5
|
+
"usage": {
|
|
6
|
+
"input_tokens": 0,
|
|
7
|
+
"output_tokens": 0
|
|
8
|
+
},
|
|
9
|
+
"stop_reason": "error",
|
|
10
|
+
"model": "test-model-1",
|
|
11
|
+
"routing": {},
|
|
12
|
+
"metadata": {
|
|
13
|
+
"error": {
|
|
14
|
+
"type": "invalid_request_error",
|
|
15
|
+
"message": "Model nonexistent-model-xyz-12345 not found",
|
|
16
|
+
"code": 404
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Fleet round-trip field mapping — R6",
|
|
3
|
+
"round_trip_request": {
|
|
4
|
+
"id": "req_fleet_rt_001",
|
|
5
|
+
"messages": [
|
|
6
|
+
{
|
|
7
|
+
"role": "user",
|
|
8
|
+
"content": [{ "type": "text", "text": "Fleet round-trip test message" }]
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"system": "You are a test assistant.",
|
|
12
|
+
"tools": {
|
|
13
|
+
"test_tool": {
|
|
14
|
+
"name": "test_tool",
|
|
15
|
+
"description": "A test tool for round-trip validation",
|
|
16
|
+
"parameters": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": { "input": { "type": "string" } },
|
|
19
|
+
"required": ["input"]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"params": { "max_tokens": 1024, "temperature": 0.5 },
|
|
24
|
+
"thinking": { "effort": "low", "budget": 1024 },
|
|
25
|
+
"stream": false,
|
|
26
|
+
"conversation_id": "conv_fleet_rt_001",
|
|
27
|
+
"caller": { "type": "anthropic_messages", "client_id": "test_client" },
|
|
28
|
+
"routing": { "tier": "primary", "provider": "anthropic", "model": "claude-sonnet-4-6" },
|
|
29
|
+
"metadata": { "trace_id": "trace_abc123", "span_id": "span_def456" }
|
|
30
|
+
},
|
|
31
|
+
"round_trip_response": {
|
|
32
|
+
"text": "This is a fleet round-trip test response.",
|
|
33
|
+
"thinking": { "content": "Processing fleet round-trip test.", "signature": "sig_test_001" },
|
|
34
|
+
"tool_calls": [
|
|
35
|
+
{
|
|
36
|
+
"id": "call_fleet_rt_001",
|
|
37
|
+
"exchange_id": "exch_fleet_rt_001",
|
|
38
|
+
"name": "test_tool",
|
|
39
|
+
"arguments": { "input": "round_trip_test" },
|
|
40
|
+
"source": "client",
|
|
41
|
+
"status": "pending"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"usage": {
|
|
45
|
+
"input_tokens": 50,
|
|
46
|
+
"output_tokens": 30,
|
|
47
|
+
"cache_read_tokens": 10,
|
|
48
|
+
"cache_write_tokens": 5,
|
|
49
|
+
"thinking_tokens": 20
|
|
50
|
+
},
|
|
51
|
+
"stop_reason": "tool_use",
|
|
52
|
+
"model": "claude-sonnet-4-6",
|
|
53
|
+
"routing": { "tier": "primary", "provider": "anthropic", "instance": "us-east-1" },
|
|
54
|
+
"metadata": { "trace_id": "trace_abc123", "span_id": "span_def456", "latency_ms": 1250 }
|
|
55
|
+
},
|
|
56
|
+
"field_mapping": {
|
|
57
|
+
"request": {
|
|
58
|
+
"id": "string — unique request identifier",
|
|
59
|
+
"messages": "array[Message] — conversation history",
|
|
60
|
+
"system": "string | nil — system prompt",
|
|
61
|
+
"tools": "hash[name -> ToolDefinition] — available tools",
|
|
62
|
+
"params": "Params — sampling parameters",
|
|
63
|
+
"thinking": "Thinking::Config | nil — thinking configuration",
|
|
64
|
+
"stream": "boolean — streaming mode",
|
|
65
|
+
"conversation_id": "string | nil — conversation grouping",
|
|
66
|
+
"caller": "hash — client translator identity",
|
|
67
|
+
"routing": "hash — routing hints",
|
|
68
|
+
"metadata": "hash — passthrough metadata"
|
|
69
|
+
},
|
|
70
|
+
"response": {
|
|
71
|
+
"text": "string — assistant text content",
|
|
72
|
+
"thinking": "Thinking | nil — thinking block with content and signature",
|
|
73
|
+
"tool_calls": "array[ToolCall] — tool call requests",
|
|
74
|
+
"usage": "Usage — token usage breakdown",
|
|
75
|
+
"stop_reason": "symbol — why generation stopped",
|
|
76
|
+
"model": "string — resolved model identifier",
|
|
77
|
+
"routing": "hash — actual routing used",
|
|
78
|
+
"metadata": "hash — provider passthrough quirks"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Metering/audit event schemas — G15e",
|
|
3
|
+
"schemas": {
|
|
4
|
+
"metering_event": {
|
|
5
|
+
"event_type": "metering",
|
|
6
|
+
"required_fields": {
|
|
7
|
+
"exchange_id": "string",
|
|
8
|
+
"request_id": "string",
|
|
9
|
+
"conversation_id": "string | nil",
|
|
10
|
+
"model": "string",
|
|
11
|
+
"provider": "string",
|
|
12
|
+
"usage": {
|
|
13
|
+
"input_tokens": "integer",
|
|
14
|
+
"output_tokens": "integer",
|
|
15
|
+
"cache_read_tokens": "integer | nil",
|
|
16
|
+
"cache_write_tokens": "integer | nil",
|
|
17
|
+
"thinking_tokens": "integer | nil"
|
|
18
|
+
},
|
|
19
|
+
"cost": {
|
|
20
|
+
"input_cost_usd": "float | nil",
|
|
21
|
+
"output_cost_usd": "float | nil",
|
|
22
|
+
"total_cost_usd": "float | nil"
|
|
23
|
+
},
|
|
24
|
+
"latency_ms": "integer",
|
|
25
|
+
"timestamp": "ISO8601 string"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"audit_event": {
|
|
29
|
+
"event_type": "audit",
|
|
30
|
+
"required_fields": {
|
|
31
|
+
"exchange_id": "string",
|
|
32
|
+
"request_id": "string",
|
|
33
|
+
"conversation_id": "string | nil",
|
|
34
|
+
"model": "string",
|
|
35
|
+
"provider": "string",
|
|
36
|
+
"caller": "hash",
|
|
37
|
+
"status": "symbol — :success, :error, :partial",
|
|
38
|
+
"stop_reason": "symbol | nil",
|
|
39
|
+
"timestamp": "ISO8601 string"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"tool_call_audit_event": {
|
|
43
|
+
"event_type": "tool_call_audit",
|
|
44
|
+
"required_fields": {
|
|
45
|
+
"exchange_id": "string",
|
|
46
|
+
"tool_call_id": "string",
|
|
47
|
+
"name": "string",
|
|
48
|
+
"source": "symbol — :client, :registry, :special, :extension, :mcp",
|
|
49
|
+
"status": "symbol — :pending, :running, :success, :error",
|
|
50
|
+
"arguments": "hash",
|
|
51
|
+
"timestamp": "ISO8601 string"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"example_events": {
|
|
56
|
+
"metering_success": {
|
|
57
|
+
"event_type": "metering",
|
|
58
|
+
"exchange_id": "exch_meter_001",
|
|
59
|
+
"request_id": "req_meter_001",
|
|
60
|
+
"conversation_id": "conv_001",
|
|
61
|
+
"model": "claude-sonnet-4-6",
|
|
62
|
+
"provider": "anthropic",
|
|
63
|
+
"usage": { "input_tokens": 150, "output_tokens": 85, "cache_read_tokens": 20 },
|
|
64
|
+
"cost": { "input_cost_usd": 0.0015, "output_cost_usd": 0.00765, "total_cost_usd": 0.00915 },
|
|
65
|
+
"latency_ms": 1250,
|
|
66
|
+
"stop_reason": "end_turn",
|
|
67
|
+
"routing": { "tier": "primary", "provider": "anthropic" },
|
|
68
|
+
"tool_calls_count": 0,
|
|
69
|
+
"timestamp": "2026-06-10T12:00:00Z"
|
|
70
|
+
},
|
|
71
|
+
"audit_success": {
|
|
72
|
+
"event_type": "audit",
|
|
73
|
+
"exchange_id": "exch_audit_001",
|
|
74
|
+
"request_id": "req_audit_001",
|
|
75
|
+
"conversation_id": "conv_001",
|
|
76
|
+
"model": "claude-sonnet-4-6",
|
|
77
|
+
"provider": "anthropic",
|
|
78
|
+
"caller": { "type": "anthropic_messages", "client_id": "test_client" },
|
|
79
|
+
"status": "success",
|
|
80
|
+
"stop_reason": "end_turn",
|
|
81
|
+
"routing": { "tier": "primary", "provider": "anthropic" },
|
|
82
|
+
"usage": { "input_tokens": 150, "output_tokens": 85 },
|
|
83
|
+
"route_attempts": [
|
|
84
|
+
{ "attempt": 1, "provider": "anthropic", "model": "claude-sonnet-4-6", "status": "success" }
|
|
85
|
+
],
|
|
86
|
+
"timestamp": "2026-06-10T12:00:00Z"
|
|
87
|
+
},
|
|
88
|
+
"tool_call_audit": {
|
|
89
|
+
"event_type": "tool_call_audit",
|
|
90
|
+
"exchange_id": "exch_tc_audit_001",
|
|
91
|
+
"tool_call_id": "call_tc_audit_001",
|
|
92
|
+
"name": "get_weather",
|
|
93
|
+
"source": "client",
|
|
94
|
+
"status": "success",
|
|
95
|
+
"arguments": { "location": "San Francisco, CA", "unit": "fahrenheit" },
|
|
96
|
+
"result": { "temperature": 68, "unit": "fahrenheit", "conditions": "partly cloudy" },
|
|
97
|
+
"duration_ms": 250,
|
|
98
|
+
"timestamp": "2026-06-10T12:00:01Z"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "req_params_mapping_001",
|
|
3
|
+
"messages": [
|
|
4
|
+
{
|
|
5
|
+
"role": "user",
|
|
6
|
+
"content": [{ "type": "text", "text": "Generate a creative story." }]
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"params": {
|
|
10
|
+
"max_tokens": 2048,
|
|
11
|
+
"temperature": 0.7,
|
|
12
|
+
"top_p": 0.9,
|
|
13
|
+
"top_k": 50,
|
|
14
|
+
"stop_sequences": ["[END]", "\\n\\n"],
|
|
15
|
+
"seed": 42,
|
|
16
|
+
"frequency_penalty": 0.1,
|
|
17
|
+
"presence_penalty": 0.2,
|
|
18
|
+
"response_format": { "type": "json_object" }
|
|
19
|
+
},
|
|
20
|
+
"stream": false
|
|
21
|
+
}
|
data/spec/legion/extensions/llm/conformance/fixtures/canonical_server_tool_continuation_request.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "G24 round-trip — when a client sends back a history that contains a completed server-side exchange (Claude: server_tool_use+server_tool_result blocks; Codex: completed function_call+function_call_output items), the client translator must parse them into canonical messages losslessly so the next turn rendering still attributes the call to the assistant and its result to the tool role. Tools array is empty because the server-side exchange is already closed; this request is just continuing the conversation.",
|
|
3
|
+
"id": "req_g24_continuation_001",
|
|
4
|
+
"messages": [
|
|
5
|
+
{
|
|
6
|
+
"role": "user",
|
|
7
|
+
"content": "what legionio tools do you have available?"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"role": "assistant",
|
|
11
|
+
"content": "I called the legion_list_all_tools tool.",
|
|
12
|
+
"tool_calls": [
|
|
13
|
+
{
|
|
14
|
+
"id": "call_legion_001",
|
|
15
|
+
"name": "legion_list_all_tools",
|
|
16
|
+
"arguments": {"filter": "all"},
|
|
17
|
+
"source": "registry",
|
|
18
|
+
"status": "success",
|
|
19
|
+
"result": "tools: legion_list_all_tools, legion_apollo_search"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"role": "tool",
|
|
25
|
+
"tool_call_id": "call_legion_001",
|
|
26
|
+
"content": "tools: legion_list_all_tools, legion_apollo_search"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"role": "user",
|
|
30
|
+
"content": "thanks — anything else?"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"system": null,
|
|
34
|
+
"tools": null,
|
|
35
|
+
"params": null,
|
|
36
|
+
"thinking": null,
|
|
37
|
+
"stream": false,
|
|
38
|
+
"conversation_id": "conv_g24_001",
|
|
39
|
+
"routing": {},
|
|
40
|
+
"metadata": {
|
|
41
|
+
"g24": "continuation"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "G24 — server-executed LegionIO tool. The provider returned tool_use, the executor ran the tool registry-side, and the canonical response carries both the call AND its result so client translators can surface it as a completed exchange (Claude: server_tool_use+server_tool_result, Codex: completed function_call+function_call_output non-actionable). stop_reason is end_turn because the server-side exchange is closed by the time the canonical response is built.",
|
|
3
|
+
"text": "I called the legion_list_all_tools tool and here is the list.",
|
|
4
|
+
"thinking": null,
|
|
5
|
+
"tool_calls": [
|
|
6
|
+
{
|
|
7
|
+
"id": "call_legion_001",
|
|
8
|
+
"exchange_id": "exch_legion_001",
|
|
9
|
+
"name": "legion_list_all_tools",
|
|
10
|
+
"arguments": {
|
|
11
|
+
"filter": "all"
|
|
12
|
+
},
|
|
13
|
+
"source": "registry",
|
|
14
|
+
"status": "success",
|
|
15
|
+
"result": "tools: legion_list_all_tools, legion_apollo_search, legion_runner_dispatch",
|
|
16
|
+
"duration_ms": 42
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"usage": {
|
|
20
|
+
"input_tokens": 50,
|
|
21
|
+
"output_tokens": 22
|
|
22
|
+
},
|
|
23
|
+
"stop_reason": "end_turn",
|
|
24
|
+
"model": "test-model-1",
|
|
25
|
+
"routing": {},
|
|
26
|
+
"metadata": {
|
|
27
|
+
"g24": true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Stop reason mapping matrix — every provider value maps to a canonical enum",
|
|
3
|
+
"canonical_stop_reasons": ["end_turn", "tool_use", "max_tokens", "stop_sequence", "content_filter", "error"],
|
|
4
|
+
"provider_mappings": {
|
|
5
|
+
"anthropic": {
|
|
6
|
+
"end_turn": "end_turn",
|
|
7
|
+
"tool_use": "tool_use",
|
|
8
|
+
"max_tokens": "max_tokens",
|
|
9
|
+
"stop_sequence": "stop_sequence",
|
|
10
|
+
"content_filter": "content_filter"
|
|
11
|
+
},
|
|
12
|
+
"openai": {
|
|
13
|
+
"stop": "end_turn",
|
|
14
|
+
"tool_calls": "tool_use",
|
|
15
|
+
"length": "max_tokens",
|
|
16
|
+
"content_filter": "content_filter"
|
|
17
|
+
},
|
|
18
|
+
"openai_responses": {
|
|
19
|
+
"completed": "end_turn",
|
|
20
|
+
"incomplete": "max_tokens",
|
|
21
|
+
"cancelled": "stop_sequence",
|
|
22
|
+
"failed": "error"
|
|
23
|
+
},
|
|
24
|
+
"vllm": {
|
|
25
|
+
"stop": "end_turn",
|
|
26
|
+
"tool_use": "tool_use",
|
|
27
|
+
"length": "max_tokens"
|
|
28
|
+
},
|
|
29
|
+
"bedrock_converse": {
|
|
30
|
+
"end_turn": "end_turn",
|
|
31
|
+
"tool_use": "tool_use",
|
|
32
|
+
"max_tokens": "max_tokens",
|
|
33
|
+
"guardrail_intervened": "content_filter"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
data/spec/legion/extensions/llm/conformance/fixtures/canonical_streaming_accumulated_response.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Expected accumulated Canonical::Response after processing canonical_streaming_tool_call_chunks.json — verify accumulate(chunks) == parse(full)",
|
|
3
|
+
"text": "Let me check the weather for you.",
|
|
4
|
+
"thinking": null,
|
|
5
|
+
"tool_calls": [
|
|
6
|
+
{
|
|
7
|
+
"id": "call_def456",
|
|
8
|
+
"name": "get_weather",
|
|
9
|
+
"arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"},
|
|
10
|
+
"source": "client",
|
|
11
|
+
"status": "pending"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"usage": {
|
|
15
|
+
"input_tokens": 45,
|
|
16
|
+
"output_tokens": 28
|
|
17
|
+
},
|
|
18
|
+
"stop_reason": "tool_use",
|
|
19
|
+
"model": "test-model-1"
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Streaming error mid-chunk sequence — provider errors during stream per G5/G6",
|
|
3
|
+
"request_id": "req_stream_error_001",
|
|
4
|
+
"chunks": [
|
|
5
|
+
{
|
|
6
|
+
"request_id": "req_stream_error_001",
|
|
7
|
+
"index": 0,
|
|
8
|
+
"type": "text_delta",
|
|
9
|
+
"delta": "The weather"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"request_id": "req_stream_error_001",
|
|
13
|
+
"index": 1,
|
|
14
|
+
"type": "error",
|
|
15
|
+
"stop_reason": "error",
|
|
16
|
+
"metadata": {
|
|
17
|
+
"error": {
|
|
18
|
+
"type": "overloaded",
|
|
19
|
+
"message": "Upstream provider timed out",
|
|
20
|
+
"code": 504
|
|
21
|
+
},
|
|
22
|
+
"partial_text": "The weather"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
data/spec/legion/extensions/llm/conformance/fixtures/canonical_streaming_server_tool_chunks.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "G24 — streaming sequence for a server-executed LegionIO tool. The server-side exchange streams as tool_call_delta(s) (with the result attached on close) followed by trailing text from round-2. Client translators must turn this into completed (non-actionable) blocks: Claude server_tool_use+server_tool_result blocks, Codex completed function_call items with results visible.",
|
|
3
|
+
"request_id": "req_stream_legion_001",
|
|
4
|
+
"chunks": [
|
|
5
|
+
{
|
|
6
|
+
"request_id": "req_stream_legion_001",
|
|
7
|
+
"index": 0,
|
|
8
|
+
"type": "tool_call_delta",
|
|
9
|
+
"tool_call": {
|
|
10
|
+
"id": "call_legion_stream_001",
|
|
11
|
+
"name": "legion_list_all_tools",
|
|
12
|
+
"arguments": {"filter": "all"},
|
|
13
|
+
"source": "registry",
|
|
14
|
+
"status": "running"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"request_id": "req_stream_legion_001",
|
|
19
|
+
"index": 0,
|
|
20
|
+
"type": "tool_call_delta",
|
|
21
|
+
"tool_call": {
|
|
22
|
+
"id": "call_legion_stream_001",
|
|
23
|
+
"name": "legion_list_all_tools",
|
|
24
|
+
"arguments": {"filter": "all"},
|
|
25
|
+
"source": "registry",
|
|
26
|
+
"status": "success",
|
|
27
|
+
"result": "tools: legion_list_all_tools, legion_apollo_search"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"request_id": "req_stream_legion_001",
|
|
32
|
+
"index": 1,
|
|
33
|
+
"type": "text_delta",
|
|
34
|
+
"delta": "I called the "
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"request_id": "req_stream_legion_001",
|
|
38
|
+
"index": 1,
|
|
39
|
+
"type": "text_delta",
|
|
40
|
+
"delta": "legion_list_all_tools tool."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"request_id": "req_stream_legion_001",
|
|
44
|
+
"type": "done",
|
|
45
|
+
"stop_reason": "end_turn",
|
|
46
|
+
"usage": {
|
|
47
|
+
"input_tokens": 50,
|
|
48
|
+
"output_tokens": 25
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Streaming text-only chunk sequence — sanitized from real E2E traffic",
|
|
3
|
+
"request_id": "req_stream_text_001",
|
|
4
|
+
"chunks": [
|
|
5
|
+
{
|
|
6
|
+
"request_id": "req_stream_text_001",
|
|
7
|
+
"index": 0,
|
|
8
|
+
"type": "text_delta",
|
|
9
|
+
"delta": "Hello"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"request_id": "req_stream_text_001",
|
|
13
|
+
"index": 1,
|
|
14
|
+
"type": "text_delta",
|
|
15
|
+
"delta": ", world!"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"request_id": "req_stream_text_001",
|
|
19
|
+
"index": 2,
|
|
20
|
+
"type": "text_delta",
|
|
21
|
+
"delta": " How can I help you today?"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"request_id": "req_stream_text_001",
|
|
25
|
+
"type": "done",
|
|
26
|
+
"stop_reason": "end_turn",
|
|
27
|
+
"usage": {
|
|
28
|
+
"input_tokens": 12,
|
|
29
|
+
"output_tokens": 10
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Streaming thinking + text chunk sequence — sanitized from real E2E traffic",
|
|
3
|
+
"request_id": "req_stream_thinking_001",
|
|
4
|
+
"chunks": [
|
|
5
|
+
{
|
|
6
|
+
"request_id": "req_stream_thinking_001",
|
|
7
|
+
"index": 0,
|
|
8
|
+
"type": "thinking_delta",
|
|
9
|
+
"delta": "Let me think about this step by step.",
|
|
10
|
+
"signature": null
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"request_id": "req_stream_thinking_001",
|
|
14
|
+
"index": 1,
|
|
15
|
+
"type": "thinking_delta",
|
|
16
|
+
"delta": "The key concepts are superposition and entanglement.",
|
|
17
|
+
"signature": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"request_id": "req_stream_thinking_001",
|
|
21
|
+
"index": 2,
|
|
22
|
+
"type": "text_delta",
|
|
23
|
+
"delta": "Quantum computing uses qubits that can be in multiple states at once."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"request_id": "req_stream_thinking_001",
|
|
27
|
+
"index": 3,
|
|
28
|
+
"type": "text_delta",
|
|
29
|
+
"delta": " This enables parallel computation on a massive scale."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"request_id": "req_stream_thinking_001",
|
|
33
|
+
"type": "done",
|
|
34
|
+
"stop_reason": "end_turn",
|
|
35
|
+
"usage": {
|
|
36
|
+
"input_tokens": 15,
|
|
37
|
+
"output_tokens": 45,
|
|
38
|
+
"thinking_tokens": 80
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
data/spec/legion/extensions/llm/conformance/fixtures/canonical_streaming_tool_call_chunks.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Streaming tool-call chunk sequence — multi-chunk stateful tool-loop per A7. Each tool_call_delta carries the complete current state (not raw incremental fragments); the assembler merges deltas via buffer_policy.",
|
|
3
|
+
"request_id": "req_stream_tool_001",
|
|
4
|
+
"chunks": [
|
|
5
|
+
{
|
|
6
|
+
"request_id": "req_stream_tool_001",
|
|
7
|
+
"index": 0,
|
|
8
|
+
"type": "text_delta",
|
|
9
|
+
"delta": "Let me check the weather for you."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"request_id": "req_stream_tool_001",
|
|
13
|
+
"index": 1,
|
|
14
|
+
"type": "tool_call_delta",
|
|
15
|
+
"tool_call": {
|
|
16
|
+
"id": "call_def456",
|
|
17
|
+
"name": "get_weather",
|
|
18
|
+
"arguments": {"location": "San Francisco, CA"}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"request_id": "req_stream_tool_001",
|
|
23
|
+
"index": 2,
|
|
24
|
+
"type": "tool_call_delta",
|
|
25
|
+
"tool_call": {
|
|
26
|
+
"id": "call_def456",
|
|
27
|
+
"name": "get_weather",
|
|
28
|
+
"arguments": {"location": "San Francisco, CA", "unit": "fahrenheit"}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"request_id": "req_stream_tool_001",
|
|
33
|
+
"type": "done",
|
|
34
|
+
"stop_reason": "tool_use",
|
|
35
|
+
"usage": {
|
|
36
|
+
"input_tokens": 45,
|
|
37
|
+
"output_tokens": 28
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|