Skip to content

fix(smolagents): adds reasoning_content #1642

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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 @@ -2,6 +2,8 @@
OPENAI_API_KEY=sk-YOUR_API_KEY
# API Key from https://e2b.dev/docs/legacy/getting-started/api-key
E2B_API_KEY=e2b_YOUR_API_KEY
# API Key from https://console.anthropic.com/
ANTHROPIC_API_KEY=YOUR_API_KEY

# Phoenix listens on the default gRPC port 4317, so you don't need to change
# exporter settings. If you prefer to export via HTTP, uncomment this:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from smolagents import (
LiteLLMModel
)
from smolagents.agents import CodeAgent

model_params = {"thinking": {
"type": "enabled",
"budget_tokens": 4000
}}

model = LiteLLMModel(model_id="anthropic/claude-3-7-sonnet-20250219", **model_params)

agent = CodeAgent(model=model, add_base_tools=False)

print(agent.run("What's the weather like in Paris?"))
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,18 @@ def _llm_output_messages(output_message: Any) -> Iterator[Tuple[str, Any]]:
f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_CONTENT}",
content,
)

# Add the reasoning_content if available in raw.choices[0].message structure
if (raw := getattr(output_message, "raw", None)) is not None:
if(choices := getattr(raw, "choices", None)) is not None:
if (type(choices) is list) and len(choices) > 0:
if(message := getattr(choices[0], "message", None)) is not None:
if(reasoning := getattr(message, "reasoning_content", None)) is not None:
yield (
f"{LLM_OUTPUT_MESSAGES}.0.message.reasoning_content",
reasoning,
)

if isinstance(tool_calls := getattr(output_message, "tool_calls", None), list):
for tool_call_index, tool_call in enumerate(tool_calls):
if (tool_call_id := getattr(tool_call, "id", None)) is not None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,70 @@ def forward(self, location: str) -> str:
assert json.loads(tool_call_arguments_json) == {"location": "Paris"}
assert not attributes

@pytest.mark.vcr(
decode_compressed_response=True,
before_record_request=remove_all_vcr_request_headers,
before_record_response=remove_all_vcr_response_headers,
)
def test_litellm_reasoning_model_has_expected_attributes(
self,
anthropic_api_key: str,
in_memory_span_exporter: InMemorySpanExporter,
) -> None:

model_params = {"thinking": {
"type": "enabled",
"budget_tokens": 4000
}}

model = LiteLLMModel(
model_id="anthropic/claude-3-7-sonnet-20250219",
api_key=os.environ["ANTHROPIC_API_KEY"],
api_base="https://api.anthropic.com/v1",
**model_params
)

input_message_content = (
"Who won the World Cup in 2018? Answer in one word with no punctuation."
)
output_message = model(
messages=[
{
"role": "user",
"content": [{"type": "text", "text": input_message_content}],
}
]
)
output_message_content = output_message.content
spans = in_memory_span_exporter.get_finished_spans()
assert len(spans) == 1
span = spans[0]
assert span.name == "LiteLLMModel.__call__"
assert span.status.is_ok
attributes = dict(span.attributes or {})
assert attributes.pop(OPENINFERENCE_SPAN_KIND) == LLM
assert attributes.pop(INPUT_MIME_TYPE) == JSON
assert isinstance(input_value := attributes.pop(INPUT_VALUE), str)
input_data = json.loads(input_value)
assert "messages" in input_data
assert attributes.pop(OUTPUT_MIME_TYPE) == JSON
assert isinstance(output_value := attributes.pop(OUTPUT_VALUE), str)
assert isinstance(json.loads(output_value), dict)
assert attributes.pop(LLM_MODEL_NAME) == "anthropic/claude-3-7-sonnet-20250219"
assert isinstance(inv_params := attributes.pop(LLM_INVOCATION_PARAMETERS), str)
assert json.loads(inv_params) == model_params
assert attributes.pop(f"{LLM_INPUT_MESSAGES}.0.{MESSAGE_ROLE}") == "user"
assert attributes.pop(f"{LLM_INPUT_MESSAGES}.0.{MESSAGE_CONTENT}") == input_message_content
assert isinstance(attributes.pop(LLM_TOKEN_COUNT_PROMPT), int)
assert isinstance(attributes.pop(LLM_TOKEN_COUNT_COMPLETION), int)
assert isinstance(attributes.pop(LLM_TOKEN_COUNT_TOTAL), int)
assert attributes.pop(f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_ROLE}") == "assistant"
assert (
attributes.pop(f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_CONTENT}") == output_message_content
)
assert isinstance(attributes.pop(f"{LLM_OUTPUT_MESSAGES}.0.message.reasoning_content"), str)
assert not attributes


class TestRun:
@pytest.mark.xfail
Expand Down