Force tool calling in langraph swarm

create_react_agent(
            model=self.model.bind_tools(all_tools, tool_choice="document_retriever"),
            tools=all_tools,
            prompt=self._make_rag_prompt(),
            name="rag_agent",
        )

While creating a react agent in swarm the bind tool returns an error.
Arg Returns in docstring not found in function signature.

But the function has the docstring

 """
    Tool to retrieve relevant documents using hybrid search (vector + BM25) from web-scraped content.
    
    Args:
        query (str): The search query
        domain_name (str): Name of the domain/website for the vector store
        user_folder (str): Username to use in the path (optional)
        website_name (str): Name of the website folder (optional, defaults to "brandsmithworldwide")
        
    Returns:
        str: Retrieved content as formatted text
    """

How to force tool call in langgraph swarm?

It could be similar to [Bug] Arg Returns in docstring not found in function signature even when it is provided · Issue #29155 · langchain-ai/langchain · GitHub
Can you share the function?

def document_retriever(query: str, domain_name: str, user_folder: Optional[str] = None, website_name: Optional[str] = None) -> str:
    """
    Tool to retrieve relevant documents using hybrid search (vector + BM25) from web-scraped content.
    
    Args:
        query (str): The search query
        domain_name (str): Name of the domain/website for the vector store
        user_folder (str, Optional): Username to use in the path (optional)
        website_name (str, Optional): Name of the website folder (optional, defaults to "brandsmithworldwide")
        
    Returns:
        Retrieved content as formatted text
    """
    logger.info(f"Document retriever tool called with query: {query}, domain: {domain_name}, website: {website_name}")
    
    try:
        # Set default website name if not provided
        website_folder = website_name or "brandsmithworldwide"
        
        # Set up paths
        if user_folder:
            vectorstore_path = os.path.join("Data/WebScrapper", user_folder, website_folder, "vectorstore")
            bm25_path = os.path.join("Data/WebScrapper", user_folder, website_folder, "vectorstore", "bm25_index")
        else:
            vectorstore_path = os.path.join("vectorstore", domain_name)
            bm25_path = os.path.join("vectorstore", domain_name, "bm25_index")
        
        # Check if vector store exists and find the correct path
        vectorstore_path, bm25_path = _find_vectorstore_path(vectorstore_path, bm25_path, user_folder)
        
        # Load the indices
        faiss_index, docs_data = _load_faiss_vectorstore(vectorstore_path)
        bm25_data = _load_bm25_index(bm25_path)
        
        # Perform hybrid search
        vector_results = _query_faiss_index(query, faiss_index, docs_data, top_k=2)
        bm25_results = _query_bm25_index(query, bm25_data, top_k=2)
        
        # Fuse results
        fused_results = _reciprocal_rank_fusion(vector_results, bm25_results)
        
        # Take top 3 results
        top_results = fused_results[:3]
        
        if not top_results:
            return f"No relevant documents found for query: '{query}' in domain: {domain_name}"
        
        # Format the results
        formatted_results = []
        for i, (idx, score, metadata) in enumerate(top_results):
            doc_title = metadata.get("doc_title", f"Document {i+1}")
            doc_url = metadata.get("doc_url", "")
            contextual_chunk = metadata.get("contextual_chunk", "")
            original_chunk = metadata.get("original_chunk", "")
            
            # Skip if the content is empty
            if not original_chunk.strip():
                continue
            
            # Source information
            source_info = f"Source: {doc_url}" if doc_url else f"Source: {doc_title}"
            
            # Format the chunk with context and content
            formatted_results.append(f"## {doc_title}\n{contextual_chunk}{original_chunk}\n{source_info}\n")
        
        if not formatted_results:
            return f"No relevant content found for query: '{query}'"
        
        return "\n".join(formatted_results)
        
    except Exception as e:
        logger.error(f"Error in document_retriever tool: {e}")
        return f"I couldn't find information about \"{query}\". Please try another question or upload content related to this topic."

The problem occurs when i bind_tools and force tool call
without it it works normally without giving me any error

You can try by removing Returns once.

I could reproduce similar issue with below minimal example:

Code:

@tool(parse_docstring=True)
def add_numbers(a: int, b: int) -> int:
    """
    Add two numbers.

    Args:
        a (int): The first number.
        b (int): The second number.
    Returns:
        The sum of the two numbers.

"""
    if a is None or b is None:
        raise ValueError("Both 'a' and 'b' must be provided.")  
    return a + b


