Skip to content

Commit 63ae89d

Browse files
committed
Improve error handling in langchain.py
1 parent b6f5c5b commit 63ae89d

1 file changed

Lines changed: 28 additions & 31 deletions

File tree

‎splunklib/ai/engines/langchain.py‎

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ async def awrap_model_call(
322322
agent_thread_ids: dict[str, set[str]] = {}
323323

324324
# Update the subagent schema definitions to include all thread_ids that the
325-
# LLM could use to continue a corespondation with a subagent.
325+
# LLM could use to continue correspondence with a subagent.
326326
new_tools: list[BaseTool | dict[str, Any]] = []
327327
for tool in request.tools:
328328
assert isinstance(tool, StructuredTool)
@@ -342,8 +342,8 @@ async def awrap_model_call(
342342

343343
# Create an updated tool, with an updated input schema, that
344344
# has a thread_id as an enum instead of a str, disallowing the
345-
# LLM from halucinating a thread_id, instead requiring that it is one
346-
# of the few specified thread_ids.
345+
# LLM from hallucinating a thread_id, instead requiring that
346+
# it is one of the few specified thread_ids.
347347
tool = tool.model_copy(
348348
update={
349349
"args_schema": create_model(
@@ -358,7 +358,7 @@ async def awrap_model_call(
358358
description=(
359359
"Provide previous thread id to continue an existing conversation with an agent. "
360360
+ "Never issue a call to the same thread_id more than once. "
361-
+ "Do not provide to start a new corespondation"
361+
+ "Omit thread_id to start a new correspondence"
362362
),
363363
default=None,
364364
),
@@ -388,7 +388,7 @@ async def awrap_model_call(
388388
args = SubagentLCArgs(**call["args"])
389389
possible_thread_ids = agent_thread_ids.get(call["name"], set())
390390
if args.thread_id and args.thread_id not in possible_thread_ids:
391-
# LLM halucinated a thread_id, start a new conversation instead.
391+
# LLM hallucinated a thread_id, start a new conversation instead.
392392
# This should not happen, since we provide an enum above, but just
393393
# in case.
394394
args.thread_id = _thread_id_new_uuid()
@@ -418,7 +418,7 @@ class _SubagentArgumentPacker(LC_AgentMiddleware):
418418
# to differentiate that we wrap the resulting args in an SubagentLCArgs.
419419
#
420420
# This middleware performs the corresponding pack/unpack at the two
421-
# points in the LangChain call graph where raw args are needed/retreived.
421+
# points in the LangChain call graph where raw args are needed/retrieved.
422422
#
423423
# TODO: we could move this logic to _Middleware.
424424
@override
@@ -586,12 +586,12 @@ async def awrap_tool_call(
586586
lc_middleware.append(_DEBUGMiddleware())
587587

588588
if agent.limits.max_tokens is not None:
589-
# Other limits are implemented using SDK middlewres, but this one
589+
# Other limits are implemented using SDK middlewares, but this one
590590
# cannot be easily implemented that way, since count_tokens_approximately needs
591591
# access to list[BaseTool] and the langchain model. We don't expose these
592592
# in our SDK middleware, thus we use the langchain middlewares directly here.
593593
#
594-
# Potentially we could implement count_tokens_approximately puerly using our SDK,
594+
# Potentially we could implement count_tokens_approximately purely using our SDK,
595595
# that would additionally require exposing list[Tool] to AgentState, such that
596596
# middlewares get access to the tools that are passed to LLMs.
597597
#
@@ -750,7 +750,7 @@ async def invoke_agent(req: AgentRequest) -> AgentResponse[Any | None]:
750750
except _InvalidMessagesException as e:
751751
raise _InvalidMessagesException(
752752
f"Agent middleware modified messages and made it invalid: {e}"
753-
)
753+
) from e
754754

755755
if self._sdk_agent.output_schema:
756756
if result.structured_output is None:
@@ -884,18 +884,18 @@ async def llm_handler(req: ModelRequest) -> ModelResponse:
884884
raise StructuredOutputGenerationException(
885885
message=msg,
886886
error=StructuredOutputMultipleToolCallsError(),
887-
)
887+
) from e
888888
case LC_StructuredOutputValidationError():
889889
raise StructuredOutputGenerationException(
890890
message=msg,
891891
error=StructuredOutputValidationError(validation_error=str(e.source)),
892-
)
892+
) from e
893893
case LC_StructuredOutputError():
894894
# Langchain only returns the above handled exceptions, LC_StructuredOutputError
895895
# is never returned alone (it is the base class for above exceptions).
896896
raise AssertionError(
897897
"internal error: LC_StructuredOutputError has been returned"
898-
)
898+
) from e
899899

900900
try:
901901
sdk_response = await self._with_model_middleware(llm_handler)(req)
@@ -937,7 +937,7 @@ async def llm_handler(req: ModelRequest) -> ModelResponse:
937937
return LC_ModelResponse(result=result)
938938
else:
939939
# Provider strategy
940-
assert isinstance(e.error, StructuredOutputValidationError)
940+
assert isinstance(e.error, StructuredOutputValidationError) # noqa: PT017
941941

942942
request.runtime.context.retry = LC_HumanMessage(
943943
content=create_structured_prompt(
@@ -1228,10 +1228,9 @@ def _convert_tool_message_from_lc(
12281228
) -> ToolMessage | SubagentMessage | StructuredOutputMessage:
12291229
match message:
12301230
case LC_ToolMessage(name=name) if name and name.startswith(AGENT_PREFIX):
1231-
assert (
1232-
isinstance(message.artifact, SubagentStructuredResult)
1233-
or isinstance(message.artifact, SubagentTextResult)
1234-
or isinstance(message.artifact, SubagentFailureResult)
1231+
assert isinstance(
1232+
message.artifact,
1233+
(SubagentStructuredResult, SubagentTextResult, SubagentFailureResult),
12351234
)
12361235
return SubagentMessage(
12371236
name=_denormalize_agent_name(name),
@@ -1250,9 +1249,7 @@ def _convert_tool_message_from_lc(
12501249
content=str(message.content), # pyright: ignore[reportUnknownArgumentType]
12511250
)
12521251

1253-
assert isinstance(message.artifact, ToolResult) or isinstance(
1254-
message.artifact, ToolFailureResult
1255-
)
1252+
assert isinstance(message.artifact, (ToolResult, ToolFailureResult))
12561253

12571254
tool_type: ToolType = (
12581255
ToolType.LOCAL if message.name.startswith(LOCAL_TOOL_PREFIX) else ToolType.REMOTE
@@ -1398,10 +1395,10 @@ async def _tool_call(
13981395
result = await tool.func(**kwargs)
13991396
except ToolException as e:
14001397
raise LC_ToolException(*e.args) from e
1401-
except LC_ToolException:
1402-
assert False, ( # noqa: PT015
1398+
except LC_ToolException as lcte:
1399+
raise AssertionError(
14031400
"ToolException from LangChain should not be raised in tool.func"
1404-
)
1401+
) from lcte
14051402

14061403
artifact = ToolResult(content=result.content, structured_content=result.structured_content)
14071404

@@ -1801,14 +1798,14 @@ def _create_langchain_model(model: PredefinedModel) -> BaseChatModel:
18011798
extra_body=model.extra_body,
18021799
http_async_client=model.httpx_client,
18031800
)
1804-
except ImportError:
1801+
except ImportError as ie:
18051802
raise ImportError(
18061803
"OpenAI support is not installed.\n"
18071804
+ "To enable OpenAI / ChatGPT models, install the optional extra:\n"
18081805
+ 'pip install "splunk-sdk[openai]"\n'
18091806
+ "# or if using uv:\n"
18101807
+ "uv add splunk-sdk[openai]"
1811-
)
1808+
) from ie
18121809
case AnthropicModel():
18131810
try:
18141811
from langchain_anthropic import ChatAnthropic
@@ -1822,14 +1819,14 @@ def _create_langchain_model(model: PredefinedModel) -> BaseChatModel:
18221819
kwargs["temperature"] = model.temperature
18231820

18241821
return ChatAnthropic(**kwargs)
1825-
except ImportError:
1822+
except ImportError as ie:
18261823
raise ImportError(
18271824
"Anthropic support is not installed.\n"
18281825
+ "To enable Anthropic models, install the optional extra:\n"
18291826
+ 'pip install "splunk-sdk[anthropic]"\n'
18301827
+ "# or if using uv:\n"
18311828
+ "uv add splunk-sdk[anthropic]"
1832-
)
1829+
) from ie
18331830
case GoogleModel():
18341831
try:
18351832
from langchain_google_genai import ChatGoogleGenerativeAI
@@ -1849,14 +1846,14 @@ def _create_langchain_model(model: PredefinedModel) -> BaseChatModel:
18491846
google_kwargs["temperature"] = model.temperature
18501847

18511848
return ChatGoogleGenerativeAI(**google_kwargs)
1852-
except ImportError:
1849+
except ImportError as ie:
18531850
raise ImportError(
18541851
"Google GenAI support is not installed.\n"
18551852
+ "To enable Google / Gemini models, install the optional extra:\n"
18561853
+ 'pip install "splunk-sdk[google]"\n'
18571854
+ "# or if using uv:\n"
18581855
+ "uv add splunk-sdk[google]"
1859-
)
1856+
) from ie
18601857
case _:
18611858
raise InvalidModelError("Cannot create langchain model - invalid SDK model provided")
18621859

@@ -2083,8 +2080,8 @@ async def model_middleware(
20832080
) -> ModelResponse:
20842081
try:
20852082
return await handler(request)
2086-
except StructuredOutputGenerationException:
2083+
except StructuredOutputGenerationException as soge:
20872084
self._retries_per_thread_id[request.state.thread_id] += 1
20882085
if self._retries_per_thread_id[request.state.thread_id] > self._limit:
2089-
raise StructuredOutputRetryLimitExceededException(self._limit)
2086+
raise StructuredOutputRetryLimitExceededException(self._limit) from soge
20902087
raise # re-raise, to retry structured output generation

0 commit comments

Comments
 (0)