-
Notifications
You must be signed in to change notification settings - Fork 956
feat: add agent span support in langchain instrumentation #3788
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
Changes from 18 commits
529ebd7
b1fe20f
d030990
87b6b05
f1d1cee
207c54d
fdacebd
d3b2528
1b98f9f
ae6bf62
744ddb6
82bfb84
6fb9f9f
7807450
9058d97
e95d658
86033c3
4de0f08
c33571b
370d930
f97424c
ca54118
e0881b9
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 |
|---|---|---|
|
|
@@ -17,11 +17,15 @@ | |
| from typing import Any | ||
| from uuid import UUID | ||
|
|
||
| from langchain_core.agents import AgentAction, AgentFinish # type: ignore | ||
| from langchain_core.callbacks import BaseCallbackHandler # type: ignore | ||
| from langchain_core.messages import BaseMessage # type: ignore | ||
| from langchain_core.outputs import LLMResult # type: ignore | ||
|
|
||
| from opentelemetry.instrumentation.langchain.span_manager import _SpanManager | ||
| from opentelemetry.instrumentation.langchain.span_manager import ( | ||
| _OPERATION_INVOKE_AGENT, | ||
| _SpanManager, | ||
| ) | ||
| from opentelemetry.semconv._incubating.attributes import ( | ||
| gen_ai_attributes as GenAI, | ||
| ) | ||
|
|
@@ -49,9 +53,9 @@ def on_chat_model_start( | |
| messages: list[list[BaseMessage]], # type: ignore | ||
| *, | ||
| run_id: UUID, | ||
| tags: list[str] | None, | ||
| parent_run_id: UUID | None, | ||
| metadata: dict[str, Any] | None, | ||
| tags: list[str] | None = None, | ||
| parent_run_id: UUID | None = None, | ||
| metadata: dict[str, Any] | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| # Other providers/LLMs may be supported in the future and telemetry for them is skipped for now. | ||
|
|
@@ -141,7 +145,7 @@ def on_llm_end( | |
| response: LLMResult, # type: ignore [reportUnknownParameterType] | ||
| *, | ||
| run_id: UUID, | ||
| parent_run_id: UUID | None, | ||
| parent_run_id: UUID | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| span = self.span_manager.get_span(run_id) | ||
|
|
@@ -218,7 +222,114 @@ def on_llm_error( | |
| error: BaseException, | ||
| *, | ||
| run_id: UUID, | ||
| parent_run_id: UUID | None, | ||
| parent_run_id: UUID | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| self.span_manager.handle_error(error, run_id) | ||
|
|
||
| def on_chain_start( | ||
| self, | ||
| serialized: dict[str, Any], | ||
| inputs: dict[str, Any], | ||
| *, | ||
| run_id: UUID, | ||
| parent_run_id: UUID | None = None, | ||
| tags: list[str] | None = None, | ||
| metadata: dict[str, Any] | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| """Run when chain starts running.""" | ||
| # Extract chain name from serialized or kwargs | ||
| chain_name = "unknown" | ||
| if ( | ||
| serialized | ||
| and "kwargs" in serialized | ||
| and serialized["kwargs"].get("name") | ||
| ): | ||
| chain_name = serialized["kwargs"]["name"] | ||
| elif kwargs.get("name"): | ||
| chain_name = kwargs["name"] | ||
| elif serialized.get("name"): | ||
| chain_name = serialized["name"] | ||
| elif "id" in serialized: | ||
| chain_name = serialized["id"][-1] | ||
|
|
||
| span = self.span_manager.create_chain_span( | ||
| run_id=run_id, | ||
| parent_run_id=parent_run_id, | ||
| chain_name=chain_name, | ||
| ) | ||
|
|
||
| # If this is an agent chain, set agent-specific attributes | ||
| if metadata and "agent_name" in metadata: | ||
| span.set_attribute(GenAI.GEN_AI_AGENT_NAME, metadata["agent_name"]) | ||
| span.set_attribute( | ||
| GenAI.GEN_AI_OPERATION_NAME, _OPERATION_INVOKE_AGENT | ||
| ) | ||
|
|
||
| def on_chain_end( | ||
| self, | ||
| outputs: dict[str, Any], | ||
| *, | ||
| run_id: UUID, | ||
| parent_run_id: UUID | None = None, | ||
| tags: list[str] | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| """Run when chain ends running.""" | ||
| self.span_manager.end_span(run_id) | ||
|
|
||
| def on_chain_error( | ||
| self, | ||
| error: BaseException, | ||
| *, | ||
| run_id: UUID, | ||
| parent_run_id: UUID | None = None, | ||
| tags: list[str] | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| """Run when chain errors.""" | ||
| self.span_manager.handle_error(error, run_id) | ||
|
|
||
| def on_agent_action( | ||
| self, | ||
| action: AgentAction, # type: ignore[type-arg] | ||
| *, | ||
| run_id: UUID, | ||
| parent_run_id: UUID | None = None, | ||
| tags: list[str] | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| """Run on agent action.""" | ||
| # Agent actions are tracked as part of the chain span | ||
| # We can add attributes to the existing span if needed | ||
| span = self.span_manager.get_span(run_id) | ||
| if span: | ||
| tool = getattr(action, "tool", None) # type: ignore[arg-type] | ||
| if tool: | ||
| span.set_attribute("langchain.agent.action.tool", tool) | ||
|
Member
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. could this be expressed as one of a standard tool attributes? (all tool attributes are in the semconv - https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-spans.md#execute-tool-span)
Contributor
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. I guess your intention is to use gen_ai.tool.name, gen_ai.tool.call.result and gen_ai.tool.call.arguments which are on execute_tool. Please use these gen-ai attributes. Also another PR updating invoke_agent with the above attributes would also be required. |
||
| tool_input = getattr(action, "tool_input", None) # type: ignore[arg-type] | ||
| if tool_input: | ||
| span.set_attribute( | ||
| "langchain.agent.action.tool_input", str(tool_input) | ||
|
Member
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. should this be gen_ai.tool.call.arguments ? |
||
| ) | ||
|
|
||
| def on_agent_finish( | ||
| self, | ||
| finish: AgentFinish, # type: ignore[type-arg] | ||
| *, | ||
| run_id: UUID, | ||
| parent_run_id: UUID | None = None, | ||
| tags: list[str] | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| """Run on agent finish.""" | ||
| # Agent finish is tracked as part of the chain span | ||
| span = self.span_manager.get_span(run_id) | ||
| if span: | ||
| return_values = getattr(finish, "return_values", None) # type: ignore[arg-type] | ||
| if return_values and "output" in return_values: | ||
| span.set_attribute( | ||
| "langchain.agent.finish.output", | ||
|
Member
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. should this be either |
||
| str(return_values["output"]), | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,10 @@ | |
| from opentelemetry.trace import Span, SpanKind, Tracer, set_span_in_context | ||
| from opentelemetry.trace.status import Status, StatusCode | ||
|
|
||
| __all__ = ["_SpanManager"] | ||
| __all__ = ["_SpanManager", "_OPERATION_INVOKE_AGENT"] | ||
|
|
||
| # Operation name constants | ||
| _OPERATION_INVOKE_AGENT = "invoke_agent" | ||
|
Member
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. |
||
|
|
||
|
|
||
| @dataclass | ||
|
|
@@ -91,6 +94,51 @@ def create_chat_span( | |
|
|
||
| return span | ||
|
|
||
| def create_agent_span( | ||
| self, | ||
| run_id: UUID, | ||
| parent_run_id: Optional[UUID], | ||
| agent_name: Optional[str] = None, | ||
| ) -> Span: | ||
| """Create a span for agent invocation.""" | ||
| # Use "unknown" as default if agent_name is not provided | ||
| effective_agent_name = agent_name or "unknown" | ||
|
lmolkova marked this conversation as resolved.
Outdated
|
||
| span_name = f"{_OPERATION_INVOKE_AGENT} {effective_agent_name}" | ||
| span = self._create_span( | ||
| run_id=run_id, | ||
| parent_run_id=parent_run_id, | ||
| span_name=span_name, | ||
| kind=SpanKind.CLIENT, | ||
|
lmolkova marked this conversation as resolved.
Outdated
|
||
| ) | ||
| span.set_attribute( | ||
| GenAI.GEN_AI_OPERATION_NAME, | ||
| _OPERATION_INVOKE_AGENT, | ||
| ) | ||
| span.set_attribute(GenAI.GEN_AI_AGENT_NAME, effective_agent_name) | ||
|
lmolkova marked this conversation as resolved.
Outdated
|
||
|
|
||
| return span | ||
|
Member
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. Invoke agent span must have there are more recommended/opt-in attributes documented in https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md#invoke-agent-span |
||
|
|
||
| def create_chain_span( | ||
| self, | ||
| run_id: UUID, | ||
| parent_run_id: Optional[UUID], | ||
| chain_name: str, | ||
| ) -> Span: | ||
| """Create a span for chain execution. | ||
|
|
||
| Chains are internal operations by default and don't have gen_ai.operation.name. | ||
| However, if the chain represents an agent (determined by metadata in the callback), | ||
| the operation name and agent name attributes will be set separately by the | ||
| callback handler to make it an agent span. | ||
| """ | ||
| span = self._create_span( | ||
| run_id=run_id, | ||
| parent_run_id=parent_run_id, | ||
| span_name=f"chain {chain_name}", | ||
|
lmolkova marked this conversation as resolved.
|
||
| kind=SpanKind.INTERNAL, | ||
| ) | ||
| return span | ||
|
|
||
| def end_span(self, run_id: UUID) -> None: | ||
| state = self.spans[run_id] | ||
| for child_id in state.children: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.