Report generation using Anthropic’s Claude 3.5 Sonnet: Comparison of two methods
Hello everyone! I'm Raphael, co-founder and CTO of Brazilian real estate company Pilar. Pilar provides software and services to real estate agents and brokerage firms, using a low success fee model. Instead of charging high upfront fees, we take a small commission from each successful transaction, tying our success directly to our clients' success. Our team of 20 technologists are constantly innovating and the latest product is Pilar Homes, a new real estate portal designed to provide the best experience for homebuyers and real estate agents.
In this post, I will share our experience using artificial intelligence to generate reports, specifically Anthropic’s Claude 3.5 Sonnet, and compare two different methods.
Our philosophy for handling tasks will be detailed in a future article (stay tuned!), but in short, these tasks end up on the "Tech Help Desk" board as Jira tickets. Generating reports is one such task, with most tasks taking engineers about 30 minutes to solve, with complex reports rarely taking more than a few hours. But things are changing. The boutique brands we started out with one or two partners are expanding into larger agencies, and we're signing more contracts with established players in the industry. While increasing engineer hours could address the growing reporting needs, I saw an opportunity to explore AI agents and learn architectural patterns in real-world environments.
Method 1: Let AI fully handle and reach the max_tokens limit
In our initial approach, we exposed the tool to Claude's 3.5 Sonnet model, enabling it to perform database queries, convert retrieved documents to CSV, and write its results to a .csv file.
Here is our structure, heavily inspired by the blog post above:
<code># 每個collection對象描述一個MongoDB集合及其字段 # 這有助于Claude理解我們的數(shù)據(jù)模式 COLLECTIONS = [ { 'name': 'companies', 'description': 'Companies are the real estate brokerages. If the user provides a code to filter the data, it will be a company code. The _id may be retrieved by querying the company with the given code. Company codes are not used to join data.', 'fields': { '_id': 'The ObjectId is the MongoDB id that uniquely identifies a company document. Its JSON representation is \"{"$oid": "the id"}\"', 'code': 'The company code is a short and human friendly string that uniquely identifies the company. Never use it for joining data.', 'name': 'A string representing the company name', } }, # 此處之后描述了更多集合,但思路相同... ] # 這是client.messages.create的“system”參數(shù) ROLE_PROMPT = "You are an engineer responsible for generating reports in CSV based on a user's description of the report content" # 這是“user”消息 task_prompt = f"{report_description}.\nAvailable collections: {COLLECTIONS}\nCompany codes: {company_codes}\n.Always demand a company code from the user to filter the data -- the user may use the terms imobiliária, marca, brand or company to reference a company. If the user wants a field that does not exist in a collection, don't add it to the report and don't ask the user for the field." </code>
report_description is just a command line argument read via argparse, company_codes is retrieved from the database and exposed to the model so that it knows which companies exist and what company codes are in the user input. Examples: (MO - Mosaic Homes, NV - Nova Real Estate, etc.).
Tools available for models include: find and docs2csv.
<code>def find(collection: str, query: str, fields: list[str]) -> Cursor: """Find documents in a collection filtering by "query" and retrieving fields via projection""" return db.get_collection(collection).find(query, projection={field: 1 for field in fields}) def docs2csv(documents: list[dict]) -> list[str]: """ Convert a dictionary to a CSV string. """ print(f"Converting {len(documents)} documents to CSV") with open('report.csv', mode='w', encoding='utf-8') as file: writer = csv.DictWriter(file, fieldnames=documents[0].keys()) writer.writeheader() writer.writerows(documents) return "report.csv"</code>
Claude was able to call the find function to perform well-structured queries and projections against our database and generate small CSV reports (less than 500 rows) using the docs2csv tool. However, larger reports trigger max_tokens errors.
After analyzing our token usage patterns, we realized that most of the token consumption comes from processing individual records through the model. This prompted us to explore another approach: letting Claude generate processing code instead of processing the data directly.
Method 2: Python code generation as a solution
While solving the max_tokens limit is not technically difficult, it requires us to rethink our approach to solving the problem.
Solution? Let Claude generate Python code that will run on our CPUs instead of processing each document through AI.
I had to modify the character and quest prompts and remove the tools.
The following is the gist of the report generation code.
The command to generate the report is:
<code># 每個collection對象描述一個MongoDB集合及其字段 # 這有助于Claude理解我們的數(shù)據(jù)模式 COLLECTIONS = [ { 'name': 'companies', 'description': 'Companies are the real estate brokerages. If the user provides a code to filter the data, it will be a company code. The _id may be retrieved by querying the company with the given code. Company codes are not used to join data.', 'fields': { '_id': 'The ObjectId is the MongoDB id that uniquely identifies a company document. Its JSON representation is \"{"$oid": "the id"}\"', 'code': 'The company code is a short and human friendly string that uniquely identifies the company. Never use it for joining data.', 'name': 'A string representing the company name', } }, # 此處之后描述了更多集合,但思路相同... ] # 這是client.messages.create的“system”參數(shù) ROLE_PROMPT = "You are an engineer responsible for generating reports in CSV based on a user's description of the report content" # 這是“user”消息 task_prompt = f"{report_description}.\nAvailable collections: {COLLECTIONS}\nCompany codes: {company_codes}\n.Always demand a company code from the user to filter the data -- the user may use the terms imobiliária, marca, brand or company to reference a company. If the user wants a field that does not exist in a collection, don't add it to the report and don't ask the user for the field." </code>
Claude-generated Python content (working well):
<code>def find(collection: str, query: str, fields: list[str]) -> Cursor: """Find documents in a collection filtering by "query" and retrieving fields via projection""" return db.get_collection(collection).find(query, projection={field: 1 for field in fields}) def docs2csv(documents: list[dict]) -> list[str]: """ Convert a dictionary to a CSV string. """ print(f"Converting {len(documents)} documents to CSV") with open('report.csv', mode='w', encoding='utf-8') as file: writer = csv.DictWriter(file, fieldnames=documents[0].keys()) writer.writeheader() writer.writerows(documents) return "report.csv"</code>
Conclusion
Our journey with Claude 3.5 Sonnet shows that AI can significantly improve operational efficiency, but the key to success lies in choosing the right architecture. The code generation approach proved more powerful than direct AI processing while maintaining the benefits of automation.
In addition to building reports correctly, the code generation method also allows engineers to review the work of the AI, which is a very good thing.
To fully automate the process, eliminate human involvement and handle larger volumes of reports, distributing work across multiple agent instances - each handling fewer tokens - would be a natural evolution of the system. For architectural challenges in such distributed AI systems, I highly recommend Phil Cal?ado’s latest article on building AI products.
Key lessons learned from this implementation:
- Direct AI processing works for smaller data sets
- Code generation provides better scalability and maintainability
- Human review increases reliability
References
- Anthropic Documentation
- Thomas Taylor’s Anthropic Claude with tools using the Python SDK
- Building AI Products - Part 1: Backend Architecture by Phil Cal?ado
The above is the detailed content of Using Anthropics Claude Sonnet for Generating Reports. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Python's unittest and pytest are two widely used testing frameworks that simplify the writing, organizing and running of automated tests. 1. Both support automatic discovery of test cases and provide a clear test structure: unittest defines tests by inheriting the TestCase class and starting with test\_; pytest is more concise, just need a function starting with test\_. 2. They all have built-in assertion support: unittest provides assertEqual, assertTrue and other methods, while pytest uses an enhanced assert statement to automatically display the failure details. 3. All have mechanisms for handling test preparation and cleaning: un

