Merge branch 'mdrxy/ollama_v1' of github.com:langchain-ai/langchain into mdrxy/ollama_v1

This commit is contained in:
Mason Daugherty 2025-08-04 16:06:51 -04:00
commit 63ade97a5f
No known key found for this signature in database
4 changed files with 104 additions and 72 deletions

View File

@ -284,31 +284,47 @@ def _convert_from_v1_to_chat_completions(message: AIMessageV1) -> AIMessageV1:
# v1 / Responses
def _convert_annotation_to_v1(annotation: dict[str, Any]) -> dict[str, Any]:
def _convert_annotation_to_v1(annotation: dict[str, Any]) -> types.Annotation:
annotation_type = annotation.get("type")
if annotation_type == "url_citation":
url_citation = {}
known_fields = {
"type",
"url",
"title",
"cited_text",
"start_index",
"end_index",
}
url_citation = cast(types.Citation, {})
for field in ("end_index", "start_index", "title"):
if field in annotation:
url_citation[field] = annotation[field]
url_citation["type"] = "citation"
url_citation["url"] = annotation["url"]
for field in annotation:
if field not in known_fields:
if "extras" not in url_citation:
url_citation["extras"] = {}
url_citation["extras"][field] = annotation[field]
return url_citation
elif annotation_type == "file_citation":
document_citation = {"type": "citation"}
known_fields = {"type", "title", "cited_text", "start_index", "end_index"}
document_citation: types.Citation = {"type": "citation"}
if "filename" in annotation:
document_citation["title"] = annotation["filename"]
if "file_id" in annotation:
document_citation["file_id"] = annotation["file_id"]
if "index" in annotation:
document_citation["file_index"] = annotation["index"]
document_citation["title"] = annotation.pop("filename")
for field in annotation:
if field not in known_fields:
if "extras" not in document_citation:
document_citation["extras"] = {}
document_citation["extras"][field] = annotation[field]
return document_citation
# TODO: standardise container_file_citation?
else:
non_standard_annotation = {
non_standard_annotation: types.NonStandardAnnotation = {
"type": "non_standard_annotation",
"value": annotation,
}
@ -320,23 +336,30 @@ def _explode_reasoning(block: dict[str, Any]) -> Iterable[types.ReasoningContent
yield cast(types.ReasoningContentBlock, block)
return
known_fields = {"type", "reasoning", "id", "index"}
unknown_fields = [
field for field in block if field != "summary" and field not in known_fields
]
if unknown_fields:
block["extras"] = {}
for field in unknown_fields:
block["extras"][field] = block.pop(field)
if not block["summary"]:
_ = block.pop("summary", None)
yield cast(types.ReasoningContentBlock, block)
return
# Common part for every exploded line, except 'summary'
common = {k: v for k, v in block.items() if k != "summary"}
common = {k: v for k, v in block.items() if k in known_fields}
# Optional keys that must appear only in the first exploded item
first_only = {
k: common.pop(k) for k in ("encrypted_content", "status") if k in common
}
first_only = block.pop("extras", None)
for idx, part in enumerate(block["summary"]):
new_block = dict(common)
new_block["reasoning"] = part.get("text", "")
if idx == 0:
if idx == 0 and first_only:
new_block.update(first_only)
yield cast(types.ReasoningContentBlock, new_block)
@ -370,9 +393,11 @@ def _convert_to_v1_from_responses(
new_block = {"type": "image", "base64": result}
if output_format := block.get("output_format"):
new_block["mime_type"] = f"image/{output_format}"
if "id" in block:
new_block["id"] = block["id"]
if "index" in block:
new_block["index"] = block["index"]
for extra_key in (
"id",
"index",
"status",
"background",
"output_format",
@ -401,7 +426,9 @@ def _convert_to_v1_from_responses(
break
if tool_call_block:
if "id" in block:
tool_call_block["item_id"] = block["id"]
if "extras" not in tool_call_block:
tool_call_block["extras"] = {}
tool_call_block["extras"]["item_id"] = block["id"] # type: ignore[typeddict-item]
if "index" in block:
tool_call_block["index"] = block["index"]
yield tool_call_block
@ -479,17 +506,26 @@ def _convert_to_v1_from_responses(
def _convert_annotation_from_v1(annotation: types.Annotation) -> dict[str, Any]:
if annotation["type"] == "citation":
new_ann: dict[str, Any] = {}
for field in ("end_index", "start_index"):
if field in annotation:
new_ann[field] = annotation[field]
if "url" in annotation:
return {**annotation, "type": "url_citation"}
# URL citation
if "title" in annotation:
new_ann["title"] = annotation["title"]
new_ann["type"] = "url_citation"
new_ann["url"] = annotation["url"]
else:
# Document citation
new_ann["type"] = "file_citation"
if "title" in annotation:
new_ann["filename"] = annotation["title"]
new_ann: dict[str, Any] = {"type": "file_citation"}
if "title" in annotation:
new_ann["filename"] = annotation["title"]
if "file_id" in annotation:
new_ann["file_id"] = annotation["file_id"] # type: ignore[typeddict-item]
if "file_index" in annotation:
new_ann["index"] = annotation["file_index"] # type: ignore[typeddict-item]
if extra_fields := annotation.get("extras"):
for field, value in extra_fields.items():
new_ann[field] = value
return new_ann
@ -515,7 +551,8 @@ def _implode_reasoning_blocks(blocks: list[dict[str, Any]]) -> Iterable[dict[str
elif "reasoning" not in block and "summary" not in block:
# {"type": "reasoning", "id": "rs_..."}
oai_format = {**block, "summary": []}
# Update key order
if "extras" in oai_format:
oai_format.update(oai_format.pop("extras"))
oai_format["type"] = oai_format.pop("type", "reasoning")
if "encrypted_content" in oai_format:
oai_format["encrypted_content"] = oai_format.pop("encrypted_content")
@ -530,6 +567,8 @@ def _implode_reasoning_blocks(blocks: list[dict[str, Any]]) -> Iterable[dict[str
]
# 'common' is every field except the exploded 'reasoning'
common = {k: v for k, v in block.items() if k != "reasoning"}
if "extras" in common:
common.update(common.pop("extras"))
i += 1
while i < n:
@ -623,12 +662,12 @@ def _convert_from_v1_to_responses(
new_content.append(new_block)
elif block["type"] == "tool_call":
new_block = {"type": "function_call", "call_id": block["id"]}
if "item_id" in block:
new_block["id"] = block["item_id"] # type: ignore[typeddict-item]
if "extras" in block and "item_id" in block["extras"]:
new_block["id"] = block["extras"]["item_id"]
if "name" in block:
new_block["name"] = block["name"]
if "arguments" in block:
new_block["arguments"] = block["arguments"] # type: ignore[typeddict-item]
if "extras" in block and "arguments" in block["extras"]:
new_block["arguments"] = block["extras"]["arguments"]
if any(key not in block for key in ("name", "arguments")):
matching_tool_calls = [
call for call in tool_calls if call["id"] == block["id"]

View File

@ -50,6 +50,11 @@ def _check_response(
key in annotation
for key in ["end_index", "start_index", "title", "type", "url"]
)
elif annotation["type"] == "citation":
assert all(key in annotation for key in ["title", "type"])
if "url" in annotation:
assert "start_index" in annotation
assert "end_index" in annotation
if output_version == "v1":
text_content = response.text

View File

@ -2421,31 +2421,24 @@ def test_convert_from_v1_to_chat_completions(
"name": "get_weather",
"args": {"location": "San Francisco"},
},
cast(
ToolCall,
{
"type": "tool_call",
"id": "call_234",
"name": "get_weather_2",
"args": {"location": "New York"},
"item_id": "fc_123",
},
),
{
"type": "tool_call",
"id": "call_234",
"name": "get_weather_2",
"args": {"location": "New York"},
"extras": {"item_id": "fc_123"},
},
{"type": "text", "text": "Hello "},
{
"type": "text",
"text": "world",
"annotations": [
{"type": "citation", "url": "https://example.com"},
cast(
types.Citation,
{
"type": "citation",
"title": "my doc",
"file_index": 1,
"file_id": "file_123",
},
),
{
"type": "citation",
"title": "my doc",
"extras": {"file_id": "file_123", "index": 1},
},
{
"type": "non_standard_annotation",
"value": {"bar": "baz"},
@ -2583,31 +2576,24 @@ def test_convert_from_v1_to_responses(
"name": "get_weather",
"args": {"location": "San Francisco"},
},
cast(
ToolCall,
{
"type": "tool_call",
"id": "call_234",
"name": "get_weather_2",
"args": {"location": "New York"},
"item_id": "fc_123",
},
),
{
"type": "tool_call",
"id": "call_234",
"name": "get_weather_2",
"args": {"location": "New York"},
"extras": {"item_id": "fc_123"},
},
{"type": "text", "text": "Hello "},
{
"type": "text",
"text": "world",
"annotations": [
{"type": "citation", "url": "https://example.com"},
cast(
types.Citation,
{
"type": "citation",
"title": "my doc",
"file_index": 1,
"file_id": "file_123",
},
),
{
"type": "citation",
"title": "my doc",
"extras": {"file_id": "file_123", "index": 1},
},
{"type": "non_standard_annotation", "value": {"bar": "baz"}},
],
},

View File

@ -338,7 +338,7 @@ responses_stream = [
id="rs_234",
summary=[],
type="reasoning",
encrypted_content=None,
encrypted_content="encrypted-content",
status=None,
),
output_index=2,
@ -417,7 +417,7 @@ responses_stream = [
Summary(text="still more reasoning", type="summary_text"),
],
type="reasoning",
encrypted_content=None,
encrypted_content="encrypted-content",
status=None,
),
output_index=2,
@ -563,7 +563,7 @@ responses_stream = [
Summary(text="still more reasoning", type="summary_text"),
],
type="reasoning",
encrypted_content=None,
encrypted_content="encrypted-content",
status=None,
),
ResponseOutputMessage(
@ -659,6 +659,7 @@ def test_responses_stream() -> None:
{"index": 0, "type": "summary_text", "text": "more reasoning"},
{"index": 1, "type": "summary_text", "text": "still more reasoning"},
],
"encrypted_content": "encrypted-content",
"type": "reasoning",
"index": 3,
},
@ -723,6 +724,7 @@ def test_responses_stream_v1() -> None:
"type": "reasoning",
"reasoning": "more reasoning",
"id": "rs_234",
"extras": {"encrypted_content": "encrypted-content"},
"index": 4,
},
{