VibecoderMcSwaggins commited on
Commit
4450782
·
1 Parent(s): ecaa2e8

fix(P0): Add function invoking marker and improve ChatMessage extraction

Browse files

Changes:
1. Add __function_invoking_chat_client__ marker to HuggingFaceChatClient
- Without this marker, agent_framework ignores tools passed to the client
- The warning "does not support function invoking" is now eliminated

2. Improve _extract_text in AdvancedOrchestrator:
- Handle ChatMessage.contents (list of FunctionCallContent, TextContent)
- Extract tool call names when text content is empty
- Return empty string instead of object repr for graceful fallback

3. Fix WorkflowOutputEvent handling:
- Use _extract_text instead of str() to avoid object repr in output
- Provide meaningful fallback message when no text available

These fixes ensure the HuggingFace Free Tier can properly:
- Declare function calling capability to the framework
- Display tool calls and text content in events
- Show clean output instead of <ChatMessage object at ...>

src/clients/huggingface.py CHANGED
@@ -29,6 +29,10 @@ logger = structlog.get_logger()
29
  class HuggingFaceChatClient(BaseChatClient): # type: ignore[misc]
30
  """Adapter for HuggingFace Inference API with full function calling support."""
31
 
 
 
 
 
32
  def __init__(
33
  self,
34
  model_id: str | None = None,
 
29
  class HuggingFaceChatClient(BaseChatClient): # type: ignore[misc]
30
  """Adapter for HuggingFace Inference API with full function calling support."""
31
 
32
+ # Marker to tell agent_framework that this client supports function calling
33
+ # Without this, the framework warns and ignores tools
34
+ __function_invoking_chat_client__ = True
35
+
36
  def __init__(
37
  self,
38
  model_id: str | None = None,
src/orchestrators/advanced.py CHANGED
@@ -337,32 +337,44 @@ The final output should be a structured research report."""
337
  """
338
  Defensively extract text from a message object.
339
 
340
- Fixes bug where message.text might return the object itself or its repr.
 
341
  """
342
  if not message:
343
  return ""
344
 
345
- # Priority 1: .content (often the raw string or list of content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  if hasattr(message, "content") and message.content:
347
  content = message.content
348
- # If it's a list (e.g., Multi-modal), join text parts
 
349
  if isinstance(content, list):
350
  return " ".join([str(c.text) for c in content if hasattr(c, "text")])
351
- return str(content)
352
-
353
- # Priority 2: .text (standard, but sometimes buggy/missing)
354
- if hasattr(message, "text") and message.text:
355
- # Verify it's not the object itself or a repr string
356
- text = str(message.text)
357
- if text.startswith("<") and "object at" in text:
358
- # Likely a repr string, ignore if possible
359
- pass
360
- else:
361
- return text
362
 
363
- # Fallback: If we can't find clean text, return str(message)
364
- # taking care to avoid infinite recursion if str() calls .text
365
- return str(message)
366
 
367
  def _get_event_type_for_agent(self, agent_name: str) -> str:
368
  """Map agent name to appropriate event type.
@@ -456,9 +468,11 @@ The final output should be a structured research report."""
456
 
457
  elif isinstance(event, WorkflowOutputEvent):
458
  if event.data:
 
 
459
  return AgentEvent(
460
  type="complete",
461
- message=str(event.data),
462
  iteration=iteration,
463
  )
464
 
 
337
  """
338
  Defensively extract text from a message object.
339
 
340
+ Handles ChatMessage objects from both OpenAI and HuggingFace clients.
341
+ ChatMessage has: .text (str), .contents (list of content objects)
342
  """
343
  if not message:
344
  return ""
345
 
346
+ # Priority 1: .text (standard ChatMessage text content)
347
+ if hasattr(message, "text") and message.text:
348
+ text = message.text
349
+ # Verify it's actually a string, not the object itself
350
+ if isinstance(text, str) and not (text.startswith("<") and "object at" in text):
351
+ return text
352
+
353
+ # Priority 2: .contents (list of FunctionCallContent, TextContent, etc.)
354
+ # This handles tool call responses from HuggingFace
355
+ if hasattr(message, "contents") and message.contents:
356
+ parts = []
357
+ for content in message.contents:
358
+ # TextContent has .text
359
+ if hasattr(content, "text") and content.text:
360
+ parts.append(str(content.text))
361
+ # FunctionCallContent has .name and .arguments
362
+ elif hasattr(content, "name"):
363
+ parts.append(f"[Tool: {content.name}]")
364
+ if parts:
365
+ return " ".join(parts)
366
+
367
+ # Priority 3: .content (legacy - some frameworks use singular)
368
  if hasattr(message, "content") and message.content:
369
  content = message.content
370
+ if isinstance(content, str):
371
+ return content
372
  if isinstance(content, list):
373
  return " ".join([str(c.text) for c in content if hasattr(c, "text")])
 
 
 
 
 
 
 
 
 
 
 
374
 
375
+ # Fallback: Return empty string instead of repr
376
+ # The repr is useless for display purposes
377
+ return ""
378
 
379
  def _get_event_type_for_agent(self, agent_name: str) -> str:
380
  """Map agent name to appropriate event type.
 
468
 
469
  elif isinstance(event, WorkflowOutputEvent):
470
  if event.data:
471
+ # Use _extract_text to properly handle ChatMessage objects
472
+ text = self._extract_text(event.data)
473
  return AgentEvent(
474
  type="complete",
475
+ message=text if text else "Research complete (no synthesis)",
476
  iteration=iteration,
477
  )
478