Repository: explodinggradients/ragas
Issue: ToolCallF1 throws TypeError: unhashable type: 'dict'
Root cause: Validation Gap

[x] I have checked the [documentation](https://docs.ragas.io/) and related resources and couldn't resolve my bug.

**Describe the bug**
I'm running a LlamaIndex AgentWorkflow, a human-in-the-loop workflow which has access to several tools. I record all the events and user/assistant messages. I then use `convert_to_ragas_messages(events)` to convert messages to Ragas format and compute metrics.

NOTE: I'm simulating a multi-turn conversation between the Llamaindex workflow assistant and another LLM playing the human role. 
There isn't a lot of variation in the multi-turn conversation, both assistant and humer messages are more or less always the same.

The Ragas evaluation works sometimes, other times it throws the error below.

Ragas version: 0.4.1
Python version: 3.13.11

**Code to Reproduce**
Record all Llamaindex events as described in the documentation: https://docs.ragas.io/en/stable/howtos/integrations/llamaindex_agents/#agent-goal-accuracy

Then, once the events have been collected in  `events: list[Event]`:

```
    client = AsyncOpenAI(
        api_key=os.environ.get("OPENAI_API_KEY"),
    )
    evaluator_llm = llm_factory(model="gpt-4o-mini", provider="openai", client=client) 

    ragas_messages = convert_to_ragas_messages(events)
    
    ### Goal accuracy
    goal_accuracy = AgentGoalAccuracyWithReference(llm=evaluator_llm)
    goal_score = await goal_accuracy.ascore(
        ragas_messages,
        reference="The user has defined a job description for a backend engineer role."
    ) 
    print(f"Agent goal accuracy score: {goal_score.value}")
    if goal_score.reason:
        print(f"Agent goal accuracy reason: {goal_score.reason}")

    ### Topic adherence
    topic_adherence_scorer = TopicAdherence(llm=evaluator_llm, mode="precision")
    topic_score = await topic_adherence_scorer.ascore(ragas_messages, reference_topics=["recruitment"]) 
    print(f"Topic adherence score: {topic_score.value}")
    if topic_score.reason:
        print(f"Topic adherence reason: {topic_score.reason}")

    ### Tool call F1
    reference_tool_calls=[
        ToolCall(
            name="store_initial_job_description",
            args={'initial_job_description': '** Backend Engineer\n\n**Descripción del Puesto:**\nBuscamos un Backend Engineer talentoso y motivado para unirse a nuestro equipo. El candidato ideal será responsable de diseñar, desarrollar y mantener la lógica del servidor y la base de datos de nuestras aplicaciones.', 'initial_job_title': 'Backend Engineer', 'kwargs': {}},
        )
    ]
    f1_scorer = ToolCallF1()
    f1_tool_score = await f1_scorer.ascore(ragas_messages, reference_tool_calls=reference_tool_calls) 
    print(f"F1 score: {f1_tool_score.value}")
    print(f"F1 score reason: {f1_tool_score.reason}")
```

**Error trace**
```
Agent goal accuracy score: 1.0
Topic adherence score: 0.9230769230698225
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/Users/ariel/Projects/tekne/platform/backend/llm_eval/test_recruiter_workflow_ragas.py", line 315, in <module>
    asyncio.run(main())
    ~~~~~~~~~~~^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.11/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 195, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.11/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.11/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/Users/ariel/Projects/tekne/platform/backend/llm_eval/test_recruiter_workflow_ragas.py", line 311, in main
    await evaluate_with_ragas(test_cases, events)
  File "/Users/ariel/Projects/tekne/platform/backend/llm_eval/test_recruiter_workflow_ragas.py", line 286, in evaluate_with_ragas
    f1_tool_score = await f1_scorer.ascore(ragas_messages, reference_tool_calls=reference_tool_calls) # type: ignore
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ariel/Projects/ragas/src/ragas/metrics/collections/tool_call_f1/metric.py", line 93, in ascore
    expected.add(tool_call_to_hashable(call))
                 ~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/Users/ariel/Projects/ragas/src/ragas/metrics/collections/tool_call_f1/util.py", line 18, in tool_call_to_hashable
    return (tc.name, frozenset(tc.args.items()))
                     ~~~~~~~~~^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'dict'
```

**Expected behavior**
I would expect to see the F1 metric of the tool call.


**Additional context**
I can attach the output of the entire multi-turn simulation if needed.

<!-- PS: bugs suck but is also part of the process. We sincerely apologies for breaking your flow because of it, but don't worry, we got your back ❤️. We will get this fixed as fast as we can and thanks for helping us out by reporting it 🙏. -->


Thanks @arielfaur 

Does PR #2507 fix it for you?

@anistark It works, thanks!

One last question, what metrics provide a reason? So far the agentic ones I've tested do not provide any reason. 


> [@anistark](https://github.com/anistark) It works, thanks!
> 
> One last question, what metrics provide a reason? So far the agentic ones I've tested do not provide any reason.

Faithfulness, AnswerCorrectness, etc give reasoning right now.
Will improve docs on how it works and also check if possible to extend to agentic ones.