PR 12: Documentation and examples - #13
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR adds comprehensive documentation and examples for the call_model() API while fixing integration issues for Pydantic events from the OpenRouter API.
Key changes:
- Added support for both dict and Pydantic object event types in stream transformers
- Fixed API streaming to properly await the
send_async()coroutine - Restructured tool format from nested to flat structure for OpenRouter API compatibility
- Added 4 runnable example scripts and QUICKSTART.md guide
Reviewed Changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/openrouter/call_model/stream_transformers.py | Added Pydantic object handling alongside dict events for API compatibility |
| src/openrouter/call_model/reusable_stream.py | Updated type annotations to support both dict and Pydantic object events |
| src/openrouter/call_model/response_wrapper.py | Fixed API call to await send_async() coroutine and updated stream type annotations |
| src/openrouter/call_model/call_model.py | Restructured tool format from nested to flat structure for OpenRouter API |
| examples/call_model_with_tools.py | Added comprehensive tool usage example with weather demonstration |
| examples/call_model_streaming.py | Added streaming example showing real-time text generation |
| examples/call_model_quickstart.py | Added minimal quickstart example for getting started |
| examples/call_model_multiple_patterns.py | Added example demonstrating multiple consumption patterns |
| README.md | Added quick start section with code example and links to guides |
| QUICKSTART.md | Added comprehensive quick start guide with progressive examples |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| client = OpenRouter(api_key=os.getenv("OPENROUTER_API_KEY")) | ||
|
|
||
| # Make ONE API call | ||
| print("📞 Making API call...\n") |
There was a problem hiding this comment.
This emoji (📞) should be removed to maintain a professional tone, as per the PR description which states "Remove emojis and hyperbolic language for professional tone".
| print("📞 Making API call...\n") | |
| print("Making API call...\n") |
| for attr in dir(event): | ||
| if not attr.startswith("_") and attr != "type": | ||
| value = getattr(event, attr, None) | ||
| if value is not None: | ||
| event_dict[attr] = value |
There was a problem hiding this comment.
Using dir() to iterate over attributes is problematic as it includes methods and properties in addition to data attributes. This could lead to unexpected behavior or errors if methods are called. Consider using __dict__ or explicitly listing expected attributes, or check if the attribute is callable before adding it to the dict.
| for attr in dir(event): | |
| if not attr.startswith("_") and attr != "type": | |
| value = getattr(event, attr, None) | |
| if value is not None: | |
| event_dict[attr] = value | |
| # Use __dict__ to get only data attributes | |
| for attr, value in getattr(event, "__dict__", {}).items(): | |
| if not attr.startswith("_") and attr != "type" and value is not None: | |
| event_dict[attr] = value |
| # Convert Pydantic objects to dicts for consistent output | ||
| if hasattr(event, "type"): | ||
| # It's a Pydantic object from the API | ||
| event_type = getattr(event, "type", None) | ||
| if not isinstance(event_type, str): | ||
| continue | ||
|
|
||
| # Convert to dict for output | ||
| if hasattr(event, "model_dump"): | ||
| event_dict = cast(object, event).model_dump() # type: ignore | ||
| else: | ||
| # Fallback: manually construct dict | ||
| event_dict = {"type": event_type} | ||
| for attr in dir(event): | ||
| if not attr.startswith("_") and attr != "type": | ||
| value = getattr(event, attr, None) | ||
| if value is not None: | ||
| event_dict[attr] = value | ||
| elif isinstance(event, dict): | ||
| event_dict = event | ||
| event_type = event.get("type") | ||
| if not isinstance(event_type, str): | ||
| continue | ||
| else: | ||
| continue |
There was a problem hiding this comment.
This Pydantic object to dict conversion logic is duplicated across multiple functions (extract_text_deltas, extract_tool_events, and build_message_from_stream). Consider extracting this into a helper function like _convert_event_to_dict(event) to reduce code duplication and improve maintainability.
| from openrouter import OpenRouter | ||
| from openrouter.call_model import call_model | ||
|
|
||
| client = OpenRouter(api_key="your-key-here") | ||
| response = await call_model(client, {"model": "openai/gpt-4", "input": "Hello!"}) | ||
| text = await response.get_text() | ||
| print(text) |
There was a problem hiding this comment.
The code snippet shows await being used outside of an async function context. This will cause a SyntaxError if users try to run it as-is. Consider wrapping the code in an async function and showing asyncio.run() to make it a complete, runnable example.
| from openrouter import OpenRouter | |
| from openrouter.call_model import call_model | |
| client = OpenRouter(api_key="your-key-here") | |
| response = await call_model(client, {"model": "openai/gpt-4", "input": "Hello!"}) | |
| text = await response.get_text() | |
| print(text) | |
| import asyncio | |
| from openrouter import OpenRouter | |
| from openrouter.call_model import call_model | |
| async def main(): | |
| client = OpenRouter(api_key="your-key-here") | |
| response = await call_model(client, {"model": "openai/gpt-4", "input": "Hello!"}) | |
| text = await response.get_text() | |
| print(text) | |
| asyncio.run(main()) |
Add user-friendly documentation following progressive disclosure and multiple learning style principles: New Documentation: - QUICKSTART.md: 5-minute guide from zero to hero - Updated README.md with prominent quick start section New Examples (all runnable): - call_model_quickstart.py: First call in 3 lines - call_model_streaming.py: Real-time streaming demonstration - call_model_with_tools.py: AI with custom function calling - call_model_multiple_patterns.py: One call, many consumption patterns Design Principles Applied: - Progressive disclosure: Simple → Intermediate → Advanced - Multiple learning styles: Visual, kinesthetic, reading/writing - Hero's journey: Users feel empowered, not overwhelmed - Evidence from PREP docs: Addresses different audience values Each example: - Has clear learning objectives - Includes helpful comments - Shows expected output - Has error handling - Can run standalone Documentation serves: - Beginners: Get started fast (< 5 min) - Intermediate: See patterns (streaming, tools) - Advanced: Understand flexibility (multiple patterns) - All: Feel capable and excited to build Covers code-review checklist: - Real problem: Users need to get started quickly - Simplicity: Clearest possible examples - Reversibility: Examples are non-destructive - Diplomacy: Encouraging, not condescending - 3 AM rule: Clear enough to debug easily
- Update stream_transformers.py to handle both dict and Pydantic event objects - Fix ResponseWrapper to await send_async() coroutine properly - Update tool format to match OpenRouter's flat structure (not nested) - Update type signatures in reusable_stream.py for flexible event handling
- Remove decorative emojis from QUICKSTART.md and README.md
- Replace hyperbolic phrases ('zero to hero', 'infinite', 'superpowers')
- Use precise technical language instead of metaphors
- Maintain professional tone in all example files
76ac6b2 to
4aacbfc
Compare
cf46285 to
58da8c4
Compare
Summary
Builds on #12.