Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -728,9 +728,12 @@ async def gen() -> AsyncGenerator[AnthropicCompletionResponse, None]:
def _map_tool_choice_to_anthropic(
self, tool_required: bool, allow_parallel_tool_calls: bool
) -> dict:
is_thinking_enabled = (
self.thinking_dict and self.thinking_dict.get("type") == "enabled"
)
return {
"disable_parallel_tool_use": not allow_parallel_tool_calls,
"type": "any" if tool_required else "auto",
"type": "any" if tool_required and not is_thinking_enabled else "auto",
}

def _prepare_chat_with_tools(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import os
import httpx
from unittest.mock import MagicMock
from typing import List

import pytest
from pathlib import Path
from pydantic import BaseModel
from llama_index.core.prompts import PromptTemplate
from llama_index.core.base.llms.base import BaseLLM
from llama_index.core.llms import (
ChatMessage,
Expand Down Expand Up @@ -385,3 +388,82 @@ def test_cache_point_to_cache_control() -> None:
assert (
ant_messages[0]["content"][-1]["cache_control"]["cache_control"]["ttl"] == "5m"
)


@pytest.mark.skipif(
os.getenv("ANTHROPIC_API_KEY") is None,
reason="Anthropic API key not available to test Anthropic document uploading ",
)
def test_thinking_with_structured_output():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good also to have the counter-test, where if we have both thinking and tool_required enabled we have an error

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for future reference: if Anthropic will ever enable forced tool calls and thinking, we will know because this counter-test would fail, and then we can act accordingly

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea -- I added the counter test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome idea, to add a counter case also @logan-markewich, I think we forgot the version bump here.

# Example from: https://docs.llamaindex.ai/en/stable/examples/llm/anthropic/#structured-prediction
class MenuItem(BaseModel):
"""A menu item in a restaurant."""

course_name: str
is_vegetarian: bool

class Restaurant(BaseModel):
"""A restaurant with name, city, and cuisine."""

name: str
city: str
cuisine: str
menu_items: List[MenuItem]

llm = Anthropic(
model="claude-sonnet-4-0",
# max_tokens must be greater than budget_tokens
max_tokens=64000,
# temperature must be 1.0 for thinking to work
temperature=1.0,
thinking_dict={"type": "enabled", "budget_tokens": 1600},
)
prompt_tmpl = PromptTemplate("Generate a restaurant in a given city {city_name}")

restaurant_obj = (
llm.as_structured_llm(Restaurant)
.complete(prompt_tmpl.format(city_name="Miami"))
.raw
)

assert isinstance(restaurant_obj, Restaurant)


@pytest.mark.skipif(
os.getenv("ANTHROPIC_API_KEY") is None,
reason="Anthropic API key not available to test Anthropic document uploading ",
)
def test_thinking_with_tool_should_fail():
class MenuItem(BaseModel):
"""A menu item in a restaurant."""

course_name: str
is_vegetarian: bool

class Restaurant(BaseModel):
"""A restaurant with name, city, and cuisine."""

name: str
city: str
cuisine: str
menu_items: List[MenuItem]

def generate_restaurant(restaurant: Restaurant) -> Restaurant:
return restaurant

llm = Anthropic(
model="claude-sonnet-4-0",
# max_tokens must be greater than budget_tokens
max_tokens=64000,
# temperature must be 1.0 for thinking to work
temperature=1.0,
thinking_dict={"type": "enabled", "budget_tokens": 1600},
)

# Raises an exception because Anthropic doesn't support tool choice when thinking is enabled
with pytest.raises(Exception):
llm.chat_with_tools(
user_msg="Generate a restaurant in a given city Miami",
tools=[generate_restaurant],
tool_choice={"type": "any"},
)
Loading