model = llm.bind_tools([add_numbers])

agent = create_react_agent(
    model=model,
    tools=[add_numbers],
    name="math_expert",
    prompt="You are a math expert. Always use one tool at a time."
)

agent.invoke({"messages": [HumanMessage(content="5 + 5")]})

Response:

File /localdisk/workspace/langgraph-learning/.venv/lib64/python3.12/site-packages/langchain_core/tools/convert.py:348, in tool.<locals>._partial(func)
    346 name_ = func.get_name() if isinstance(func, Runnable) else func.__name__
    347 tool_factory = _create_tool_factory(name_)
--> 348 return tool_factory(func)

File /localdisk/workspace/langgraph-learning/.venv/lib64/python3.12/site-packages/langchain_core/tools/convert.py:266, in tool.<locals>._create_tool_factory.<locals>._tool_factory(dec_func)
    263     schema = args_schema
    265 if infer_schema or args_schema is not None:
--> 266     return StructuredTool.from_function(
    267         func,
    268         coroutine,
...
    185 if docstring_arg not in annotations:
    186     msg = f"Arg {docstring_arg} in docstring not found in function signature."
--> 187     raise ValueError(msg)

ValueError: Arg Returns in docstring not found in function signature.

This was due to a missing blank line above Returns in docstring, Some of the invalid
docstring examples:

    .. code-block:: python

            # No args section
            def invalid_docstring_1(bar: str, baz: int) -> str:
                \"\"\"The foo.\"\"\"
                return bar

            # Improper whitespace between summary and args section
            def invalid_docstring_2(bar: str, baz: int) -> str:
                \"\"\"The foo.
                Args:
                    bar: The bar.
                    baz: The baz.
                \"\"\"
                return bar

            # Documented args absent from function signature
            def invalid_docstring_3(bar: str, baz: int) -> str:
                \"\"\"The foo.

                Args:
                    banana: The bar.
                    monkey: The baz.
                \"\"\"
                return bar

Still the same issue
I have send what i did as per your instructions

@tool(parse_docstring=True)
def document_retriever(query: str, domain_name: str, user_folder: Optional[str] = None, website_name: Optional[str] = None) -> str:
    """
    Tool to retrieve relevant documents using hybrid search (vector + BM25) from web-scraped content.
    
    Args:
        query (str): The search query
        domain_name (str): Name of the domain/website for the vector store
        user_folder (str, optional): Username to use in the path (optional)
        website_name (str, optional): Name of the website folder (optional, defaults to "brandsmithworldwide")
    """
    logger.info(f"Document retriever tool called with query: {query}, domain: {domain_name}, website: {website_name}")
    
    try:
        # Set default website name if not provided
        website_folder = website_name or "brandsmithworldwide"
        
        # Set up paths
        if user_folder:
            vectorstore_path = os.path.join("Data/WebScrapper", user_folder, website_folder, "vectorstore")
            bm25_path = os.path.join("Data/WebScrapper", user_folder, website_folder, "vectorstore", "bm25_index")
        else:
            vectorstore_path = os.path.join("vectorstore", domain_name)
            bm25_path = os.path.join("vectorstore", domain_name, "bm25_index")
        
        # Check if vector store exists and find the correct path
        vectorstore_path, bm25_path = _find_vectorstore_path(vectorstore_path, bm25_path, user_folder)
        
        # Load the indices
        faiss_index, docs_data = _load_faiss_vectorstore(vectorstore_path)
        bm25_data = _load_bm25_index(bm25_path)
        
        # Perform hybrid search
        vector_results = _query_faiss_index(query, faiss_index, docs_data, top_k=2)
        bm25_results = _query_bm25_index(query, bm25_data, top_k=2)
        
        # Fuse results
        fused_results = _reciprocal_rank_fusion(vector_results, bm25_results)
        
        # Take top 3 results
        top_results = fused_results[:3]
        
        if not top_results:
            return f"No relevant documents found for query: '{query}' in domain: {domain_name}"
        
        # Format the results
        formatted_results = []
        for i, (idx, score, metadata) in enumerate(top_results):
            doc_title = metadata.get("doc_title", f"Document {i+1}")
            doc_url = metadata.get("doc_url", "")
            contextual_chunk = metadata.get("contextual_chunk", "")
            original_chunk = metadata.get("original_chunk", "")
            
            # Skip if the content is empty
            if not original_chunk.strip():
                continue
            
            # Source information
            source_info = f"Source: {doc_url}" if doc_url else f"Source: {doc_title}"
            
            # Format the chunk with context and content
            formatted_results.append(f"## {doc_title}\n{contextual_chunk}{original_chunk}\n{source_info}\n")
        
        if not formatted_results:
            return f"No relevant content found for query: '{query}'"
        
        return "\n".join(formatted_results)
        
    except Exception as e:
        logger.error(f"Error in document_retriever tool: {e}")
        return f"I couldn't find information about \"{query}\". Please try another question or upload content related to this topic."

This is what i changed the tool to, removed the Retrurns from the doc string and previously there was a space so both are not working
The error is still the same
Arg Returns in docstring not found in function signature.

Let me once again tell you
that when i am creating a react agent normally which is without binding the tool it is working properly

return create_react_agent(
            model=self.model,
            tools=all_tools,
            prompt=self._make_rag_prompt(),
            name="rag_agent",
        )


This here works normally and donsent give any error
But as soon as i add bindtools it throws the error

return create_react_agent(
            model=self.model.bind_tools(all_tools, tool_choice="document_retriever"),
            tools=all_tools,
            prompt=self._make_rag_prompt(),
            name="rag_agent",
        )

Please paste complete exception stack trace if possible.

So i have a class where the init method initializes everything
During the initialization the error occurs

def __init__(
        self, 
        user_id: str,
        domain_name: str,
        openai_api_key: Optional[str] = None,
        model_name: str = "gpt-4.1-mini",
        user_folder: Optional[str] = None,
        website_name: Optional[str] = None
    ):
        """
        Initialize the RAG Swarm with tools based on user subscriptions.
        
        Args:
            user_id: User ID to determine tool and assistant access
            domain_name: Name of the domain/website for the vector store
            openai_api_key: OpenAI API key for LLM (or from env var)
            model_name: Name of the LLM model to use
            user_folder: Username to use in the path (optional)
            website_name: Name of the website folder (optional)
        """
        if not RAGSwarm._mongo:
            raise ValueError("MongoDB not initialized. Call RAGSwarm.init_mongo() first.")
            
        self.user_id = user_id
        self.domain_name = domain_name
        self.user_folder = user_folder
        self.website_name = website_name or "brandsmithworldwide"  # Default fallback
        
        # Set up API keys
        self.openai_api_key = openai_api_key or os.environ.get("OPENAI_DEV_API_KEY") or os.environ.get("OPENAI_API_KEY")
        
        if not self.openai_api_key:
            raise ValueError("OpenAI API key is required for the LLM")
        
        # Load services from MongoDB based on website_name
        self.services = self._load_services_from_mongodb()
        logger.info(f"Loaded {len(self.services)} services for user {user_id}, website: {self.website_name}")
        
        # Get user subscription tier
        self.subscription_tier = self._get_user_subscription_tier()
        logger.info(f"User {user_id} has WebsiteScrapper subscription tier: {self.subscription_tier}")
        
        # Initialize ToolFactory for this user
        self.tool_factory = ToolFactory(user_id, "WebsiteScrapper")
        
        # Get the list of assistants this user can access based on subscription
        self.available_assistants = self._get_available_assistants()
        logger.info(f"Available assistants for user {user_id}: {self.available_assistants}")
        
        # Initialize LLM
        self.model = ChatOpenAI(
            model=model_name,
            openai_api_key=self.openai_api_key,
            temperature=0.2
        )
        
        # Create the RAG agent with tools from ToolFactory
        self.rag_agent = self._create_rag_agent()
        
        # Set up checkpointer and swarm
        self.checkpointer = MemorySaver()
        
        # Build the swarm with the RAG agent
        self.builder = create_swarm(
            [self.rag_agent],
            default_active_agent="rag_agent"
        )

There is more but the error occurs here specifically during _create_rag_agent

    def _create_rag_agent(self):
        """Create the RAG agent with all tools from ToolFactory"""
        # Get all tools for the agent
        all_tools = self._get_all_agent_tools()
        print('--'*30)
        print(f"Creating RAG agent with {len(all_tools)} tools")
        for tool in all_tools:
            tool_name = getattr(tool, 'name', 'unnamed_tool')
            print(f"Tool: {tool_name}")
        print('--'*30)
        # model_with_tools = self.model.bind_tools(tools=all_tools, tool_choice={"type": "tool", "name": "document_retriever"})
        return create_react_agent(
            model=self.model.bind_tools(all_tools, tool_choice="document_retriever"),
            tools=all_tools,
            prompt=self._make_rag_prompt(),
            name="rag_agent",
        )

This is the _create_rag_agent function which basically checks which tools the user has access to and creates an agent for that user based on those tools
and this is where i get the error

I meant the complete error, maybe that gives some idea as document_retriever looks okay

Something like:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[113], line 18
     15         raise ValueError("Both 'a' and 'b' must be provided.")  
     16     return a + b
---> 18 @tool(parse_docstring=True)
     19 def multiply_numbers(a: int, b: int) -> int:
     20     """
     21     Multiply two numbers.
     22 
   (...)     28 
     29 """
     30     if a is None or b is None:

File /localdisk/workspace/langgraph-learning/.venv/lib64/python3.12/site-packages/langchain_core/tools/convert.py:348, in tool.<locals>._partial(func)
    346 name_ = func.get_name() if isinstance(func, Runnable) else func.__name__
    347 tool_factory = _create_tool_factory(name_)
--> 348 return tool_factory(func)

File /localdisk/workspace/langgraph-learning/.venv/lib64/python3.12/site-packages/langchain_core/tools/convert.py:266, in tool.<locals>._create_tool_factory.<locals>._tool_factory(dec_func)
    263     schema = args_schema
    265 if infer_schema or args_schema is not None:
--> 266     return StructuredTool.from_function(
    267         func,
    268         coroutine,
    269         name=tool_name,
    270         description=tool_description,
    271         return_direct=return_direct,
    272         args_schema=schema,
    273         infer_schema=infer_schema,
    274         response_format=response_format,
    275         parse_docstring=parse_docstring,
    276         error_on_invalid_docstring=error_on_invalid_docstring,
    277     )
    278 # If someone doesn't want a schema applied, we must treat it as
    279 # a simple string->string function
    280 if dec_func.__doc__ is None:

File /localdisk/workspace/langgraph-learning/.venv/lib64/python3.12/site-packages/langchain_core/tools/structured.py:189, in StructuredTool.from_function(cls, func, coroutine, name, description, return_direct, args_schema, infer_schema, response_format, parse_docstring, error_on_invalid_docstring, **kwargs)
    186 name = name or source_function.__name__
    187 if args_schema is None and infer_schema:
    188     # schema name is appended within function
--> 189     args_schema = create_schema_from_function(
    190         name,
    191         source_function,
    192         parse_docstring=parse_docstring,
    193         error_on_invalid_docstring=error_on_invalid_docstring,
    194         filter_args=_filter_schema_args(source_function),
    195     )
    196 description_ = description
    197 if description is None and not parse_docstring:

File /localdisk/workspace/langgraph-learning/.venv/lib64/python3.12/site-packages/langchain_core/tools/base.py:357, in create_schema_from_function(model_name, func, filter_args, parse_docstring, error_on_invalid_docstring, include_injected)
    352         if not include_injected and _is_injected_arg_type(
    353             sig.parameters[existing_param].annotation
    354         ):
    355             filter_args_.append(existing_param)
--> 357 description, arg_descriptions = _infer_arg_descriptions(
    358     func,
    359     parse_docstring=parse_docstring,
    360     error_on_invalid_docstring=error_on_invalid_docstring,
    361 )
    362 # Pydantic adds placeholder virtual fields we need to strip
    363 valid_properties = []

File /localdisk/workspace/langgraph-learning/.venv/lib64/python3.12/site-packages/langchain_core/tools/base.py:215, in _infer_arg_descriptions(fn, parse_docstring, error_on_invalid_docstring)
    213     arg_descriptions = {}
    214 if parse_docstring:
--> 215     _validate_docstring_args_against_annotations(arg_descriptions, annotations)
    216 for arg, arg_type in annotations.items():
    217     if arg in arg_descriptions:

File /localdisk/workspace/langgraph-learning/.venv/lib64/python3.12/site-packages/langchain_core/tools/base.py:187, in _validate_docstring_args_against_annotations(arg_descriptions, annotations)
    185 if docstring_arg not in annotations:
    186     msg = f"Arg {docstring_arg} in docstring not found in function signature."
--> 187     raise ValueError(msg)

ValueError: Arg Returns in docstring not found in function signature.
------------------------------------------------------------
All base tools available: ['document_retriever', 'user_information_saver']
------------------------------------------------------------
------------------------------------------------------------
Base tools available: ['document_retriever', 'user_information_saver']
------------------------------------------------------------
Added enhanced document_retriever tool
Added tool: user_information_saver
Total agent tools: 2
------------------------------------------------------------
Creating RAG agent with 2 tools
Tool: document_retriever
Tool: user_information_saver
------------------------------------------------------------
2025-10-02 21:56:42 - ERROR - ai-agents - Error in chat route: Arg Returns in docstring not found in function signature.
Traceback (most recent call last):
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/api/routes/Client/chat.py", line 69, in chat
    rag_swarm = RAGSwarmFactory.get_instance(
            user_id=user_id,  # Use ObjectId for database operations
    ...<3 lines>...
            model_name="gpt-4.1-mini"
    )
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/agents/WebsiteScrapper/website_agent.py", line 602, in get_instance
    cls._instances[instance_key] = RAGSwarm(
                                   ~~~~~~~~^
        user_id=user_id,
        ^^^^^^^^^^^^^^^^
    ...<3 lines>...
        model_name=model_name
        ^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/agents/WebsiteScrapper/website_agent.py", line 98, in __init__
    self.rag_agent = self._create_rag_agent()
                     ~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/agents/WebsiteScrapper/website_agent.py", line 453, in _create_rag_agent
    model=self.model.bind_tools(all_tools, tool_choice="document_retriever"),
          ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_openai/chat_models/base.py", line 1652, in bind_tools
    convert_to_openai_tool(tool, strict=strict) for tool in tools
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/utils/function_calling.py", line 584, in convert_to_openai_tool
    oai_function = convert_to_openai_function(tool, strict=strict)
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/utils/function_calling.py", line 477, in convert_to_openai_function
    "dict", _convert_python_function_to_openai_function(function)
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/utils/function_calling.py", line 223, in _convert_python_function_to_openai_function
    model = create_schema_from_function(
        func_name,
    ...<4 lines>...
        include_injected=False,
    )
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/tools/base.py", line 350, in create_schema_from_function
    description, arg_descriptions = _infer_arg_descriptions(
                                    ~~~~~~~~~~~~~~~~~~~~~~~^
        func,
        ^^^^^
        parse_docstring=parse_docstring,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        error_on_invalid_docstring=error_on_invalid_docstring,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/tools/base.py", line 208, in _infer_arg_descriptions
    _validate_docstring_args_against_annotations(arg_descriptions, annotations)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/tools/base.py", line 180, in _validate_docstring_args_against_annotations
    raise ValueError(msg)
ValueError: Arg Returns in docstring not found in function signature.
2025-10-02 21:56:42 - ERROR - ai-agents - Traceback (most recent call last):
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/api/routes/Client/chat.py", line 69, in chat
    rag_swarm = RAGSwarmFactory.get_instance(
            user_id=user_id,  # Use ObjectId for database operations
    ...<3 lines>...
            model_name="gpt-4.1-mini"
    )
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/agents/WebsiteScrapper/website_agent.py", line 602, in get_instance
    cls._instances[instance_key] = RAGSwarm(
                                   ~~~~~~~~^
        user_id=user_id,
        ^^^^^^^^^^^^^^^^
    ...<3 lines>...
        model_name=model_name
        ^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/agents/WebsiteScrapper/website_agent.py", line 98, in __init__
    self.rag_agent = self._create_rag_agent()
                     ~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/agents/WebsiteScrapper/website_agent.py", line 453, in _create_rag_agent
    model=self.model.bind_tools(all_tools, tool_choice="document_retriever"),
          ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_openai/chat_models/base.py", line 1652, in bind_tools
    convert_to_openai_tool(tool, strict=strict) for tool in tools
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/utils/function_calling.py", line 584, in convert_to_openai_tool
    oai_function = convert_to_openai_function(tool, strict=strict)
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/utils/function_calling.py", line 477, in convert_to_openai_function
    "dict", _convert_python_function_to_openai_function(function)
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/utils/function_calling.py", line 223, in _convert_python_function_to_openai_function
    model = create_schema_from_function(
        func_name,
    ...<4 lines>...
        include_injected=False,
    )
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/tools/base.py", line 350, in create_schema_from_function
    description, arg_descriptions = _infer_arg_descriptions(
                                    ~~~~~~~~~~~~~~~~~~~~~~~^
        func,
        ^^^^^
        parse_docstring=parse_docstring,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        error_on_invalid_docstring=error_on_invalid_docstring,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/tools/base.py", line 208, in _infer_arg_descriptions
    _validate_docstring_args_against_annotations(arg_descriptions, annotations)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/tools/base.py", line 180, in _validate_docstring_args_against_annotations
    raise ValueError(msg)
ValueError: Arg Returns in docstring not found in function signature.
Traceback (most recent call last):
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/api/routes/Client/chat.py", line 69, in chat
    rag_swarm = RAGSwarmFactory.get_instance(
            user_id=user_id,  # Use ObjectId for database operations
    ...<3 lines>...
            model_name="gpt-4.1-mini"
    )
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/agents/WebsiteScrapper/website_agent.py", line 602, in get_instance
    cls._instances[instance_key] = RAGSwarm(
                                   ~~~~~~~~^
        user_id=user_id,
        ^^^^^^^^^^^^^^^^
    ...<3 lines>...
        model_name=model_name
        ^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/agents/WebsiteScrapper/website_agent.py", line 98, in __init__
    self.rag_agent = self._create_rag_agent()
                     ~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/parth/Desktop/gradscaler/Langgraph/ai-agents/agents/WebsiteScrapper/website_agent.py", line 453, in _create_rag_agent
    model=self.model.bind_tools(all_tools, tool_choice="document_retriever"),
          ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_openai/chat_models/base.py", line 1652, in bind_tools
    convert_to_openai_tool(tool, strict=strict) for tool in tools
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/utils/function_calling.py", line 584, in convert_to_openai_tool
    oai_function = convert_to_openai_function(tool, strict=strict)
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/utils/function_calling.py", line 477, in convert_to_openai_function
    "dict", _convert_python_function_to_openai_function(function)
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/utils/function_calling.py", line 223, in _convert_python_function_to_openai_function
    model = create_schema_from_function(
        func_name,
    ...<4 lines>...
        include_injected=False,
    )
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/tools/base.py", line 350, in create_schema_from_function
    description, arg_descriptions = _infer_arg_descriptions(
                                    ~~~~~~~~~~~~~~~~~~~~~~~^
        func,
        ^^^^^
        parse_docstring=parse_docstring,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        error_on_invalid_docstring=error_on_invalid_docstring,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/tools/base.py", line 208, in _infer_arg_descriptions
    _validate_docstring_args_against_annotations(arg_descriptions, annotations)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/parth/Library/Caches/pypoetry/virtualenvs/ai-agents-0zqm8XZH-py3.13/lib/python3.13/site-packages/langchain_core/tools/base.py", line 180, in _validate_docstring_args_against_annotations
    raise ValueError(msg)
ValueError: Arg Returns in docstring not found in function signature.
2025-10-02 21:56:42,724 - INFO - 127.0.0.1 - - [02/Oct/2025 21:56:42] "POST /api/v1/WebsiteScrapper/madhuvan/chat HTTP/1.1" 500 -

Did you checked docstring for both tools [‘document_retriever’, ‘user_information_saver’]? You can try with one tool at a time as well to see for which tool it is failing as exception trace didn’t print the name of the tool.

I tried doing one tool at a time but its still the same issue.

@tool(parse_docstring=True)
def user_information_saver(thread_id: str, agent_id: str, updates: dict) -> dict:
    """Retrieve upcoming appointments for a user by their phone number.

    Args:
        thread_id (str): the thread ID associated with the user session.
        agent_id (str): the agent ID associated with the user session.
        updates (str): Dictionary of {field: value} pairs to update the user information.Only 'Name', 'Email', 'Phone' and 'services' are allowed.
    """
    invalid_keys = []
    updated_keys = []
    valid_keys = { 'Name', 'Email', 'Phone', 'services' }
    for key in updates:
            if key not in valid_keys:
                invalid_keys.append(key)
    if invalid_keys:
            return {
                "status": "error",
                "message": f"Found invalid keys in update request",
                "invalid_keys": invalid_keys,
                "valid_keys": list(valid_keys),
                "updated_keys": []
            }
    
    # MongoDB operations
    try:
        
        mongo = current_app.mongo
        user_data_collection = mongo.db.user_data
        
        # Check if thread_id exists
        existing_data = user_data_collection.find_one({"thread_id": thread_id})
        
        if existing_data:
            # Update existing document
            update_data = {
                **updates,
                "agent_id": agent_id,
                "updated_at": datetime.utcnow()
            }
            user_data_collection.update_one(
                {"thread_id": thread_id},
                {"$set": update_data}
            )
            updated_keys = list(updates.keys())
        else:
            # Create new document
            new_data = {
                "thread_id": thread_id,
                "agent_id": agent_id,
                "created_at": datetime.utcnow(),
                "updated_at": datetime.utcnow(),
                **updates
            }
            user_data_collection.insert_one(new_data)
            updated_keys = list(updates.keys())
        
        return {
            "status": "success",
            "message": "User information saved successfully",
            "invalid_keys": [],
            "updated_keys": updated_keys
        }
        
    except Exception as e:
        return {
            "status": "error",
            "message": f"Database error: {str(e)}",
            "invalid_keys": [],
            "updated_keys": []
        }


this is the second function

Hi @polo15s

parse_docstring if False by default as far as I can see it in the source code.

Something else must be wrong/… let’s investigate :slight_smile:

But inside the tool decorator i have enabled it. Please check the functions.

Yeah, I see it, thanks

Are you getting error with @tool(parse_docstring=False) as well?

I sent the error,
Arg Returns in docstring not found in function signature.

Can you try this?

@tool(parse_docstring=True)
def document_retriever(query: str, domain_name: str, user_folder: Optional[str] = None, website_name: Optional[str] = None) -> str:
    """
    Tool to retrieve relevant documents using hybrid search (vector + BM25) from web-scraped content.
    
    Args:
        query (str): The search query
        domain_name (str): Name of the domain/website for the vector store
        user_folder (str, optional): Username to use in the path (optional)
        website_name (str, optional): Name of the website folder (optional, defaults to "brandsmithworldwide")

    Returns:
        str: Retrieved content as formatted text

    """
    logger.info(f"Document retriever tool called with query: {query}, domain: {domain_name}, website: {website_name}")
    
    try:
        # Set default website name if not provided
        website_folder = website_name or "brandsmithworldwide"
        
        # Set up paths
        if user_folder:
            vectorstore_path = os.path.join("Data/WebScrapper", user_folder, website_folder, "vectorstore")
            bm25_path = os.path.join("Data/WebScrapper", user_folder, website_folder, "vectorstore", "bm25_index")
        else:
            vectorstore_path = os.path.join("vectorstore", domain_name)
            bm25_path = os.path.join("vectorstore", domain_name, "bm25_index")
        
        # Check if vector store exists and find the correct path
        vectorstore_path, bm25_path = _find_vectorstore_path(vectorstore_path, bm25_path, user_folder)
        
        # Load the indices
        faiss_index, docs_data = _load_faiss_vectorstore(vectorstore_path)
        bm25_data = _load_bm25_index(bm25_path)
        
        # Perform hybrid search
        vector_results = _query_faiss_index(query, faiss_index, docs_data, top_k=2)
        bm25_results = _query_bm25_index(query, bm25_data, top_k=2)
        
        # Fuse results
        fused_results = _reciprocal_rank_fusion(vector_results, bm25_results)
        
        # Take top 3 results
        top_results = fused_results[:3]
        
        if not top_results:
            return f"No relevant documents found for query: '{query}' in domain: {domain_name}"
        
        # Format the results
        formatted_results = []
        for i, (idx, score, metadata) in enumerate(top_results):
            doc_title = metadata.get("doc_title", f"Document {i+1}")
            doc_url = metadata.get("doc_url", "")
            contextual_chunk = metadata.get("contextual_chunk", "")
            original_chunk = metadata.get("original_chunk", "")
            
            # Skip if the content is empty
            if not original_chunk.strip():
                continue
            
            # Source information
            source_info = f"Source: {doc_url}" if doc_url else f"Source: {doc_title}"
            
            # Format the chunk with context and content
            formatted_results.append(f"## {doc_title}\n{contextual_chunk}{original_chunk}\n{source_info}\n")
        
        if not formatted_results:
            return f"No relevant content found for query: '{query}'"
        
        return "\n".join(formatted_results)
        
    except Exception as e:
        logger.error(f"Error in document_retriever tool: {e}")
        return f"I couldn't find information about \"{query}\". Please try another question or upload content related to this topic."


its still the same, I had tried it eaerlier with the return inside the doc string. I tried your code again but still the same error

Is that possible for you to share all the code to me (without any credentials/sercrets of course) so that I could run the entire code and debug it myself?