|
17 | 17 | default_agent, |
18 | 18 | get_agent_manager, |
19 | 19 | ) |
| 20 | +from homeassistant.components.conversation.chat_log import ( |
| 21 | + AssistantContent, |
| 22 | + ToolResultContent, |
| 23 | + async_get_chat_log, |
| 24 | +) |
20 | 25 | from homeassistant.components.conversation.default_agent import METADATA_CUSTOM_SENTENCE |
21 | 26 | from homeassistant.components.conversation.models import ConversationInput |
22 | 27 | from homeassistant.components.conversation.trigger import TriggerDetails |
|
52 | 57 | ) |
53 | 58 | from homeassistant.helpers import ( |
54 | 59 | area_registry as ar, |
| 60 | + chat_session, |
55 | 61 | device_registry as dr, |
56 | 62 | entity_registry as er, |
57 | 63 | floor_registry as fr, |
@@ -3424,3 +3430,149 @@ async def test_fuzzy_matching( |
3424 | 3430 | if slot_name != "preferred_area_id" # context area |
3425 | 3431 | } |
3426 | 3432 | assert actual_slots == slots |
| 3433 | + |
| 3434 | + |
| 3435 | +@pytest.mark.usefixtures("init_components") |
| 3436 | +async def test_intent_tool_call_in_chat_log(hass: HomeAssistant) -> None: |
| 3437 | + """Test that intent tool calls are stored in the chat log.""" |
| 3438 | + hass.states.async_set( |
| 3439 | + "light.test_light", "off", attributes={ATTR_FRIENDLY_NAME: "Test Light"} |
| 3440 | + ) |
| 3441 | + async_mock_service(hass, "light", "turn_on") |
| 3442 | + |
| 3443 | + result = await conversation.async_converse( |
| 3444 | + hass, "turn on test light", None, Context(), None |
| 3445 | + ) |
| 3446 | + |
| 3447 | + assert result.response.response_type == intent.IntentResponseType.ACTION_DONE |
| 3448 | + |
| 3449 | + with ( |
| 3450 | + chat_session.async_get_chat_session(hass, result.conversation_id) as session, |
| 3451 | + async_get_chat_log(hass, session) as chat_log, |
| 3452 | + ): |
| 3453 | + pass |
| 3454 | + |
| 3455 | + # Find the tool call in the chat log |
| 3456 | + tool_call_content: AssistantContent | None = None |
| 3457 | + tool_result_content: ToolResultContent | None = None |
| 3458 | + assistant_content: AssistantContent | None = None |
| 3459 | + |
| 3460 | + for content in chat_log.content: |
| 3461 | + if content.role == "assistant" and content.tool_calls: |
| 3462 | + tool_call_content = content |
| 3463 | + if content.role == "tool_result": |
| 3464 | + tool_result_content = content |
| 3465 | + if content.role == "assistant" and not content.tool_calls: |
| 3466 | + assistant_content = content |
| 3467 | + |
| 3468 | + # Verify tool call was stored |
| 3469 | + assert tool_call_content is not None and tool_call_content.tool_calls is not None |
| 3470 | + assert len(tool_call_content.tool_calls) == 1 |
| 3471 | + assert tool_call_content.tool_calls[0].tool_name == "HassTurnOn" |
| 3472 | + assert tool_call_content.tool_calls[0].external is True |
| 3473 | + assert tool_call_content.tool_calls[0].tool_args.get("name") == "Test Light" |
| 3474 | + |
| 3475 | + # Verify tool result was stored |
| 3476 | + assert tool_result_content is not None |
| 3477 | + assert tool_result_content.tool_name == "HassTurnOn" |
| 3478 | + assert tool_result_content.tool_result["response_type"] == "action_done" |
| 3479 | + |
| 3480 | + # Verify final assistant content with speech |
| 3481 | + assert assistant_content is not None |
| 3482 | + assert assistant_content.content is not None |
| 3483 | + |
| 3484 | + |
| 3485 | +@pytest.mark.usefixtures("init_components") |
| 3486 | +async def test_trigger_tool_call_in_chat_log(hass: HomeAssistant) -> None: |
| 3487 | + """Test that trigger tool calls are stored in the chat log.""" |
| 3488 | + trigger_sentence = "test automation trigger" |
| 3489 | + trigger_response = "Trigger activated!" |
| 3490 | + |
| 3491 | + manager = get_agent_manager(hass) |
| 3492 | + callback = AsyncMock(return_value=trigger_response) |
| 3493 | + manager.register_trigger(TriggerDetails([trigger_sentence], callback)) |
| 3494 | + |
| 3495 | + result = await conversation.async_converse( |
| 3496 | + hass, trigger_sentence, None, Context(), None |
| 3497 | + ) |
| 3498 | + |
| 3499 | + assert result.response.response_type == intent.IntentResponseType.ACTION_DONE |
| 3500 | + |
| 3501 | + with ( |
| 3502 | + chat_session.async_get_chat_session(hass, result.conversation_id) as session, |
| 3503 | + async_get_chat_log(hass, session) as chat_log, |
| 3504 | + ): |
| 3505 | + pass |
| 3506 | + |
| 3507 | + # Find the tool call in the chat log |
| 3508 | + tool_call_content: AssistantContent | None = None |
| 3509 | + tool_result_content: ToolResultContent | None = None |
| 3510 | + |
| 3511 | + for content in chat_log.content: |
| 3512 | + if content.role == "assistant" and content.tool_calls: |
| 3513 | + tool_call_content = content |
| 3514 | + if content.role == "tool_result": |
| 3515 | + tool_result_content = content |
| 3516 | + |
| 3517 | + # Verify tool call was stored |
| 3518 | + assert tool_call_content is not None and tool_call_content.tool_calls is not None |
| 3519 | + assert len(tool_call_content.tool_calls) == 1 |
| 3520 | + assert tool_call_content.tool_calls[0].tool_name == "trigger_sentence" |
| 3521 | + assert tool_call_content.tool_calls[0].external is True |
| 3522 | + assert tool_call_content.tool_calls[0].tool_args == {} |
| 3523 | + |
| 3524 | + # Verify tool result was stored |
| 3525 | + assert tool_result_content is not None |
| 3526 | + assert tool_result_content.tool_name == "trigger_sentence" |
| 3527 | + assert tool_result_content.tool_result["response"] == trigger_response |
| 3528 | + |
| 3529 | + |
| 3530 | +@pytest.mark.usefixtures("init_components") |
| 3531 | +async def test_no_tool_call_on_no_intent_match(hass: HomeAssistant) -> None: |
| 3532 | + """Test that no tool call is stored when no intent is matched.""" |
| 3533 | + result = await conversation.async_converse( |
| 3534 | + hass, "this is a random sentence that should not match", None, Context(), None |
| 3535 | + ) |
| 3536 | + |
| 3537 | + assert result.response.response_type == intent.IntentResponseType.ERROR |
| 3538 | + |
| 3539 | + with ( |
| 3540 | + chat_session.async_get_chat_session(hass, result.conversation_id) as session, |
| 3541 | + async_get_chat_log(hass, session) as chat_log, |
| 3542 | + ): |
| 3543 | + pass |
| 3544 | + |
| 3545 | + # Verify no tool call was stored |
| 3546 | + for content in chat_log.content: |
| 3547 | + if content.role == "assistant": |
| 3548 | + assert content.tool_calls is None or len(content.tool_calls) == 0 |
| 3549 | + break |
| 3550 | + else: |
| 3551 | + pytest.fail("No assistant content found in chat log") |
| 3552 | + |
| 3553 | + |
| 3554 | +@pytest.mark.usefixtures("init_components") |
| 3555 | +async def test_intent_tool_call_with_error_response(hass: HomeAssistant) -> None: |
| 3556 | + """Test that intent tool calls store error information correctly.""" |
| 3557 | + # Request to turn on a non-existent device |
| 3558 | + result = await conversation.async_converse( |
| 3559 | + hass, "turn on the non existent device", None, Context(), None |
| 3560 | + ) |
| 3561 | + |
| 3562 | + assert result.response.response_type == intent.IntentResponseType.ERROR |
| 3563 | + assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS |
| 3564 | + |
| 3565 | + with ( |
| 3566 | + chat_session.async_get_chat_session(hass, result.conversation_id) as session, |
| 3567 | + async_get_chat_log(hass, session) as chat_log, |
| 3568 | + ): |
| 3569 | + pass |
| 3570 | + |
| 3571 | + # Verify no tool call was stored for unmatched entities |
| 3572 | + tool_call_found = False |
| 3573 | + for content in chat_log.content: |
| 3574 | + if content.role == "assistant" and content.tool_calls: |
| 3575 | + tool_call_found = True |
| 3576 | + |
| 3577 | + # No tool call should be stored since the entity could not be matched |
| 3578 | + assert not tool_call_found |
0 commit comments