Skip to content

Commit 82a2e1c

Browse files
Added GenAI support for Amazon Nova model (#153)
Co-authored-by: Michael He <[email protected]>
1 parent d92e944 commit 82a2e1c

File tree

4 files changed

+134
-3
lines changed

4 files changed

+134
-3
lines changed

src/OpenTelemetry.Instrumentation.AWS/Implementation/AWSLlmModelProcessor.cs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ internal static void ProcessGenAiAttributes<T>(Activity activity, T message, str
3737
}
3838

3939
// extract model specific attributes based on model name
40-
if (modelName.Contains("amazon.titan"))
40+
if (modelName.Contains("amazon.nova"))
41+
{
42+
ProcessNovaModelAttributes(activity, jsonObject, isRequest);
43+
}
44+
else if (modelName.Contains("amazon.titan"))
4145
{
4246
ProcessTitanModelAttributes(activity, jsonObject, isRequest);
4347
}
@@ -70,6 +74,55 @@ internal static void ProcessGenAiAttributes<T>(Activity activity, T message, str
7074
}
7175
}
7276

77+
private static void ProcessNovaModelAttributes(Activity activity, Dictionary<string, JsonElement> jsonBody, bool isRequest)
78+
{
79+
try
80+
{
81+
if (isRequest)
82+
{
83+
if (jsonBody.TryGetValue("inferenceConfig", out var inferenceConfig))
84+
{
85+
if (inferenceConfig.TryGetProperty("top_p", out var topP))
86+
{
87+
activity.SetTag(AWSSemanticConventions.AttributeGenAiTopP, topP.GetDouble());
88+
}
89+
90+
if (inferenceConfig.TryGetProperty("temperature", out var temperature))
91+
{
92+
activity.SetTag(AWSSemanticConventions.AttributeGenAiTemperature, temperature.GetDouble());
93+
}
94+
95+
if (inferenceConfig.TryGetProperty("max_new_tokens", out var maxTokens))
96+
{
97+
activity.SetTag(AWSSemanticConventions.AttributeGenAiMaxTokens, maxTokens.GetInt32());
98+
}
99+
}
100+
}
101+
else
102+
{
103+
if (jsonBody.TryGetValue("usage", out var usage))
104+
{
105+
if (usage.TryGetProperty("inputTokens", out var inputTokens))
106+
{
107+
activity.SetTag(AWSSemanticConventions.AttributeGenAiInputTokens, inputTokens.GetInt32());
108+
}
109+
if (usage.TryGetProperty("outputTokens", out var outputTokens))
110+
{
111+
activity.SetTag(AWSSemanticConventions.AttributeGenAiOutputTokens, outputTokens.GetInt32());
112+
}
113+
}
114+
if (jsonBody.TryGetValue("stopReason", out var finishReasons))
115+
{
116+
activity.SetTag(AWSSemanticConventions.AttributeGenAiFinishReasons, new string[] { finishReasons.GetString() ?? string.Empty });
117+
}
118+
}
119+
}
120+
catch (Exception ex)
121+
{
122+
Console.WriteLine("Exception: " + ex.Message);
123+
}
124+
}
125+
73126
private static void ProcessTitanModelAttributes(Activity activity, Dictionary<string, JsonElement> jsonBody, bool isRequest)
74127
{
75128
try

test/contract-tests/images/applications/TestSimpleApp.AWSSDK.Core/BedrockTests.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,55 @@ public GetGuardrailResponse GetGuardrailResponse()
3939
};
4040
}
4141

42-
// 6 InvokeModel test calls and responses, one for each supported model
42+
// 7 InvokeModel test calls and responses, one for each supported model
4343
// The manual responses are automatically serialized to a MemoryStream and used as the response body
4444

