ohollo commited on
Commit
31e6fb5
·
1 Parent(s): 3d8b2e6

Adapt fixture and runner

Browse files
agents/claude.py CHANGED
@@ -8,6 +8,12 @@ from .base import Agent, AgentResult, ToolCall
8
 
9
  _DEFAULT_MODEL = "claude-sonnet-4-6"
10
 
 
 
 
 
 
 
11
 
12
  class ClaudeAgent(Agent):
13
  """Agent using the Anthropic SDK, connecting to tools via an MCP SSE server."""
@@ -32,6 +38,7 @@ class ClaudeAgent(Agent):
32
  response = self._client.messages.create(
33
  model=self.model,
34
  max_tokens=4096,
 
35
  tools=tools,
36
  messages=messages,
37
  )
@@ -45,21 +52,23 @@ class ClaudeAgent(Agent):
45
  tool_results = []
46
  for block in response.content:
47
  if block.type == "tool_use":
48
- mcp_result = await session.call_tool(block.name, dict(block.input))
49
- result_text = "\n".join(
50
- item.text for item in mcp_result.content if hasattr(item, "text")
51
- )
52
  tool_calls.append(ToolCall(name=block.name, args=dict(block.input), result=result_text))
53
  tool_results.append({
54
  "type": "tool_result",
55
  "tool_use_id": block.id,
56
- "content": str(mcp_result.content),
57
  })
58
 
59
  messages.append({"role": "assistant", "content": response.content})
60
  messages.append({"role": "user", "content": tool_results})
61
 
62
 
 
 
 
 
 
63
  def _to_anthropic(tool) -> dict:
