-
Notifications
You must be signed in to change notification settings - Fork 37
feat: add Claude Agent SDK provider integration #35
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
base: main
Are you sure you want to change the base?
feat: add Claude Agent SDK provider integration #35
Conversation
Add support for Claude Agent SDK as a new LLM provider, enabling the use of local Claude Code without API keys. Key changes: - Created ClaudeAgentSDKModel wrapper implementing Agno Model interface - Implemented aresponse() and aresponse_stream() methods using claude_agent_sdk.query() - Added ClaudeAgentSDKStrategy to provider configuration system - Registered new provider in ConfigurationManager - Added claude-agent-sdk to project dependencies - Updated documentation (CLAUDE.md, README.md) with new provider details Benefits: - No API key required - uses locally installed Claude Code - Seamless integration with existing Multi-Thinking architecture - Full support for all thinking agents and workflows - Native Agno framework compatibility Provider usage: ```bash LLM_PROVIDER="claude-agent-sdk" # No additional configuration needed ```
Summary of ChangesHello @spumer, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a significant enhancement by integrating the Claude Agent SDK as a new Large Language Model (LLM) provider. This integration allows the system to leverage locally installed Claude Code, eliminating the need for external API keys and offering a more self-contained and potentially more secure operational mode for LLM interactions within the Multi-Thinking architecture. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request effectively integrates the Claude Agent SDK as a new LLM provider. The changes are well-structured, including the new model wrapper, provider strategy, and updates to documentation and dependencies. The code is generally clean and follows good practices like lazy loading the SDK. My review includes suggestions to refactor duplicated code in the new model wrapper for better maintainability, improve robustness in message processing, and clarify a potentially unintended dependency update.
| for msg in messages: | ||
| role = msg.role if hasattr(msg, "role") else "user" | ||
| content = msg.content if hasattr(msg, "content") else str(msg) | ||
|
|
||
| # Format based on role | ||
| if role == "system": | ||
| prompt_parts.append(f"System: {content}") | ||
| elif role == "user": | ||
| prompt_parts.append(f"User: {content}") | ||
| elif role == "assistant": | ||
| prompt_parts.append(f"Assistant: {content}") | ||
| else: | ||
| prompt_parts.append(str(content)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of hasattr with ternary operators can be simplified using getattr with default values, making the code more concise. Additionally, the else block for an unknown role silently appends the content; it would be more robust to log a warning when an unexpected role is encountered. This helps in debugging and understanding the conversation flow.
| for msg in messages: | |
| role = msg.role if hasattr(msg, "role") else "user" | |
| content = msg.content if hasattr(msg, "content") else str(msg) | |
| # Format based on role | |
| if role == "system": | |
| prompt_parts.append(f"System: {content}") | |
| elif role == "user": | |
| prompt_parts.append(f"User: {content}") | |
| elif role == "assistant": | |
| prompt_parts.append(f"Assistant: {content}") | |
| else: | |
| prompt_parts.append(str(content)) | |
| for msg in messages: | |
| role = getattr(msg, "role", "user") | |
| content = getattr(msg, "content", str(msg)) | |
| # Format based on role | |
| if role == "system": | |
| prompt_parts.append(f"System: {content}") | |
| elif role == "user": | |
| prompt_parts.append(f"User: {content}") | |
| elif role == "assistant": | |
| prompt_parts.append(f"Assistant: {content}") | |
| else: | |
| logger.warning("Unknown message role '%s', appending content directly.", role) | |
| prompt_parts.append(str(content)) |
| if hasattr(message, "content"): | ||
| # Handle different content formats | ||
| if isinstance(message.content, str): | ||
| full_response += message.content | ||
| elif isinstance(message.content, list): | ||
| # Handle content blocks | ||
| for block in message.content: | ||
| if hasattr(block, "text"): | ||
| full_response += block.text | ||
| elif isinstance(block, dict) and "text" in block: | ||
| full_response += block["text"] | ||
| else: | ||
| # Fallback: convert to string | ||
| full_response += str(message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic to extract content from the message object is duplicated here and in aresponse_stream (lines 216-226). To improve maintainability and avoid code duplication, consider refactoring this logic into a private helper method. For example:
def _extract_text_from_message(self, message: Any) -> str:
# ... implementation ...You could then call this helper method in both aresponse and aresponse_stream.
| except Exception as e: | ||
| logger.exception("Claude Agent SDK query failed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Catching a broad Exception can mask unexpected issues and make debugging harder. It's better to catch more specific exceptions that _claude_query might raise, if the SDK provides them (e.g., APIError, ConnectionError). This allows for more targeted error handling and avoids accidentally catching exceptions like KeyboardInterrupt. This applies to the exception handling in aresponse_stream as well.
Improve Claude Agent SDK integration by properly using ClaudeAgentOptions: - Import and use ClaudeAgentOptions for query configuration - Extract system prompts from messages and pass via options.system_prompt - Configure max_turns using tool_call_limit parameter - Separate system messages from user/assistant messages - Add proper logging for system prompt length Benefits: - Proper separation of system prompts (not inline in main prompt) - Better control over conversation flow via max_turns - More idiomatic usage of Claude Agent SDK API - Improved debugging with system prompt visibility in logs
Implement comprehensive Claude Agent SDK integration improvements: **1. Tool Management (allowed_tools)** - Map Agno tools to Claude Agent SDK allowed_tools - ReasoningTools → 'Think' tool - ExaTools → 'search_exa' tool - Support for Function objects and dict-based tools - Intelligent tool name mapping **2. Model Configuration** - Pass model ID explicitly via options.model - Ensures Claude Code uses correct model version **3. Permission Mode** - Add permission_mode parameter with 4 modes: * 'default': Standard permissions with prompts * 'acceptEdits': Auto-accept file edits * 'plan': Plan mode for reviewing actions * 'bypassPermissions': Bypass all checks (default for automation) - Configurable per-instance for different use cases **4. Working Directory (cwd)** - Support custom working directory - Defaults to current directory via Path.cwd() - Enables context-aware file operations **5. Tool Calls Extraction** - Extract and parse tool_use blocks from responses - Include tool calls in ModelResponse.tool_calls - Support both object and dict-based tool blocks - Track tool invocations (Think, search, etc.) **Benefits:** - Full control over Claude Code behavior - Better tool integration with Agno framework - Visibility into tool usage via tool_calls - Context-aware operations via cwd - Flexible permission management for different scenarios **What's NOT included (as requested):** - max_thinking_tokens (skipped) - max_budget_usd (skipped)
Add comprehensive support for medium-priority Claude Agent SDK features: **1. MCP Servers Integration (mcp_servers)** - Support dict configuration, path to config file, or None - Enables additional MCP servers for extended tool capabilities - Integrated into ClaudeAgentOptions **2. Environment Variables (env)** - Pass custom environment variables to Claude Code - Support tool-specific configuration via env vars - Dict[str, str] format for key-value pairs **3. Additional Directories (add_dirs)** - Extend file access context with additional directories - List[str | Path] format, converted to string paths - Enables broader project context for agents **4. Event Hooks (hooks)** - Support for Claude Agent SDK event hooks: * PreToolUse: Before tool execution * PostToolUse: After tool execution * UserPromptSubmit: On user prompt submission * Stop: On agent stop * SubagentStop: On subagent stop * PreCompact: Before memory compaction - Dict[str, List[Any]] format for hook matchers - Enables event-driven integrations **5. Tool Permission Callback (can_use_tool)** - Runtime permission checks for tool usage - Async callback: (tool_name, args, context) -> PermissionResult - Enables fine-grained security control - Integration with Agno permission system **6. Enhanced Documentation** - Comprehensive docstring with feature list - Usage examples for each feature - Clear parameter documentation - Provider data tracking for all configurations **Implementation Details:** - Conditional parameter passing (only if provided) - Type-safe with proper type hints - Logging for all configuration options - Provider data includes config status for debugging **Benefits:** - Full Claude Agent SDK feature parity - Fine-grained control over agent behavior - Event-driven architecture support - Security through permission callbacks - Extensible via MCP servers - Environment-specific configurations
|
Sorry, Claude code web create PR to your project instead my fork ) |
Add support for Claude Agent SDK as a new LLM provider, enabling the use of local Claude Code without API keys.
Key changes:
Benefits:
Provider usage: