From aadbf3a37c838620a8b031fa4f8f292743b9edfb Mon Sep 17 00:00:00 2001 From: Maximilian Hoffmann Date: Fri, 10 Apr 2026 08:57:07 +0200 Subject: [PATCH 1/6] added websearch for python --- README.md | 3 +- .../Python/05-add-the-grounding-service.md | 3 +- .../Python/06-discover-connected-crimes.md | 445 ++++++++++++++++++ ...lve-the-crime.md => 07-solve-the-crime.md} | 88 ++-- project/JavaScript/solution/src/types.ts | 1 + project/Python/solution/config/agents.yaml | 20 +- project/Python/solution/config/tasks.yaml | 38 +- project/Python/solution/investigator_crew.py | 59 ++- project/Python/solution/requirements.txt | 4 + .../Python/starter-project/requirements.txt | 4 + 10 files changed, 619 insertions(+), 46 deletions(-) create mode 100644 exercises/Python/06-discover-connected-crimes.md rename exercises/Python/{06-solve-the-crime.md => 07-solve-the-crime.md} (77%) create mode 100644 project/Python/solution/requirements.txt create mode 100644 project/Python/starter-project/requirements.txt diff --git a/README.md b/README.md index 208c22f..516506b 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ If you are unsure on which path you should choose, ask the instructor for guidan - [Exercise 03 - Build your first agent tool](./exercises/Python/03-add-your-first-tool.md) - [Exercise 04 - Building a multi-agent system](./exercises/Python/04-building-multi-agent-system.md) - [Exercise 05 - Add the Grounding service](./exercises/Python/05-add-the-grounding-service.md) -- [Exercise 06 - Use your AI Agents to solve the crime](./exercises/Python/06-solve-the-crime.md) +- [Exercise 06 - Discover Connected Crimes with Web Search](./exercises/Python/06-discover-connected-crimes.md) +- [Exercise 07 - Use your AI Agents to solve the crime](./exercises/Python/07-solve-the-crime.md) The instructor will start you on the first exercise. Proceed to the next exercise once the instructor tells you to. diff --git a/exercises/Python/05-add-the-grounding-service.md b/exercises/Python/05-add-the-grounding-service.md index 55a61fe..2bd47ac 100644 --- a/exercises/Python/05-add-the-grounding-service.md +++ b/exercises/Python/05-add-the-grounding-service.md @@ -460,7 +460,8 @@ In the following exercises, you will: 2. ✅ Add custom tools to your agents so they can access external data 3. ✅ Create a complete crew with multiple agents working together 4. ✅ Integrate the Grounding Service for better reasoning and fact-checking (this exercise) -5. 📌 [Solve the museum art theft mystery](06-solve-the-crime.md) using your fully-featured agent team +5. 📌 [Add web search capabilities](06-discover-connected-crimes.md) to gather external intelligence +6. 📌 [Solve the museum art theft mystery](07-solve-the-crime.md) using your fully-featured agent team --- diff --git a/exercises/Python/06-discover-connected-crimes.md b/exercises/Python/06-discover-connected-crimes.md new file mode 100644 index 0000000..b55ddab --- /dev/null +++ b/exercises/Python/06-discover-connected-crimes.md @@ -0,0 +1,445 @@ +# Discover Connected Crimes with Web Search + +## Overview + +In Exercise 05, you learned to search internal documents using the Grounding Service. Your Evidence Analyst can now retrieve factual evidence from security logs, bank records, and termination letters without hallucination. + +But investigations don't happen in a vacuum. What if similar crimes happened elsewhere? What if your suspects have public criminal records? What if this heist is part of a larger criminal network? + +In this exercise, you'll add **web search capabilities** using Perplexity's **sonar-pro model** to gather external intelligence and discover patterns across the internet. + +--- + +## Understand Web Search with Sonar-Pro + +### Why Do We Need Web Search? + +Your current investigation system is powerful but limited to **internal data**: + +| **What You Can Do Now** | **What You Can't Do Yet** | +| ----------------------------------------------- | ---------------------------------------------------- | +| ✅ Search museum's internal evidence documents | ❌ Search public criminal databases | +| ✅ Retrieve bank records, security logs | ❌ Find similar crimes in other cities | +| ✅ Analyze suspects based on internal evidence | ❌ Check if suspects have public criminal records | +| ✅ Ground responses in factual documents | ❌ Discover criminal network connections | +| ✅ Avoid hallucination for internal data | ❌ Monitor online art markets for stolen items | + +**The Problem**: Real investigations combine **internal evidence** (what happened here) with **external intelligence** (what's happening elsewhere). Your agents need both! + +### Document Grounding vs. Web Search + +You now have access to **two complementary search capabilities**: + +| Aspect | Document Grounding (Exercise 05) | Web Search (This Exercise) | +| ---------------------- | ---------------------------------------- | ----------------------------------------- | +| **Data Source** | Internal documents (pre-uploaded) | Real-time web information | +| **Coverage** | Organization-specific evidence | Global public information | +| **Freshness** | Historical documents (static) | Current information (updated constantly) | +| **Use Cases** | Internal logs, private records, policies | News, criminal records, pattern analysis | +| **Search Method** | Vector similarity (semantic) | Web search + LLM synthesis | +| **Source Control** | You control what documents exist | Public internet (no control) | +| **Tool** | `call_grounding_service` | `call_sonar_pro_search` | +| **Privacy** | Private, secure | Public information only | +| **When to Use** | "What does our evidence say?" | "What do public records show?" | + +> 🎯 **Key Insight**: These are **not alternatives** — they work **together**! The best investigations use both internal evidence AND external intelligence. + +### What is Sonar-Pro? + +**Sonar-Pro** is Perplexity's AI model with built-in web search capabilities. Unlike regular LLMs: + +| Regular LLM (e.g., GPT-4, Claude) | Sonar-Pro (Perplexity) | +| --------------------------------------- | -------------------------------------------- | +| Knowledge cutoff (training data only) | Real-time web search | +| Can't access recent events | Finds current information | +| No source verification | Returns citations with URLs | +| "I think..." or "Based on my training" | "According to [source], dated [date]" | +| Static knowledge | Dynamic, up-to-date intelligence | + +**How Sonar-Pro Works**: +``` +Your Query → Sonar-Pro searches web → Retrieves relevant pages → +Synthesizes answer → Returns result with source citations +``` + +**Example**: +- **Query**: "Marcus Chen security technician criminal record" +- **Sonar-Pro**: Searches news articles, court records, public databases +- **Returns**: "According to [police-records.gov], Marcus Chen was charged with unauthorized access in 2022. Source: https://..." + +### How Sonar-Pro Integrates with SAP AI Core + +Sonar-Pro is called as a **model** through SAP's orchestration service, just like GPT-4 or Claude: + +```python +from litellm import completion + +response = completion( + model="sap/sonar-pro", # Perplexity via SAP orchestration + messages=[ + {"role": "system", "content": "Search for factual information"}, + {"role": "user", "content": "Your search query"} + ] +) +``` + +**Key Differences**: +- `sap/gpt-4o` → Regular reasoning LLM +- `sap/sonar-pro` → Web search-enabled LLM +- Both use the same `completion()` API! + +--- + +## Add The Web Search Tool + +### Step 1: Create the Sonar-Pro Search Tool + +You'll create a tool that enables your agents to search the web for criminal patterns, suspect backgrounds, and related incidents. + +👉 Open [`/project/Python/starter-project/investigator_crew.py`](/project/Python/starter-project/investigator_crew.py) + +👉 Add this tool **after** the `call_grounding_service` tool (around line 50): + +```python +@tool("call_sonar_pro_search") +def call_sonar_pro_search(search_query: str) -> str: + """Search the web using Perplexity's sonar-pro model for real-time information + about crimes, suspects, and criminal patterns. Use this to find similar incidents, + criminal networks, public records, or patterns that are not in internal documents. + + Args: + search_query: The search query about crimes, suspects, or criminal patterns + + Returns: + Search results with source citations from the web + """ + from litellm import completion + + try: + response = completion( + model="sap/sonar-pro", # Perplexity model with web search + messages=[ + { + "role": "system", + "content": "You are a web search assistant specializing in criminal intelligence. Search for accurate, recent information and always provide source citations with URLs and dates." + }, + { + "role": "user", + "content": search_query + } + ], + temperature=0.2, # Lower temperature for factual search + ) + + result = response.choices[0].message.content + return result + + except Exception as e: + return f"Error calling sonar-pro web search: {str(e)}" +``` + +> 💡 **Understanding the Web Search Tool:** +> +> **1. Model Selection** +> - `model="sap/sonar-pro"` - Uses Perplexity's web search-enabled model +> - Automatically searches the web and returns cited results +> - No additional configuration needed for basic web search +> +> **2. Search Configuration** +> - `temperature=0.2` - Lower temperature for factual, consistent results +> - System prompt guides the search focus (criminal intelligence) +> - User query contains the specific search (e.g., "Marcus Chen criminal history") +> +> **3. Response Format** +> - Sonar-pro returns synthesized answers with web sources +> - Includes URLs, publication dates, and source reliability +> - Agent receives structured information to inform investigation +> +> **4. Error Handling** +> - Returns error as string (agent can handle gracefully) +> - No exceptions raised (agent workflow continues) + +--- + +## Add The Intelligence Researcher Agent + +### Step 1: Add Agent Configuration + +👉 Open [`/project/Python/starter-project/config/agents.yaml`](/project/Python/starter-project/config/agents.yaml) + +👉 Add this configuration **after** the `evidence_analyst_agent` section: + +```yaml +intelligence_researcher_agent: + role: > + Open-Source Intelligence (OSINT) Researcher + goal: > + Search the web for similar art thefts, criminal patterns, and suspect backgrounds + to determine if this heist is part of a larger criminal network. Use the + call_sonar_pro_search tool to find recent incidents, news reports, and public + criminal records for all three suspects: {suspect_names}. + backstory: > + You are an OSINT specialist who excels at finding patterns across multiple + crime scenes. You search public databases, news archives, and criminal records + to connect seemingly isolated incidents. Your expertise has uncovered several + international art theft rings, and you know how to distinguish professional + criminals from amateurs. + llm: sap/gpt-4o +``` + +> 💡 **Why This Agent Design?** +> +> - **Role**: OSINT Researcher - Establishes expertise in public information gathering +> - **Goal**: Specific search objectives (patterns, backgrounds, networks) with explicit tool mention +> - **Backstory**: Provides context and authority for web-based investigations +> - **LLM**: Uses `sap/gpt-4o` for agent reasoning (the tool calls sonar-pro for searches) + +### Step 2: Add Task Configuration + +👉 Open [`/project/Python/starter-project/config/tasks.yaml`](/project/Python/starter-project/config/tasks.yaml) + +👉 Add this configuration **after** the `analyze_evidence_task` and **before** the `solve_crime` task: + +```yaml +research_criminal_network: + description: > + Search the web for intelligence about the three suspects ({suspect_names}) and + related crimes. Use the call_sonar_pro_search tool to find: + 1. Public criminal records or prior convictions for each suspect + 2. Similar art theft incidents with the same modus operandi (insider job, no forced entry) + 3. Connections to known art theft rings or criminal networks + 4. News reports or public information about any of the suspects + 5. Recent museum heists in Europe with similar patterns + + Cross-reference your web findings with the internal evidence analyzed by the + evidence analyst. Focus on discovering whether this is an isolated incident or + part of a larger criminal operation. + expected_output: > + A comprehensive intelligence report containing: + - Background checks for all three suspects with web sources + - List of similar art thefts found online (dates, locations, MO) + - Evidence of criminal network connections (if any) + - Assessment: isolated incident vs. organized crime ring + - All findings MUST include web sources with URLs and dates + agent: intelligence_researcher_agent +``` + +### Step 3: Add Agent and Task Methods to the Crew + +👉 Open [`/project/Python/starter-project/investigator_crew.py`](/project/Python/starter-project/investigator_crew.py) + +👉 Find the `InvestigatorCrew` class + +👉 Add these methods **after** the `analyze_evidence_task()` method and **BEFORE** the `lead_detective_agent()` method: + +```python + @agent + def intelligence_researcher_agent(self) -> Agent: + return Agent( + config=self.agents_config['intelligence_researcher_agent'], + verbose=True, + tools=[call_sonar_pro_search] # Web search tool + ) + + @task + def research_criminal_network(self) -> Task: + return Task( + config=self.tasks_config['research_criminal_network'], + context=[self.analyze_evidence_task()] # Uses internal evidence to inform web searches + ) +``` + +> 💡 **Method Positioning Matters!** +> +> Your class should now have this order: +> 1. `appraiser_agent()` method +> 2. `appraise_loss_task()` method +> 3. `evidence_analyst_agent()` method +> 4. `analyze_evidence_task()` method +> 5. **👈 `intelligence_researcher_agent()` method (NEW!)** +> 6. **👈 `research_criminal_network()` method (NEW!)** +> 7. `lead_detective_agent()` method (will be added in Exercise 07) +> 8. `solve_crime()` method (will be added in Exercise 07) +> 9. `crew()` method (stays at the end) + +> 💡 **Understanding Task Context**: +> - `context=[self.analyze_evidence_task()]` means the Intelligence Researcher receives the Evidence Analyst's findings +> - This allows the researcher to use internal evidence to formulate better web searches +> - Example: Evidence says "Marcus fired on 2024-01-15" → Researcher searches "Marcus Chen security technician fired 2024 criminal" + +--- + +## Run Your Enhanced Investigation + +👉 Run your crew to test the web search capability! + +**From repository root:** + +```bash +# macOS / Linux +python3 ./project/Python/starter-project/main.py +``` + +```powershell +# Windows (PowerShell) +python .\project\Python\starter-project\main.py +``` + +**From starter-project folder:** + +```bash +# macOS / Linux +python3 main.py +``` + +```powershell +# Windows (PowerShell) +python main.py +``` + +> ⏱️ **This may take 3-6 minutes** as your agents: +> +> 1. Predict stolen item values (Appraiser with RPT-1) +> 2. Search internal evidence documents (Evidence Analyst with Grounding) +> 3. Search the web for criminal patterns (Intelligence Researcher with Sonar-Pro) ← NEW! + +👉 Review the intelligence report from the web search: +- Did it find similar crimes? +- Do any suspects have public criminal records? +- Is there evidence of a criminal network? + +--- + +## Understanding Web Search Integration + +### What Just Happened? + +You created a complete multi-source intelligence gathering system that: + +1. **Searches Internal Documents** (Grounding Service) - Evidence from within the museum +2. **Searches External Web** (Sonar-Pro) - Public information from across the internet +3. **Combines Intelligence** - Both sources inform the investigation + +### The Enhanced Investigation Flow + +```mermaid +flowchart TD + A[Appraiser Agent] --> B[Predict Values
RPT-1 Tool] + B --> C[Evidence Analyst] + C --> D[Search Internal Docs
Grounding Service] + D --> E[Intelligence Researcher] + E --> F[Search Web
Sonar-Pro] + F --> G[Multi-Source Intelligence
Internal + External] + G --> H[Ready for Exercise 07:
Lead Detective Solves Crime] +``` + +### When to Use Each Search Type + +**Use Document Grounding** (`call_grounding_service`) when: +- ✅ Searching organization-specific documents +- ✅ Accessing private/confidential information +- ✅ Finding internal evidence (logs, records, policies) +- ✅ You control the document collection +- ✅ Need semantic search across your own data + +**Use Web Search** (`call_sonar_pro_search`) when: +- ✅ Searching public information +- ✅ Finding current events or recent news +- ✅ Checking criminal databases or public records +- ✅ Discovering patterns across multiple organizations +- ✅ Need real-time, up-to-date information + +**Use Both** when: +- ✅ You need comprehensive intelligence (internal + external) +- ✅ Cross-referencing private evidence with public records +- ✅ Verifying internal findings against external sources +- ✅ Building a complete picture from multiple angles + +### Example Investigation Workflow + +**Internal Evidence** (Grounding Service): +> "SECURITY_LOG.txt shows Marcus Chen accessed Gallery 2C at 23:47 on the night of the theft. MARCUS_TERMINATION_LETTER.txt indicates he was fired for 'unauthorized access to secured areas' on 2024-01-15." + +**External Intelligence** (Web Search): +> "Web search reveals Marcus Chen was previously investigated for a museum break-in in Berlin (2023). News reports from kunstdiebstahl-news.de show two similar heists with identical MO: former security staff, no forced entry, fine art targets." + +**Combined Analysis** (For Exercise 07): +> Internal evidence proves Marcus was at the scene + Web intelligence shows a pattern of similar crimes = Strong case that Marcus is a repeat offender and likely part of an organized theft ring. + +--- + +## Key Takeaways + +- **Web Search** extends your investigation beyond internal documents to public intelligence +- **Sonar-Pro** provides real-time web search with source citations +- **Multi-Source Intelligence** combines internal evidence + external intelligence +- **Complementary Tools**: Document grounding and web search work together, not in competition +- **Complete Investigation**: Real detectives use both internal records AND external research +- **Pattern Discovery**: Web search reveals connections that internal documents can't show + +--- + +## Next Steps + +You now have a complete intelligence gathering system with: +1. ✅ Structured data predictions (RPT-1) +2. ✅ Internal document search (Grounding Service) +3. ✅ External web intelligence (Sonar-Pro) + +In the next exercise, you'll add the **Lead Detective Agent** who will synthesize findings from all three sources to [solve the museum art theft mystery](07-solve-the-crime.md). + +--- + +## Troubleshooting + +**Issue**: `ModuleNotFoundError: No module named 'litellm'` + +- **Solution**: LiteLLM should already be installed from Exercise 02. If not, run: + ```bash + pip install litellm==1.82.6 + ``` + +**Issue**: `Error: Model sap/sonar-pro not found` + +- **Solution**: Verify that: + - Sonar-pro is available in your SAP AI Core Generative AI Hub model catalog + - Your resource group has access to Perplexity models + - Check SAP AI Launchpad → Generative AI Hub → Models for available models + +**Issue**: Web search returns no results or very generic information + +- **Solution**: Make your search queries more specific: + - ❌ Bad: "art theft" + - ✅ Good: "Marcus Chen security technician unauthorized access criminal record Europe" + - Include suspect names, locations, and specific details + +**Issue**: `AttributeError: 'NoneType' object has no attribute 'content'` + +- **Solution**: The sonar-pro API response structure might differ. Update error handling: + ```python + result = response.choices[0].message.content if response.choices else "No results" + ``` + +**Issue**: Agent doesn't use the web search tool + +- **Solution**: Ensure: + - The tool is assigned: `tools=[call_sonar_pro_search]` + - The task description explicitly mentions using `call_sonar_pro_search` + - The agent's goal references web search or OSINT + +**Issue**: Web search takes too long or times out + +- **Solution**: + - Sonar-pro queries can take 10-30 seconds per search + - This is normal for real-time web crawling + - If timeout occurs, increase LiteLLM timeout or retry + +--- + +## Resources + +- [Perplexity API Documentation](https://docs.perplexity.ai/) +- [SAP AI Core Orchestration Workflow](https://help.sap.com/docs/sap-ai-core/generative-ai/orchestration-workflow-v2) +- [LiteLLM Documentation](https://docs.litellm.ai/) + +[Next exercise](07-solve-the-crime.md) diff --git a/exercises/Python/06-solve-the-crime.md b/exercises/Python/07-solve-the-crime.md similarity index 77% rename from exercises/Python/06-solve-the-crime.md rename to exercises/Python/07-solve-the-crime.md index 7b3d73f..1c3c841 100644 --- a/exercises/Python/06-solve-the-crime.md +++ b/exercises/Python/07-solve-the-crime.md @@ -1,6 +1,13 @@ # Use your AI Agents to solve the crime -The only thing missing now is your **Lead Detective Agent**. This agent will then use the information retrieved from the other two agents to solve the crime and determine the value of the stolen items. +The only thing missing now is your **Lead Detective Agent**. This agent will synthesize information from all three specialized agents to solve the crime and determine the value of the stolen items. + +You now have complete intelligence gathering capabilities: +- 📊 **Financial predictions** from the Appraiser (RPT-1) +- 📄 **Internal evidence** from the Evidence Analyst (Grounding Service) +- 🌐 **External intelligence** from the Intelligence Researcher (Web Search) + +The Lead Detective will combine all three sources for a comprehensive conclusion. ## Build Your Lead Detective Agent @@ -30,8 +37,13 @@ lead_detective_agent: ```yaml solve_crime: description: > - Find the thief among the suspects by activating the evidence analyst agent and instructing them to look for information on each of the three suspects - using the grounding tool. They should find information on alibis and motives and return a report for you to analyze. + Find the thief among the suspects by reviewing: + 1. The insurance appraisal values from the appraiser agent + 2. The internal evidence analysis from the evidence analyst agent + 3. The web intelligence report from the intelligence researcher agent + + Synthesize all three sources to identify the culprit with high confidence. + Consider both internal evidence and external patterns/connections found online. expected_output: > The name of the thief and the total value of the stolen goods for the insurance. agent: lead_detective_agent @@ -43,7 +55,7 @@ Now you'll add the Lead Detective Agent and its task to your investigator crew. 👉 Open [`/project/Python/starter-project/investigator_crew.py`](/project/Python/starter-project/investigator_crew.py) -👉 **Inside the `InvestigatorCrew` class**, add the new agent and task methods **after** the existing `analyze_evidence_task` method and **before** the `@crew` method: +👉 **Inside the `InvestigatorCrew` class**, add the new agent and task methods **after** the existing `research_criminal_network` method and **before** the `@crew` method: ```python @agent @@ -57,25 +69,27 @@ Now you'll add the Lead Detective Agent and its task to your investigator crew. def solve_crime(self) -> Task: return Task( config=self.tasks_config['solve_crime'], - context=[self.appraise_loss_task(), self.analyze_evidence_task()] # 👈 Lead detective uses results from other tasks + context=[self.appraise_loss_task(), self.analyze_evidence_task(), self.research_criminal_network()] # Lead detective uses all three sources ) ``` -> 💡 **Where to place this code**: Add these methods inside the `InvestigatorCrew` class, after your `analyze_evidence_task()` method. The final order should be: +> 💡 **Where to place this code**: Add these methods inside the `InvestigatorCrew` class, after your `research_criminal_network()` method. The final order should be: > > 1. `appraiser_agent()` method > 2. `appraise_loss_task()` method > 3. `evidence_analyst_agent()` method > 4. `analyze_evidence_task()` method -> 5. **👈 Add `lead_detective_agent()` here** -> 6. **👈 Add `solve_crime()` here** -> 7. `crew()` method (keep at the end) +> 5. `intelligence_researcher_agent()` method (from Exercise 06) +> 6. `research_criminal_network()` method (from Exercise 06) +> 7. **👈 Add `lead_detective_agent()` here** +> 8. **👈 Add `solve_crime()` here** +> 9. `crew()` method (keep at the end) > 💡 **Understanding the `context` parameter:** > -> - `context=[self.appraise_loss_task(), self.analyze_evidence_task()]` tells CrewAI that the `solve_crime` task depends on the other two tasks -> - The Lead Detective will receive the output from both the Loss Appraiser and Evidence Analyst -> - This enables the detective to combine financial predictions with evidence analysis to solve the crime +> - `context=[self.appraise_loss_task(), self.analyze_evidence_task(), self.research_criminal_network()]` tells CrewAI that the `solve_crime` task depends on all three prior tasks +> - The Lead Detective receives output from the Appraiser, Evidence Analyst, AND Intelligence Researcher +> - This enables comprehensive analysis using financial data, internal evidence, and web intelligence ### Step 4: Verify Crew Configuration @@ -87,9 +101,9 @@ Your crew configuration should already be set from Exercise 04, but let's verify @crew def crew(self) -> Crew: return Crew( - agents=self.agents, # Automatically collected by @agent decorator (all 3 agents) - tasks=self.tasks, # Automatically collected by @task decorator (all 3 tasks) - process=Process.sequential, # Tasks run in order: appraise → analyze → solve + agents=self.agents, # Automatically collected by @agent decorator (all 4 agents) + tasks=self.tasks, # Automatically collected by @task decorator (all 4 tasks) + process=Process.sequential, # Tasks run in order: appraise → analyze → research → solve verbose=True # Print detailed execution logs ) ``` @@ -98,9 +112,9 @@ Your crew configuration should already be set from Exercise 04, but let's verify ### Step 5: Verify main.py (No Changes Needed) -Your `main.py` from Exercise 04 should already be correct. It doesn't need any changes for Exercise 06! +Your `main.py` from Exercise 04 should already be correct. It doesn't need any changes for Exercise 07! -> 💡 **What's happening:** The same `main.py` that ran 2 agents in Exercise 04 will now automatically run all 3 agents (including your new Lead Detective). CrewAI collects all `@agent` and `@task` decorated methods automatically. +> 💡 **What's happening:** The same `main.py` that ran 2 agents in Exercise 04 will now automatically run all 4 agents (Appraiser, Evidence Analyst, Intelligence Researcher, and Lead Detective). CrewAI collects all `@agent` and `@task` decorated methods automatically. 👉 (Optional) Double-check your [`/project/Python/starter-project/main.py`](/project/Python/starter-project/main.py) has both required inputs: @@ -153,11 +167,12 @@ python main.py python main.py ``` -> ⏱️ **This may take 2-5 minutes** as your agents: +> ⏱️ **This may take 3-6 minutes** as your agents: > -> 1. Search evidence documents for each suspect -> 2. Predict values of stolen items using RPT-1 -> 3. Analyze findings and identify the culprit +> 1. Search evidence documents for each suspect (Evidence Analyst) +> 2. Predict values of stolen items using RPT-1 (Appraiser) +> 3. Search the web for criminal patterns (Intelligence Researcher) +> 4. Synthesize all findings and identify the culprit (Lead Detective) 👉 Review the final output—who does your Lead Detective identify as the thief? @@ -236,11 +251,12 @@ python main.py You created a complete multi-agent system where: -1. **The Lead Detective Agent** orchestrates the investigation by delegating tasks -2. **The Evidence Analyst Agent** retrieves and analyzes evidence from documents -3. **The Loss Appraiser Agent** predicts financial values of stolen items -4. **Agent Communication** flows through task delegation and result aggregation -5. **Reasoning Integration** combines evidence, alibis, motives, and values to solve the crime +1. **The Lead Detective Agent** orchestrates the investigation by synthesizing multiple sources +2. **The Evidence Analyst Agent** retrieves and analyzes evidence from internal documents +3. **The Intelligence Researcher Agent** gathers external intelligence via web search +4. **The Loss Appraiser Agent** predicts financial values of stolen items +5. **Agent Communication** flows through task delegation and result aggregation +6. **Multi-Source Reasoning** combines internal evidence, web intelligence, and financial data to solve the crime ### The Investigation Flow @@ -252,10 +268,14 @@ flowchart TD A --> E[Loss Appraisal] E --> F[RPT-1 Predictions] F --> G[Value Determination] - D --> H[Crime Resolution] - G --> H - H --> I[Suspect Identification] - I --> J[Final Report] + A --> H[Web Intelligence] + H --> I[Sonar-Pro Search] + I --> J[Pattern Analysis] + D --> K[Crime Resolution] + G --> K + J --> K + K --> L[Suspect Identification] + L --> M[Final Report] ``` ### Why This Matters @@ -290,14 +310,16 @@ In the following exercises, you will: 2. ✅ Add custom tools to your agents so they can access external data 3. ✅ Create a complete crew with multiple agents working together 4. ✅ Integrate the Grounding Service for better reasoning and fact-checking -5. ✅ Solve the museum art theft mystery using your fully-featured agent team (this exercise) +5. ✅ Add web search for external intelligence gathering +6. ✅ Solve the museum art theft mystery using your fully-featured agent team (this exercise) Congratulations on completing the CodeJam! You've successfully built a sophisticated multi-agent AI system that can: -- Analyze evidence from documents +- Analyze evidence from internal documents +- Search the web for external intelligence - Predict financial values using the SAP-RPT-1 model - Coordinate between multiple specialized agents -- Solve complex real-world problems through collaborative reasoning +- Solve complex real-world problems through collaborative reasoning with multi-source intelligence --- diff --git a/project/JavaScript/solution/src/types.ts b/project/JavaScript/solution/src/types.ts index e25a479..3181f69 100644 --- a/project/JavaScript/solution/src/types.ts +++ b/project/JavaScript/solution/src/types.ts @@ -52,6 +52,7 @@ export interface RPT1Payload { index_column: string rows: StolenItem[] parse_data_types?: boolean // Optional: control data type parsing + data_schema?: any // Optional: schema definition for data types } /** diff --git a/project/Python/solution/config/agents.yaml b/project/Python/solution/config/agents.yaml index ec5a7f0..6018a35 100644 --- a/project/Python/solution/config/agents.yaml +++ b/project/Python/solution/config/agents.yaml @@ -12,13 +12,29 @@ evidence_analyst_agent: role: > Criminal Evidence Analyst goal: > - Retrieve and analyze evidence ONLY via the call_grounding_service tool. - Search for each suspect by name: {suspect_names}. Do NOT fabricate any evidence or alibis. + Retrieve and analyze evidence ONLY via the call_grounding_service tool. + Search for each suspect by name: {suspect_names}. Do NOT fabricate any evidence or alibis. Report only what the tool returns. backstory: > You are a methodical evidence analyst who bases conclusions strictly on retrieved documents. You never assume facts. llm: sap/anthropic--claude-4.5-opus +intelligence_researcher_agent: + role: > + Open-Source Intelligence (OSINT) Researcher + goal: > + Search the web for similar art thefts, criminal patterns, and suspect backgrounds + to determine if this heist is part of a larger criminal network. Use the + call_sonar_pro_search tool to find recent incidents, news reports, and public + criminal records for all three suspects: {suspect_names}. + backstory: > + You are an OSINT specialist who excels at finding patterns across multiple + crime scenes. You search public databases, news archives, and criminal records + to connect seemingly isolated incidents. Your expertise has uncovered several + international art theft rings, and you know how to distinguish professional + criminals from amateurs. + llm: sap/gpt-4o + lead_detective_agent: role: > Lead Detective and Case Manager diff --git a/project/Python/solution/config/tasks.yaml b/project/Python/solution/config/tasks.yaml index da2a6e7..1512ee4 100644 --- a/project/Python/solution/config/tasks.yaml +++ b/project/Python/solution/config/tasks.yaml @@ -7,19 +7,45 @@ appraise_loss_task: analyze_evidence_task: description: > - Analyze the evidence of the theft that you can access via the grounding tool. - Provide any insights that can help in the investigation especially regarding alabies. - Check the evidence for all three suspect names 1. Sophie Dubois, 2. Marcus Chen and + Analyze the evidence of the theft that you can access via the grounding tool. + Provide any insights that can help in the investigation especially regarding alabies. + Check the evidence for all three suspect names 1. Sophie Dubois, 2. Marcus Chen and 3. Viktor Petrovand and provide an analysis for each of them. expected_output: > A detailed analysis of the evidence for each suspect, including any insights that can help in the investigation. agent: evidence_analyst_agent +research_criminal_network: + description: > + Search the web for intelligence about the three suspects ({suspect_names}) and + related crimes. Use the call_sonar_pro_search tool to find: + 1. Public criminal records or prior convictions for each suspect + 2. Similar art theft incidents with the same modus operandi (insider job, no forced entry) + 3. Connections to known art theft rings or criminal networks + 4. News reports or public information about any of the suspects + 5. Recent museum heists in Europe with similar patterns + + Cross-reference your web findings with the internal evidence analyzed by the + evidence analyst. Focus on discovering whether this is an isolated incident or + part of a larger criminal operation. + expected_output: > + A comprehensive intelligence report containing: + - Background checks for all three suspects with web sources + - List of similar art thefts found online (dates, locations, MO) + - Evidence of criminal network connections (if any) + - Assessment: isolated incident vs. organized crime ring + - All findings MUST include web sources with URLs and dates + agent: intelligence_researcher_agent + solve_crime: description: > - Find the thief from, the suspects by activating the evidence investigator agent and instructing him to look for the three suspects - using the grounding tool. He should find information on alibies and motives and return a report for you to analyze. And use the appraise_loss_task from - the appraiser agent to find the value of the stolen goods. + Find the thief among the suspects by reviewing: + 1. The insurance appraisal values from the appraiser agent + 2. The internal evidence analysis from the evidence analyst agent + 3. The web intelligence report from the intelligence researcher agent + + Synthesize all three sources to identify the culprit with high confidence. + Consider both internal evidence and external patterns/connections found online. expected_output: > The name of the thief and the total value of the stolen goods for the insurance. agent: lead_detective_agent \ No newline at end of file diff --git a/project/Python/solution/investigator_crew.py b/project/Python/solution/investigator_crew.py index f92dff5..a78967f 100644 --- a/project/Python/solution/investigator_crew.py +++ b/project/Python/solution/investigator_crew.py @@ -70,7 +70,45 @@ def call_grounding_service(user_question: str) -> str: response_dict = json.dumps(response.model_dump(), indent=2) # Convert to JSON string return response_dict # Return retrieved document chunks to the agent - + + +@tool("call_sonar_pro_search") +def call_sonar_pro_search(search_query: str) -> str: + """Search the web using Perplexity's sonar-pro model for real-time information + about crimes, suspects, and criminal patterns. Use this to find similar incidents, + criminal networks, public records, or patterns that are not in internal documents. + + Args: + search_query: The search query about crimes, suspects, or criminal patterns + + Returns: + Search results with source citations from the web + """ + from litellm import completion + + try: + response = completion( + model="sap/sonar-pro", # Perplexity model with web search + messages=[ + { + "role": "system", + "content": "You are a web search assistant specializing in criminal intelligence. Search for accurate, recent information and always provide source citations with URLs and dates." + }, + { + "role": "user", + "content": search_query + } + ], + temperature=0.2, # Lower temperature for factual search + ) + + result = response.choices[0].message.content + return result + + except Exception as e: + return f"Error calling sonar-pro web search: {str(e)}" + + @CrewBase class InvestigatorCrew(): """InvestigatorCrew crew""" @@ -105,7 +143,22 @@ def analyze_evidence_task(self) -> Task: return Task( config=self.tasks_config['analyze_evidence_task'] ) - + + @agent + def intelligence_researcher_agent(self) -> Agent: + return Agent( + config=self.agents_config['intelligence_researcher_agent'], + verbose=True, + tools=[call_sonar_pro_search] # Web search tool + ) + + @task + def research_criminal_network(self) -> Task: + return Task( + config=self.tasks_config['research_criminal_network'], + context=[self.analyze_evidence_task()] # Uses internal evidence to inform web searches + ) + @agent def lead_detective_agent(self) -> Agent: return Agent( @@ -117,7 +170,7 @@ def lead_detective_agent(self) -> Agent: def solve_crime(self) -> Task: return Task( config=self.tasks_config['solve_crime'], - context=[self.appraise_loss_task(), self.analyze_evidence_task()] # 👈 Lead detective uses results from other tasks + context=[self.appraise_loss_task(), self.analyze_evidence_task(), self.research_criminal_network()] # Lead detective uses all three sources ) @crew diff --git a/project/Python/solution/requirements.txt b/project/Python/solution/requirements.txt new file mode 100644 index 0000000..56a646e --- /dev/null +++ b/project/Python/solution/requirements.txt @@ -0,0 +1,4 @@ +crewai==0.86.0 +litellm==1.82.6 +python-dotenv==1.0.0 +sap-ai-sdk-gen diff --git a/project/Python/starter-project/requirements.txt b/project/Python/starter-project/requirements.txt new file mode 100644 index 0000000..56a646e --- /dev/null +++ b/project/Python/starter-project/requirements.txt @@ -0,0 +1,4 @@ +crewai==0.86.0 +litellm==1.82.6 +python-dotenv==1.0.0 +sap-ai-sdk-gen From cb387794cb302b7d2f92db61818043475f5ebafe Mon Sep 17 00:00:00 2001 From: Maximilian Hoffmann Date: Fri, 10 Apr 2026 09:46:11 +0200 Subject: [PATCH 2/6] javascript working --- .gitignore | 9 + .../06-discover-connected-crimes.md | 563 ++++++++++++++++++ exercises/JavaScript/07-solve-the-crime.md | 311 ++++++++++ .../JavaScript/solution/src/agentConfigs.ts | 35 +- .../solution/src/investigationWorkflow.ts | 58 +- project/JavaScript/solution/src/tools.ts | 39 ++ project/JavaScript/solution/src/types.ts | 1 + 7 files changed, 1009 insertions(+), 7 deletions(-) create mode 100644 exercises/JavaScript/06-discover-connected-crimes.md create mode 100644 exercises/JavaScript/07-solve-the-crime.md diff --git a/.gitignore b/.gitignore index b7faf40..2ff72e9 100644 --- a/.gitignore +++ b/.gitignore @@ -205,3 +205,12 @@ cython_debug/ marimo/_static/ marimo/_lsp/ __marimo__/ + + +#Claude +.claude/ + +# Test folder - excluded from version control +test/ +test_*.py +test_*.ts \ No newline at end of file diff --git a/exercises/JavaScript/06-discover-connected-crimes.md b/exercises/JavaScript/06-discover-connected-crimes.md new file mode 100644 index 0000000..51d63e9 --- /dev/null +++ b/exercises/JavaScript/06-discover-connected-crimes.md @@ -0,0 +1,563 @@ +# Discover Connected Crimes with Web Search (JavaScript/TypeScript) + +## Overview + +In Exercise 05, you learned to search internal documents using the Grounding Service. Your Evidence Analyst can now retrieve factual evidence from security logs, bank records, and termination letters without hallucination. + +But investigations don't happen in a vacuum. What if similar crimes happened elsewhere? What if your suspects have public criminal records? What if this heist is part of a larger criminal network? + +In this exercise, you'll add **web search capabilities** using Perplexity's **sonar-pro model** to gather external intelligence and discover patterns across the internet. + +--- + +## Understand Web Search with Sonar-Pro + +### Why Do We Need Web Search? + +Your current investigation system is powerful but limited to **internal data**: + +| **What You Can Do Now** | **What You Can't Do Yet** | +| ----------------------------------------------- | ---------------------------------------------------- | +| ✅ Search museum's internal evidence documents | ❌ Search public criminal databases | +| ✅ Retrieve bank records, security logs | ❌ Find similar crimes in other cities | +| ✅ Analyze suspects based on internal evidence | ❌ Check if suspects have public criminal records | +| ✅ Ground responses in factual documents | ❌ Discover criminal network connections | +| ✅ Avoid hallucination for internal data | ❌ Monitor online art markets for stolen items | + +**The Problem**: Real investigations combine **internal evidence** (what happened here) with **external intelligence** (what's happening elsewhere). Your agents need both! + +### Document Grounding vs. Web Search + +You now have access to **two complementary search capabilities**: + +| Aspect | Document Grounding (Exercise 05) | Web Search (This Exercise) | +| ---------------------- | ---------------------------------------- | ----------------------------------------- | +| **Data Source** | Internal documents (pre-uploaded) | Real-time web information | +| **Coverage** | Organization-specific evidence | Global public information | +| **Freshness** | Historical documents (static) | Current information (updated constantly) | +| **Use Cases** | Internal logs, private records, policies | News, criminal records, pattern analysis | +| **Search Method** | Vector similarity (semantic) | Web search + LLM synthesis | +| **Source Control** | You control what documents exist | Public internet (no control) | +| **Tool Function** | `callGroundingServiceTool()` | `callSonarProSearchTool()` | +| **Privacy** | Private, secure | Public information only | +| **When to Use** | "What does our evidence say?" | "What do public records show?" | + +> 🎯 **Key Insight**: These are **not alternatives** — they work **together**! The best investigations use both internal evidence AND external intelligence. + +### How Sonar-Pro Integrates with SAP Cloud SDK for AI + +Sonar-Pro is called through the **OrchestrationClient** using the SAP Cloud SDK for AI, just like GPT-4 or Claude: + +```typescript +import { OrchestrationClient } from '@sap-ai-sdk/orchestration' + +const webSearchClient = new OrchestrationClient( + { + llm: { + model_name: 'sonar-pro', // Perplexity's web search model + model_params: { + temperature: 0.2, // Lower for factual results + }, + }, + templating: { + template: [ + { + role: 'system', + content: 'Search for factual information with citations', + }, + { role: 'user', content: '{{?search_query}}' }, + ], + }, + }, + { resourceGroup: process.env.RESOURCE_GROUP }, +) +``` + +**Key Differences**: +- `model_name: 'gpt-4o'` → Regular reasoning LLM +- `model_name: 'sonar-pro'` → Web search-enabled LLM +- Both use the same `OrchestrationClient` API! + +--- + +## Add The Web Search Tool + +### Step 1: Create the Sonar-Pro Search Tool + +You'll create a tool that enables your agents to search the web for criminal patterns, suspect backgrounds, and related incidents. + +👉 Open [`/project/JavaScript/solution/src/tools.ts`](/project/JavaScript/solution/src/tools.ts) + +👉 Add this tool **after** the `callGroundingServiceTool` function (around line 68): + +```typescript +/** + * Web Search Tool - Uses Perplexity's sonar-pro model for real-time web search + */ +const webSearchClient = new OrchestrationClient( + { + llm: { + model_name: 'sonar-pro', + model_params: { + temperature: 0.2, // Lower temperature for factual search + }, + }, + templating: { + template: [ + { + role: 'system', + content: + 'You are a web search assistant specializing in criminal intelligence. Search for accurate, recent information and always provide source citations with URLs and dates.', + }, + { role: 'user', content: '{{?search_query}}' }, + ], + }, + }, + { resourceGroup: process.env.RESOURCE_GROUP }, +) + +export async function callSonarProSearchTool(search_query: string): Promise { + try { + const response = await webSearchClient.chatCompletion({ + inputParams: { search_query }, + }) + return response.getContent() ?? 'No search results found' + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + console.error('❌ Sonar-pro web search failed:', errorMessage) + if (error instanceof Error && error.stack) console.error(error.stack) + return `Error calling sonar-pro web search: ${errorMessage}` + } +} +``` + +> 💡 **Understanding the Web Search Tool:** +> +> **1. OrchestrationClient Configuration** +> - `model_name: 'sonar-pro'` - Uses Perplexity's web search-enabled model +> - `temperature: 0.2` - Lower temperature for factual, consistent results +> - Templating system defines the search context and user query format +> +> **2. Search Configuration** +> - System prompt guides the search focus (criminal intelligence) +> - `{{?search_query}}` template parameter receives the search query +> - User query contains specific search (e.g., "Marcus Chen criminal history") +> +> **3. Response Format** +> - Sonar-pro returns synthesized answers with web sources +> - Includes URLs, publication dates, and source reliability +> - Agent receives structured information to inform investigation +> +> **4. Error Handling** +> - Returns error as string (agent can handle gracefully) +> - Logs detailed error information for debugging +> - Agent workflow continues even if search fails + +--- + +## Add The Intelligence Researcher Agent + +### Step 1: Add Agent Configuration + +👉 Open [`/project/JavaScript/solution/src/agentConfigs.ts`](/project/JavaScript/solution/src/agentConfigs.ts) + +👉 Add this configuration **after** the `evidenceAnalyst` section and **before** the `leadDetective` section: + +```typescript + intelligenceResearcher: { + systemPrompt: (suspectNames: string) => `You are an Open-Source Intelligence (OSINT) Researcher. + You are an OSINT specialist who excels at finding patterns across multiple crime scenes. + You search public databases, news archives, and criminal records to connect seemingly isolated incidents. + Your expertise has uncovered several international art theft rings, and you know how to distinguish professional criminals from amateurs. + + Your goal: Search the web for similar art thefts, criminal patterns, and suspect backgrounds to determine if this heist is part of a larger criminal network + + You have access to the call_sonar_pro_search tool to find recent incidents, news reports, and public criminal records. + Analyze the suspects: ${suspectNames} + + Search for: + 1. Public criminal records or prior convictions for each suspect + 2. Similar art theft incidents with the same modus operandi (insider job, no forced entry) + 3. Connections to known art theft rings or criminal networks + 4. News reports or public information about any of the suspects + 5. Recent museum heists in Europe with similar patterns`, + }, +``` + +> 💡 **Why This Agent Design?** +> +> - **Role**: OSINT Researcher - Establishes expertise in public information gathering +> - **Goal**: Specific search objectives (patterns, backgrounds, networks) with explicit tool mention +> - **Backstory**: Provides context and authority for web-based investigations +> - **System Prompt**: Includes detailed search criteria to guide the agent's web research + +### Step 2: Update Lead Detective Configuration + +👉 In the same file, update the `leadDetective` system prompt to include the intelligence report parameter: + +```typescript + leadDetective: { + systemPrompt: ( + appraisalResult: string, + evidenceAnalysis: string, + intelligenceReport: string, // NEW parameter + suspectNames: string, + ) => + `You are the lead detective on this high-profile art theft case. With years of + experience solving complex crimes, you excel at synthesizing information from + multiple sources and identifying the culprit based on evidence and expert analysis. + + Your goal: Synthesize all findings from the team to identify the most likely suspect and build a comprehensive case + + You have received the following information from your team: + + 1. INSURANCE APPRAISAL: ${appraisalResult} + 2. EVIDENCE ANALYSIS (Internal Documents): ${evidenceAnalysis} + 3. INTELLIGENCE REPORT (Web Search): ${intelligenceReport} // NEW + 4. SUSPECTS: ${suspectNames} + + Based on all the evidence and analysis, determine: + - Who is the most likely culprit? + - What evidence supports this conclusion? + - What was their motive and opportunity? + - Is this an isolated incident or part of a larger criminal network? // NEW + - Summarise the insurance appraisal values of the stolen artworks. + - Calculate the total estimated insurance value of the stolen items based on the appraisal results. + - Provide a comprehensive summary of the case. + + Be thorough and analytical in your conclusion.`, + }, +``` + +### Step 3: Update Type Definitions + +👉 Open [`/project/JavaScript/solution/src/types.ts`](/project/JavaScript/solution/src/types.ts) + +👉 Add the `intelligence_report` field to the `AgentState` interface: + +```typescript +export interface AgentState { + payload: RPT1Payload + suspect_names: string + appraisal_result?: string + evidence_analysis?: string + intelligence_report?: string // NEW + final_conclusion?: string + messages: Array<{ + role: string + content: string + }> +} +``` + +### Step 4: Add Intelligence Researcher Node to Workflow + +👉 Open [`/project/JavaScript/solution/src/investigationWorkflow.ts`](/project/JavaScript/solution/src/investigationWorkflow.ts) + +👉 **First**, update the imports at the top to include the new tool: + +```typescript +import { callRPT1Tool, callGroundingServiceTool, callSonarProSearchTool } from './tools.js' +``` + +👉 **Second**, add the Intelligence Researcher node **after** the `evidenceAnalystNode()` method and **before** the `leadDetectiveNode()` method: + +```typescript + /** + * Intelligence Researcher Agent Node - Uses web search to find criminal patterns + */ + private async intelligenceResearcherNode(state: AgentState): Promise> { + console.log('\n🔍 Intelligence Researcher starting web search...') + + try { + const suspects = state.suspect_names.split(',').map(s => s.trim()) + const intelligenceResults: string[] = [] + + // Search for criminal records for each suspect + for (const suspect of suspects) { + console.log(` Searching public records for: ${suspect}`) + const query = `${suspect} criminal record art theft security technician Europe background check` + const result = await callSonarProSearchTool(query) + intelligenceResults.push(`Background check for ${suspect}:\n${result}`) + } + + // Search for similar art theft patterns + console.log(' Searching for similar art theft incidents...') + const patternQuery = + 'museum art theft insider job no forced entry Europe similar incidents criminal network' + const patternResult = await callSonarProSearchTool(patternQuery) + intelligenceResults.push(`Similar Art Theft Patterns:\n${patternResult}`) + + const intelligenceReport = `Intelligence Research Complete: ${intelligenceResults.join('\n\n')} + Summary: Conducted OSINT research on all suspects and identified similar crime patterns` + + console.log('✅ Intelligence research complete') + + return { + intelligence_report: intelligenceReport, + messages: [...state.messages, { role: 'assistant', content: intelligenceReport }], + } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error) + console.error('❌ Intelligence research failed:', errorMsg) + if (error instanceof Error && error.stack) { + console.error(error.stack) + } + return { + intelligence_report: `Error during intelligence research: ${errorMsg}`, + messages: [ + ...state.messages, + { role: 'assistant', content: `Error during intelligence research: ${errorMsg}` }, + ], + } + } + } +``` + +👉 **Third**, update the `leadDetectiveNode()` method to pass the intelligence report: + +```typescript + private async leadDetectiveNode(state: AgentState): Promise> { + console.log('\n🔍 Lead Detective analyzing all findings...') + + const userMessage = 'Analyze all the evidence and identify the culprit. Provide a detailed conclusion.' + + try { + const response = await this.orchestrationClient.chatCompletion({ + messages: [ + { + role: 'system', + content: AGENT_CONFIGS.leadDetective.systemPrompt( + state.appraisal_result || 'No appraisal result available', + state.evidence_analysis || 'No evidence analysis available', + state.intelligence_report || 'No intelligence report available', // NEW + state.suspect_names, + ), + }, + { role: 'user', content: userMessage }, + ], + }) + const conclusion = response.getContent() || 'No conclusion could be drawn.' + + console.log('✅ Investigation complete') + + return { + final_conclusion: conclusion, + messages: [...state.messages, { role: 'assistant', content: conclusion }], + } + } catch (error) { + const errorMsg = `Error during final analysis: ${error}` + console.error('❌', errorMsg) + return { + final_conclusion: errorMsg, + messages: [...state.messages, { role: 'assistant', content: errorMsg }], + } + } + } +``` + +👉 **Fourth**, update the `buildGraph()` method to include the intelligence researcher node: + +```typescript + private buildGraph(): StateGraph { + const workflow = new StateGraph({ + channels: { + payload: null, + suspect_names: null, + appraisal_result: null, + evidence_analysis: null, + intelligence_report: null, // NEW + final_conclusion: null, + messages: null, + }, + }) + + // Add nodes and edges using chained API (required in LangGraph 0.2+) + workflow + .addNode('appraiser', this.appraiserNode.bind(this)) + .addNode('evidence_analyst', this.evidenceAnalystNode.bind(this)) + .addNode('intelligence_researcher', this.intelligenceResearcherNode.bind(this)) // NEW + .addNode('lead_detective', this.leadDetectiveNode.bind(this)) + .addEdge(START, 'appraiser') + .addEdge('appraiser', 'evidence_analyst') + .addEdge('evidence_analyst', 'intelligence_researcher') // NEW + .addEdge('intelligence_researcher', 'lead_detective') // NEW + .addEdge('lead_detective', END) + + return workflow + } +``` + +--- + +## Build and Run Your Enhanced Investigation + +### Step 1: Build the TypeScript Project + +👉 Build the project to compile your TypeScript changes: + +```bash +# From repository root +npm run build --prefix ./project/JavaScript/solution +``` + +```bash +# From solution folder +npm run build +``` + +### Step 2: Run the Investigation + +👉 Run your enhanced multi-agent system with web search: + +```bash +# From repository root +npm start --prefix ./project/JavaScript/solution +``` + +```bash +# From solution folder +npm start +``` + +> ⏱️ **This may take 4-7 minutes** as your agents: +> +> 1. Predict stolen item values (Appraiser with RPT-1) +> 2. Search internal evidence documents (Evidence Analyst with Grounding) +> 3. Search the web for criminal patterns (Intelligence Researcher with Sonar-Pro) ← NEW! +> 4. Synthesize all findings (Lead Detective) + +👉 Review the intelligence report from the web search: +- Did it find similar crimes? +- Do any suspects have public criminal records? +- Is there evidence of a criminal network? + +--- + +## Understanding Web Search Integration + +### What Just Happened? + +You created a complete multi-source intelligence gathering system that: + +1. **Searches Internal Documents** (Grounding Service) - Evidence from within the museum +2. **Searches External Web** (Sonar-Pro) - Public information from across the internet +3. **Combines Intelligence** - Both sources inform the investigation + +### The Enhanced Investigation Flow + +```mermaid +flowchart TD + A[Appraiser Agent] --> B[Predict Values
RPT-1 Tool] + B --> C[Evidence Analyst] + C --> D[Search Internal Docs
Grounding Service] + D --> E[Intelligence Researcher] + E --> F[Search Web
Sonar-Pro] + F --> G[Lead Detective] + G --> H[Multi-Source Analysis
Internal + External] + H --> I[Case Solved] +``` + +### When to Use Each Search Type + +**Use Document Grounding** (`callGroundingServiceTool()`) when: +- ✅ Searching organization-specific documents +- ✅ Accessing private/confidential information +- ✅ Finding internal evidence (logs, records, policies) +- ✅ You control the document collection +- ✅ Need semantic search across your own data + +**Use Web Search** (`callSonarProSearchTool()`) when: +- ✅ Searching public information +- ✅ Finding current events or recent news +- ✅ Checking criminal databases or public records +- ✅ Discovering patterns across multiple organizations +- ✅ Need real-time, up-to-date information + +**Use Both** when: +- ✅ You need comprehensive intelligence (internal + external) +- ✅ Cross-referencing private evidence with public records +- ✅ Verifying internal findings against external sources +- ✅ Building a complete picture from multiple angles + +--- + +## Key Takeaways + +- **Web Search** extends your investigation beyond internal documents to public intelligence +- **Sonar-Pro** provides real-time web search with source citations via OrchestrationClient +- **SAP Cloud SDK for AI** uses the same API pattern for both regular LLMs and web search models +- **Multi-Source Intelligence** combines internal evidence + external intelligence +- **Complementary Tools**: Document grounding and web search work together, not in competition +- **Complete Investigation**: Real detectives use both internal records AND external research +- **Pattern Discovery**: Web search reveals connections that internal documents can't show +- **TypeScript Integration**: Type-safe implementation with proper error handling + +--- + +## Next Steps + +You now have a complete intelligence gathering system with: +1. ✅ Structured data predictions (RPT-1) +2. ✅ Internal document search (Grounding Service) +3. ✅ External web intelligence (Sonar-Pro) +4. ✅ Multi-agent coordination (LangGraph) + +In the next exercise, you can refine your investigation or explore additional agent capabilities! + +--- + +## Troubleshooting + +**Issue**: `Module not found: @sap-ai-sdk/orchestration` + +- **Solution**: Ensure all dependencies are installed: + ```bash + npm install + ``` + +**Issue**: `Error: Model sonar-pro not found` + +- **Solution**: Verify that: + - Sonar-pro is available in your SAP AI Core Generative AI Hub model catalog + - Your resource group has access to Perplexity models + - Check SAP AI Launchpad → Generative AI Hub → Models for available models + +**Issue**: Web search returns no results or very generic information + +- **Solution**: Make your search queries more specific: + - ❌ Bad: "art theft" + - ✅ Good: "Marcus Chen security technician unauthorized access criminal record Europe" + - Include suspect names, locations, and specific details + +**Issue**: `TypeError: response.getContent is not a function` + +- **Solution**: Ensure you're using the latest version of `@sap-ai-sdk/orchestration`: + ```bash + npm update @sap-ai-sdk/orchestration + ``` + +**Issue**: TypeScript compilation errors + +- **Solution**: + - Run `npm run clean` to remove old build artifacts + - Run `npm run build` to rebuild + - Check that all type definitions are properly imported + +**Issue**: Web search takes too long or times out + +- **Solution**: + - Sonar-pro queries can take 10-30 seconds per search + - This is normal for real-time web crawling + - Consider adjusting timeout settings in OrchestrationClient if needed + +--- + +## Resources + +- [SAP Cloud SDK for AI Documentation](https://sap.github.io/ai-sdk/docs/js/orchestration/chat-completion) +- [Perplexity API Documentation](https://docs.perplexity.ai/) +- [SAP AI Core Orchestration Workflow](https://help.sap.com/docs/sap-ai-core/generative-ai/orchestration-workflow-v2) +- [LangGraph Documentation](https://langchain-ai.github.io/langgraphjs/) + +[Next exercise](07-solve-the-crime.md) diff --git a/exercises/JavaScript/07-solve-the-crime.md b/exercises/JavaScript/07-solve-the-crime.md new file mode 100644 index 0000000..f80d24e --- /dev/null +++ b/exercises/JavaScript/07-solve-the-crime.md @@ -0,0 +1,311 @@ +# Solve the Crime + +The only thing missing now is your **Lead Detective**. This node will synthesize findings from the Appraiser, Evidence Analyst, and Intelligence Researcher to identify the culprit and calculate the total value of the stolen items. + +## Build the Lead Detective Node + +### What You Already Have + +After completing Exercise 06, your system already includes: +1. ✅ Appraiser Agent (RPT-1 predictions) +2. ✅ Evidence Analyst (Internal document search) +3. ✅ Intelligence Researcher (Web search) ← NEW in Exercise 06 +4. ✅ Lead Detective configuration in `agentConfigs.ts` ← Already updated in Exercise 06 +5. ✅ Lead Detective node in `investigationWorkflow.ts` ← Already updated in Exercise 06 + +### Verify Your Implementation + +👉 Open [`/project/JavaScript/solution/src/investigationWorkflow.ts`](/project/JavaScript/solution/src/investigationWorkflow.ts) + +👉 Verify that you have the complete workflow with all four agents: + +```typescript + private buildGraph(): StateGraph { + const workflow = new StateGraph({ + channels: { + payload: null, + suspect_names: null, + appraisal_result: null, + evidence_analysis: null, + intelligence_report: null, // Should be present from Exercise 06 + final_conclusion: null, + messages: null, + }, + }) + + workflow + .addNode('appraiser', this.appraiserNode.bind(this)) + .addNode('evidence_analyst', this.evidenceAnalystNode.bind(this)) + .addNode('intelligence_researcher', this.intelligenceResearcherNode.bind(this)) // Should be present + .addNode('lead_detective', this.leadDetectiveNode.bind(this)) + .addEdge(START, 'appraiser') + .addEdge('appraiser', 'evidence_analyst') + .addEdge('evidence_analyst', 'intelligence_researcher') // Should be present + .addEdge('intelligence_researcher', 'lead_detective') // Should be present + .addEdge('lead_detective', END) + + return workflow + } +``` + +> 💡 **The execution order is defined entirely by the edges:** +> +> 1. `START → appraiser` — workflow begins with the Appraiser +> 2. `appraiser → evidence_analyst` — after RPT-1 completes, Evidence Analyst runs +> 3. `evidence_analyst → intelligence_researcher` — after internal search completes, web search runs +> 4. `intelligence_researcher → lead_detective` — after web search completes, Lead Detective synthesizes +> 5. `lead_detective → END` — Lead Detective's conclusion becomes the final result + +### Verify main.ts + +👉 Check your [`/project/JavaScript/solution/src/main.ts`](/project/JavaScript/solution/src/main.ts): it needs no changes from Exercise 04. + +```typescript +import 'dotenv/config' +import { InvestigationWorkflow } from './investigationWorkflow.js' +import { payload } from './payload.js' + +async function main() { + console.log('═══════════════════════════════════════════════════════════') + console.log(' 🔍 ART THEFT INVESTIGATION - MULTI-AGENT SYSTEM') + console.log('═══════════════════════════════════════════════════════════\n') + + const workflow = new InvestigationWorkflow(process.env.MODEL_NAME!) + const suspectNames = 'Sophie Dubois, Marcus Chen, Viktor Petrov' + + console.log('📋 Case Details:') + console.log(` • Stolen Items: ${payload.rows.length} artworks`) + console.log(` • Suspects: ${suspectNames}`) + console.log(` • Investigation Team: 4 specialized agents\n`) // Updated to 4 agents + + const startTime = Date.now() + + const result = await workflow.kickoff({ + payload, + suspect_names: suspectNames, + }) + + const duration = ((Date.now() - startTime) / 1000).toFixed(2) + + console.log('\n═══════════════════════════════════════════════════════════') + console.log(' 📘 FINAL INVESTIGATION REPORT') + console.log('═══════════════════════════════════════════════════════════\n') + console.log(result) + console.log('\n═══════════════════════════════════════════════════════════') + console.log(` ⏱️ Investigation completed in ${duration} seconds`) + console.log('═══════════════════════════════════════════════════════════\n') +} + +main() +``` + +--- + +## Solve the Crime + +👉 Run your complete investigation workflow: + +```bash +# From repository root +npm start --prefix ./project/JavaScript/solution +``` + +```bash +# From solution folder +npm start +``` + +> ⏱️ **This may take 4-7 minutes** as your agents: +> +> 1. Predict insurance values for stolen items using SAP-RPT-1 +> 2. Search internal evidence documents for each suspect using the Grounding Service +> 3. Search the web for criminal patterns and suspect backgrounds using Sonar-Pro +> 4. Analyze all findings and identify the culprit + +👉 Review the final output. Who does your Lead Detective identify as the thief? + +👉 Call for the instructor and share your suspect. + +### If Your Answer is Incorrect + +If the Lead Detective identifies the wrong suspect, refine the system prompts in `agentConfigs.ts`. + +**Which prompts to adjust:** + +1. **Lead Detective's system prompt** (`agentConfigs.ts → leadDetective.systemPrompt`) + - Make it more specific about what evidence to prioritize + - Example: Add "Focus on alibis, financial motives, and access to the museum on the night of the theft" + +2. **Evidence Analyst's grounding query** (`investigationWorkflow.ts → evidenceAnalystNode`) + - Make the search query more specific + - Example: `"Find evidence about ${suspect}'s alibi, financial records, and museum access on the night of the theft"` + +3. **Intelligence Researcher's web queries** (`investigationWorkflow.ts → intelligenceResearcherNode`) + - Add more specific search terms + - Example: Include specific dates, locations, or patterns mentioned in internal evidence + +**Tips for improving prompts:** + +- ✅ Be specific about what to analyze (alibi, motive, opportunity) +- ✅ Ask the detective to cite specific documents and web sources +- ✅ Request cross-referencing of internal and external evidence +- ✅ Instruct the detective to explain reasoning step-by-step +- ✅ Ask the detective to assess whether it's an isolated incident or organized crime +- ❌ Avoid vague instructions like "solve the crime" without guidance +- ❌ Don't assume the LLM knows which evidence is most important + +--- + +## Understanding Multi-Agent Orchestration + +### What Just Happened? + +You completed a full multi-agent investigation system where: + +1. **Appraiser Node** — Calls SAP-RPT-1 to predict missing insurance values from structured data +2. **Evidence Analyst Node** — Searches 8 evidence documents via the Grounding Service for each suspect +3. **Intelligence Researcher Node** — Searches the web via Sonar-Pro for criminal patterns and public records +4. **Lead Detective Node** — Synthesizes all findings using an LLM to identify the culprit and calculate total losses +5. **State** — Flows through all nodes, accumulating results that later nodes build upon + +### The Complete Investigation Flow + +```mermaid +flowchart TD + A[START] --> B["Appraiser Node\nRPT-1 predictions → appraisal_result"] + B --> C["Evidence Analyst Node\nGrounding searches × 3 suspects → evidence_analysis"] + C --> D["Intelligence Researcher Node\nWeb searches × 3 suspects + patterns → intelligence_report"] + D --> E["Lead Detective Node\nLLM synthesis → final_conclusion"] + E --> F[END] +``` + +### The Role of agentConfigs.ts + +The `AGENT_CONFIGS` object in `agentConfigs.ts` serves the same purpose as CrewAI's YAML files: it separates agent "personality" from orchestration logic. But as TypeScript objects: + +- System prompts are **functions** that accept runtime data and return a string +- No YAML parsing, no indentation errors, no key synchronization issues +- Your IDE can trace exactly where a system prompt is used and refactor it + +### Multi-Source Intelligence + +Your Lead Detective now analyzes **three independent sources**: + +1. **Structured Data** (`appraisal_result`) — Financial impact from RPT-1 +2. **Internal Documents** (`evidence_analysis`) — Private evidence from Grounding Service +3. **External Web** (`intelligence_report`) — Public intelligence from Sonar-Pro + +This multi-source approach mirrors real-world investigations: +- **Internal evidence** proves what happened at the scene +- **External intelligence** reveals patterns and backgrounds +- **Financial data** establishes motive and impact + +### Why This Matters + +Multi-agent systems with multi-source intelligence are powerful because they: + +- **Distribute Responsibilities** across specialized agents with distinct roles +- **Enable Collaboration** through task delegation and information sharing +- **Combine Data Sources** by integrating internal and external intelligence +- **Improve Reasoning** by providing multiple expert perspectives +- **Handle Complexity** by breaking down large problems into manageable subtasks +- **Scale Efficiently** as new agents and tools can be added without disrupting existing ones + +### Why This Architecture Matters + +**Benefits of multi-agent LangGraph systems:** + +- **Specialization** — Each node has exactly the tools and context it needs +- **Different models per node** — You could use GPT-4o for the detective and a cheaper model for search +- **Explicit data flow** — State fields make it clear what each node produces and consumes +- **Debuggability** — Every state transition is observable; add `console.log` to any node +- **Extensibility** — Adding a new agent is `.addNode()` + `.addEdge()` + a new node function +- **Multi-Source Analysis** — Seamlessly combine internal documents, web search, and structured data + +**Real-world applications:** + +- **Customer service**: Routing agent → Internal KB search → Web search → Escalation agent +- **Research**: Data collection agent → Internal archive search → Web research → Analysis agent → Report generation agent +- **DevOps**: Monitoring agent → Internal logs → Public status pages → Diagnosis agent → Remediation agent +- **Due Diligence**: Company info agent → Internal records → Public filings → News search → Risk assessment + +--- + +## Key Takeaways + +- **Multi-node LangGraph workflows** decompose complex problems into specialized, sequential steps +- **Shared state** is how nodes communicate: earlier results flow to later nodes via state fields +- **Multi-source intelligence** combines internal documents (Grounding) + external web (Sonar-Pro) + structured data (RPT-1) +- **System prompts with runtime data** (`AGENT_CONFIGS.leadDetective.systemPrompt(...)`) enable context-aware synthesis +- **Edges define execution order**: the Lead Detective waits for all predecessors to complete +- **`state.intelligence_report || 'No intelligence report available'`**: always provide fallbacks when reading optional state fields +- **SAP Cloud SDK for AI** provides a unified API for LLMs, web search, grounding, and structured data models +- **Prompt engineering** is iterative: run, observe, refine until the detective identifies the right suspect + +--- + +## Congratulations! + +You've successfully built a sophisticated multi-agent AI investigation system in TypeScript that can: + +- **Predict financial values** using the SAP-RPT-1 structured data model +- **Search internal evidence documents** using the SAP Grounding Service (RAG) +- **Search the web for public intelligence** using Perplexity's Sonar-Pro model +- **Synthesize multi-source findings** across multiple agents using LangGraph state +- **Solve complex problems** through collaborative, code-based agent orchestration + +--- + +## Next Steps Checklist + +1. ✅ [Understand Generative AI Hub](00-understanding-genAI-hub.md) +2. ✅ [Set up your development space](01-setup-dev-space.md) +3. ✅ [Build a basic agent](02-build-a-basic-agent.md) +4. ✅ [Add custom tools](03-add-your-first-tool.md) (RPT-1 model integration) +5. ✅ [Build a multi-agent workflow](04-building-multi-agent-system.md) +6. ✅ [Integrate the Grounding Service](05-add-the-grounding-service.md) +7. ✅ [Add web search capabilities](06-discover-connected-crimes.md) +8. ✅ [Solve the museum art theft mystery](07-solve-the-crime.md) (this exercise) + +--- + +## Troubleshooting + +**Issue**: Lead Detective's conclusion doesn't include intelligence report findings + +- **Solution**: Ensure the `leadDetective.systemPrompt` in `agentConfigs.ts` explicitly references the `intelligenceReport` parameter and asks the LLM to analyze web findings. The LLM only includes what you ask for. + +**Issue**: `state.intelligence_report` is `undefined` in the Lead Detective node + +- **Solution**: Check that the Intelligence Researcher node is returning the `intelligence_report` field in its return object. Add `console.log(state.intelligence_report)` at the start of `leadDetectiveNode` to debug. + +**Issue**: Web search returns no relevant information + +- **Solution**: Refine the search queries in `intelligenceResearcherNode`. Make them more specific by including suspect names, locations, and dates from internal evidence. + +**Issue**: Investigation runs but conclusion doesn't cross-reference internal and external evidence + +- **Solution**: Update the Lead Detective's system prompt to explicitly ask: "Cross-reference internal evidence with web findings to determine if this is an isolated incident or part of a pattern." + +**Issue**: Agent identifies the wrong suspect after multiple runs + +- **Solution**: LLMs are non-deterministic by default. Lower `temperature` in `model_params` (try `0.3`) for more consistent reasoning. Also refine the Lead Detective's system prompt to be more specific about how to weigh evidence from different sources. + +**Issue**: `Error during intelligence research: Error: 429 Too Many Requests` + +- **Solution**: You've hit the API rate limit. Wait a moment and retry. Web search is more resource-intensive than grounding, so consider reducing the number of searches or adding delays between them. + +**Issue**: TypeScript compilation errors after adding intelligence_report + +- **Solution**: Ensure you've updated the `AgentState` interface in `types.ts` to include the `intelligence_report?: string` field. + +--- + +## Resources + +- [LangGraph.js Documentation](https://langchain-ai.github.io/langgraphjs/) +- [SAP Cloud SDK for AI (JavaScript)](https://sap.github.io/ai-sdk/docs/js/orchestration/chat-completion) +- [SAP Generative AI Hub](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/generative-ai-hub-in-sap-ai-core-7db524ee75e74bf8b50c167951fe34a5) +- [SAP-RPT-1 Playground](https://rpt.cloud.sap/) +- [SAP AI Core Grounding Management](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/document-grounding) +- [Perplexity API Documentation](https://docs.perplexity.ai/) diff --git a/project/JavaScript/solution/src/agentConfigs.ts b/project/JavaScript/solution/src/agentConfigs.ts index 3919ab2..bd9854e 100644 --- a/project/JavaScript/solution/src/agentConfigs.ts +++ b/project/JavaScript/solution/src/agentConfigs.ts @@ -11,24 +11,49 @@ export const AGENT_CONFIGS = { Search for evidence related to each suspect and identify connections to the crime.`, }, + intelligenceResearcher: { + systemPrompt: (suspectNames: string) => `You are an Open-Source Intelligence (OSINT) Researcher. + You are an OSINT specialist who excels at finding patterns across multiple crime scenes. + You search public databases, news archives, and criminal records to connect seemingly isolated incidents. + Your expertise has uncovered several international art theft rings, and you know how to distinguish professional criminals from amateurs. + + Your goal: Search the web for similar art thefts, criminal patterns, and suspect backgrounds to determine if this heist is part of a larger criminal network + + You have access to the call_sonar_pro_search tool to find recent incidents, news reports, and public criminal records. + Analyze the suspects: ${suspectNames} + + Search for: + 1. Public criminal records or prior convictions for each suspect + 2. Similar art theft incidents with the same modus operandi (insider job, no forced entry) + 3. Connections to known art theft rings or criminal networks + 4. News reports or public information about any of the suspects + 5. Recent museum heists in Europe with similar patterns`, + }, leadDetective: { - systemPrompt: (appraisalResult: string, evidenceAnalysis: string, suspectNames: string) => + systemPrompt: ( + appraisalResult: string, + evidenceAnalysis: string, + intelligenceReport: string, + suspectNames: string, + ) => `You are the lead detective on this high-profile art theft case. With years of experience solving complex crimes, you excel at synthesizing information from multiple sources and identifying the culprit based on evidence and expert analysis. - + Your goal: Synthesize all findings from the team to identify the most likely suspect and build a comprehensive case - + You have received the following information from your team: 1. INSURANCE APPRAISAL: ${appraisalResult} - 2. EVIDENCE ANALYSIS: ${evidenceAnalysis} - 3. SUSPECTS: ${suspectNames} + 2. EVIDENCE ANALYSIS (Internal Documents): ${evidenceAnalysis} + 3. INTELLIGENCE REPORT (Web Search): ${intelligenceReport} + 4. SUSPECTS: ${suspectNames} Based on all the evidence and analysis, determine: - Who is the most likely culprit? - What evidence supports this conclusion? - What was their motive and opportunity? + - Is this an isolated incident or part of a larger criminal network? - Summarise the insurance appraisal values of the stolen artworks. - Calculate the total estimated insurance value of the stolen items based on the appraisal results. - Provide a comprehensive summary of the case. diff --git a/project/JavaScript/solution/src/investigationWorkflow.ts b/project/JavaScript/solution/src/investigationWorkflow.ts index bafafde..c73debb 100644 --- a/project/JavaScript/solution/src/investigationWorkflow.ts +++ b/project/JavaScript/solution/src/investigationWorkflow.ts @@ -1,7 +1,7 @@ import { StateGraph, END, START } from '@langchain/langgraph' import { OrchestrationClient } from '@sap-ai-sdk/orchestration' import type { AgentState, ModelParams } from './types.js' -import { callRPT1Tool, callGroundingServiceTool } from './tools.js' +import { callRPT1Tool, callGroundingServiceTool, callSonarProSearchTool } from './tools.js' import { AGENT_CONFIGS } from './agentConfigs.js' /** @@ -102,6 +102,56 @@ export class InvestigationWorkflow { } } + /** + * Intelligence Researcher Agent Node - Uses web search to find criminal patterns + */ + private async intelligenceResearcherNode(state: AgentState): Promise> { + console.log('\n🔍 Intelligence Researcher starting web search...') + + try { + const suspects = state.suspect_names.split(',').map(s => s.trim()) + const intelligenceResults: string[] = [] + + // Search for criminal records for each suspect + for (const suspect of suspects) { + console.log(` Searching public records for: ${suspect}`) + const query = `${suspect} criminal record art theft security technician Europe background check` + const result = await callSonarProSearchTool(query) + intelligenceResults.push(`Background check for ${suspect}:\n${result}`) + } + + // Search for similar art theft patterns + console.log(' Searching for similar art theft incidents...') + const patternQuery = + 'museum art theft insider job no forced entry Europe similar incidents criminal network' + const patternResult = await callSonarProSearchTool(patternQuery) + intelligenceResults.push(`Similar Art Theft Patterns:\n${patternResult}`) + + const intelligenceReport = `Intelligence Research Complete: ${intelligenceResults.join('\n\n')} + Summary: Conducted OSINT research on all suspects and identified similar crime patterns` + + console.log('✅ Intelligence research complete') + + return { + intelligence_report: intelligenceReport, + messages: [...state.messages, { role: 'assistant', content: intelligenceReport }], + } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error) + console.error('❌ Intelligence research failed:', errorMsg) + if (error instanceof Error && error.stack) { + console.error(error.stack) + } + return { + intelligence_report: `Error during intelligence research: ${errorMsg}`, + messages: [ + ...state.messages, + { role: 'assistant', content: `Error during intelligence research: ${errorMsg}` }, + ], + } + } + } + /** * Lead Detective Agent Node - Synthesizes findings and identifies the culprit */ @@ -118,6 +168,7 @@ export class InvestigationWorkflow { content: AGENT_CONFIGS.leadDetective.systemPrompt( state.appraisal_result || 'No appraisal result available', state.evidence_analysis || 'No evidence analysis available', + state.intelligence_report || 'No intelligence report available', state.suspect_names, ), }, @@ -152,6 +203,7 @@ export class InvestigationWorkflow { suspect_names: null, appraisal_result: null, evidence_analysis: null, + intelligence_report: null, final_conclusion: null, messages: null, }, @@ -161,10 +213,12 @@ export class InvestigationWorkflow { workflow .addNode('appraiser', this.appraiserNode.bind(this)) .addNode('evidence_analyst', this.evidenceAnalystNode.bind(this)) + .addNode('intelligence_researcher', this.intelligenceResearcherNode.bind(this)) .addNode('lead_detective', this.leadDetectiveNode.bind(this)) .addEdge(START, 'appraiser') .addEdge('appraiser', 'evidence_analyst') - .addEdge('evidence_analyst', 'lead_detective') + .addEdge('evidence_analyst', 'intelligence_researcher') + .addEdge('intelligence_researcher', 'lead_detective') .addEdge('lead_detective', END) return workflow diff --git a/project/JavaScript/solution/src/tools.ts b/project/JavaScript/solution/src/tools.ts index 0463837..9a9f0b6 100644 --- a/project/JavaScript/solution/src/tools.ts +++ b/project/JavaScript/solution/src/tools.ts @@ -65,3 +65,42 @@ export async function callGroundingServiceTool(user_question: string): Promise { + try { + const response = await webSearchClient.chatCompletion({ + inputParams: { search_query }, + }) + return response.getContent() ?? 'No search results found' + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + console.error('❌ Sonar-pro web search failed:', errorMessage) + if (error instanceof Error && error.stack) console.error(error.stack) + return `Error calling sonar-pro web search: ${errorMessage}` + } +} diff --git a/project/JavaScript/solution/src/types.ts b/project/JavaScript/solution/src/types.ts index 3181f69..1568eba 100644 --- a/project/JavaScript/solution/src/types.ts +++ b/project/JavaScript/solution/src/types.ts @@ -63,6 +63,7 @@ export interface AgentState { suspect_names: string appraisal_result?: string evidence_analysis?: string + intelligence_report?: string final_conclusion?: string messages: Array<{ role: string From e573aa9dc0bc0aee3b51dfc35c40086e6151657e Mon Sep 17 00:00:00 2001 From: Kevin Riedelsheimer Date: Mon, 13 Apr 2026 20:15:55 +0200 Subject: [PATCH 3/6] Upgrade package dependencies and devDependencies Updated dependencies and devDependencies to newer versions. --- project/JavaScript/solution/package.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/project/JavaScript/solution/package.json b/project/JavaScript/solution/package.json index 40b4ede..b815acb 100644 --- a/project/JavaScript/solution/package.json +++ b/project/JavaScript/solution/package.json @@ -19,19 +19,19 @@ "author": "Kevin Riedelsheimer", "license": "UNLICENSED", "dependencies": { - "@langchain/core": "^0.3.0", - "@langchain/langgraph": "^0.2.0", - "@sap-ai-sdk/orchestration": "^1.0.0", - "@sap-ai-sdk/ai-api": "^1.0.0", - "@sap-ai-sdk/foundation-models": "^1.0.0", - "@sap-ai-sdk/rpt": "latest", - "axios": "^1.7.0", - "dotenv": "^16.4.0", - "zod": "^3.23.0" + "@langchain/core": "1.1.39", + "@langchain/langgraph": "1.2.8", + "@sap-ai-sdk/orchestration": "2.9.0", + "@sap-ai-sdk/ai-api": "2.9.0", + "@sap-ai-sdk/foundation-models": "2.9.0", + "@sap-ai-sdk/rpt": "2.9.0", + "axios": "1.15.0", + "dotenv": "17.4.2", + "zod": "4.3.6" }, "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.19.0", - "typescript": "^5.6.0" + "@types/node": "25.6.0", + "tsx": "4.21.0", + "typescript": "6.0.2" } } From f56d8c51ba9da2365385330ac8ead98ff984eb37 Mon Sep 17 00:00:00 2001 From: Kevin Riedelsheimer Date: Mon, 13 Apr 2026 20:25:52 +0200 Subject: [PATCH 4/6] chore: update dependencies and remove package-lock.json from .gitignore --- project/JavaScript/solution/.gitignore | 1 - project/JavaScript/solution/package-lock.json | 2080 +++++++++++++++++ project/JavaScript/starter-project/.gitignore | 1 - .../JavaScript/starter-project/package.json | 24 +- 4 files changed, 2092 insertions(+), 14 deletions(-) create mode 100644 project/JavaScript/solution/package-lock.json diff --git a/project/JavaScript/solution/.gitignore b/project/JavaScript/solution/.gitignore index 9243183..8bcaf29 100644 --- a/project/JavaScript/solution/.gitignore +++ b/project/JavaScript/solution/.gitignore @@ -1,6 +1,5 @@ # Dependencies node_modules/ -package-lock.json yarn.lock pnpm-lock.yaml diff --git a/project/JavaScript/solution/package-lock.json b/project/JavaScript/solution/package-lock.json new file mode 100644 index 0000000..3acf0e2 --- /dev/null +++ b/project/JavaScript/solution/package-lock.json @@ -0,0 +1,2080 @@ +{ + "name": "investigator-crew-typescript", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "investigator-crew-typescript", + "version": "1.0.0", + "license": "UNLICENSED", + "dependencies": { + "@langchain/core": "1.1.39", + "@langchain/langgraph": "1.2.8", + "@sap-ai-sdk/ai-api": "2.9.0", + "@sap-ai-sdk/foundation-models": "2.9.0", + "@sap-ai-sdk/orchestration": "2.9.0", + "@sap-ai-sdk/rpt": "2.9.0", + "axios": "1.15.0", + "dotenv": "17.4.2", + "zod": "4.3.6" + }, + "devDependencies": { + "@types/node": "25.6.0", + "tsx": "4.21.0", + "typescript": "6.0.2" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT" + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/core": { + "version": "1.1.39", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.39.tgz", + "integrity": "sha512-DP9c7TREy6iA7HnywstmUAsNyJNYTFpRg2yBfQ+6H0l1HnvQzei9GsQ36GeOLxgRaD3vm9K8urCcawSC7yQpCw==", + "license": "MIT", + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "@standard-schema/spec": "^1.1.0", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": ">=0.5.0 <1.0.0", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "uuid": "^11.1.0", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@langchain/core/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@langchain/langgraph": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.2.8.tgz", + "integrity": "sha512-kKkRpC5xFz1e6vPivE7lwRJa5oahLAMaVQvVGZdTa6uJIchIYJDIuM1n93FqGvg8aYVcgYU4FENtKKC5Eh1JYw==", + "license": "MIT", + "dependencies": { + "@langchain/langgraph-checkpoint": "^1.0.1", + "@langchain/langgraph-sdk": "~1.8.8", + "@standard-schema/spec": "1.1.0", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": "^1.1.16", + "zod": "^3.25.32 || ^4.2.0", + "zod-to-json-schema": "^3.x" + }, + "peerDependenciesMeta": { + "zod-to-json-schema": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-checkpoint": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.0.1.tgz", + "integrity": "sha512-HM0cJLRpIsSlWBQ/xuDC67l52SqZ62Bh2Y61DX+Xorqwoh5e1KxYvfCD7GnSTbWWhjBOutvnR0vPhu4orFkZfw==", + "license": "MIT", + "dependencies": { + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": "^1.0.1" + } + }, + "node_modules/@langchain/langgraph-sdk": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.8.8.tgz", + "integrity": "sha512-4OoqFAvPloOTZ6oPxXbJngz4FLJO8QSXb+BQV3qvNTvmfu1LQA7cCEqSNLYX9MoC340PbnDkHNgUtjajwkDHRg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "p-queue": "^9.0.1", + "p-retry": "^7.1.1", + "uuid": "^13.0.0" + }, + "peerDependencies": { + "@langchain/core": "^1.1.16", + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "svelte": "^4.0.0 || ^5.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@langchain/core": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/@langchain/langgraph-sdk/node_modules/p-queue": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.2.tgz", + "integrity": "sha512-ktsDOALzTYTWWF1PbkNVg2rOt+HaOaMWJMUnt7T3qf5tvZ1L8dBW3tObzprBcXNMKkwj+yFSLqHso0x+UFcJXw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/langgraph-sdk/node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/@sap-ai-sdk/ai-api": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@sap-ai-sdk/ai-api/-/ai-api-2.9.0.tgz", + "integrity": "sha512-cyIpuaB+dldIi4lgKGXRUBiW+4QGyQni15an0xSnZa9lnBRSmsznJvi4NN8k9DC6r4+fkLtvwt/R8SXQsqeLFw==", + "license": "Apache-2.0", + "dependencies": { + "@sap-ai-sdk/core": "^2.9.0", + "@sap-cloud-sdk/connectivity": "^4.5.1", + "@sap-cloud-sdk/util": "^4.5.1" + } + }, + "node_modules/@sap-ai-sdk/core": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@sap-ai-sdk/core/-/core-2.9.0.tgz", + "integrity": "sha512-1yHmBqqsfEJWhGkV08nrDoRI7inl6YWufP/5mE1gikO9vJnnNQW2iKGONSrp/dPhltnZxmRMibv5cJ5MboarCQ==", + "license": "Apache-2.0", + "dependencies": { + "@sap-cloud-sdk/connectivity": "^4.5.1", + "@sap-cloud-sdk/http-client": "^4.5.1", + "@sap-cloud-sdk/openapi": "^4.5.1", + "@sap-cloud-sdk/util": "^4.5.1" + } + }, + "node_modules/@sap-ai-sdk/foundation-models": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@sap-ai-sdk/foundation-models/-/foundation-models-2.9.0.tgz", + "integrity": "sha512-jBv/m0yGHTi+RIzUsu05vzaaOYOfBvcio4UTsJy/cx0soqumzJCu3jBUyOZsyU3XXmgX0/hXDzdbacJgcoTYcg==", + "license": "Apache-2.0", + "dependencies": { + "@sap-ai-sdk/ai-api": "^2.9.0", + "@sap-ai-sdk/core": "^2.9.0", + "@sap-cloud-sdk/connectivity": "^4.5.1", + "@sap-cloud-sdk/http-client": "^4.5.1", + "@sap-cloud-sdk/util": "^4.5.1" + } + }, + "node_modules/@sap-ai-sdk/orchestration": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@sap-ai-sdk/orchestration/-/orchestration-2.9.0.tgz", + "integrity": "sha512-dnNsxc+/OVY/X7obZWXhhg3VQUcUrniwIQMu1jglTq2yCmlnKc6/znpEypePltnRruSoOSuFELhLk1cWbejutg==", + "license": "Apache-2.0", + "dependencies": { + "@sap-ai-sdk/ai-api": "^2.9.0", + "@sap-ai-sdk/core": "^2.9.0", + "@sap-ai-sdk/prompt-registry": "^2.9.0", + "@sap-cloud-sdk/util": "^4.5.1", + "yaml": "^2.8.2" + } + }, + "node_modules/@sap-ai-sdk/prompt-registry": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@sap-ai-sdk/prompt-registry/-/prompt-registry-2.9.0.tgz", + "integrity": "sha512-b7bcNwMwOUgmk85BScFzAz7OmOgff/K9DuEtsQqxtXzH0rsyev0gwxaSmH2gGst4i6mUgNbA8S1GrlJA78vdsA==", + "license": "Apache-2.0", + "dependencies": { + "@sap-ai-sdk/core": "^2.9.0", + "zod": "^4.3.6" + } + }, + "node_modules/@sap-ai-sdk/rpt": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@sap-ai-sdk/rpt/-/rpt-2.9.0.tgz", + "integrity": "sha512-LsSE96NF1mjGURkVG0tNSNwvTQ0QzV2pdy13gu5M86hdXITHjG64mmYRuVGEhnz/UgTSBt8BKV2gzmCPurfh9w==", + "license": "Apache-2.0", + "dependencies": { + "@sap-ai-sdk/ai-api": "^2.9.0", + "@sap-ai-sdk/core": "^2.9.0", + "@sap-cloud-sdk/connectivity": "^4.5.1", + "@sap-cloud-sdk/http-client": "^4.5.1", + "@sap-cloud-sdk/util": "^4.5.1" + } + }, + "node_modules/@sap-cloud-sdk/connectivity": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@sap-cloud-sdk/connectivity/-/connectivity-4.5.1.tgz", + "integrity": "sha512-ihjiCQhLHri84cl425FHCN7086mrgDd6b2uiBneLMp1tpCF7k1kK3AeeKhvREXCGF+P912TaDzieDZgXCR4j9A==", + "license": "Apache-2.0", + "dependencies": { + "@sap-cloud-sdk/resilience": "^4.5.1", + "@sap-cloud-sdk/util": "^4.5.1", + "@sap/xsenv": "^6.1.0", + "@sap/xssec": "^4.12.2", + "async-retry": "^1.3.3", + "axios": "^1.13.5", + "jks-js": "^1.1.4", + "jsonwebtoken": "^9.0.3" + } + }, + "node_modules/@sap-cloud-sdk/http-client": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@sap-cloud-sdk/http-client/-/http-client-4.5.1.tgz", + "integrity": "sha512-VmM63N3feKxOMjeNcKuG4kqFjCX4QLQy/B4eTixKt7WJM0C30b7P0e7LPon/vkkjLRr3lQcLeucy5o4SGvgRxg==", + "license": "Apache-2.0", + "dependencies": { + "@sap-cloud-sdk/connectivity": "^4.5.1", + "@sap-cloud-sdk/resilience": "^4.5.1", + "@sap-cloud-sdk/util": "^4.5.1", + "axios": "^1.13.5" + } + }, + "node_modules/@sap-cloud-sdk/openapi": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@sap-cloud-sdk/openapi/-/openapi-4.5.1.tgz", + "integrity": "sha512-wElbMsBVzWL+hiIzMSXIZItprpc628cIJFZ2gGxEGBvaKzBQ3eXhSRuptxzOFS8bkusGzXtrEgsqu0Gf+pUNjQ==", + "license": "Apache-2.0", + "dependencies": { + "@sap-cloud-sdk/connectivity": "^4.5.1", + "@sap-cloud-sdk/http-client": "^4.5.1", + "@sap-cloud-sdk/resilience": "^4.5.1", + "@sap-cloud-sdk/util": "^4.5.1", + "axios": "^1.13.5" + } + }, + "node_modules/@sap-cloud-sdk/resilience": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@sap-cloud-sdk/resilience/-/resilience-4.5.1.tgz", + "integrity": "sha512-vkGa2u7YCYfCzjdFDZy8feyOLZOD+3+xl1ve8xjllhO7FUiUtEAmW3b/UeBQ1i7Gf/EBVd9sH0c5WRcirAYYew==", + "license": "Apache-2.0", + "dependencies": { + "@sap-cloud-sdk/util": "^4.5.1", + "async-retry": "^1.3.3", + "axios": "^1.13.5", + "opossum": "^9.0.0" + } + }, + "node_modules/@sap-cloud-sdk/util": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@sap-cloud-sdk/util/-/util-4.5.1.tgz", + "integrity": "sha512-1p/k8MUFv7XcEJkd7c7qfUry+Bg2JtGiVeLx1QjRj49XuM8fpjGeWP2tV+EQ594yWS541Pb1K/xbTRj4uaIDIA==", + "license": "Apache-2.0", + "dependencies": { + "axios": "^1.13.5", + "chalk": "^4.1.0", + "logform": "^2.7.0", + "voca": "^1.4.1", + "winston": "^3.19.0", + "winston-transport": "^4.9.0" + } + }, + "node_modules/@sap/xsenv": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@sap/xsenv/-/xsenv-6.1.0.tgz", + "integrity": "sha512-vlW4Zad3uiDqHtnYdQ0TsEIH8VIO4HmPGDowfBL5dIcHPmeKDISEQ9ibeHL5FkceqvYcXJEQAVZ5/hsHDqlXZg==", + "license": "SEE LICENSE IN LICENSE file", + "dependencies": { + "debug": "4.4.3", + "node-cache": "^5.1.2", + "verror": "1.10.1" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + } + }, + "node_modules/@sap/xssec": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@sap/xssec/-/xssec-4.13.0.tgz", + "integrity": "sha512-8e+bU+OyAIpAGXQanOopZa5YEK+yHKw84dhhihcCotF40MSNFbVHjQ4xM5hf4QndlqDGfXIuvXmoOMuDATa/gA==", + "license": "SAP DEVELOPER LICENSE AGREEMENT", + "dependencies": { + "debug": "^4.4.3", + "jwt-decode": "^4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-network-error": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", + "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jks-js": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/jks-js/-/jks-js-1.1.5.tgz", + "integrity": "sha512-Kdl/twc+Nk8jPWqH3jCp3YE8jlG4Q7ijbAhhG65chfNnkQxOyXY60xLryz1Fnew8MV64rcXLtIT1PuTW0B15eA==", + "license": "MIT", + "dependencies": { + "node-forge": "^1.3.2", + "node-int64": "^0.4.0", + "node-rsa": "^1.1.1" + } + }, + "node_modules/js-tiktoken": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz", + "integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/langsmith": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.5.18.tgz", + "integrity": "sha512-3zuZUWffTHQ+73EAwnodADtf534VNEZUpXr9jC12qyG8/IQuJET7PRsCpTb9wX2lmBspakwLUpqpj3tNm/0bVA==", + "license": "MIT", + "dependencies": { + "p-queue": "6.6.2", + "uuid": "10.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*", + "ws": ">=7" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "license": "MIT", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, + "node_modules/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "license": "MIT", + "dependencies": { + "asn1": "^0.2.4" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/opossum": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/opossum/-/opossum-9.0.0.tgz", + "integrity": "sha512-K76U0QkxOfUZamneQuzz+AP0fyfTJcCplZ2oZL93nxeupuJbN4s6uFNbmVCt4eWqqGqRnnowdFuBicJ1fLMVxw==", + "license": "Apache-2.0", + "engines": { + "node": "^24 || ^22 || ^20" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz", + "integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==", + "license": "MIT", + "dependencies": { + "is-network-error": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/voca": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/voca/-/voca-1.4.1.tgz", + "integrity": "sha512-NJC/BzESaHT1p4B5k4JykxedeltmNbau4cummStd4RjFojgq/kLew5TzYge9N2geeWyI2w8T30wUET5v+F7ZHA==", + "license": "MIT" + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/project/JavaScript/starter-project/.gitignore b/project/JavaScript/starter-project/.gitignore index 9243183..8bcaf29 100644 --- a/project/JavaScript/starter-project/.gitignore +++ b/project/JavaScript/starter-project/.gitignore @@ -1,6 +1,5 @@ # Dependencies node_modules/ -package-lock.json yarn.lock pnpm-lock.yaml diff --git a/project/JavaScript/starter-project/package.json b/project/JavaScript/starter-project/package.json index 40b4ede..b815acb 100644 --- a/project/JavaScript/starter-project/package.json +++ b/project/JavaScript/starter-project/package.json @@ -19,19 +19,19 @@ "author": "Kevin Riedelsheimer", "license": "UNLICENSED", "dependencies": { - "@langchain/core": "^0.3.0", - "@langchain/langgraph": "^0.2.0", - "@sap-ai-sdk/orchestration": "^1.0.0", - "@sap-ai-sdk/ai-api": "^1.0.0", - "@sap-ai-sdk/foundation-models": "^1.0.0", - "@sap-ai-sdk/rpt": "latest", - "axios": "^1.7.0", - "dotenv": "^16.4.0", - "zod": "^3.23.0" + "@langchain/core": "1.1.39", + "@langchain/langgraph": "1.2.8", + "@sap-ai-sdk/orchestration": "2.9.0", + "@sap-ai-sdk/ai-api": "2.9.0", + "@sap-ai-sdk/foundation-models": "2.9.0", + "@sap-ai-sdk/rpt": "2.9.0", + "axios": "1.15.0", + "dotenv": "17.4.2", + "zod": "4.3.6" }, "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.19.0", - "typescript": "^5.6.0" + "@types/node": "25.6.0", + "tsx": "4.21.0", + "typescript": "6.0.2" } } From cb6ecbb8e212ce01259f03ae01f48569c14a873b Mon Sep 17 00:00:00 2001 From: Antonio Maradiaga Date: Thu, 16 Apr 2026 12:23:41 +0200 Subject: [PATCH 5/6] Fixed connection name --- exercises/Python/03-add-your-first-tool.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/Python/03-add-your-first-tool.md b/exercises/Python/03-add-your-first-tool.md index 8f0d10a..b4144c4 100644 --- a/exercises/Python/03-add-your-first-tool.md +++ b/exercises/Python/03-add-your-first-tool.md @@ -526,9 +526,9 @@ if __name__ == "__main__": 👉 Go to **Workspaces**. -👉 Select your workspace (like `codejam-aicore-connection`) and your resource group `ai-agents-codejam`. +👉 Select your workspace (like `codejam`) and your resource group `ai-agents-codejam`. -👉 Make sure it is set as a context. The proper name of the context, like `codejam-aicore-connection (ai-agents-codejam)` should show up at the top next to SAP AI Launchpad. +👉 Make sure it is set as a context. The proper name of the context, like `codejam (ai-agents-codejam)` should show up at the top next to SAP AI Launchpad. 👉 Navigate to `ML Operations > Deployments > sap-rpt-1-large_autogenerated` From cdcf8effef4f0c4d37d971dd2308baac93ac3ae2 Mon Sep 17 00:00:00 2001 From: noravth Date: Thu, 16 Apr 2026 10:07:51 -0300 Subject: [PATCH 6/6] added a2a cf deployment --- exercises/Python/01-setup-dev-space.md | 6 +- exercises/Python/02-build-a-basic-agent.md | 2 +- project/Python/solution/.cfignore | 8 ++ project/Python/solution/.python-version | 1 + project/Python/solution/config/agents.yaml | 2 +- project/Python/solution/manifest.yml | 20 +++++ project/Python/solution/requirements.txt | 20 +++++ project/Python/solution/rpt_client.py | 45 ----------- project/Python/solution/runtime.txt | 1 + project/Python/solution/server.py | 92 ++++++++++++++++++++++ 10 files changed, 147 insertions(+), 50 deletions(-) create mode 100644 project/Python/solution/.cfignore create mode 100644 project/Python/solution/.python-version create mode 100644 project/Python/solution/manifest.yml create mode 100644 project/Python/solution/requirements.txt delete mode 100644 project/Python/solution/rpt_client.py create mode 100644 project/Python/solution/runtime.txt create mode 100644 project/Python/solution/server.py diff --git a/exercises/Python/01-setup-dev-space.md b/exercises/Python/01-setup-dev-space.md index 2019efd..0e229a9 100644 --- a/exercises/Python/01-setup-dev-space.md +++ b/exercises/Python/01-setup-dev-space.md @@ -6,7 +6,7 @@ ## Open SAP Business Application Studio -👉 Go back to the [BTP cockpit](https://emea.cockpit.btp.cloud.sap/cockpit#/globalaccount/275320f9-4c26-4622-8728-b6f5196075f5/subaccount/a5a420d8-58c6-4820-ab11-90c7145da589/subaccountoverview). +👉 Go back to the [BTP cockpit](https://emea.cockpit.btp.cloud.sap/cockpit/?idp=a7rg4vxjp.accounts.ondemand.com#/globalaccount/275320f9-4c26-4622-8728-b6f5196075f5/subaccount/a5a420d8-58c6-4820-ab11-90c7145da589?layout=TwoColumnsMidExpanded). 👉 Navigate to `Instances and Subscriptions` and open `SAP Business Application Studio`. @@ -56,7 +56,7 @@ https://github.com/SAP-samples/codejam-code-based-agents.git ```Python LITELLM_PROVIDER="sap" -AICORE_AUTH_URL="https://#####.authentication.eu10.hana.ondemand.com/oauth/token" +AICORE_AUTH_URL="https://#####.authentication.eu10.hana.ondemand.com/" + "oauth/token" AICORE_CLIENT_ID="sb-3c636fc2-d352-496a-851d-7a7d6005dcd4!b505946|aicore!b540" AICORE_CLIENT_SECRET="#####" AICORE_RESOURCE_GROUP="ai-agents-codejam" @@ -65,7 +65,7 @@ RPT1_DEPLOYMENT_URL="https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com/v ``` 👉 You will need to UPDATE these variables but keep the given structure. The correct information you can find in the SAP AI Core service-key in your BTP cockpit. -👉 Go back to the Subaccount in the [BTP cockpit](https://emea.cockpit.btp.cloud.sap/cockpit#/globalaccount/275320f9-4c26-4622-8728-b6f5196075f5/subaccount/a5a420d8-58c6-4820-ab11-90c7145da589/subaccountoverview). +👉 Go back to the Subaccount in the [BTP cockpit](https://emea.cockpit.btp.cloud.sap/cockpit/?idp=a7rg4vxjp.accounts.ondemand.com#/globalaccount/275320f9-4c26-4622-8728-b6f5196075f5/subaccount/a5a420d8-58c6-4820-ab11-90c7145da589?layout=TwoColumnsMidExpanded). 👉 Navigate to `Instances and Subscriptions` and open the SAP AI Core instance's service binding. diff --git a/exercises/Python/02-build-a-basic-agent.md b/exercises/Python/02-build-a-basic-agent.md index 1862d0b..ea89add 100644 --- a/exercises/Python/02-build-a-basic-agent.md +++ b/exercises/Python/02-build-a-basic-agent.md @@ -111,7 +111,7 @@ if __name__ == "__main__": 👉 Execute the crew with the basic agent: -> ☝️ Make sure you're in the repository root directory (e.g., `codejam-code-based-agents-1`) when running this command. If you're already in the `starter-project` folder, use the appropriate command for your OS. +> ☝️ Make sure you're in the repository root directory (e.g., `codejam-code-based-agents`) when running this command. If you're already in the `starter-project` folder, use the appropriate command for your OS. **From repository root:** diff --git a/project/Python/solution/.cfignore b/project/Python/solution/.cfignore new file mode 100644 index 0000000..15cb1d3 --- /dev/null +++ b/project/Python/solution/.cfignore @@ -0,0 +1,8 @@ +.venv/ +__pycache__/ +*.pyc +*.pyo +.env +.git/ +exercises/ +assets/ \ No newline at end of file diff --git a/project/Python/solution/.python-version b/project/Python/solution/.python-version new file mode 100644 index 0000000..2c45fe3 --- /dev/null +++ b/project/Python/solution/.python-version @@ -0,0 +1 @@ +3.13.11 diff --git a/project/Python/solution/config/agents.yaml b/project/Python/solution/config/agents.yaml index ec5a7f0..52c8281 100644 --- a/project/Python/solution/config/agents.yaml +++ b/project/Python/solution/config/agents.yaml @@ -13,7 +13,7 @@ evidence_analyst_agent: Criminal Evidence Analyst goal: > Retrieve and analyze evidence ONLY via the call_grounding_service tool. - Search for each suspect by name: {suspect_names}. Do NOT fabricate any evidence or alibis. + Search for each suspect by name: Sophie Dubois, Marcus Chen, Viktor Petrov. Do NOT fabricate any evidence or alibis. Report only what the tool returns. backstory: > You are a methodical evidence analyst who bases conclusions strictly on retrieved documents. You never assume facts. diff --git a/project/Python/solution/manifest.yml b/project/Python/solution/manifest.yml new file mode 100644 index 0000000..79a332e --- /dev/null +++ b/project/Python/solution/manifest.yml @@ -0,0 +1,20 @@ +applications: + - name: investigator-crew-a2a + memory: 1024M + disk_quota: 2048M + instances: 1 + buildpacks: + - https://github.com/cloudfoundry/python-buildpack/releases/download/v1.8.43/python-buildpack-cflinuxfs4-v1.8.43.zip + health-check-type: http + health-check-http-endpoint: /health + timeout: 180 + command: python -m uvicorn server:app --host 0.0.0.0 --port $PORT --workers 1 + services: + - generative-ai-hub + env: + APP_URL: https://developer-advocates-free-tier-genai-codejam-luyq1wkg-dev9296031.cfapps.eu10-004.hana.ondemand.com + LITELLM_PROVIDER: sap + AICORE_RESOURCE_GROUP: ai-agents-codejam + RPT1_DEPLOYMENT_URL: https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com/v2/inference/deployments/d67ab78ae72fd41d/predict + CREWAI_TRACING_ENABLED: "false" + BP_PYTHON_VERSION: "3.13.11" diff --git a/project/Python/solution/requirements.txt b/project/Python/solution/requirements.txt new file mode 100644 index 0000000..00f44d6 --- /dev/null +++ b/project/Python/solution/requirements.txt @@ -0,0 +1,20 @@ +# Core CrewAI agent framework with A2A protocol support +crewai[a2a] +# A2A SDK with HTTP server support +a2a-sdk[http-server] +# LLM interaction with SAP Generative AI Hub +litellm==1.82.6 +# Environment configuration +python-dotenv +# Data validation +pydantic +# HTTP client +httpx +# HTTP requests +requests +# YAML configuration files +PyYAML +# SAP AI Core SDK for integration with SAP Generative AI Hub +sap-ai-sdk-base==3.4.0 +sap-ai-sdk-core==3.3.0 +sap-ai-sdk-gen==6.7.0 \ No newline at end of file diff --git a/project/Python/solution/rpt_client.py b/project/Python/solution/rpt_client.py deleted file mode 100644 index 516b5de..0000000 --- a/project/Python/solution/rpt_client.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -import requests - -class RPT1Client: - def __init__(self): - # Read env vars (assume dotenv already loaded in main) - self.client_id = os.getenv("AICORE_CLIENT_ID") - self.client_secret = os.getenv("AICORE_CLIENT_SECRET") - self.auth_url = os.getenv("AICORE_AUTH_URL") - self.deployment_url = os.getenv("RPT1_DEPLOYMENT_URL") - self.token = self._fetch_token() - self.resource_group = os.getenv("AICORE_RESOURCE_GROUP", "default") - - # Function to fetch OAuth token - def _fetch_token(self, timeout: int = 30) -> str: - if not self.auth_url: - raise ValueError("AICORE_AUTH_URL must be provided (env or arg).") - if not self.client_id: - raise ValueError("AICORE_CLIENT_ID must be provided (env or arg).") - if not self.client_secret: - raise ValueError("AICORE_CLIENT_SECRET must be provided (env or arg).") - data = { - "grant_type": "client_credentials", - "client_id": self.client_id, - "client_secret": self.client_secret - } - headers = {"Content-Type": "application/x-www-form-urlencoded"} - resp = requests.post(self.auth_url, data=data, headers=headers, timeout=timeout) - resp.raise_for_status() - token = resp.json() - access_token = token["access_token"] - return access_token - - def post_request(self, json_payload: dict, timeout: int = 60): - headers = { - "Authorization": f"Bearer {self.token}", - "Content-Type": "application/json", - "AI-Resource-Group": self.resource_group - } - - # Send the POST request to the deployment URL - response = requests.post( - self.deployment_url, json=json_payload, headers=headers - ) - return response diff --git a/project/Python/solution/runtime.txt b/project/Python/solution/runtime.txt new file mode 100644 index 0000000..c94f676 --- /dev/null +++ b/project/Python/solution/runtime.txt @@ -0,0 +1 @@ +python-3.13.x diff --git a/project/Python/solution/server.py b/project/Python/solution/server.py new file mode 100644 index 0000000..e9ecb41 --- /dev/null +++ b/project/Python/solution/server.py @@ -0,0 +1,92 @@ +import asyncio +import json +import os + +from a2a.server.agent_execution import AgentExecutor, RequestContext +from a2a.server.apps.jsonrpc import A2AFastAPIApplication +from a2a.server.events import EventQueue +from a2a.server.request_handlers import DefaultRequestHandler +from a2a.server.tasks import InMemoryTaskStore +from a2a.types import Artifact, TaskState, TaskStatus, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, TextPart, AgentCard, AgentCapabilities, AgentSkill +from fastapi.middleware.cors import CORSMiddleware + +from investigator_crew import InvestigatorCrew +from payload import payload + + +class InvestigatorExecutor(AgentExecutor): + async def execute(self, context: RequestContext, event_queue: EventQueue) -> None: + await event_queue.enqueue_event( + TaskStatusUpdateEvent(task_id=context.task_id, context_id=context.context_id, status=TaskStatus(state=TaskState.working), final=False) + ) + user_input = context.get_user_input() + try: + parsed = json.loads(user_input) + user_request = parsed.get("user_request", user_input) + suspect_names = parsed.get("suspect_names", user_input) + except (json.JSONDecodeError, TypeError): + user_request = user_input + suspect_names = user_input + + loop = asyncio.get_event_loop() + result = await loop.run_in_executor( + None, + lambda: InvestigatorCrew().crew().kickoff( + inputs={"payload": payload, "user_request": user_request, "suspect_names": suspect_names} + ), + ) + await event_queue.enqueue_event( + TaskArtifactUpdateEvent( + task_id=context.task_id, + context_id=context.context_id, + artifact=Artifact(artifactId="investigation_result", parts=[TextPart(text=str(result))], name="investigation_result"), + ) + ) + await event_queue.enqueue_event( + TaskStatusUpdateEvent(task_id=context.task_id, context_id=context.context_id, status=TaskStatus(state=TaskState.completed), final=True) + ) + + async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None: + await event_queue.enqueue_event( + TaskStatusUpdateEvent(task_id=context.task_id, context_id=context.context_id, status=TaskStatus(state=TaskState.canceled), final=True) + ) + + +agent_card = AgentCard( + name="Investigator Crew", + description="Multi-agent art theft investigation crew exposed as an A2A server", + url=os.environ.get("APP_URL", "http://localhost:8080"), + version="1.0.0", + capabilities=AgentCapabilities(streaming=False), + skills=[ + AgentSkill( + id="investigate", + name="Investigate Art Theft", + description="Investigates art theft cases by appraising losses and analyzing evidence", + tags=["investigation", "art", "insurance", "theft"], + inputModes=["text/plain"], + outputModes=["text/markdown"], + ) + ], + defaultInputModes=["text/plain"], + defaultOutputModes=["text/markdown"], +) +handler = DefaultRequestHandler(agent_executor=InvestigatorExecutor(), task_store=InMemoryTaskStore()) +app = A2AFastAPIApplication(agent_card=agent_card, http_handler=handler).build() + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/health") +def health(): + return {"status": "ok"} + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))