-
Notifications
You must be signed in to change notification settings - Fork 355
feat(llmobs): add annotateAgentManifest manual API to LLMObs SDK #12318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e1e7f1a
ff0f77c
0985ede
b5f56f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,8 @@ public class DDLLMObsSpan implements LLMObsSpan { | |
| private static final String SPAN_KIND = LLMOBS_TAG_PREFIX + Tags.SPAN_KIND; | ||
| private static final String METADATA = LLMOBS_TAG_PREFIX + LLMObsTags.METADATA; | ||
| private static final String TOOL_DEFINITIONS = LLMOBS_TAG_PREFIX + LLMObsTags.TOOL_DEFINITIONS; | ||
| private static final String AGENT_MANIFEST = LLMOBS_TAG_PREFIX + LLMObsTags.AGENT_MANIFEST; | ||
| private static final String MANUAL_FRAMEWORK = "AgentObs SDK"; | ||
| private static final String PROMPT_TRACKING_INSTRUMENTATION_METHOD = | ||
| LLMOBS_TAG_PREFIX + "prompt_tracking_instrumentation_method"; | ||
| private static final String INSTRUMENTATION_METHOD_ANNOTATED = "annotated"; | ||
|
|
@@ -291,6 +293,64 @@ public void annotatePrompt(LLMObs.Prompt prompt) { | |
| span.setTag(PROMPT_TRACKING_INSTRUMENTATION_METHOD, INSTRUMENTATION_METHOD_ANNOTATED); | ||
| } | ||
|
|
||
| @Override | ||
| public void annotateAgentManifest(LLMObs.AgentManifest manifest) { | ||
| if (finished || manifest == null) { | ||
| return; | ||
| } | ||
| if (!Tags.LLMOBS_AGENT_SPAN_KIND.equals(spanKind)) { | ||
| LOGGER.warn( | ||
| "dropping agent manifest on non-agent span kind; annotateAgentManifest is only supported for agent spans"); | ||
| return; | ||
| } | ||
| Map<String, Object> manifestMap = buildManifestMap(manifest); | ||
| if (!manifestMap.isEmpty()) { | ||
| manifestMap.put("framework", MANUAL_FRAMEWORK); | ||
| span.setTag(AGENT_MANIFEST, manifestMap); | ||
| } | ||
| } | ||
|
|
||
| private Map<String, Object> buildManifestMap(LLMObs.AgentManifest manifest) { | ||
| Map<String, Object> map = new LinkedHashMap<>(); | ||
| CharSequence sn = span.getSpanName(); | ||
| String name = | ||
| manifest.getName() != null ? manifest.getName() : (sn != null ? sn.toString() : null); | ||
|
Comment on lines
+316
to
+317
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens when name is an empty string here? |
||
| if (name != null && !name.isEmpty()) { | ||
|
Comment on lines
+316
to
+318
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a caller supplies Useful? React with 👍 / 👎. |
||
| map.put("name", name); | ||
| } | ||
| if (manifest.getInstructions() != null && !manifest.getInstructions().isEmpty()) { | ||
| map.put("instructions", manifest.getInstructions()); | ||
| } | ||
| if (manifest.getModel() != null && !manifest.getModel().isEmpty()) { | ||
| map.put("model", manifest.getModel()); | ||
| } | ||
| if (manifest.getModelSettings() != null && !manifest.getModelSettings().isEmpty()) { | ||
| map.put("model_settings", new LinkedHashMap<>(manifest.getModelSettings())); | ||
| } | ||
| if (manifest.getTools() != null && !manifest.getTools().isEmpty()) { | ||
| List<Map<String, Object>> toolList = new ArrayList<>(); | ||
| for (LLMObs.AgentTool tool : manifest.getTools()) { | ||
| if (tool == null || tool.getName() == null || tool.getName().isEmpty()) { | ||
| LOGGER.warn("agent manifest tool missing required name; skipping"); | ||
| continue; | ||
| } | ||
| Map<String, Object> toolMap = new LinkedHashMap<>(); | ||
| toolMap.put("name", tool.getName()); | ||
| if (tool.getDescription() != null) { | ||
| toolMap.put("description", tool.getDescription()); | ||
| } | ||
| if (tool.getParameters() != null && !tool.getParameters().isEmpty()) { | ||
| toolMap.put("parameters", new LinkedHashMap<>(tool.getParameters())); | ||
| } | ||
| toolList.add(toolMap); | ||
| } | ||
| if (!toolList.isEmpty()) { | ||
| map.put("tools", toolList); | ||
| } | ||
| } | ||
| return map; | ||
| } | ||
|
|
||
| private static Map<String, Object> copyStringKeyedMap(Map<?, ?> source) { | ||
| Map<String, Object> copy = new LinkedHashMap<>(); | ||
| for (Map.Entry<?, ?> entry : source.entrySet()) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1109,4 +1109,139 @@ public Double getScore() { | |
| return score; | ||
| } | ||
| } | ||
|
|
||
| /** A tool declared in an agent manifest. */ | ||
| public static final class AgentTool { | ||
| private final String name; | ||
| private final String description; | ||
| private final Map<String, Object> parameters; | ||
|
|
||
| public static AgentTool from(String name) { | ||
| return new AgentTool(name, null, null); | ||
| } | ||
|
|
||
| public static AgentTool from( | ||
| String name, @Nullable String description, @Nullable Map<String, Object> parameters) { | ||
| return new AgentTool(name, description, parameters); | ||
| } | ||
|
|
||
| private AgentTool(String name, String description, Map<String, Object> parameters) { | ||
| this.name = name; | ||
| this.description = description; | ||
| this.parameters = | ||
| parameters == null ? null : Collections.unmodifiableMap(new LinkedHashMap<>(parameters)); | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| @Nullable | ||
| public String getDescription() { | ||
| return description; | ||
| } | ||
|
|
||
| @Nullable | ||
| public Map<String, Object> getParameters() { | ||
| return parameters; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Declares the configuration of an agent span: what model it calls, what instructions it runs | ||
| * with, and which tools it has available. | ||
| * | ||
| * <p>Build via {@link AgentManifest#builder()} and pass to {@link | ||
| * LLMObsSpan#annotateAgentManifest(AgentManifest)}. Only applied on agent spans; ignored on other | ||
| * span kinds. A subsequent call on the same span overwrites the previous manifest. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is different from the Python implementation, right? I think in Python, we were merging the two manifests together whenever possible. It's probably best that we align the implementations and choose one approach. Personally, I would lean towards merging the fields whenever possible. |
||
| */ | ||
| public static final class AgentManifest { | ||
| private final String name; | ||
| private final String instructions; | ||
| private final String model; | ||
| private final Map<String, Object> modelSettings; | ||
| private final List<AgentTool> tools; | ||
|
|
||
| public static Builder builder() { | ||
| return new Builder(); | ||
| } | ||
|
|
||
| private AgentManifest(Builder builder) { | ||
| this.name = builder.name; | ||
| this.instructions = builder.instructions; | ||
| this.model = builder.model; | ||
| this.modelSettings = | ||
| builder.modelSettings == null | ||
| ? null | ||
| : Collections.unmodifiableMap(new LinkedHashMap<>(builder.modelSettings)); | ||
| this.tools = | ||
| builder.tools == null | ||
| ? null | ||
| : Collections.unmodifiableList(new ArrayList<>(builder.tools)); | ||
| } | ||
|
|
||
| @Nullable | ||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| @Nullable | ||
| public String getInstructions() { | ||
| return instructions; | ||
| } | ||
|
|
||
| @Nullable | ||
| public String getModel() { | ||
| return model; | ||
| } | ||
|
|
||
| @Nullable | ||
| public Map<String, Object> getModelSettings() { | ||
| return modelSettings; | ||
| } | ||
|
|
||
| @Nullable | ||
| public List<AgentTool> getTools() { | ||
| return tools; | ||
| } | ||
|
|
||
| public static final class Builder { | ||
| private String name; | ||
| private String instructions; | ||
| private String model; | ||
| private Map<String, Object> modelSettings; | ||
| private List<AgentTool> tools; | ||
|
|
||
| private Builder() {} | ||
|
|
||
| public Builder name(String name) { | ||
| this.name = name; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder instructions(String instructions) { | ||
| this.instructions = instructions; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder model(String model) { | ||
| this.model = model; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder modelSettings(Map<String, Object> modelSettings) { | ||
| this.modelSettings = modelSettings; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder tools(List<AgentTool> tools) { | ||
| this.tools = tools; | ||
| return this; | ||
| } | ||
|
|
||
| public AgentManifest build() { | ||
| return new AgentManifest(this); | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk if
"AgentObs SDK"makes sense here or if we should default to"custom"or"manual"since the former makes it seem like we are determining the agent manifest via the SDK rather than the user supplying it. (same comment applies to the python PR)