Skip to content

Commit 36c05e9

Browse files
committed
feat(langchain): support async invoke & stream in langchain integration
1 parent e012ff1 commit 36c05e9

File tree

2 files changed

+469
-0
lines changed

2 files changed

+469
-0
lines changed

sentry_sdk/integrations/langchain.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ def setup_once():
7878
AgentExecutor.invoke = _wrap_agent_executor_invoke(AgentExecutor.invoke)
7979
AgentExecutor.stream = _wrap_agent_executor_stream(AgentExecutor.stream)
8080

81+
AgentExecutor.ainvoke = _wrap_agent_executor_ainvoke(AgentExecutor.ainvoke)
82+
AgentExecutor.astream = _wrap_agent_executor_astream(AgentExecutor.astream)
83+
8184

8285
class WatchedSpan:
8386
span = None # type: Span
@@ -768,3 +771,138 @@ async def new_iterator_async():
768771
return result
769772

770773
return new_stream
774+
775+
776+
def _wrap_agent_executor_ainvoke(f):
777+
# type: (Callable[..., Any]) -> Callable[..., Any]
778+
779+
@wraps(f)
780+
async def new_ainvoke(self, *args, **kwargs):
781+
# type: (Any, Any, Any) -> Any
782+
integration = sentry_sdk.get_client().get_integration(LangchainIntegration)
783+
if integration is None:
784+
return await f(self, *args, **kwargs)
785+
786+
agent_name, tools = _get_request_data(self, args, kwargs)
787+
788+
with sentry_sdk.start_span(
789+
op=OP.GEN_AI_INVOKE_AGENT,
790+
name=f"invoke_agent {agent_name}" if agent_name else "invoke_agent",
791+
origin=LangchainIntegration.origin,
792+
) as span:
793+
if agent_name:
794+
span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name)
795+
796+
span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent")
797+
span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, False)
798+
799+
if tools:
800+
set_data_normalized(
801+
span, SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, tools, unpack=False
802+
)
803+
804+
# Run the agent
805+
result = await f(self, *args, **kwargs)
806+
807+
input = result.get("input")
808+
if (
809+
input is not None
810+
and should_send_default_pii()
811+
and integration.include_prompts
812+
):
813+
set_data_normalized(
814+
span,
815+
SPANDATA.GEN_AI_REQUEST_MESSAGES,
816+
[
817+
input,
818+
],
819+
)
820+
821+
output = result.get("output")
822+
if (
823+
output is not None
824+
and should_send_default_pii()
825+
and integration.include_prompts
826+
):
827+
span.set_data(SPANDATA.GEN_AI_RESPONSE_TEXT, output)
828+
829+
return result
830+
831+
new_ainvoke.__wrapped__ = True
832+
return new_ainvoke
833+
834+
835+
def _wrap_agent_executor_astream(f):
836+
# type: (Callable[..., Any]) -> Callable[..., Any]
837+
838+
@wraps(f)
839+
def new_astream(self, *args, **kwargs):
840+
# type: (Any, Any, Any) -> Any
841+
integration = sentry_sdk.get_client().get_integration(LangchainIntegration)
842+
if integration is None:
843+
return f(self, *args, **kwargs)
844+
845+
agent_name, tools = _get_request_data(self, args, kwargs)
846+
847+
span = sentry_sdk.start_span(
848+
op=OP.GEN_AI_INVOKE_AGENT,
849+
name=f"invoke_agent {agent_name}".strip(),
850+
origin=LangchainIntegration.origin,
851+
)
852+
span.__enter__()
853+
854+
if agent_name:
855+
span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name)
856+
857+
span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent")
858+
span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, True)
859+
860+
if tools:
861+
set_data_normalized(
862+
span, SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, tools, unpack=False
863+
)
864+
865+
input = args[0].get("input") if len(args) >= 1 else None
866+
if (
867+
input is not None
868+
and should_send_default_pii()
869+
and integration.include_prompts
870+
):
871+
set_data_normalized(
872+
span,
873+
SPANDATA.GEN_AI_REQUEST_MESSAGES,
874+
[
875+
input,
876+
],
877+
)
878+
879+
# Run the agent - this returns an async iterator
880+
result = f(self, *args, **kwargs)
881+
882+
old_iterator = result
883+
884+
async def new_iterator_async():
885+
# type: () -> AsyncIterator[Any]
886+
event = None
887+
try:
888+
async for event in old_iterator:
889+
yield event
890+
finally:
891+
try:
892+
output = event.get("output") if event else None
893+
except Exception:
894+
output = None
895+
896+
if (
897+
output is not None
898+
and should_send_default_pii()
899+
and integration.include_prompts
900+
):
901+
span.set_data(SPANDATA.GEN_AI_RESPONSE_TEXT, output)
902+
903+
span.__exit__(None, None, None)
904+
905+
return new_iterator_async()
906+
907+
new_astream.__wrapped__ = True
908+
return new_astream

0 commit comments

Comments
 (0)