Skip to content

Commit 1da3a99

Browse files
committed
feat: Add extra_content field to ToolCall for provider-specific metadata
1 parent dad50e5 commit 1da3a99

File tree

6 files changed

+66
-2
lines changed

6 files changed

+66
-2
lines changed

libs/core/langchain_core/messages/tool.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,25 +227,37 @@ class ToolCall(TypedDict):
227227
228228
"""
229229
type: NotRequired[Literal["tool_call"]]
230+
extra_content: NotRequired[dict[str, Any]]
231+
"""Optional provider-specific metadata.
232+
233+
For example, Google's Gemini API returns thought_signature in:
234+
extra_content.google.thought_signature
235+
236+
"""
230237

231238

232239
def tool_call(
233240
*,
234241
name: str,
235242
args: dict[str, Any],
236243
id: str | None,
244+
extra_content: dict[str, Any] | None = None,
237245
) -> ToolCall:
238246
"""Create a tool call.
239247
240248
Args:
241249
name: The name of the tool to be called.
242250
args: The arguments to the tool call.
243251
id: An identifier associated with the tool call.
252+
extra_content: Optional provider-specific metadata.
244253
245254
Returns:
246255
The created tool call.
247256
"""
248-
return ToolCall(name=name, args=args, id=id, type="tool_call")
257+
result = ToolCall(name=name, args=args, id=id, type="tool_call")
258+
if extra_content is not None:
259+
result["extra_content"] = extra_content
260+
return result
249261

250262

251263
class ToolCallChunk(TypedDict):
@@ -347,6 +359,7 @@ def default_tool_parser(
347359
name=function_name or "",
348360
args=function_args or {},
349361
id=raw_tool_call.get("id"),
362+
extra_content=raw_tool_call.get("extra_content"),
350363
)
351364
tool_calls.append(parsed)
352365
except json.JSONDecodeError:

libs/core/langchain_core/output_parsers/openai_tools.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ def parse_tool_call(
7272
}
7373
if return_id:
7474
parsed["id"] = raw_tool_call.get("id")
75+
# Preserve extra_content if present (e.g., for Google's thought_signature)
76+
if "extra_content" in raw_tool_call:
77+
parsed["extra_content"] = raw_tool_call["extra_content"]
7578
parsed = create_tool_call(**parsed) # type: ignore[assignment,arg-type]
7679
return parsed
7780

libs/core/tests/unit_tests/prompts/__snapshots__/test_chat.ambr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,10 @@
988988
'title': 'Args',
989989
'type': 'object',
990990
}),
991+
'extra_content': dict({
992+
'title': 'Extra Content',
993+
'type': 'object',
994+
}),
991995
'id': dict({
992996
'anyOf': list([
993997
dict({
@@ -2404,6 +2408,10 @@
24042408
'title': 'Args',
24052409
'type': 'object',
24062410
}),
2411+
'extra_content': dict({
2412+
'title': 'Extra Content',
2413+
'type': 'object',
2414+
}),
24072415
'id': dict({
24082416
'anyOf': list([
24092417
dict({

libs/core/tests/unit_tests/runnables/__snapshots__/test_graph.ambr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,6 +1412,10 @@
14121412
'title': 'Args',
14131413
'type': 'object',
14141414
}),
1415+
'extra_content': dict({
1416+
'title': 'Extra Content',
1417+
'type': 'object',
1418+
}),
14151419
'id': dict({
14161420
'anyOf': list([
14171421
dict({

libs/core/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2932,6 +2932,10 @@
29322932
'title': 'Args',
29332933
'type': 'object',
29342934
}),
2935+
'extra_content': dict({
2936+
'title': 'Extra Content',
2937+
'type': 'object',
2938+
}),
29352939
'id': dict({
29362940
'anyOf': list([
29372941
dict({
@@ -4410,6 +4414,10 @@
44104414
'title': 'Args',
44114415
'type': 'object',
44124416
}),
4417+
'extra_content': dict({
4418+
'title': 'Extra Content',
4419+
'type': 'object',
4420+
}),
44134421
'id': dict({
44144422
'anyOf': list([
44154423
dict({
@@ -5900,6 +5908,10 @@
59005908
'title': 'Args',
59015909
'type': 'object',
59025910
}),
5911+
'extra_content': dict({
5912+
'title': 'Extra Content',
5913+
'type': 'object',
5914+
}),
59035915
'id': dict({
59045916
'anyOf': list([
59055917
dict({
@@ -7246,6 +7258,10 @@
72467258
'title': 'Args',
72477259
'type': 'object',
72487260
}),
7261+
'extra_content': dict({
7262+
'title': 'Extra Content',
7263+
'type': 'object',
7264+
}),
72497265
'id': dict({
72507266
'anyOf': list([
72517267
dict({
@@ -8766,6 +8782,10 @@
87668782
'title': 'Args',
87678783
'type': 'object',
87688784
}),
8785+
'extra_content': dict({
8786+
'title': 'Extra Content',
8787+
'type': 'object',
8788+
}),
87698789
'id': dict({
87708790
'anyOf': list([
87718791
dict({
@@ -10157,6 +10177,10 @@
1015710177
'title': 'Args',
1015810178
'type': 'object',
1015910179
}),
10180+
'extra_content': dict({
10181+
'title': 'Extra Content',
10182+
'type': 'object',
10183+
}),
1016010184
'id': dict({
1016110185
'anyOf': list([
1016210186
dict({
@@ -11596,6 +11620,10 @@
1159611620
'title': 'Args',
1159711621
'type': 'object',
1159811622
}),
11623+
'extra_content': dict({
11624+
'title': 'Extra Content',
11625+
'type': 'object',
11626+
}),
1159911627
'id': dict({
1160011628
'anyOf': list([
1160111629
dict({
@@ -13036,6 +13064,10 @@
1303613064
'title': 'Args',
1303713065
'type': 'object',
1303813066
}),
13067+
'extra_content': dict({
13068+
'title': 'Extra Content',
13069+
'type': 'object',
13070+
}),
1303913071
'id': dict({
1304013072
'anyOf': list([
1304113073
dict({

libs/partners/openai/langchain_openai/chat_models/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3406,14 +3406,18 @@ def _is_pydantic_class(obj: Any) -> bool:
34063406

34073407

34083408
def _lc_tool_call_to_openai_tool_call(tool_call: ToolCall) -> dict:
3409-
return {
3409+
result = {
34103410
"type": "function",
34113411
"id": tool_call["id"],
34123412
"function": {
34133413
"name": tool_call["name"],
34143414
"arguments": json.dumps(tool_call["args"], ensure_ascii=False),
34153415
},
34163416
}
3417+
# Include extra_content if present (for thought_signature, etc.)
3418+
if "extra_content" in tool_call:
3419+
result["extra_content"] = tool_call["extra_content"]
3420+
return result
34173421

34183422

34193423
def _lc_invalid_tool_call_to_openai_tool_call(

0 commit comments

Comments
 (0)