Fix Mistral Rate Limiting and Provider Detection#2
Fix Mistral Rate Limiting and Provider Detection#2Esashiero wants to merge 1 commit intolitellm-rate-limiting-4314386133060355796from
Conversation
- Updated Mistral default config to 20 RPM, 1 max burst, and 1 max concurrent. - Enabled provider-specific config overrides from RLM_PROVIDER_LIMITS env var. - Fixed a bug in LiteLLMClient where the provider index was incorrectly set to 0 (model name) instead of 1 (provider name). - Improved stagger calculation in batched requests to be based on actual RPM. - Added debug logging to track rate limit acquisition and provider config usage. - Updated unit tests to reflect new Mistral default RPM. Co-authored-by: Esashiero <[email protected]>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
Summary of ChangesHello @Esashiero, 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 focuses on improving the robustness and efficiency of the rate limiting mechanism, particularly for Mistral models. It addresses critical issues related to incorrect provider detection and sub-optimal default limits, ensuring that API calls adhere to provider-specific constraints. Additionally, it enhances logging for better operational insight and introduces a dynamic staggering strategy for batched requests to manage traffic more smoothly. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
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.
Code Review
This pull request effectively addresses the Mistral rate limiting issues by correcting the provider detection logic and adjusting the default limits. The refactoring to centralize provider configuration is a good improvement. However, there are several critical and high-severity issues that need to be addressed: a patch file and multiple log files have been incorrectly added to the commit. These should be removed, and the logs directory should be added to .gitignore. Additionally, I've provided some medium-severity suggestions to improve code quality by moving local imports and avoiding access to private class members.
| {"type": "metadata", "timestamp": "2026-02-11T13:05:09.006637", "root_model": "mistral/mistral-large-latest", "max_depth": 5, "max_iterations": 2, "backend": "litellm", "backend_kwargs": {"model_name": "mistral/mistral-large-latest"}, "environment_type": "local", "environment_kwargs": {}, "other_backends": null} | ||
| {"type": "iteration", "iteration": 1, "timestamp": "2026-02-11T13:05:25.347917", "depth": 0, "agent_name": "rlm_agent", "parent_agent": null, "prompt": [{"role": "system", "content": "You are tasked with answering a query with associated context. You can access, transform, and analyze this context interactively in a REPL environment that can recursively query sub-LLMs, which you are strongly encouraged to use as much as possible. You will be queried iteratively until you provide a final answer.\n\nThe REPL environment is initialized with:\n1. A `context` variable that contains extremely important information about your query. You should check the content of the `context` variable to understand what you are working with. Make sure you look through it sufficiently as you answer your query.\n2. A `llm_query(prompt, context=None, model=None, recursive=True)` function that allows you to query an LLM inside your REPL environment. By default (recursive=True), this creates a nested RLM execution that can itself execute code and make further llm_query calls - enabling deep recursive reasoning. Set recursive=False for simple LLM calls without code execution. The optional `context` parameter lets you pass objects (files, collections, dicts) directly to the child agent - the child receives this as its `context` variable.\n3. A `llm_query_batched(prompts, contexts=None, model=None, recursive=False)` function for concurrent queries. IMPORTANT: Keep recursive=False (the default) for extraction, summarization, and Q&A tasks - embed file.content in your prompts. Only use recursive=True when children genuinely need to execute code. Results are returned in the same order as prompts.\n4. The ability to use `print()` statements to view the output of your REPL code and continue your reasoning.\n\nIMPORTANT: The `llm_query` function with recursive=True creates a full RLM execution at the next depth level. This means the sub-LLM can analyze your prompt, write and execute code, and recursively call its own llm_query. This is powerful for hierarchical decomposition of complex problems.\n\nWHEN TO USE CHILD AGENTS (recursive=True):\n- Complex sub-analyses: When a sub-problem requires its own multi-step reasoning with code execution\n- Hierarchical decomposition: When breaking a large problem into sub-problems that each need iteration\n- Delegation with large context: When passing substantial context to a child that needs to explore it programmatically\n\nWHEN TO USE SIMPLE LLM CALLS (recursive=False):\n- Parallel processing: Use `llm_query_batched` with recursive=False to process many chunks concurrently\n- Summarization: Condensing text into a shorter form\n- Extraction: Pulling specific information from provided text\n- Classification: Categorizing or labeling content\n- Simple Q&A: Questions answerable directly from the provided context without code execution\n- Aggregation: Combining multiple answers into a final result\n\nMost tasks should use recursive=False. Only use recursive=True when the sub-task itself requires writing and executing code to explore the problem.\n\nWHEN TO AVOID llm_query ENTIRELY:\n- Direct lookups: When you can find the answer by reading the context directly\n- Small contexts: When the context fits easily in your window and doesn't need chunking\n- Simple computations: When Python code alone can compute the answer\n- Pattern matching: When regex or string operations can extract what you need\n\nIMPORTANT: Default to the simplest approach. Only spawn child agents when the sub-task genuinely requires autonomous multi-step reasoning with code execution.\n\nIMPORTANT: Dont turn off your brain. Just because you can spawn a child agent doesnt mean you should. And if you do spawn a child agent, make sure you give it a good prompt and context.\n\nWORKING WITH FILES (LazyFile / LazyFileCollection):\nWhen your context contains files, you have two approaches:\n\nAPPROACH 1 - Simple extraction (recursive=False, PREFERRED for most tasks):\nEmbed file.content directly in your prompt. This is fast and efficient for summarization, extraction, and Q&A.\nMake sure to properly format the string!\n```repl\nfiles = list(context['files'])\nprompts = [f\"Summarize this document titled '{{f.name}}':\\n\\n{{f.content}}\" for f in files]\nresults = llm_query_batched(prompts, recursive=False) # Fast parallel processing\n```\n\nAPPROACH 2 - Complex analysis (recursive=True, use sparingly):\nPass the file object via context= when the child needs to write code to explore the file.\n```repl\n# Only use this when the child genuinely needs to run code\nresult = llm_query(\"Analyze this document programmatically\", context=file, recursive=True)\n```\n\nKEY FILE PROPERTIES:\n- `file.name` - the filename (string, e.g., \"report.md\")\n- `file.content` - the actual text content (string, loads the file)\n\nCRITICAL WARNING - Common Mistake:\n- WRONG: Using file.name in prompts without file.content (passes just the filename string!)\n- WRONG: contexts=[f.name for f in files] (passes list of filename strings!)\n- CORRECT: Embed file.content in prompt, OR pass file object via context=\n\nYou will only be able to see truncated outputs from the REPL environment, so you should use the query LLM function on variables you want to analyze. You will find this function especially useful when you have to analyze the semantics of the context. Use these variables as buffers to build up your final answer.\nMake sure to explicitly look through the entire context in REPL before answering your query. An example strategy is to first look at the context and figure out a chunking strategy, then break up the context into smart chunks, and query an LLM per chunk with a particular question and save the answers to a buffer, then query an LLM with all the buffers to produce your final answer.\n\nYou can use the REPL environment to help you understand your context, especially if it is huge. Remember that your sub LLMs are powerful -- they can fit around 500K characters in their context window, so don't be afraid to put a lot of context into them. For example, a viable strategy is to feed 10 documents per sub-LLM query. Analyze your input data and see if it is sufficient to just fit it in a few sub-LLM calls!\n\nWhen you want to execute Python code in the REPL environment, wrap it in triple backticks with 'repl' language identifier. For example, say we want our recursive model to search for the magic number in the context (assuming the context is a string), and the context is very long, so we want to chunk it:\n```repl\nchunk = context[:10000]\nanswer = llm_query(f\"What is the magic number in the context? Here is the chunk: {{chunk}}\")\nprint(answer)\n```\n\n\nAs an example, when the context isn't that long (e.g. >100M characters), a simple but viable strategy is, based on the context chunk lengths, to combine them and query an LLM over chunks. For example, if the context is a List[str], we ask the same query over each chunk using `llm_query_batched` for concurrent processing:\n```repl\nquery = \"A man became famous for his book \"The Great Gatsby\". How many jobs did he have?\"\n# Suppose our context is ~1M chars, and we want each sub-LLM query to be ~0.1M chars so we split it into 10 chunks\nchunk_size = len(context) // 10\nchunks = []\nfor i in range(10):\n if i < 9:\n chunk_str = \"\\n\".join(context[i*chunk_size:(i+1)*chunk_size])\n else:\n chunk_str = \"\\n\".join(context[i*chunk_size:])\n chunks.append(chunk_str)\n\n# Use batched query for concurrent processing - much faster than sequential calls!\nprompts = [f\"Try to answer the following query: {{query}}. Here are the documents:\\n{{chunk}}. Only answer if you are confident in your answer based on the evidence.\" for chunk in chunks]\nanswers = llm_query_batched(prompts, recursive=False)\nfor i, answer in enumerate(answers):\n print(f\"I got the answer from chunk {{i}}: {{answer}}\")\nfinal_answer = llm_query(f\"Aggregating all the answers per chunk, answer the original query about total number of jobs: {{query}}\\n\\nAnswers:\\n\" + \"\\n\".join(answers))\n```\n\nAs another example, after analyzing the context and realizing its separated by Markdown headers, we can maintain state through buffers by chunking the context by headers, and iteratively querying an LLM over it:\n```repl\n# After finding out the context is separated by Markdown headers, we can chunk, summarize, and answer\nimport re\nsections = re.split(r'### (.+)', context[\"content\"])\nbuffers = []\nfor i in range(1, len(sections), 2):\n header = sections[i]\n info = sections[i+1]\n summary = llm_query(f\"Summarize this {{header}} section: {{info}}\")\n buffers.append(f\"{{header}}: {{summary}}\")\nfinal_answer = llm_query(f\"Based on these summaries, answer the original query: {{query}}\\n\\nSummaries:\\n\" + \"\\n\".join(buffers))\n```\nIn the next step, we can return FINAL_VAR(final_answer).\n\nRECURSIVE CHILD AGENTS - When They Actually Help:\nUse recursive=True ONLY when the child must DISCOVER its approach by examining the data - not when you can specify the extraction upfront.\n\nWRONG approach (can be flattened to recursive=False):\n```repl\n# DON'T DO THIS - the extraction task is fully specified upfront\nresult = llm_query(\n \"Extract all API endpoints and their auth status from this file\",\n context=file,\n recursive=True # WASTEFUL - child doesn't need to write code\n)\n```\n\nRIGHT approach - when child must explore to find its approach:\nTask: \"These 50 config files should follow a common schema. Find schema violations and explain each one.\"\n\n```repl\n# STEP 1: Sample the data to understand structure\nfiles = list(context['files'])\nsamples = [f.content[:2000] for f in files[:5]]\nprint(f\"Examining {len(files)} config files\")\nprint(f\"Sample structure:\n{samples[0][:500]}\")\n```\n\n```repl\n# STEP 2: Delegate schema inference to a child that must EXPLORE\n# The child doesn't know the schema - it must:\n# - Read multiple files to infer common patterns\n# - Write code to parse and compare structures\n# - Iteratively refine its understanding\nschema_analysis = llm_query(\n \"Infer the common schema from these config files. Write code to: \"\n \"1) Parse each file's structure, 2) Find fields that appear in >80% of files, \"\n \"3) Detect type patterns (string vs number vs array). Return the inferred schema.\",\n context={'files': files},\n recursive=True # Child must iterate: parse \u2192 compare \u2192 refine\n)\nprint(f\"Inferred schema: {schema_analysis[:500]}\")\n```\n\n```repl\n# STEP 3: Delegate violation detection - child must write validation code\n# Each file may violate differently - child needs to:\n# - Apply the schema programmatically\n# - Trace WHY each violation occurred\n# - Handle edge cases it discovers\nviolations = llm_query(\n f\"Using this schema:\n{schema_analysis}\n\n\"\n \"Validate each file and explain violations. Write code to check each field \"\n \"and trace the cause of mismatches.\",\n context={'files': files},\n recursive=True # Child writes custom validation logic\n)\nprint(violations)\n```\n\nWhy recursive=True was needed:\n- Schema wasn't known upfront - child had to DISCOVER it by examining files\n- Validation logic depended on what schema was found\n- Each violation type needed different investigation code\n- Children maintained state across iterations (schema \u2192 validation \u2192 explanation)\n\nThe test: If you can write the full extraction prompt without seeing the data,\nuse recursive=False. If the child must look at data to decide what to do, use recursive=True\n\nCOMPLETE FILE PROCESSING WORKFLOW:\nWhen analyzing multiple files, follow this pattern to avoid iteration explosion:\n\n```repl\n# STEP 1: Examine what you have\nfiles = list(context['files'])\nprint(f\"Processing {{len(files)}} files: {{[f.name for f in files[:5]]}}...\")\n\n# STEP 2: Process files in parallel with recursive=False (IMPORTANT!)\n# Embed file.content in prompts - this is fast and avoids spawning recursive agents\nprompts = [\n f\"Analyze '{{f.name}}' for key themes, risks, and opportunities:\\n\\n{{f.content}}\"\n for f in files\n]\nresults = llm_query_batched(prompts, recursive=False) # NOT recursive=True!\n\n# STEP 3: Collect results\nfor f, result in zip(files, results):\n print(f\"{{f.name}}: {{result[:200]}}...\")\n\n# STEP 4: Aggregate AT THIS LEVEL - do not delegate aggregation!\ncombined = \"\\n\\n\".join(f\"## {{f.name}}\\n{{r}}\" for f, r in zip(files, results))\nfinal = llm_query(\n f\"Synthesize these analyses into a comprehensive answer:\\n\\n{{combined}}\",\n recursive=False\n)\nprint(final)\n```\n\nCRITICAL - YOU MUST AGGREGATE:\nAfter spawning child queries, YOU must collect and synthesize their results.\nDo NOT expect children to produce your final answer - they return to you, not to the user.\nAlways end with FINAL() or FINAL_VAR() after aggregating.\n\nCONTEXT VALIDATION (if you are a child agent):\nIf your context seems unexpectedly small or looks like filenames instead of content, something went wrong.\n```repl\n# First thing: check what you actually received\nprint(f\"Context type: {{type(context)}}\")\nprint(f\"Context length: {{len(str(context))}}\")\nif isinstance(context, str) and len(context) < 500:\n print(f\"WARNING: Context is very small - may be filename instead of content: {{context}}\")\n```\nIf you only received a filename string (like \"document.md\") instead of actual content, inform the parent that you cannot proceed without the file content.\n\nIMPORTANT: When you are done with the iterative process, you MUST provide a final answer inside a FINAL function when you have completed your task, NOT in code. Do not use these tags unless you have completed your task. You have two options:\n1. Use FINAL(your final answer here) to provide the answer directly\n2. Use FINAL_VAR(variable_name) to return a variable you have created in the REPL environment as your final output\n\nThink step by step carefully, plan, and execute this plan immediately in your response -- do not just say \"I will do this\" or \"I will do that\". Output to the REPL environment and recursive LLMs as much as possible. Remember to explicitly answer the original query in your final answer.\n"}, {"role": "assistant", "content": "Your context is a dict with 47 total characters, and is broken up into chunks of char lengths: [47]."}, {"role": "user", "content": "You have not interacted with the REPL environment or seen your prompt / context yet. Your next action should be to look through and figure out how to answer the prompt, so don't just provide a final answer yet.\n\nThink step-by-step on what to do using the REPL environment (which contains the context) to answer the original prompt: \"What is 2 + 2?\".\n\nContinue using the REPL environment, which has the `context` variable, and querying sub-LLMs by writing to ```repl``` tags, and determine your answer. Your next action:"}], "response": "[LLM query failed: litellm.RateLimitError: RateLimitError: MistralException - {\"object\":\"error\",\"message\":\"Rate limit exceeded\",\"type\":\"rate_limited\",\"param\":null,\"code\":\"1300\"}]", "code_blocks": [], "final_answer": null, "iteration_time": null} | ||
| {"type": "iteration", "iteration": 2, "timestamp": "2026-02-11T13:05:42.161385", "depth": 0, "agent_name": "rlm_agent", "parent_agent": null, "prompt": [{"role": "system", "content": "You are tasked with answering a query with associated context. You can access, transform, and analyze this context interactively in a REPL environment that can recursively query sub-LLMs, which you are strongly encouraged to use as much as possible. You will be queried iteratively until you provide a final answer.\n\nThe REPL environment is initialized with:\n1. A `context` variable that contains extremely important information about your query. You should check the content of the `context` variable to understand what you are working with. Make sure you look through it sufficiently as you answer your query.\n2. A `llm_query(prompt, context=None, model=None, recursive=True)` function that allows you to query an LLM inside your REPL environment. By default (recursive=True), this creates a nested RLM execution that can itself execute code and make further llm_query calls - enabling deep recursive reasoning. Set recursive=False for simple LLM calls without code execution. The optional `context` parameter lets you pass objects (files, collections, dicts) directly to the child agent - the child receives this as its `context` variable.\n3. A `llm_query_batched(prompts, contexts=None, model=None, recursive=False)` function for concurrent queries. IMPORTANT: Keep recursive=False (the default) for extraction, summarization, and Q&A tasks - embed file.content in your prompts. Only use recursive=True when children genuinely need to execute code. Results are returned in the same order as prompts.\n4. The ability to use `print()` statements to view the output of your REPL code and continue your reasoning.\n\nIMPORTANT: The `llm_query` function with recursive=True creates a full RLM execution at the next depth level. This means the sub-LLM can analyze your prompt, write and execute code, and recursively call its own llm_query. This is powerful for hierarchical decomposition of complex problems.\n\nWHEN TO USE CHILD AGENTS (recursive=True):\n- Complex sub-analyses: When a sub-problem requires its own multi-step reasoning with code execution\n- Hierarchical decomposition: When breaking a large problem into sub-problems that each need iteration\n- Delegation with large context: When passing substantial context to a child that needs to explore it programmatically\n\nWHEN TO USE SIMPLE LLM CALLS (recursive=False):\n- Parallel processing: Use `llm_query_batched` with recursive=False to process many chunks concurrently\n- Summarization: Condensing text into a shorter form\n- Extraction: Pulling specific information from provided text\n- Classification: Categorizing or labeling content\n- Simple Q&A: Questions answerable directly from the provided context without code execution\n- Aggregation: Combining multiple answers into a final result\n\nMost tasks should use recursive=False. Only use recursive=True when the sub-task itself requires writing and executing code to explore the problem.\n\nWHEN TO AVOID llm_query ENTIRELY:\n- Direct lookups: When you can find the answer by reading the context directly\n- Small contexts: When the context fits easily in your window and doesn't need chunking\n- Simple computations: When Python code alone can compute the answer\n- Pattern matching: When regex or string operations can extract what you need\n\nIMPORTANT: Default to the simplest approach. Only spawn child agents when the sub-task genuinely requires autonomous multi-step reasoning with code execution.\n\nIMPORTANT: Dont turn off your brain. Just because you can spawn a child agent doesnt mean you should. And if you do spawn a child agent, make sure you give it a good prompt and context.\n\nWORKING WITH FILES (LazyFile / LazyFileCollection):\nWhen your context contains files, you have two approaches:\n\nAPPROACH 1 - Simple extraction (recursive=False, PREFERRED for most tasks):\nEmbed file.content directly in your prompt. This is fast and efficient for summarization, extraction, and Q&A.\nMake sure to properly format the string!\n```repl\nfiles = list(context['files'])\nprompts = [f\"Summarize this document titled '{{f.name}}':\\n\\n{{f.content}}\" for f in files]\nresults = llm_query_batched(prompts, recursive=False) # Fast parallel processing\n```\n\nAPPROACH 2 - Complex analysis (recursive=True, use sparingly):\nPass the file object via context= when the child needs to write code to explore the file.\n```repl\n# Only use this when the child genuinely needs to run code\nresult = llm_query(\"Analyze this document programmatically\", context=file, recursive=True)\n```\n\nKEY FILE PROPERTIES:\n- `file.name` - the filename (string, e.g., \"report.md\")\n- `file.content` - the actual text content (string, loads the file)\n\nCRITICAL WARNING - Common Mistake:\n- WRONG: Using file.name in prompts without file.content (passes just the filename string!)\n- WRONG: contexts=[f.name for f in files] (passes list of filename strings!)\n- CORRECT: Embed file.content in prompt, OR pass file object via context=\n\nYou will only be able to see truncated outputs from the REPL environment, so you should use the query LLM function on variables you want to analyze. You will find this function especially useful when you have to analyze the semantics of the context. Use these variables as buffers to build up your final answer.\nMake sure to explicitly look through the entire context in REPL before answering your query. An example strategy is to first look at the context and figure out a chunking strategy, then break up the context into smart chunks, and query an LLM per chunk with a particular question and save the answers to a buffer, then query an LLM with all the buffers to produce your final answer.\n\nYou can use the REPL environment to help you understand your context, especially if it is huge. Remember that your sub LLMs are powerful -- they can fit around 500K characters in their context window, so don't be afraid to put a lot of context into them. For example, a viable strategy is to feed 10 documents per sub-LLM query. Analyze your input data and see if it is sufficient to just fit it in a few sub-LLM calls!\n\nWhen you want to execute Python code in the REPL environment, wrap it in triple backticks with 'repl' language identifier. For example, say we want our recursive model to search for the magic number in the context (assuming the context is a string), and the context is very long, so we want to chunk it:\n```repl\nchunk = context[:10000]\nanswer = llm_query(f\"What is the magic number in the context? Here is the chunk: {{chunk}}\")\nprint(answer)\n```\n\n\nAs an example, when the context isn't that long (e.g. >100M characters), a simple but viable strategy is, based on the context chunk lengths, to combine them and query an LLM over chunks. For example, if the context is a List[str], we ask the same query over each chunk using `llm_query_batched` for concurrent processing:\n```repl\nquery = \"A man became famous for his book \"The Great Gatsby\". How many jobs did he have?\"\n# Suppose our context is ~1M chars, and we want each sub-LLM query to be ~0.1M chars so we split it into 10 chunks\nchunk_size = len(context) // 10\nchunks = []\nfor i in range(10):\n if i < 9:\n chunk_str = \"\\n\".join(context[i*chunk_size:(i+1)*chunk_size])\n else:\n chunk_str = \"\\n\".join(context[i*chunk_size:])\n chunks.append(chunk_str)\n\n# Use batched query for concurrent processing - much faster than sequential calls!\nprompts = [f\"Try to answer the following query: {{query}}. Here are the documents:\\n{{chunk}}. Only answer if you are confident in your answer based on the evidence.\" for chunk in chunks]\nanswers = llm_query_batched(prompts, recursive=False)\nfor i, answer in enumerate(answers):\n print(f\"I got the answer from chunk {{i}}: {{answer}}\")\nfinal_answer = llm_query(f\"Aggregating all the answers per chunk, answer the original query about total number of jobs: {{query}}\\n\\nAnswers:\\n\" + \"\\n\".join(answers))\n```\n\nAs another example, after analyzing the context and realizing its separated by Markdown headers, we can maintain state through buffers by chunking the context by headers, and iteratively querying an LLM over it:\n```repl\n# After finding out the context is separated by Markdown headers, we can chunk, summarize, and answer\nimport re\nsections = re.split(r'### (.+)', context[\"content\"])\nbuffers = []\nfor i in range(1, len(sections), 2):\n header = sections[i]\n info = sections[i+1]\n summary = llm_query(f\"Summarize this {{header}} section: {{info}}\")\n buffers.append(f\"{{header}}: {{summary}}\")\nfinal_answer = llm_query(f\"Based on these summaries, answer the original query: {{query}}\\n\\nSummaries:\\n\" + \"\\n\".join(buffers))\n```\nIn the next step, we can return FINAL_VAR(final_answer).\n\nRECURSIVE CHILD AGENTS - When They Actually Help:\nUse recursive=True ONLY when the child must DISCOVER its approach by examining the data - not when you can specify the extraction upfront.\n\nWRONG approach (can be flattened to recursive=False):\n```repl\n# DON'T DO THIS - the extraction task is fully specified upfront\nresult = llm_query(\n \"Extract all API endpoints and their auth status from this file\",\n context=file,\n recursive=True # WASTEFUL - child doesn't need to write code\n)\n```\n\nRIGHT approach - when child must explore to find its approach:\nTask: \"These 50 config files should follow a common schema. Find schema violations and explain each one.\"\n\n```repl\n# STEP 1: Sample the data to understand structure\nfiles = list(context['files'])\nsamples = [f.content[:2000] for f in files[:5]]\nprint(f\"Examining {len(files)} config files\")\nprint(f\"Sample structure:\n{samples[0][:500]}\")\n```\n\n```repl\n# STEP 2: Delegate schema inference to a child that must EXPLORE\n# The child doesn't know the schema - it must:\n# - Read multiple files to infer common patterns\n# - Write code to parse and compare structures\n# - Iteratively refine its understanding\nschema_analysis = llm_query(\n \"Infer the common schema from these config files. Write code to: \"\n \"1) Parse each file's structure, 2) Find fields that appear in >80% of files, \"\n \"3) Detect type patterns (string vs number vs array). Return the inferred schema.\",\n context={'files': files},\n recursive=True # Child must iterate: parse \u2192 compare \u2192 refine\n)\nprint(f\"Inferred schema: {schema_analysis[:500]}\")\n```\n\n```repl\n# STEP 3: Delegate violation detection - child must write validation code\n# Each file may violate differently - child needs to:\n# - Apply the schema programmatically\n# - Trace WHY each violation occurred\n# - Handle edge cases it discovers\nviolations = llm_query(\n f\"Using this schema:\n{schema_analysis}\n\n\"\n \"Validate each file and explain violations. Write code to check each field \"\n \"and trace the cause of mismatches.\",\n context={'files': files},\n recursive=True # Child writes custom validation logic\n)\nprint(violations)\n```\n\nWhy recursive=True was needed:\n- Schema wasn't known upfront - child had to DISCOVER it by examining files\n- Validation logic depended on what schema was found\n- Each violation type needed different investigation code\n- Children maintained state across iterations (schema \u2192 validation \u2192 explanation)\n\nThe test: If you can write the full extraction prompt without seeing the data,\nuse recursive=False. If the child must look at data to decide what to do, use recursive=True\n\nCOMPLETE FILE PROCESSING WORKFLOW:\nWhen analyzing multiple files, follow this pattern to avoid iteration explosion:\n\n```repl\n# STEP 1: Examine what you have\nfiles = list(context['files'])\nprint(f\"Processing {{len(files)}} files: {{[f.name for f in files[:5]]}}...\")\n\n# STEP 2: Process files in parallel with recursive=False (IMPORTANT!)\n# Embed file.content in prompts - this is fast and avoids spawning recursive agents\nprompts = [\n f\"Analyze '{{f.name}}' for key themes, risks, and opportunities:\\n\\n{{f.content}}\"\n for f in files\n]\nresults = llm_query_batched(prompts, recursive=False) # NOT recursive=True!\n\n# STEP 3: Collect results\nfor f, result in zip(files, results):\n print(f\"{{f.name}}: {{result[:200]}}...\")\n\n# STEP 4: Aggregate AT THIS LEVEL - do not delegate aggregation!\ncombined = \"\\n\\n\".join(f\"## {{f.name}}\\n{{r}}\" for f, r in zip(files, results))\nfinal = llm_query(\n f\"Synthesize these analyses into a comprehensive answer:\\n\\n{{combined}}\",\n recursive=False\n)\nprint(final)\n```\n\nCRITICAL - YOU MUST AGGREGATE:\nAfter spawning child queries, YOU must collect and synthesize their results.\nDo NOT expect children to produce your final answer - they return to you, not to the user.\nAlways end with FINAL() or FINAL_VAR() after aggregating.\n\nCONTEXT VALIDATION (if you are a child agent):\nIf your context seems unexpectedly small or looks like filenames instead of content, something went wrong.\n```repl\n# First thing: check what you actually received\nprint(f\"Context type: {{type(context)}}\")\nprint(f\"Context length: {{len(str(context))}}\")\nif isinstance(context, str) and len(context) < 500:\n print(f\"WARNING: Context is very small - may be filename instead of content: {{context}}\")\n```\nIf you only received a filename string (like \"document.md\") instead of actual content, inform the parent that you cannot proceed without the file content.\n\nIMPORTANT: When you are done with the iterative process, you MUST provide a final answer inside a FINAL function when you have completed your task, NOT in code. Do not use these tags unless you have completed your task. You have two options:\n1. Use FINAL(your final answer here) to provide the answer directly\n2. Use FINAL_VAR(variable_name) to return a variable you have created in the REPL environment as your final output\n\nThink step by step carefully, plan, and execute this plan immediately in your response -- do not just say \"I will do this\" or \"I will do that\". Output to the REPL environment and recursive LLMs as much as possible. Remember to explicitly answer the original query in your final answer.\n"}, {"role": "assistant", "content": "Your context is a dict with 47 total characters, and is broken up into chunks of char lengths: [47]."}, {"role": "assistant", "content": "[LLM query failed: litellm.RateLimitError: RateLimitError: MistralException - {\"object\":\"error\",\"message\":\"Rate limit exceeded\",\"type\":\"rate_limited\",\"param\":null,\"code\":\"1300\"}]"}, {"role": "user", "content": "The history before is your previous interactions with the REPL environment. Think step-by-step on what to do using the REPL environment (which contains the context) to answer the original prompt: \"What is 2 + 2?\".\n\nContinue using the REPL environment, which has the `context` variable, and querying sub-LLMs by writing to ```repl``` tags, and determine your answer. Your next action:"}], "response": "[LLM query failed: litellm.RateLimitError: RateLimitError: MistralException - {\"object\":\"error\",\"message\":\"Rate limit exceeded\",\"type\":\"rate_limited\",\"param\":null,\"code\":\"1300\"}]", "code_blocks": [], "final_answer": null, "iteration_time": null} | ||
| {"type": "iteration", "iteration": 3, "timestamp": "2026-02-11T13:05:58.361345", "depth": 0, "agent_name": "rlm_agent", "parent_agent": null, "prompt": [{"role": "system", "content": "You are tasked with answering a query with associated context. You can access, transform, and analyze this context interactively in a REPL environment that can recursively query sub-LLMs, which you are strongly encouraged to use as much as possible. You will be queried iteratively until you provide a final answer.\n\nThe REPL environment is initialized with:\n1. A `context` variable that contains extremely important information about your query. You should check the content of the `context` variable to understand what you are working with. Make sure you look through it sufficiently as you answer your query.\n2. A `llm_query(prompt, context=None, model=None, recursive=True)` function that allows you to query an LLM inside your REPL environment. By default (recursive=True), this creates a nested RLM execution that can itself execute code and make further llm_query calls - enabling deep recursive reasoning. Set recursive=False for simple LLM calls without code execution. The optional `context` parameter lets you pass objects (files, collections, dicts) directly to the child agent - the child receives this as its `context` variable.\n3. A `llm_query_batched(prompts, contexts=None, model=None, recursive=False)` function for concurrent queries. IMPORTANT: Keep recursive=False (the default) for extraction, summarization, and Q&A tasks - embed file.content in your prompts. Only use recursive=True when children genuinely need to execute code. Results are returned in the same order as prompts.\n4. The ability to use `print()` statements to view the output of your REPL code and continue your reasoning.\n\nIMPORTANT: The `llm_query` function with recursive=True creates a full RLM execution at the next depth level. This means the sub-LLM can analyze your prompt, write and execute code, and recursively call its own llm_query. This is powerful for hierarchical decomposition of complex problems.\n\nWHEN TO USE CHILD AGENTS (recursive=True):\n- Complex sub-analyses: When a sub-problem requires its own multi-step reasoning with code execution\n- Hierarchical decomposition: When breaking a large problem into sub-problems that each need iteration\n- Delegation with large context: When passing substantial context to a child that needs to explore it programmatically\n\nWHEN TO USE SIMPLE LLM CALLS (recursive=False):\n- Parallel processing: Use `llm_query_batched` with recursive=False to process many chunks concurrently\n- Summarization: Condensing text into a shorter form\n- Extraction: Pulling specific information from provided text\n- Classification: Categorizing or labeling content\n- Simple Q&A: Questions answerable directly from the provided context without code execution\n- Aggregation: Combining multiple answers into a final result\n\nMost tasks should use recursive=False. Only use recursive=True when the sub-task itself requires writing and executing code to explore the problem.\n\nWHEN TO AVOID llm_query ENTIRELY:\n- Direct lookups: When you can find the answer by reading the context directly\n- Small contexts: When the context fits easily in your window and doesn't need chunking\n- Simple computations: When Python code alone can compute the answer\n- Pattern matching: When regex or string operations can extract what you need\n\nIMPORTANT: Default to the simplest approach. Only spawn child agents when the sub-task genuinely requires autonomous multi-step reasoning with code execution.\n\nIMPORTANT: Dont turn off your brain. Just because you can spawn a child agent doesnt mean you should. And if you do spawn a child agent, make sure you give it a good prompt and context.\n\nWORKING WITH FILES (LazyFile / LazyFileCollection):\nWhen your context contains files, you have two approaches:\n\nAPPROACH 1 - Simple extraction (recursive=False, PREFERRED for most tasks):\nEmbed file.content directly in your prompt. This is fast and efficient for summarization, extraction, and Q&A.\nMake sure to properly format the string!\n```repl\nfiles = list(context['files'])\nprompts = [f\"Summarize this document titled '{{f.name}}':\\n\\n{{f.content}}\" for f in files]\nresults = llm_query_batched(prompts, recursive=False) # Fast parallel processing\n```\n\nAPPROACH 2 - Complex analysis (recursive=True, use sparingly):\nPass the file object via context= when the child needs to write code to explore the file.\n```repl\n# Only use this when the child genuinely needs to run code\nresult = llm_query(\"Analyze this document programmatically\", context=file, recursive=True)\n```\n\nKEY FILE PROPERTIES:\n- `file.name` - the filename (string, e.g., \"report.md\")\n- `file.content` - the actual text content (string, loads the file)\n\nCRITICAL WARNING - Common Mistake:\n- WRONG: Using file.name in prompts without file.content (passes just the filename string!)\n- WRONG: contexts=[f.name for f in files] (passes list of filename strings!)\n- CORRECT: Embed file.content in prompt, OR pass file object via context=\n\nYou will only be able to see truncated outputs from the REPL environment, so you should use the query LLM function on variables you want to analyze. You will find this function especially useful when you have to analyze the semantics of the context. Use these variables as buffers to build up your final answer.\nMake sure to explicitly look through the entire context in REPL before answering your query. An example strategy is to first look at the context and figure out a chunking strategy, then break up the context into smart chunks, and query an LLM per chunk with a particular question and save the answers to a buffer, then query an LLM with all the buffers to produce your final answer.\n\nYou can use the REPL environment to help you understand your context, especially if it is huge. Remember that your sub LLMs are powerful -- they can fit around 500K characters in their context window, so don't be afraid to put a lot of context into them. For example, a viable strategy is to feed 10 documents per sub-LLM query. Analyze your input data and see if it is sufficient to just fit it in a few sub-LLM calls!\n\nWhen you want to execute Python code in the REPL environment, wrap it in triple backticks with 'repl' language identifier. For example, say we want our recursive model to search for the magic number in the context (assuming the context is a string), and the context is very long, so we want to chunk it:\n```repl\nchunk = context[:10000]\nanswer = llm_query(f\"What is the magic number in the context? Here is the chunk: {{chunk}}\")\nprint(answer)\n```\n\n\nAs an example, when the context isn't that long (e.g. >100M characters), a simple but viable strategy is, based on the context chunk lengths, to combine them and query an LLM over chunks. For example, if the context is a List[str], we ask the same query over each chunk using `llm_query_batched` for concurrent processing:\n```repl\nquery = \"A man became famous for his book \"The Great Gatsby\". How many jobs did he have?\"\n# Suppose our context is ~1M chars, and we want each sub-LLM query to be ~0.1M chars so we split it into 10 chunks\nchunk_size = len(context) // 10\nchunks = []\nfor i in range(10):\n if i < 9:\n chunk_str = \"\\n\".join(context[i*chunk_size:(i+1)*chunk_size])\n else:\n chunk_str = \"\\n\".join(context[i*chunk_size:])\n chunks.append(chunk_str)\n\n# Use batched query for concurrent processing - much faster than sequential calls!\nprompts = [f\"Try to answer the following query: {{query}}. Here are the documents:\\n{{chunk}}. Only answer if you are confident in your answer based on the evidence.\" for chunk in chunks]\nanswers = llm_query_batched(prompts, recursive=False)\nfor i, answer in enumerate(answers):\n print(f\"I got the answer from chunk {{i}}: {{answer}}\")\nfinal_answer = llm_query(f\"Aggregating all the answers per chunk, answer the original query about total number of jobs: {{query}}\\n\\nAnswers:\\n\" + \"\\n\".join(answers))\n```\n\nAs another example, after analyzing the context and realizing its separated by Markdown headers, we can maintain state through buffers by chunking the context by headers, and iteratively querying an LLM over it:\n```repl\n# After finding out the context is separated by Markdown headers, we can chunk, summarize, and answer\nimport re\nsections = re.split(r'### (.+)', context[\"content\"])\nbuffers = []\nfor i in range(1, len(sections), 2):\n header = sections[i]\n info = sections[i+1]\n summary = llm_query(f\"Summarize this {{header}} section: {{info}}\")\n buffers.append(f\"{{header}}: {{summary}}\")\nfinal_answer = llm_query(f\"Based on these summaries, answer the original query: {{query}}\\n\\nSummaries:\\n\" + \"\\n\".join(buffers))\n```\nIn the next step, we can return FINAL_VAR(final_answer).\n\nRECURSIVE CHILD AGENTS - When They Actually Help:\nUse recursive=True ONLY when the child must DISCOVER its approach by examining the data - not when you can specify the extraction upfront.\n\nWRONG approach (can be flattened to recursive=False):\n```repl\n# DON'T DO THIS - the extraction task is fully specified upfront\nresult = llm_query(\n \"Extract all API endpoints and their auth status from this file\",\n context=file,\n recursive=True # WASTEFUL - child doesn't need to write code\n)\n```\n\nRIGHT approach - when child must explore to find its approach:\nTask: \"These 50 config files should follow a common schema. Find schema violations and explain each one.\"\n\n```repl\n# STEP 1: Sample the data to understand structure\nfiles = list(context['files'])\nsamples = [f.content[:2000] for f in files[:5]]\nprint(f\"Examining {len(files)} config files\")\nprint(f\"Sample structure:\n{samples[0][:500]}\")\n```\n\n```repl\n# STEP 2: Delegate schema inference to a child that must EXPLORE\n# The child doesn't know the schema - it must:\n# - Read multiple files to infer common patterns\n# - Write code to parse and compare structures\n# - Iteratively refine its understanding\nschema_analysis = llm_query(\n \"Infer the common schema from these config files. Write code to: \"\n \"1) Parse each file's structure, 2) Find fields that appear in >80% of files, \"\n \"3) Detect type patterns (string vs number vs array). Return the inferred schema.\",\n context={'files': files},\n recursive=True # Child must iterate: parse \u2192 compare \u2192 refine\n)\nprint(f\"Inferred schema: {schema_analysis[:500]}\")\n```\n\n```repl\n# STEP 3: Delegate violation detection - child must write validation code\n# Each file may violate differently - child needs to:\n# - Apply the schema programmatically\n# - Trace WHY each violation occurred\n# - Handle edge cases it discovers\nviolations = llm_query(\n f\"Using this schema:\n{schema_analysis}\n\n\"\n \"Validate each file and explain violations. Write code to check each field \"\n \"and trace the cause of mismatches.\",\n context={'files': files},\n recursive=True # Child writes custom validation logic\n)\nprint(violations)\n```\n\nWhy recursive=True was needed:\n- Schema wasn't known upfront - child had to DISCOVER it by examining files\n- Validation logic depended on what schema was found\n- Each violation type needed different investigation code\n- Children maintained state across iterations (schema \u2192 validation \u2192 explanation)\n\nThe test: If you can write the full extraction prompt without seeing the data,\nuse recursive=False. If the child must look at data to decide what to do, use recursive=True\n\nCOMPLETE FILE PROCESSING WORKFLOW:\nWhen analyzing multiple files, follow this pattern to avoid iteration explosion:\n\n```repl\n# STEP 1: Examine what you have\nfiles = list(context['files'])\nprint(f\"Processing {{len(files)}} files: {{[f.name for f in files[:5]]}}...\")\n\n# STEP 2: Process files in parallel with recursive=False (IMPORTANT!)\n# Embed file.content in prompts - this is fast and avoids spawning recursive agents\nprompts = [\n f\"Analyze '{{f.name}}' for key themes, risks, and opportunities:\\n\\n{{f.content}}\"\n for f in files\n]\nresults = llm_query_batched(prompts, recursive=False) # NOT recursive=True!\n\n# STEP 3: Collect results\nfor f, result in zip(files, results):\n print(f\"{{f.name}}: {{result[:200]}}...\")\n\n# STEP 4: Aggregate AT THIS LEVEL - do not delegate aggregation!\ncombined = \"\\n\\n\".join(f\"## {{f.name}}\\n{{r}}\" for f, r in zip(files, results))\nfinal = llm_query(\n f\"Synthesize these analyses into a comprehensive answer:\\n\\n{{combined}}\",\n recursive=False\n)\nprint(final)\n```\n\nCRITICAL - YOU MUST AGGREGATE:\nAfter spawning child queries, YOU must collect and synthesize their results.\nDo NOT expect children to produce your final answer - they return to you, not to the user.\nAlways end with FINAL() or FINAL_VAR() after aggregating.\n\nCONTEXT VALIDATION (if you are a child agent):\nIf your context seems unexpectedly small or looks like filenames instead of content, something went wrong.\n```repl\n# First thing: check what you actually received\nprint(f\"Context type: {{type(context)}}\")\nprint(f\"Context length: {{len(str(context))}}\")\nif isinstance(context, str) and len(context) < 500:\n print(f\"WARNING: Context is very small - may be filename instead of content: {{context}}\")\n```\nIf you only received a filename string (like \"document.md\") instead of actual content, inform the parent that you cannot proceed without the file content.\n\nIMPORTANT: When you are done with the iterative process, you MUST provide a final answer inside a FINAL function when you have completed your task, NOT in code. Do not use these tags unless you have completed your task. You have two options:\n1. Use FINAL(your final answer here) to provide the answer directly\n2. Use FINAL_VAR(variable_name) to return a variable you have created in the REPL environment as your final output\n\nThink step by step carefully, plan, and execute this plan immediately in your response -- do not just say \"I will do this\" or \"I will do that\". Output to the REPL environment and recursive LLMs as much as possible. Remember to explicitly answer the original query in your final answer.\n"}, {"role": "assistant", "content": "Your context is a dict with 47 total characters, and is broken up into chunks of char lengths: [47]."}, {"role": "assistant", "content": "[LLM query failed: litellm.RateLimitError: RateLimitError: MistralException - {\"object\":\"error\",\"message\":\"Rate limit exceeded\",\"type\":\"rate_limited\",\"param\":null,\"code\":\"1300\"}]"}, {"role": "assistant", "content": "[LLM query failed: litellm.RateLimitError: RateLimitError: MistralException - {\"object\":\"error\",\"message\":\"Rate limit exceeded\",\"type\":\"rate_limited\",\"param\":null,\"code\":\"1300\"}]"}, {"role": "user", "content": "Please provide a final answer to the user's question based on the information gathered so far."}], "response": "[LLM query failed: litellm.RateLimitError: RateLimitError: MistralException - {\"object\":\"error\",\"message\":\"Rate limit exceeded\",\"type\":\"rate_limited\",\"param\":null,\"code\":\"1300\"}]", "code_blocks": [], "final_answer": "[LLM query failed: litellm.RateLimitError: RateLimitError: MistralException - {\"object\":\"error\",\"message\":\"Rate limit exceeded\",\"type\":\"rate_limited\",\"param\":null,\"code\":\"1300\"}]", "iteration_time": null} |
There was a problem hiding this comment.
Log files and other generated artifacts should not be committed to the repository. They can bloat the repository size and may contain sensitive information. Please remove the rlm-google/logs/ directory from this pull request and add rlm-google/logs/ to your .gitignore file to prevent this from happening in the future.
| --- rlm-google/adk_rlm/config.py | ||
| +++ rlm-google/adk_rlm/config.py | ||
| @@ -2,12 +2,15 @@ | ||
|
|
||
| import os | ||
| import json | ||
| +import logging | ||
| from dataclasses import dataclass, field | ||
| from typing import Optional, Dict, Any | ||
|
|
||
| from dotenv import load_dotenv | ||
|
|
||
| load_dotenv() | ||
| + | ||
| +logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| @dataclass | ||
| @@ -75,6 +78,19 @@ | ||
| except Exception as e: | ||
| print(f"Error parsing RLM_PROVIDER_LIMITS: {e}") | ||
|
|
||
| + def get_provider_config(self, provider: str) -> ProviderConfig: | ||
| + """Get config for a specific provider, falling back to defaults.""" | ||
| + if provider in self.provider_configs: | ||
| + config = self.provider_configs[provider] | ||
| + else: | ||
| + config = ProviderConfig( | ||
| + requests_per_minute=self.requests_per_minute, | ||
| + max_concurrent=self.max_concurrent_requests, | ||
| + max_burst=self.max_burst | ||
| + ) | ||
| + | ||
| + logger.info(f"Using config for {provider}: {config}") | ||
| + return config | ||
| + | ||
| @classmethod | ||
| def from_env(cls) -> "RLMConfig": | ||
| """Create config from environment variables.""" |
| from adk_rlm.rate_limiter import get_rate_limiter | ||
| limiter = get_rate_limiter(self.provider) |
| for attempt in range(self.max_retries): | ||
| try: | ||
| async with await rate_limit_async(provider=self.provider, timeout=120): | ||
| logger.debug(f"Acquiring rate limit for {self.provider} - tokens available: {limiter._tokens:.2f}") |
There was a problem hiding this comment.
This debug log is helpful, but it accesses a private member _tokens of the TokenBucketRateLimiter. To respect encapsulation, it would be better to add a public property to the TokenBucketRateLimiter class (e.g., tokens_available) to expose this value safely. This would require a change in rlm-google/adk_rlm/rate_limiter.py.
| from adk_rlm.rate_limiter import get_rate_limiter | ||
| limiter = get_rate_limiter(self.provider) |
Improved Mistral rate limiting by lowering defaults to 20 RPM and ensuring sequential processing. Fixed a bug where provider-specific limits were ignored because the provider name was incorrectly extracted from LiteLLM. Added better logging and stagger calculation for batched requests.
PR created automatically by Jules for task 14646782312242387544 started by @Esashiero