45+
public void InvokeModelAmazonNova()
46+
{
47+
bedrockRuntime.InvokeModelAsync(new InvokeModelRequest
48+
{
49+
ModelId = "us.amazon.nova-micro-v1:0",
50+
Body = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new
51+
{
52+
messages = new object[]
53+
{
54+
new
55+
{
56+
role = "user",
57+
content = new object[]
58+
{
59+
new
60+
{
61+
text = "sample input text",
62+
}
63+
}
64+
},
65+
},
66+
inferenceConfig = new
67+
{
68+
temperature = 0.123,
69+
top_p = 0.456,
70+
max_new_tokens = 123,
71+
},
72+
}))),
73+
ContentType = "application/json",
74+
});
75+
return;
76+
}
77+
78+
public object InvokeModelAmazonNovaResponse()
79+
{
80+
return new
81+
{
82+
usage = new
83+
{
84+
inputTokens = 456,
85+
outputTokens = 789,
86+
},
87+
stopReason = "finish_reason",
88+
};
89+
}
90+
4591
public void InvokeModelAmazonTitan()
4692
{
4793
bedrockRuntime.InvokeModelAsync(new InvokeModelRequest

test/contract-tests/images/applications/TestSimpleApp.AWSSDK.Core/Program.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@
169169
.WithName("get-guardrail")
170170
.WithOpenApi();
171171

172+
app.MapGet("bedrock/invokemodel/invoke-model-nova", (BedrockTests bedrock) => bedrock.InvokeModelAmazonNova())
173+
.WithName("invoke-model-nova")
174+
.WithOpenApi();
175+
172176
app.MapGet("bedrock/invokemodel/invoke-model-titan", (BedrockTests bedrock) => bedrock.InvokeModelAmazonTitan())
173177
.WithName("invoke-model-titan")
174178
.WithOpenApi();
@@ -232,7 +236,8 @@ async Task PrepareAWSServer(IServiceProvider services)
232236
// Reroute the Bedrock API calls to our mock responses in BedrockTests. While other services use localstack to handle the requests,
233237
// we write our own responses with the necessary data to mimic the expected behavior of the Bedrock services.
234238
app.MapGet("guardrails/test-guardrail", (BedrockTests bedrock) => bedrock.GetGuardrailResponse());
235-
// For invoke model, we have one test case for each of the 6 suppported models.
239+
// For invoke model, we have one test case for each of the 7 suppported models.
240+
app.MapPost("model/us.amazon.nova-micro-v1:0/invoke", (BedrockTests bedrock) => bedrock.InvokeModelAmazonNovaResponse());
236241
app.MapPost("model/amazon.titan-text-express-v1/invoke", (BedrockTests bedrock) => bedrock.InvokeModelAmazonTitanResponse());
237242
app.MapPost("model/us.anthropic.claude-3-5-haiku-20241022-v1:0/invoke", (BedrockTests bedrock) => bedrock.InvokeModelAnthropicClaudeResponse());
238243
app.MapPost("model/meta.llama3-8b-instruct-v1:0/invoke", (BedrockTests bedrock) => bedrock.InvokeModelMetaLlamaResponse());

test/contract-tests/tests/test/amazon/awssdk/awssdk_test.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,32 @@ def test_bedrock_get_guardrail(self):
550550
span_name="Bedrock.GetGuardrail",
551551
)
552552

553+
def test_bedrock_runtime_invoke_model_nova(self):
554+
self.do_test_requests(
555+
"bedrock/invokemodel/invoke-model-nova",
556+
"GET",
557+
200,
558+
0,
559+
0,
560+
rpc_service="Bedrock Runtime",
561+
remote_service="AWS::BedrockRuntime",
562+
remote_operation="InvokeModel",
563+
remote_resource_type="AWS::Bedrock::Model",
564+
remote_resource_identifier="us.amazon.nova-micro-v1:0",
565+
cloudformation_primary_identifier="us.amazon.nova-micro-v1:0",
566+
request_response_specific_attributes={
567+
_GEN_AI_SYSTEM: "aws.bedrock",
568+
_GEN_AI_REQUEST_MODEL: "us.amazon.nova-micro-v1:0",
569+
_GEN_AI_REQUEST_TEMPERATURE: 0.123,
570+
_GEN_AI_REQUEST_TOP_P: 0.456,
571+
_GEN_AI_REQUEST_MAX_TOKENS: 123,
572+
_GEN_AI_USAGE_INPUT_TOKENS: 456,
573+
_GEN_AI_USAGE_OUTPUT_TOKENS: 789,
574+
_GEN_AI_RESPONSE_FINISH_REASONS: ["finish_reason"],
575+
},
576+
span_name="Bedrock Runtime.InvokeModel",
577+
)
578+
553579
def test_bedrock_runtime_invoke_model_titan(self):
554580
self.do_test_requests(
555581
"bedrock/invokemodel/invoke-model-titan",
@@ -982,6 +1008,7 @@ def _filter_bedrock_metrics(self, target_metrics: List[Metric]):
9821008
"GET knowledgebases/test-knowledge-base",
9831009
"GET knowledgebases/test-knowledge-base/datasources/test-data-source",
9841010
"POST agents/test-agent/agentAliases/test-agent-alias/sessions/test-session/text",
1011+
"POST model/us.amazon.nova-micro-v1:0/invoke",
9851012
"POST model/amazon.titan-text-express-v1/invoke",
9861013
"POST model/us.anthropic.claude-3-5-haiku-20241022-v1:0/invoke",
9871014
"POST model/meta.llama3-8b-instruct-v1:0/invoke",

0 commit comments

Comments
 (0)