64
  return {
65
  "name": tool.name,
 
8
 
9
  _DEFAULT_MODEL = "claude-sonnet-4-6"
10
 
11
+ _SYSTEM = (
12
+ "You are a harmonic analysis assistant. After analysing a chord sequence, present only "
13
+ "well-known results — songs that are recognisable hits or by widely-known artists. "
14
+ "Use your knowledge to filter out obscure tracks. If no well-known matches exist, say so."
15
+ )
16
+
17
 
18
  class ClaudeAgent(Agent):
19
  """Agent using the Anthropic SDK, connecting to tools via an MCP SSE server."""
 
38
  response = self._client.messages.create(
39
  model=self.model,
40
  max_tokens=4096,
41
+ system=_SYSTEM,
42
  tools=tools,
43
  messages=messages,
44
  )
 
52
  tool_results = []
53
  for block in response.content:
54
  if block.type == "tool_use":
55
+ result_text = await self._invoke_tool(session, block.name, dict(block.input))
 
 
 
56
  tool_calls.append(ToolCall(name=block.name, args=dict(block.input), result=result_text))
57
  tool_results.append({
58
  "type": "tool_result",
59
  "tool_use_id": block.id,
60
+ "content": result_text,
61
  })
62
 
63
  messages.append({"role": "assistant", "content": response.content})
64
  messages.append({"role": "user", "content": tool_results})
65
 
66
 
67
+ async def _invoke_tool(self, session: ClientSession, name: str, args: dict) -> str:
68
+ mcp_result = await session.call_tool(name, args)
69
+ return "\n".join(item.text for item in mcp_result.content if hasattr(item, "text"))
70
+
71
+
72
  def _to_anthropic(tool) -> dict:
73
  return {
74
  "name": tool.name,
langsmith_evals/evaluators.py CHANGED
@@ -22,11 +22,8 @@ def _count_tool_use_in_llm_runs(llm_runs: list[Run], tool_name: str) -> int:
22
  return count
23
 
24
 
25
- def evaluate_formats_tool_call_count(run: Run, example: Example) -> EvaluationResult:
26
- """Count how many times the supported-formats tool was called.
27
-
28
- Ideally the agent calls it exactly once before analysing chords.
29
- A score of 0 means it was never called; higher values indicate redundant calls.
30
 
31
  :param run: LangSmith run — LLM child runs are fetched via the client.
32
  :param example: Dataset example (unused).
@@ -35,12 +32,59 @@ def evaluate_formats_tool_call_count(run: Run, example: Example) -> EvaluationRe
35
  llm_runs = list(client.list_runs(trace_id=run.id, run_type="llm"))
36
  count = _count_tool_use_in_llm_runs(llm_runs, _FORMATS_TOOL)
37
  return EvaluationResult(
38
- key="formats_tool_call_count",
39
- score=count,
40
  comment=f"{_FORMATS_TOOL} called {count} time(s)",
41
  )
42
 
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  def evaluate_result_count(run: Run, example: Example) -> EvaluationResult:
45
  """Check the number of returned songs does not exceed the requested limit.
46
 
 
22
  return count
23
 
24
 
25
+ def evaluate_formats_tool_called_once(run: Run, example: Example) -> EvaluationResult:
26
+ """Check that the supported-formats tool was called exactly once.
 
 
 
27
 
28
  :param run: LangSmith run — LLM child runs are fetched via the client.
29
  :param example: Dataset example (unused).
 
32
  llm_runs = list(client.list_runs(trace_id=run.id, run_type="llm"))
33
  count = _count_tool_use_in_llm_runs(llm_runs, _FORMATS_TOOL)
34
  return EvaluationResult(
35
+ key="formats_tool_called_once",
36
+ score=int(count == 1),
37
  comment=f"{_FORMATS_TOOL} called {count} time(s)",
38
  )
39
 
40
 
41
+ def _title_in_text(title: str, text: str) -> bool:
42
+ return title.lower() in text.lower()
43
+
44
+
45
+ def evaluate_notable_songs_recalled(run: Run, example: Example) -> EvaluationResult:
46
+ """Fraction of expected notable songs that appear in the agent's response.
47
+
48
+ 1.0 = all present (green), 0.0 = none present (red).
49
+
50
+ :param run: LangSmith run — expects ``outputs["response"]`` as a string.
51
+ :param example: Dataset example — expects ``outputs["expected_in_response"]`` as a list of titles.
52
+ """
53
+ response = (run.outputs or {}).get("response", "")
54
+ expected = (example.outputs or {}).get("expected_in_response", [])
55
+ if not expected:
56
+ return EvaluationResult(key="notable_songs_recalled", score=None,
57
+ comment="no expected_in_response in example outputs")
58
+ found = [t for t in expected if _title_in_text(t, response)]
59
+ return EvaluationResult(
60
+ key="notable_songs_recalled",
61
+ score=len(found) / len(expected),
62
+ comment=f"{len(found)}/{len(expected)} notable songs present in response",
63
+ )
64
+
65
+
66
+ def evaluate_non_notable_songs_excluded(run: Run, example: Example) -> EvaluationResult:
67
+ """Fraction of expected-excluded songs that do NOT appear in the agent's response.
68
+
69
+ 1.0 = none leaked (green), 0.0 = all leaked (red).
70
+
71
+ :param run: LangSmith run — expects ``outputs["response"]`` as a string.
72
+ :param example: Dataset example — expects ``outputs["expected_not_in_response"]`` as a list of titles.
73
+ """
74
+ response = (run.outputs or {}).get("response", "")
75
+ excluded = (example.outputs or {}).get("expected_not_in_response", [])
76
+ if not excluded:
77
+ return EvaluationResult(key="non_notable_songs_excluded", score=None,
78
+ comment="no expected_not_in_response in example outputs")
79
+ leaked = [t for t in excluded if _title_in_text(t, response)]
80
+ return EvaluationResult(
81
+ key="non_notable_songs_excluded",
82
+ score=1 - len(leaked) / len(excluded),
83
+ comment=(f"{len(leaked)} excluded song(s) found: {leaked}" if leaked
84
+ else "no excluded songs in response"),
85
+ )
86
+
87
+
88
  def evaluate_result_count(run: Run, example: Example) -> EvaluationResult:
89
  """Check the number of returned songs does not exceed the requested limit.
90
 
langsmith_evals/runner.py CHANGED
@@ -4,19 +4,23 @@ from langsmith import Client
4
  from langsmith import evaluate as ls_evaluate
5
 
6
  from langsmith_evals.dataset import DATASET_NAME
7
- from langsmith_evals.evaluators import evaluate_formats_tool_call_count, evaluate_result_count
8
 
 
9
 
10
- def run(target, prefix: str) -> None:
11
- """Run a LangSmith experiment against the harmonic analysis dataset.
 
12
 
13
  :param target: Callable ``(inputs: dict) -> dict`` to evaluate.
14
  :param prefix: Experiment name prefix shown in the LangSmith UI.
 
 
15
  """
16
  ls_evaluate(
17
  target,
18
- data=DATASET_NAME,
19
- evaluators=[evaluate_result_count, evaluate_formats_tool_call_count],
20
  experiment_prefix=prefix,
21
  client=Client(),
22
  )
 
4
  from langsmith import evaluate as ls_evaluate
5
 
6
  from langsmith_evals.dataset import DATASET_NAME
7
+ from langsmith_evals.evaluators import evaluate_formats_tool_called_once, evaluate_result_count
8
 
9
+ _DEFAULT_EVALUATORS = [evaluate_result_count, evaluate_formats_tool_called_once]
10
 
11
+
12
+ def run(target, prefix: str, evaluators: list | None = None, dataset: str | None = None) -> None:
13
+ """Run a LangSmith experiment against a dataset.
14
 
15
  :param target: Callable ``(inputs: dict) -> dict`` to evaluate.
16
  :param prefix: Experiment name prefix shown in the LangSmith UI.
17
+ :param evaluators: Evaluator functions. Defaults to result-count + formats-tool-called-once.
18
+ :param dataset: Dataset name. Defaults to the main chord-sequences dataset.
19
  """
20
  ls_evaluate(
21
  target,
22
+ data=dataset or DATASET_NAME,
23
+ evaluators=evaluators or _DEFAULT_EVALUATORS,
24
  experiment_prefix=prefix,
25
  client=Client(),
26
  )
langsmith_evals/similarity_fixture.json CHANGED
@@ -1,57 +1,59 @@
1
  [
2
  {"title": "Let It Be", "artist": "The Beatles", "similarity": 0.97},
3
- {"title": "No Woman No Cry", "artist": "Bob Marley", "similarity": 0.961},
4
- {"title": "Africa", "artist": "Toto", "similarity": 0.952},
5
  {"title": "With or Without You", "artist": "U2", "similarity": 0.943},
 
6
  {"title": "Stand By Me", "artist": "Ben E. King", "similarity": 0.934},
7
- {"title": "Zombie", "artist": "The Cranberries", "similarity": 0.925},
8
- {"title": "Knockin on Heavens Door", "artist": "Bob Dylan", "similarity": 0.916},
9
- {"title": "Wonderful Tonight", "artist": "Eric Clapton", "similarity": 0.907},
10
- {"title": "Brown Eyed Girl", "artist": "Van Morrison", "similarity": 0.898},
11
- {"title": "Take Me Home Country Roads", "artist": "John Denver", "similarity": 0.889},
12
  {"title": "Someone Like You", "artist": "Adele", "similarity": 0.880},
13
- {"title": "Counting Stars", "artist": "OneRepublic", "similarity": 0.871},
 
14
  {"title": "Wagon Wheel", "artist": "Old Crow Medicine Show", "similarity": 0.862},
15
- {"title": "I'm Yours", "artist": "Jason Mraz", "similarity": 0.853},
16
- {"title": "Hey Soul Sister", "artist": "Train", "similarity": 0.844},
17
- {"title": "Photograph", "artist": "Ed Sheeran", "similarity": 0.835},
18
- {"title": "Perfect", "artist": "Ed Sheeran", "similarity": 0.826},
19
  {"title": "Can't Help Falling in Love", "artist": "Elvis Presley", "similarity": 0.817},
 
20
  {"title": "Hallelujah", "artist": "Leonard Cohen", "similarity": 0.808},
21
- {"title": "Horse With No Name", "artist": "America", "similarity": 0.799},
22
- {"title": "Wonderwall", "artist": "Oasis", "similarity": 0.790},
23
- {"title": "Don't Look Back in Anger", "artist": "Oasis", "similarity": 0.781},
24
- {"title": "21 Guns", "artist": "Green Day", "similarity": 0.772},
25
  {"title": "Save Tonight", "artist": "Eagle-Eye Cherry", "similarity": 0.763},
 
26
  {"title": "Mr Jones", "artist": "Counting Crows", "similarity": 0.754},
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  {"title": "Good Riddance", "artist": "Green Day", "similarity": 0.745},
28
- {"title": "I Will Follow You Into the Dark", "artist": "Death Cab for Cutie", "similarity": 0.736},
29
- {"title": "Riptide", "artist": "Vance Joy", "similarity": 0.727},
30
- {"title": "Ho Hey", "artist": "The Lumineers", "similarity": 0.718},
31
- {"title": "Home", "artist": "Phillip Phillips", "similarity": 0.709},
32
- {"title": "Little Talks", "artist": "Of Monsters and Men", "similarity": 0.700},
33
  {"title": "Iris", "artist": "Goo Goo Dolls", "similarity": 0.691},
34
- {"title": "More Than Words", "artist": "Extreme", "similarity": 0.682},
35
  {"title": "Tears in Heaven", "artist": "Eric Clapton", "similarity": 0.673},
 
36
  {"title": "The Scientist", "artist": "Coldplay", "similarity": 0.664},
 
37
  {"title": "Yellow", "artist": "Coldplay", "similarity": 0.655},
 
38
  {"title": "Fix You", "artist": "Coldplay", "similarity": 0.646},
 
39
  {"title": "Thinking Out Loud", "artist": "Ed Sheeran", "similarity": 0.637},
 
40
  {"title": "Love Yourself", "artist": "Justin Bieber", "similarity": 0.628},
41
- {"title": "Despacito", "artist": "Luis Fonsi", "similarity": 0.619},
42
  {"title": "Shape of You", "artist": "Ed Sheeran", "similarity": 0.610},
 
43
  {"title": "Just the Way You Are", "artist": "Bruno Mars", "similarity": 0.601},
44
- {"title": "The Lazy Song", "artist": "Bruno Mars", "similarity": 0.592},
45
  {"title": "Grenade", "artist": "Bruno Mars", "similarity": 0.583},
46
  {"title": "Viva la Vida", "artist": "Coldplay", "similarity": 0.574},
47
  {"title": "Under the Bridge", "artist": "Red Hot Chili Peppers", "similarity": 0.565},
48
- {"title": "What's Up", "artist": "4 Non Blondes", "similarity": 0.556},
49
  {"title": "La Bamba", "artist": "Ritchie Valens", "similarity": 0.547},
50
  {"title": "Shallow", "artist": "Lady Gaga and Bradley Cooper", "similarity": 0.538},
51
- {"title": "Let Her Go", "artist": "Passenger", "similarity": 0.529},
52
  {"title": "Fast Car", "artist": "Tracy Chapman", "similarity": 0.520},
53
  {"title": "Blowin in the Wind", "artist": "Bob Dylan", "similarity": 0.511},
54
- {"title": "Piano Man", "artist": "Billy Joel", "similarity": 0.502},
55
- {"title": "Hotel California", "artist": "Eagles", "similarity": 0.493},
56
  {"title": "Losing My Religion", "artist": "R.E.M.", "similarity": 0.484}
57
  ]
 
1
  [
2
  {"title": "Let It Be", "artist": "The Beatles", "similarity": 0.97},
3
+ {"title": "Machine Hit", "artist": "John Harald", "similarity": 0.718},
4
+ {"title": "Ho Hey", "artist": "The Lumineers", "similarity": 0.718},
5
  {"title": "With or Without You", "artist": "U2", "similarity": 0.943},
6
+ {"title": "Spring Song", "artist": "Gryphon", "similarity": 0.709},
7
  {"title": "Stand By Me", "artist": "Ben E. King", "similarity": 0.934},
8
+ {"title": "Walter Murphy and the Big Apple Band", "artist": "A Fifth of Beethoven", "similarity": 0.934},
9
+ {"title": "My Life", "artist": "Oliver Holloway", "similarity": 0.736},
 
 
 
10
  {"title": "Someone Like You", "artist": "Adele", "similarity": 0.880},
11
+ {"title": "Little Talks", "artist": "Of Monsters and Men", "similarity": 0.700},
12
+ {"title": "Wonderwall", "artist": "Oasis", "similarity": 0.790},
13
  {"title": "Wagon Wheel", "artist": "Old Crow Medicine Show", "similarity": 0.862},
 
 
 
 
14
  {"title": "Can't Help Falling in Love", "artist": "Elvis Presley", "similarity": 0.817},
15
+ {"title": "Riptide", "artist": "Vance Joy", "similarity": 0.727},
16
  {"title": "Hallelujah", "artist": "Leonard Cohen", "similarity": 0.808},
 
 
 
 
17
  {"title": "Save Tonight", "artist": "Eagle-Eye Cherry", "similarity": 0.763},
18
+ {"title": "No Woman No Cry", "artist": "Bob Marley", "similarity": 0.961},
19
  {"title": "Mr Jones", "artist": "Counting Crows", "similarity": 0.754},
20
+ {"title": "Piano Man", "artist": "Billy Joel", "similarity": 0.502},
21
+ {"title": "Hey Soul Sister", "artist": "Train", "similarity": 0.844},
22
+ {"title": "Hotel California", "artist": "Eagles", "similarity": 0.493},
23
+ {"title": "Horse With No Name", "artist": "America", "similarity": 0.799},
24
+ {"title": "Africa", "artist": "Toto", "similarity": 0.952},
25
+ {"title": "More Than Words", "artist": "Extreme", "similarity": 0.682},
26
+ {"title": "Zombie", "artist": "The Cranberries", "similarity": 0.925},
27
+ {"title": "Let Her Go", "artist": "Passenger", "similarity": 0.529},
28
+ {"title": "Knockin on Heavens Door", "artist": "Bob Dylan", "similarity": 0.916},
29
+ {"title": "What's Up", "artist": "4 Non Blondes", "similarity": 0.556},
30
+ {"title": "Wonderful Tonight", "artist": "Eric Clapton", "similarity": 0.907},
31
+ {"title": "The Lazy Song", "artist": "Bruno Mars", "similarity": 0.592},
32
+ {"title": "Brown Eyed Girl", "artist": "Van Morrison", "similarity": 0.898},
33
  {"title": "Good Riddance", "artist": "Green Day", "similarity": 0.745},
34
+ {"title": "Take Me Home Country Roads", "artist": "John Denver", "similarity": 0.889},
 
 
 
 
35
  {"title": "Iris", "artist": "Goo Goo Dolls", "similarity": 0.691},
36
+ {"title": "Counting Stars", "artist": "OneRepublic", "similarity": 0.871},
37
  {"title": "Tears in Heaven", "artist": "Eric Clapton", "similarity": 0.673},
38
+ {"title": "I'm Yours", "artist": "Jason Mraz", "similarity": 0.853},
39
  {"title": "The Scientist", "artist": "Coldplay", "similarity": 0.664},
40
+ {"title": "Photograph", "artist": "Ed Sheeran", "similarity": 0.835},
41
  {"title": "Yellow", "artist": "Coldplay", "similarity": 0.655},
42
+ {"title": "Perfect", "artist": "Ed Sheeran", "similarity": 0.826},
43
  {"title": "Fix You", "artist": "Coldplay", "similarity": 0.646},
44
+ {"title": "Don't Look Back in Anger", "artist": "Oasis", "similarity": 0.781},
45
  {"title": "Thinking Out Loud", "artist": "Ed Sheeran", "similarity": 0.637},
46
+ {"title": "21 Guns", "artist": "Green Day", "similarity": 0.772},
47
  {"title": "Love Yourself", "artist": "Justin Bieber", "similarity": 0.628},
 
48
  {"title": "Shape of You", "artist": "Ed Sheeran", "similarity": 0.610},
49
+ {"title": "Despacito", "artist": "Luis Fonsi", "similarity": 0.619},
50
  {"title": "Just the Way You Are", "artist": "Bruno Mars", "similarity": 0.601},
 
51
  {"title": "Grenade", "artist": "Bruno Mars", "similarity": 0.583},
52
  {"title": "Viva la Vida", "artist": "Coldplay", "similarity": 0.574},
53
  {"title": "Under the Bridge", "artist": "Red Hot Chili Peppers", "similarity": 0.565},
 
54
  {"title": "La Bamba", "artist": "Ritchie Valens", "similarity": 0.547},
55
  {"title": "Shallow", "artist": "Lady Gaga and Bradley Cooper", "similarity": 0.538},
 
56
  {"title": "Fast Car", "artist": "Tracy Chapman", "similarity": 0.520},
57
  {"title": "Blowin in the Wind", "artist": "Bob Dylan", "similarity": 0.511},
 
 
58
  {"title": "Losing My Religion", "artist": "R.E.M.", "similarity": 0.484}
59
  ]