PythonisidealfordataanalysisduetoNumPyandPandas.1)NumPyexcelsatnumericalcomputationswithfast,multi-dimensionalarraysandvectorizedoperationslikenp.sqrt().2)PandashandlesstructureddatawithSeriesandDataFrames,supportingtaskslikeloading,cleaning,filterin

Dynamic programming (DP) optimizes the solution process by breaking down complex problems into simpler subproblems and storing their results to avoid repeated calculations. There are two main methods: 1. Top-down (memorization): recursively decompose the problem and use cache to store intermediate results; 2. Bottom-up (table): Iteratively build solutions from the basic situation. Suitable for scenarios where maximum/minimum values, optimal solutions or overlapping subproblems are required, such as Fibonacci sequences, backpacking problems, etc. In Python, it can be implemented through decorators or arrays, and attention should be paid to identifying recursive relationships, defining the benchmark situation, and optimizing the complexity of space.

To implement a custom iterator, you need to define the __iter__ and __next__ methods in the class. ① The __iter__ method returns the iterator object itself, usually self, to be compatible with iterative environments such as for loops; ② The __next__ method controls the value of each iteration, returns the next element in the sequence, and when there are no more items, StopIteration exception should be thrown; ③ The status must be tracked correctly and the termination conditions must be set to avoid infinite loops; ④ Complex logic such as file line filtering, and pay attention to resource cleaning and memory management; ⑤ For simple logic, you can consider using the generator function yield instead, but you need to choose a suitable method based on the specific scenario.

Future trends in Python include performance optimization, stronger type prompts, the rise of alternative runtimes, and the continued growth of the AI/ML field. First, CPython continues to optimize, improving performance through faster startup time, function call optimization and proposed integer operations; second, type prompts are deeply integrated into languages ??and toolchains to enhance code security and development experience; third, alternative runtimes such as PyScript and Nuitka provide new functions and performance advantages; finally, the fields of AI and data science continue to expand, and emerging libraries promote more efficient development and integration. These trends indicate that Python is constantly adapting to technological changes and maintaining its leading position.

Python's socket module is the basis of network programming, providing low-level network communication functions, suitable for building client and server applications. To set up a basic TCP server, you need to use socket.socket() to create objects, bind addresses and ports, call .listen() to listen for connections, and accept client connections through .accept(). To build a TCP client, you need to create a socket object and call .connect() to connect to the server, then use .sendall() to send data and .recv() to receive responses. To handle multiple clients, you can use 1. Threads: start a new thread every time you connect; 2. Asynchronous I/O: For example, the asyncio library can achieve non-blocking communication. Things to note

The core answer to Python list slicing is to master the [start:end:step] syntax and understand its behavior. 1. The basic format of list slicing is list[start:end:step], where start is the starting index (included), end is the end index (not included), and step is the step size; 2. Omit start by default start from 0, omit end by default to the end, omit step by default to 1; 3. Use my_list[:n] to get the first n items, and use my_list[-n:] to get the last n items; 4. Use step to skip elements, such as my_list[::2] to get even digits, and negative step values ??can invert the list; 5. Common misunderstandings include the end index not

Python's datetime module can meet basic date and time processing requirements. 1. You can get the current date and time through datetime.now(), or you can extract .date() and .time() respectively. 2. Can manually create specific date and time objects, such as datetime(year=2025, month=12, day=25, hour=18, minute=30). 3. Use .strftime() to output strings in format. Common codes include %Y, %m, %d, %H, %M, and %S; use strptime() to parse the string into a datetime object. 4. Use timedelta for date shipping
