Compare commits
221 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a27630ccf | |||
| ed91260468 | |||
| c13aeb3a74 | |||
| 287704778f | |||
| a9fa23f01b | |||
| c5cb18f0f1 | |||
| c78f4b023d | |||
| 1b69f69c7d | |||
| 3891099eaa | |||
| 268d340e2f | |||
| 189bc2efcf | |||
| 92b3e4081f | |||
| 750eff483b | |||
| 5cc35c78f4 | |||
| ed5415d92a | |||
| 6b3d575ea0 | |||
| da16c80a0a | |||
| 069240912b | |||
| 2aa0d1e9a4 | |||
| 03e1dd7628 | |||
| 70296a3bf2 | |||
| 75b2ce5978 | |||
| d8172a7fbe | |||
| d29a413159 | |||
| 0ddbcf9ca1 | |||
| 04e75e61b8 | |||
| c1ef544734 | |||
| e60cbc67c4 | |||
| 8e356f06bc | |||
| 131973edd4 | |||
| 0f16e045b9 | |||
| 02e93cae57 | |||
| 4df1043032 | |||
| 7b465baddd | |||
| 9e637f92bd | |||
| 197a7a0cb7 | |||
| 14bac755ea | |||
| 2de4fa07c1 | |||
| b08121ee50 | |||
| d63ddd3d27 | |||
| 0a6af36b34 | |||
| d0bacbb538 | |||
| 51fca0aa8c | |||
| ebd79aa686 | |||
| dde9b019dd | |||
| e592441387 | |||
| 8232946e91 | |||
| 8a2da0f5c3 | |||
| 4cb01c71bb | |||
| ec28e0ff54 | |||
| afc866a3a5 | |||
| a3f9e39249 | |||
| ba13ccc4b9 | |||
| a90203dc0a | |||
| 2c0f25189b | |||
| c6f97be63a | |||
| af0fa30107 | |||
| 6f971abe5d | |||
| e622a522dc | |||
| 96ff9f0924 | |||
| 2072ebe554 | |||
| 52eaeab5fc | |||
| 937d52053f | |||
| 569f85333f | |||
| 92eee07168 | |||
| c626d2cec5 | |||
| 1ab97b3972 | |||
| d2081333f6 | |||
| 1cb9ca106b | |||
| 94dd44236d | |||
| be42fc4738 | |||
| 7c4e84ba93 | |||
| 368307742e | |||
| b7658af76b | |||
| 9e3f5a0967 | |||
| 30b79a3b34 | |||
| e5b8518c00 | |||
| d115e60ddf | |||
| e0e6aced33 | |||
| c803238f86 | |||
| 4d6cdbcf4a | |||
| 206c0d2c62 | |||
| 597319a147 | |||
| abfe6f45fb | |||
| f8b3150c17 | |||
| caed2a82d6 | |||
| 7ec3edfd77 | |||
| d7adfaffa8 | |||
| 5bde0ca1f2 | |||
| a3ab288cfe | |||
| 17b0974d82 | |||
| 8fed0b5e8f | |||
| 567d6b79d7 | |||
| 74be3e5717 | |||
| 402b6fcadd | |||
| ef523aaa48 | |||
| ddbb135b6b | |||
| afda364484 | |||
| af73d955eb | |||
| 39cf9a72a1 | |||
| 1c829ad854 | |||
| da98baddb6 | |||
| c5fbaabf42 | |||
| 8080905bad | |||
| b349c3a8b6 | |||
| 0148e03d6a | |||
| e718cc4a5c | |||
| 87bc6a46a1 | |||
| 44bb8baf7c | |||
| 18b2d54ba7 | |||
| b3c3bb9b75 | |||
| d004193b19 | |||
| 8898226825 | |||
| 686b9b2e92 | |||
| 3acf46964b | |||
| 5c7caf0b49 | |||
| ad917ea8d0 | |||
| edeef4ed2a | |||
| 31daa805f3 | |||
| c9937ab5d7 | |||
| 4610137f04 | |||
| 9d7eed7cde | |||
| aedc53bf86 | |||
| 286da3cf2c | |||
| 7fa988313d | |||
| e5b19dd268 | |||
| 0df4159261 | |||
| 45e8ded111 | |||
| 9167ece0c0 | |||
| a6a9395ecc | |||
| a503d4d759 | |||
| f45a036971 | |||
| 24b85be58b | |||
| a798cd119e | |||
| fa338dd0f8 | |||
| 8d4bf5f01f | |||
| cd6f6ef961 | |||
| fdec34832d | |||
| 3c72373b85 | |||
| 688a8c4df2 | |||
| 8bd4986be2 | |||
| 6e5809fc9b | |||
| 2b7c0041e5 | |||
| 9ff0b48eec | |||
| afeb4c7aef | |||
| 2942a89730 | |||
| 6a66f58e63 | |||
| 1d0353d793 | |||
| 0f6aa7c79f | |||
| 0320fd321f | |||
| c29dccf597 | |||
| 70cf04b0db | |||
| 4d57f0146b | |||
| d33aa14dc8 | |||
| 0ed3edd48a | |||
| fb91b51573 | |||
| 0bbd227920 | |||
| 3487770f77 | |||
| 7d27f9e567 | |||
| 511b4d682d | |||
| 5ba91d8acc | |||
| 709f7e7115 | |||
| 3529384cdb | |||
| d1921fa403 | |||
| b4f2a6185b | |||
| d076d5f912 | |||
| e63dd7d898 | |||
| 299a485e4e | |||
| 57dd6df942 | |||
| 9c22cf2e31 | |||
| de20764610 | |||
| 906afc6422 | |||
| f6eaf4751b | |||
| 99b2fda461 | |||
| b5a00bc694 | |||
| 4eb55537f7 | |||
| 0a1032c545 | |||
| 68a20b5080 | |||
| 8a9c9606c7 | |||
| bad2ca35ed | |||
| f2b56640cc | |||
| 5d552d96c4 | |||
| e0dc7d29b2 | |||
| 932611a439 | |||
| a5c6360b4e | |||
| 03f50379c9 | |||
| a7da0b8123 | |||
| e524813021 | |||
| 3444f00062 | |||
| 919d8ec85e | |||
| 3a88e0e7d4 | |||
| 68c2c2f12b | |||
| 3e79c0bfed | |||
| d0c26e52e8 | |||
| a0152a3c29 | |||
| 1fc5dfe820 | |||
| 4b2575f4a4 | |||
| a01a91e7b9 | |||
| aa8436c0ed | |||
| cccad676db | |||
| 03de659c9b | |||
| affb96f0cf | |||
| f19f302bd9 | |||
| 7ca4f5276d | |||
| 44804041a3 | |||
| 48a3704f6d | |||
| 8321a13afc | |||
| b26ae31d4c | |||
| b397bf7bdb | |||
| c0edf7dadf | |||
| c21f943b12 | |||
| b8fd772a28 | |||
| 883f581b2a | |||
| 5a890860a6 | |||
| 7d5bc14a09 | |||
| 37ba3a9d31 | |||
| bfadd53033 | |||
| 8fc3afe348 | |||
| c60037226a | |||
| db6c9c5f2b | |||
| 6504099959 |
+460
-218
File diff suppressed because it is too large
Load Diff
+13
-5
@@ -1,17 +1,20 @@
|
|||||||
name = "YiemAgent"
|
name = "YiemAgent"
|
||||||
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
||||||
|
version = "0.8.0"
|
||||||
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
||||||
version = "0.1.2"
|
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
|
||||||
|
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
||||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||||
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
||||||
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
|
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
|
||||||
GeneralUtils = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
GeneralUtils = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
||||||
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
|
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
|
||||||
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
|
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||||
LLMMCTS = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
|
LLMMCTS = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
|
||||||
LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
|
LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
|
||||||
|
NATS = "55e73f9c-eeeb-467f-b4cc-a633fde63d2a"
|
||||||
PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337"
|
PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337"
|
||||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||||
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
||||||
@@ -21,7 +24,12 @@ URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
|||||||
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
||||||
|
|
||||||
[compat]
|
[compat]
|
||||||
|
Base64 = "1.11.0"
|
||||||
|
CSV = "0.10.15"
|
||||||
DataFrames = "1.7.0"
|
DataFrames = "1.7.0"
|
||||||
GeneralUtils = "0.1, 0.2"
|
GeneralUtils = "0.5.10"
|
||||||
LLMMCTS = "0.1.2"
|
HTTP = "2.4.0"
|
||||||
SQLLLM = "0.2.0"
|
JSON = "1.6.1"
|
||||||
|
LLMMCTS = "0.1.5"
|
||||||
|
NATS = "0.1.0"
|
||||||
|
SQLLLM = "0.2.8"
|
||||||
|
|||||||
@@ -1,7 +1,34 @@
|
|||||||
version 0.1.0
|
# YiemAgent
|
||||||
TODO:
|
|
||||||
[WORKING] build MCTS() for planning
|
|
||||||
[] executeplan() to execute the plan
|
|
||||||
|
|
||||||
Change from version: 0.0.9
|
Julia framework for building agents with tool use.
|
||||||
-
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Install dependencies: `]add JSON, DataStructures, UUIDs, Dates, ...`
|
||||||
|
2. Create a `yiemAgent` with `loadTools("src/tools")`
|
||||||
|
3. Call `run_agent(agent, "message")` then `take_response(agent)`
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── YiemAgent.jl # Module entry point
|
||||||
|
├── type.jl # Core types (messages, tools, agent state)
|
||||||
|
├── utils.jl # Message formatting, validation
|
||||||
|
├── agentCore.jl # Agent loop, tool execution pipeline
|
||||||
|
├── api.jl # Public API (run_agent, take_response, etc.)
|
||||||
|
└── tools/
|
||||||
|
├── registry.jl # Tool registry (loadTools, registerTool, listTools)
|
||||||
|
├── getWeather.jl # Weather lookup tool
|
||||||
|
├── getTime.jl # Time lookup tool
|
||||||
|
├── writeTool.jl # Create new tool files (self-modifying)
|
||||||
|
└── README.md # Tool development guide
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tool Development
|
||||||
|
|
||||||
|
See `src/tools/README.md` for:
|
||||||
|
- Tool anatomy (schema, execute, getTool)
|
||||||
|
- Validation hooks
|
||||||
|
- Agent loop lifecycle
|
||||||
|
- Self-modifying tools (`writeTool`)
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# Store Policy
|
||||||
|
|
||||||
|
- Generally speaking, the store inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know exactly until you check your inventory.
|
||||||
|
- If you found wines in the store's database, they are in stock.
|
||||||
|
- You can only recommend wines that are currently in our inventory
|
||||||
|
- Before searching the database for wine, ensure you have at least the following information: 1) budget, 2) wine type, and 3) occasion. Additional details are always helpful. If the user is unsure, provide relevant information and gather insights to make reasonable inferences.
|
||||||
|
- Ask the user one question at a time.
|
||||||
|
- Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar as these terms cannot be used to search the database.
|
||||||
|
- Once the user has selected their wine, if you haven't already, ask the user whether they need any further assistance. Do not offer any additional services.
|
||||||
|
- Only end the conversation when the user explicitly intends to do so. When ending, ensure a polite farewell and an invitation to return in the future.
|
||||||
|
- Spicy foods should be paired only with light red wines.
|
||||||
|
- We do not sell organic, sustainable, gluten-free, and sulfite-free wine. Inform the user imediately if they are looking for these types of wines. Do not sell our wines as such.
|
||||||
|
- Gift box, gift card, and custom messages are available. Inform the user to contact our sales team.
|
||||||
|
|
||||||
|
# Store Guidelines
|
||||||
|
|
||||||
|
- Greeting the customer warmly by ask them how could you help. Do not ask any other questions during this greeting.
|
||||||
|
- Customer may provide images for you to look up.
|
||||||
|
- Encourage the customer to explore different options and try new things.
|
||||||
|
- If you are unable to locate the desired item in the database after 2 attempts, it may not be available in your inventory. In such cases, inform the user that the item is unavailable and suggest an alternative instead.
|
||||||
|
- Your store carries only wine.
|
||||||
|
- Vintage 0 means non-vintage.
|
||||||
|
- Start searching the database as broadly as possible within the given information boundary to maximize the chances of finding. Avoid unnecessary parameters unless specified by the user. Refine the search subsequently.
|
||||||
|
|
||||||
|
# Prompt
|
||||||
|
|
||||||
|
Search the database as broad as possible under the informantion you have will increase the chance to find wine. Avoid uneccessary parameter such as region, country, tasting notes unless the user specify
|
||||||
|
|
||||||
|
# Situation
|
||||||
|
|
||||||
|
Your customer is coming into the store
|
||||||
|
|
||||||
|
# Role
|
||||||
|
|
||||||
|
Your name is $(newAgent.name). You are a helpful sommelier for website-based $(newAgent.retailername)'s wine store. You are working under your mentor supervision.
|
||||||
|
|
||||||
|
# Objective
|
||||||
|
|
||||||
|
1. Establish a connection with the customer by talking to them politely and showing your enthusiasm for their wine preferences.
|
||||||
|
2. Provide relevant information and guide them to select the best wines only from your store's inventory that align with their preferences.
|
||||||
|
|
||||||
|
# Responsibility Includes
|
||||||
|
|
||||||
|
1. According to the store's policy and guidelines, make an informed decision about what you need to do to achieve the objective
|
||||||
|
2. Keep the conversation with the customer going smoothly
|
||||||
|
3. Obey your mentor's suggestions.
|
||||||
|
|
||||||
|
# Responsibility Does NOT Include
|
||||||
|
|
||||||
|
1. Requesting the user to place an order, make a purchase, or confirm the order. These are the job of our sales team at the store.
|
||||||
|
2. Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
|
||||||
|
3. Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
|
||||||
|
|
||||||
|
# Available Actions
|
||||||
|
|
||||||
|
- **CHAT_BOX** which you can use to talk with the user.
|
||||||
|
- **SEARCH_WINE_DATABASE** allows you to check information about wines you want in your inventory's database. The input is text that specify supported search criteria includeing: retailer_name, wine price, winery, name, vintage, region, country, type, grape varietal, tasting notes, occasion, food pairing, intensity, tannin, sweetness, and acidity.
|
||||||
|
- Example query 1: "Dry, full-bodied red wine from 1) region: Burgundy, country: France or 2) region: Tuscany, country: Italy. Grape varietal: Merlot or Syrah. price 100 to 1000 USD."
|
||||||
|
- Example query 2: "Red or white wine, medium tannin, price under 700 USD"
|
||||||
|
- Example query 3: "white wine, region: Tuscany or Bordeaux, country: Italy or France
|
||||||
|
- **PRESENT_WINE_GUIDELINE** which you can use to check the store guidelines about how to present wines you have found to the user. The input is "nothing" keyword. The output is the guidelines that you can follow.
|
||||||
|
- **END_CONVER_GUIDELINE** which you can use to check the store guidelines about how to end the conversation with the user. The input is "nothing" keyword. The output is the guidelines that you can follow.
|
||||||
|
|
||||||
|
# Response Format
|
||||||
|
|
||||||
|
You should respond to the user with interleaving plan, action_name, action_input:
|
||||||
|
|
||||||
|
1. **plan**: Based on the current situation, state a complete action plan to complete the task and rationale. Be specific.
|
||||||
|
2. **action_name**: (Typically corresponds to the execution of the first step in your plan) Can be one of the available_actions name
|
||||||
|
3. **action_input**: The input to the action you are about to perform according to your plan.
|
||||||
|
|
||||||
|
After the action is executed you gets "action_result". It is the output from the action you selected.
|
||||||
|
|
||||||
|
Assistant should only respond in JSON format as described below:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plan": "...",
|
||||||
|
"action_name": "...",
|
||||||
|
"action_input": "..."
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"nats_server_info": {
|
||||||
|
"description": "nats server",
|
||||||
|
"url": "nats.yiem.cc"
|
||||||
|
},
|
||||||
|
"testingOrProduction": "testing",
|
||||||
|
"agentId": "2b74b87a-5413-4fe2-a4d3-405891051680",
|
||||||
|
"agentCentralConfigSubject": "/yiem/hq/agent/sommelier/backend/config/api/v1.1",
|
||||||
|
"this_service_name": "agent_backend",
|
||||||
|
"this_service_input_channel": {
|
||||||
|
"mqtt": [
|
||||||
|
"/yiem/hq/agent/sommpanion/backend/db/api_v1"
|
||||||
|
],
|
||||||
|
"nats": [
|
||||||
|
"sommpanion.backend.agentbackend.v1.inbox"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"agentRole": "sommelier",
|
||||||
|
"organization": "yiem_hq",
|
||||||
|
"externalservice": {
|
||||||
|
"servicesloadbalancer": {
|
||||||
|
"nats": "sommpanion.backend.servicesloadbalancer.v1.inbox"
|
||||||
|
},
|
||||||
|
"textembedding": {
|
||||||
|
"url": "textembedding.api.v1"
|
||||||
|
},
|
||||||
|
"textimage_to_text_llm": {
|
||||||
|
"url": "https://llmcoder.yiem.cc/v1/chat/completions",
|
||||||
|
"modelname": "Qwen3.6-35B-A3B-UD-Q4_K_M"
|
||||||
|
},
|
||||||
|
"virtualWineCustomer_1": {
|
||||||
|
"serviceSubject": "",
|
||||||
|
"modelName": "qwen3:8b"
|
||||||
|
},
|
||||||
|
"sommpanion_db" : {
|
||||||
|
"description": "A database connection info for LibPQ client",
|
||||||
|
"url": "192.168.88.106:5432",
|
||||||
|
"dbname": "winedb",
|
||||||
|
"user": "admin",
|
||||||
|
"password": "admin@Sommpanion_0.0"
|
||||||
|
},
|
||||||
|
"sommpanion_vectordb" : {
|
||||||
|
"description": "A wine database connection info for LibPQ client",
|
||||||
|
"url": "192.168.88.106:5433",
|
||||||
|
"dbname": "vectordb",
|
||||||
|
"user": "admin",
|
||||||
|
"password": "admin@Sommpanion_0.0"
|
||||||
|
},
|
||||||
|
"fileserver": {
|
||||||
|
"description": "temporary file server",
|
||||||
|
"url": "https://fileserver.yiem.cc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# ── executeToolCalls() Julia pseudo code ──────────────────────────
|
||||||
|
# Full call stack from runLoop → executeToolCalls → prepare → execute → finalize → emit
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
To make **LLM-driven inference** fast while maintaining its dynamic capabilities, there are a few practices or approaches to avoid, as they could lead to performance bottlenecks or inefficiencies. Here's what *not* to do:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **1. Avoid Using Overly Large Models for Every Query**
|
||||||
|
While larger LLMs like GPT-4 provide high accuracy and nuanced responses, they may slow down real-time processing due to their computational complexity. Instead:
|
||||||
|
- Use distilled or smaller models (e.g., GPT-3.5 Turbo or fine-tuned versions) for faster inference without compromising much on quality.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **2. Avoid Excessive Entity Preprocessing**
|
||||||
|
Don’t rely on overly complicated preprocessing steps (like advanced NER models or regex-heavy pipelines) to extract entities from the query before invoking the LLM. This could add latency. Instead:
|
||||||
|
- Design efficient prompts that allow the LLM to extract entities and generate responses simultaneously.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **3. Avoid Asking the LLM Multiple Separate Questions**
|
||||||
|
Running the LLM for multiple subtasks—for example, entity extraction first and response generation second—can significantly slow down the pipeline. Instead:
|
||||||
|
- Create prompts that combine tasks into one pass, e.g., *"Identify the city name and generate a weather response for this query: 'What's the weather in London?'"*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **4. Don’t Overload the LLM with Context History**
|
||||||
|
Excessively lengthy conversation history or irrelevant context in your prompts can slow down inference times. Instead:
|
||||||
|
- Provide only the relevant context for each query, trimming unnecessary parts of the conversation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **5. Avoid Real-Time Dependence on External APIs**
|
||||||
|
Using external APIs to fetch supplementary data (e.g., weather details or location info) during every query can introduce latency. Instead:
|
||||||
|
- Pre-fetch API data asynchronously and use the LLM to integrate it dynamically into responses.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **6. Avoid Running LLM on Underpowered Hardware**
|
||||||
|
Running inference on CPUs or low-spec GPUs will result in slower response times. Instead:
|
||||||
|
- Deploy the LLM on optimized infrastructure (e.g., high-performance GPUs like NVIDIA A100 or cloud platforms like Azure AI) to reduce latency.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **7. Skip Lengthy Generative Prompts**
|
||||||
|
Avoid prompts that encourage the LLM to produce overly detailed or verbose responses, as these take longer to process. Instead:
|
||||||
|
- Use concise prompts that focus on generating actionable or succinct answers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **8. Don’t Ignore Optimization Techniques**
|
||||||
|
Failing to optimize your LLM setup can drastically impact performance. For example:
|
||||||
|
- Avoid skipping techniques like model quantization (reducing numerical precision to speed up inference) or distillation (training smaller models).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **9. Don’t Neglect Response Caching**
|
||||||
|
While you may not want a full caching system to avoid sunk costs, dismissing lightweight caching entirely can impact speed. Instead:
|
||||||
|
- Use temporary session-based caching for very frequent queries, without committing to a full-fledged cache infrastructure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **10. Avoid One-Size-Fits-All Solutions**
|
||||||
|
Applying the same LLM inference method to all queries—whether simple or complex—will waste processing resources. Instead:
|
||||||
|
- Route basic queries to faster, specialized models and use the LLM for nuanced or multi-step queries only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Summary: Focus on Efficient Design
|
||||||
|
By avoiding these pitfalls, you can ensure that LLM-driven inference remains fast and responsive:
|
||||||
|
- Optimize prompts.
|
||||||
|
- Use smaller models for simpler queries.
|
||||||
|
- Run the LLM on high-performance hardware.
|
||||||
|
- Trim unnecessary preprocessing or contextual steps.
|
||||||
|
|
||||||
|
Would you like me to help refine a prompt or suggest specific tools to complement your implementation? Let me know!
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAtGVYSWZJSSoACAAAAAYAEgEDAAEAAAABAAAAGgEFAAEAAABWAAAAGwEFAAEAAABeAAAAKAEDAAEAAAACAAAAEwIDAAEAAAABAAAAaYcEAAEAAABmAAAAAAAAAEgAAAABAAAASAAAAAEAAAAGAACQBwAEAAAAMDIxMAGRBwAEAAAAAQIDAACgBwAEAAAAMDEwMAGgAwABAAAA//8AAAKgBAABAAAAgAAAAAOgBAABAAAAgAAAAAAAAABKviP0AAANMElEQVR4nO2de3BU1R3Hv7+zu9nNO9mQTQIJzxAeQkKI5MFjAgJJrEiSVmZqq2L/UdvOVO34Xx9WWzvTTutjHMdHrVbb6UyLDqC0jQ9s66ABLaIiCAgmQEhIYh6bBLLPezpnIZDdvQs3mMfde+5n5s5kz97du9nzu79zft/z+50FTExMTExMTExMTExMTExMTExMTEwMTeWWLYnigMRYITODvrsv/vU4JIUgKdXVWx0eh/s4gVv8tuC8/bt2nYeEMEiKN7H/HiLMAFGuLWC5C5JCUt/9hBmhBs7PyuoFmNR3/wgSewHpPEDU3T+CpF5AOg/gcbgfiOp8AVGu1W/9MSRDGg9QWbt5A8B+BULFlc/knxDYI81N218RD2BwDG8A2jteTkMwrAFce8fLZQgWGJCqmvqVYOxFEJZ8/XejXADXF8xd8GHbiaOnYTAMOQlsfnPn+/5u1wKQshXgX1zr+3DOT3KO+zLgWSTeEwbEsEPACGVld9lsrrO3gtNPAZqvteMBeiyTPM80NTV5YWAMbwBhhpDdeQdADwIoiHHaaYA/5O/OeXn//uf8kABpDGD0/IAzek/tOWJKdfM/X3sXEmHIOcCVEGM559gd2c45dsvW+VIagIBx/vOoNovyMCRESgOI9AJc0rtfWgOI9AJM0rtfeipqG94WByRG6pxADv4QJEdqAwDxHFLIcPr+WJBOBxihvK4ujbjjMBFndjsW/3fnzn5IiLSTQIL9kQuJIZTn8ZC0k0ApPUBVXeMKhSvNRHRhNZRzhYFWv//GjmZIhnQeoLq62qpw/uylzhcQMQV4RqwXQDKkM4DhxIz7iFAa9QSh2JrV+SNIhlRDwKoNm2YGrJZDBEpRPYHjvKIEl3zw1ustkASpPEDQan0yZucLCEmMsacgEdIYQHnd5lsAbL7qiUQ3VtTUfwuSQHLF/Cr1AKrwDlm0ASZXzB9O7uz+0KHyCmm0ASZDzA+O70e2W6wKSta3YtmGFlhtStTrCPyHK2sbqmBwmHQx/0UWVbUhOd2DpDQvFlSciX6xJNoAkzHmT5t2HoXLz156XHR9O9KzVWpCJdAGmJFjfgJEBnAYBI7lG1pEEggutTGO5Ru/DD0XdT7Rw+Ubb54Dg8Jki/nnlHTBOX0w6vzM3CHMLumSThtgMsX8jmQfrlt9Kubrlqw5BUeKTyptgBkz5mequ34VrzsJmz0Y87W2hCCWVqsbCDE8WV1fnwGDwWSJ+V2z3cgv6rnq6wsWfIXcuX3SaANMlpi/dL329Z1lN7RKow0wmWJ+rSRJpA0w2WJ+rRRJog0w2WJ+rZAk2gCTMebXSqYE2gCTNebXyhKDawNM1phfKzaDawNMtpg/rc+G6a1JsA9r/9eNrA2QYXL7R8X8G7Z+qhr2LdvjxJIPnKHN3nx2Bf9pbEfXDG3h4fkBO95+qQQBf4ThxHlNAZMl5hd3/kjnCxK8DCveyR6TNrCwss1w2gCTJeZP6bdFbfOY7B5bbez8sg7DaQNMlpi/J9cTcvuj6Zg1DNm1ASZLzO9NvDDm97m88NmDOLlgCB9s6L4mbUBcyyjaAMVbzM/AtqnF/Bvv/GRcwj4t+H0WvP2nZRgeih72ucJv2ffmzlcRJzBDxPxrxyfmH5M2sLbVENoAi/eYP2dOP/IXXH2df7zJL+oxhDbA4n2dX6zdTxXLDJA3wGRa5x9vkgygDVjjIeZniI75iYCe9lTs23V5A/CsGUMoLO0Y8zUK04PIT1Yw4Gc42GOB/wqrx8cP5KHnzOUgRAmy0GfhPKY28HvoGIrrev5RMAvH+ts/RapzbLH98uwgKlyXNwbvGmbY3pIAJcbeYYN9Duz+czGUgAbnGQf7DbC4rucfhXDFY+18wVJn+K7wrkQldMQiNdODBeUq6WJqxIE2wOK+nv+iBFy0on3SHOPCcpEudk7jW+k7b4DFW8wfiZBlS4UEzK5tv8eDveHzNDEEdA3TVSXh0o0tII17TOpZG9DlJJAhYT0n/v7I10ucCMTrAYqaVc8p7USWigTMSLhzHpqcdXso5pj+UbcFAz5CQUoQbi/hYJ815rmjcYYk4U58+bH4TalIuB+cdnK6PDX0evk6ANuhM3Q9CRyhorb+TiJ6MbJdpGpt3BotAdsswOZZvktjeecw4bVWOwJjzw3VIAmXYHgoQe3pO/c27XgJOkeXQ8BoyjZtmgbgt2rPiWIPNQm42BkIm8jlJHIsdQYnRBJetkF9gs/BH63a2OiCztG9AVgD1ieIKCpzY0ZRL/LmqUmxQFpCtA9PTxjn2/8ieXP7MH1+LyIhkFNhyqPQObo2gMqaxjoCvqN25xWvjR1atw1F/x7m6SE2oZKwTcUTEdF3q2oaNkHH6PaXQ8s2bUqyKLQLRJmRz5Wsa0V2Qey1/17vhZSNTDsPqXoffWXD4b6J+1etCUEk2IM425KpNhSsKswvfv7kycMqueVTjy6jAIEtYPkliOZGtjvzBjG7+OqJHPu7raHjWln8YSYWHkgPpZEdLXXjULn6cDOCKCA5dWQaes6khrUT0Sxvov8XAB6ADtGlByivaSwB4Y8UMUSJWH/lN4/AkTSxv+k461gKKt9yweZjoSPvVBLcWT64p/muGE5NmzGIls9c4Ep4cMU5r8ift+gfZ04cGftChWxzALH6R0x5gVS8k6jYTcsau9w7VvJaE6PaRC3B1UgRMrGKIilWMonwgh5XB3VnAF5H5v0EWq71y50IhtIC0W0Z2rxOTCMlFFuyO++FztCVAVTU1s8GqWT8ElBWcwLMOjGhXCRHy9zozbn8m9G9Li+OlLqhBTFMLa9RzxxmnB5aWdcwDzpCV5NAAp4V6fpqs+zjB3JDx2gdQMuWL6PhfhvOHShDwJ0Ox+wWOOYfUz3Pb1PQdGsbsjscocfdeR4oltj6cNuxLJw55gz/zHYFfm/EFIuQpAB/ALA+FCDoAN1IweW19bczope1nHtNWcAKw1d/vQ2+jumXmtLW/hspK/bh6+L3WkNlY2pZwmpw4PZ9TTv+Ah2giyFgxfqGLCL8Tuv5QgcYaxawr316WOcLzu1fgfHAZg+geJ32nA8Cf3x13RbtdWlGNwCLDY8RSJNuLjJxhfsfF1+nYNyYMb8X0wv7tH6YLD/3azZ4QxtAec1msUx6m5ZzhQQs1v6vBVtuB2yuzrC25OUfYTwpibE4pQYR7iiva6iB7EJQQeGiezhxIZAcvnBQGwGFaueW3CAk4IGodpuHwdV+IXb3OZSYSRxJiz4HWQOwpA0gtWIfkkvHbgABdwb8XTlgdg/IGrEMLSRhRwBnv4yWhAUceAPgB0b+V8Ypo+3EkSn97WLdTAJHqKirf5pA90S2T8sfwJoth0Mh4WhcZxxYu2M6EjwsNK3+ZFUPPqvU6orHxmDzKgzuWR362pjDA2fjK0jID08LFykge15dhO5T6dFvwPnTe9/Y+QPoiCkfAkYjiimI4y61jF/h+iM7X1C+OzvU+QLxdMl7WUgVpeDjTKAv81LnCxSPA+7d0R5cfMbS9a2hohUV7i6vrV8DHaEbA6irqxPF28+Logq1ApBYGb+huv9RiO5Jdo+/AQQHREofRRmF6mfKHMaiSvWNJhnwfHX11gsCgw7QjQH0c/vPQFgc2S42ZBAbM8Ti7MxwwxB7APTmjn+1kC2nA8xxWR0U2GfFLkubf307MlwqmcNERd7E/p9AJ0z5JFBQeWP9UgAvA+HlX2LiVtVwFEmpsVfhOgs8SO+zIXnQBrfTh+ZvdGEgItd/PCBrALa8dgQ6c8H9djjmnkBGTRMoQf1aYihw5p3DyUPZ4DxidRC0cmZh0a6240fHvoWp4SaBDz7IKvZ9vIdAUcWUItdf7NMXzxx8dya++F+4ACXgnH84My2hatu2bZNX167HIaCi+eP71To/OT1G4WWcsXhlG5IzoockIlpxasB/r9RDQGj1j+HvBEqIdJ/lN32BVOfUVf6OF2J1MH3aeZz+PFr5JWDNzPkL/3b6+JGJiVv17gHE6p9a7d+s67rgmqVt+TUeyC4YwMzF6vsKXVwdJOkMoKJm8/dAVKO20re0+iSMRvHaU0hMUZ0wrquord+KKYKmqtjD5rd+DoIo+ojK/NFceBlnuLuTMdQXLQFw8F74lMX73nk9fLHCqAkh1oDlKbXOF4gvSO1LMjIEcsJmeQLAtyf/2pNMeW3jTYz4rsm+bjxAxBub/7Vzx6Rec7LLvhm3HwJR/mReN37gk/6z9ZM6CSTYf2N2/pWgPK8Hv4YRPUDFDTfnkM3yBCc+4UZHHFWRhpZoZciwa5vy9HsDGI6sJee8jRMmfEt44qRwf/DeyZoQTr0UPAFU1jW8AiBsW5b81ARU5mrabgh7zw6hbTBq/eHVvU07xLY1hmLKpWCTqcU0AMkxDUByTAOQHF2Vhk0kZwZ92D6kbdFNidr31bhIYwCiS4MSdaxWzCFAckwDkBzTACTHkHMArtCzYPzNcX1ThXS75buJiYmJiYmJiYmJiYmJiYmJiYmJCa7I/wEjf8WA9CzV0gAAAABJRU5ErkJggg==
|
||||||
@@ -1,537 +0,0 @@
|
|||||||
https://github.com/andyz245/LanguageAgentTreeSearch/blob/main/hotpot/hotpot.py
|
|
||||||
|
|
||||||
|
|
||||||
standard_prompt = '''
|
|
||||||
Write a coherent passage of 4 short paragraphs. The end sentence of each paragraph must be: {input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
reflection_prompt = '''You are an advanced reasoning agent that can improve based on self refection. You will be given a previous reasoning trial in which you were given access to an Docstore API environment and a question to answer. You were unsuccessful in answering the question either because you guessed the wrong answer with Finish[<answer>], or you used up your set number of reasoning steps. In a few sentences, Diagnose a possible reason for failure and devise a new, concise, high level plan that aims to mitigate the same failure. Use complete sentences.
|
|
||||||
Here are some examples:
|
|
||||||
Previous Trial:
|
|
||||||
Question: The Rome Protocols were signed by three Prime Ministers one of which was assassinated as part of what?
|
|
||||||
Thought 1: I need to search Rome Protocols, find the three Prime Ministers, then find what they were assassinated as part of.
|
|
||||||
Action 1: Search[Rome Protocols]
|
|
||||||
Observation 1: The Rome Protocols were a series of three international agreements concluded in Rome on 17 March 1934 between the governments of Austria, Hungary and Italy. They were signed by Italian Prime Minister Benito Mussolini, Austrian Chancellor Engelbert Dollfuss and Hungarian Prime Minister Gyula Gömbös. All of the protocols went into effect on 12 July 1934 and were registered in League of Nations Treaty Series on 12 December 1934.
|
|
||||||
Thought 2: The three Prime Ministers were Benito Mussolini, Engelbert Dollfuss, and Gyula Gömbös. I need to search Benito Mussolini and find what he was assassinated as part of.
|
|
||||||
Action 2: Search[Benito Mussolini]
|
|
||||||
Observation 2: Benito Amilcare Andrea Mussolini (UK: MU(U)SS-ə-LEE-nee, US: MOOSS-, Italian: [beˈniːto aˈmilkare anˈdrɛːa mussoˈliːni]; 29 July 1883 – 28 April 1945) was an Italian politician and journalist who founded and led the National Fascist Party (PNF). He was Prime Minister of Italy from the March on Rome in 1922 until his deposition in 1943, as well as "Duce" of Italian fascism from the establishment of the Italian Fasces of Combat in 1919 until his summary execution in 1945 by Italian partisans. As dictator of Italy and principal founder of fascism, Mussolini inspired and supported the international spread of fascist movements during the inter-war period.Mussolini was originally a socialist politician and a journalist at the Avanti! newspaper. In 1912, he became a member of the National Directorate of the Italian Socialist Party (PSI), but he was expelled from the PSI for advocating military intervention in World War I, in opposition to the party's stance on neutrality. In 1914, Mussolini founded a new journal, Il Popolo d'Italia, and served in the Royal Italian Army during the war until he was wounded and discharged in 1917. Mussolini denounced the PSI, his views now centering on Italian nationalism instead of socialism, and later founded the fascist movement which came to oppose egalitarianism and class conflict, instead advocating "revolutionary nationalism" transcending class lines. On 31 October 1922, following the March on Rome (28–30 October), Mussolini was appointed prime minister by King Victor Emmanuel III, becoming the youngest individual to hold the office up to that time. After removing all political opposition through his secret police and outlawing labor strikes, Mussolini and his followers consolidated power through a series of laws that transformed the nation into a one-party dictatorship. Within five years, Mussolini had established dictatorial authority by both legal and illegal means and aspired to create a totalitarian state. In 1929, Mussolini signed the Lateran Treaty with the Holy See to establish Vatican City.
|
|
||||||
Mussolini's foreign policy aimed to restore the ancient grandeur of the Roman Empire by expanding Italian colonial possessions and the fascist sphere of influence. In the 1920s, he ordered the Pacification of Libya, instructed the bombing of Corfu over an incident with Greece, established a protectorate over Albania, and incorporated the city of Fiume into the Italian state via agreements with Yugoslavia. In 1936, Ethiopia was conquered following the Second Italo-Ethiopian War and merged into Italian East Africa (AOI) with Eritrea and Somalia. In 1939, Italian forces annexed Albania. Between 1936 and 1939, Mussolini ordered the successful Italian military intervention in Spain in favor of Francisco Franco during the Spanish Civil War. Mussolini's Italy initially tried to avoid the outbreak of a second global war, sending troops at the Brenner Pass to delay Anschluss and taking part in the Stresa Front, the Lytton Report, the Treaty of Lausanne, the Four-Power Pact and the Munich Agreement. However, Italy then alienated itself from Britain and France by aligning with Germany and Japan. Germany invaded Poland on 1 September 1939, resulting in declarations of war by France and the UK and the start of World War II.
|
|
||||||
On 10 June 1940, Mussolini decided to enter the war on the Axis side. Despite initial success, the subsequent Axis collapse on multiple fronts and eventual Allied invasion of Sicily made Mussolini lose the support of the population and members of the Fascist Party. As a consequence, early on 25 July 1943, the Grand Council of Fascism passed a motion of no confidence in Mussolini; later that day King Victor Emmanuel III dismissed him as head of government and had him placed in custody, appointing Pietro Badoglio to succeed him as Prime Minister. After the king agreed to an armistice with the Allies, on 12 September 1943 Mussolini was rescued from captivity in the Gran Sasso raid by German paratroopers and Waffen-SS commandos led by Major Otto-Harald Mors. Adolf Hitler, after meeting with the rescued former dictator, then put Mussolini in charge of a puppet regime in northern Italy, the Italian Social Republic (Italian: Repubblica Sociale Italiana, RSI), informally known as the Salò Republic, causing a civil war. In late April 1945, in the wake of near total defeat, Mussolini and his mistress Clara Petacci attempted to flee to Switzerland, but both were captured by Italian communist partisans and summarily executed by firing squad on 28 April 1945 near Lake Como. The bodies of Mussolini and his mistress were then taken to Milan, where they were hung upside down at a service station to publicly confirm their demise.
|
|
||||||
Thought 3: Benito Mussolini was assassinated as part of the collapse of the Axis powers during World War II. So the Rome Protocols were signed by three Prime Ministers one of which was assassinated as part of World War II.
|
|
||||||
Action 3: Finish[World War II]
|
|
||||||
|
|
||||||
Reflection: I searched one of the prime ministers involved in the signing, then attemted to answer right away. I should have searched each of the prime ministers, then looked up 'death' on each of their pages in order to get more information before answering.
|
|
||||||
|
|
||||||
Previous Trial:
|
|
||||||
Question: Kam Heskin plays Paige Morgan in a 2004 film directed by who?
|
|
||||||
Thought 1: I need to search Kam Heskin and Paige Morgan, find the 2004 film they are in, then find the director of the film.
|
|
||||||
Action 1: Search[Kam Heskin]
|
|
||||||
Observation 1: Kam Erika Heskin (born May 8, 1973) is an American actress. She began her career playing Caitlin Deschanel on the NBC daytime soap opera Sunset Beach (1998–1999), before appearing in films Planet of the Apes (2001) and Catch Me If You Can (2002). Heskin went on in 2003 to play Elizabeth Bennet in an independent film Pride & Prejudice: A Latter-Day Comedy, Robin in Charmed (2004), and Paige Morgan in The Prince and Me film franchise (2006–2010).
|
|
||||||
Thought 2: Kam Heskin plays Paige Morgan in The Prince and Me film franchise. I need to search The Prince and Me film franchise and find the director of the 2004 film.
|
|
||||||
Action 2: Search[The Prince and Me film franchise]
|
|
||||||
Observation 2: Could not find [The Prince and Me film franchise]. Similar: ['Kam Heskin', 'Prince of Persia', 'Harry Potter and the Half-Blood Prince (film)', 'Prince of Persia: The Sands of Time (film)', 'The Chronicles of Narnia (film series)', 'Majestic Prince (franchise)', 'Kingsman (franchise)', 'The Little Prince (2015 film)', 'Shrek (franchise)', 'Harry Potter (film series)']
|
|
||||||
Thought 3: I need to search The Prince and Me (2004 film) instead.
|
|
||||||
Action 3: Search[The Prince and Me (2004 film)]
|
|
||||||
Observation 3: Could not find [The Prince and Me (2004 film)]. Similar: ['The Prince & Me', 'The Prince & Me 2: The Royal Wedding', 'Kam Heskin', 'Prince of Darkness (film)', 'Prince of Persia: The Sands of Time (film)', 'Rob Knox', 'Alexander (2004 film)', 'Prince of Persia', 'Brooklynn Prince', 'Prince (musician)']
|
|
||||||
Thought 4: I need to search The Prince & Me (2004 film).
|
|
||||||
Action 4: Search[The Prince & Me (2004 film)]
|
|
||||||
Observation 4: Could not find [The Prince & Me (2004 film)]. Similar: ['The Prince & Me', 'The Prince & Me 2: The Royal Wedding', 'Prince of Darkness (film)', 'Prince of Persia: The Sands of Time (film)', 'Rob Knox', 'Alexander (2004 film)', 'Prince (musician)', 'Prince of Persia', 'Kam Heskin', 'Brooklynn Prince']
|
|
||||||
Thought 5: I need to search The Prince & Me (2004 film) instead.
|
|
||||||
Action 5: Search[The Prince & Me (2004 film)]
|
|
||||||
Observation 5: Could not find [The Prince & Me (2004 film)]. Similar: ['The Prince & Me', 'The Prince & Me 2: The Royal Wedding', 'Prince of Darkness (film)', 'Prince of Persia: The Sands of Time (film)', 'Rob Knox', 'Alexander (2004 film)', 'Prince (musician)', 'Prince of Persia', 'Kam Heskin', 'Brooklynn Prince']
|
|
||||||
Thought 6: I need to search The Prince & Me (2004 film).
|
|
||||||
Action 6: Search[The Prince & Me (2004 film)]
|
|
||||||
Observation 6: Could not find [The Prince & Me (2004 film)]. Similar: ['The Prince & Me', 'The Prince & Me 2: The Royal Wedding', 'Prince of Darkness (film)', 'Prince of Persia: The Sands of Time (film)', 'Rob Knox', 'Alexander (2004 film)', 'Prince (musician)', 'Prince of Persia', 'Kam Heskin', 'Brooklynn Prince']
|
|
||||||
|
|
||||||
Reflection: I got stuck in a loop where I kept trying to search 'The Prince & Me (2004 film)' but the page could not be found. Instead I should have tried to search the similar results that had a similar name to see and they were made in 2004.
|
|
||||||
|
|
||||||
Previous Trial:
|
|
||||||
Question: The Oberoi family is part of a hotel company that has a head office in what city?
|
|
||||||
Thought 1: I need to search the Oberoi family and find the city where the hotel company's head office is located.
|
|
||||||
Action 1: Search[Oberoi family]
|
|
||||||
Observation 1: The Oberoi Group is a luxury hotel group with its head office in New Delhi, India.[2] Founded in 1934, the company owns and operates 32 luxury hotels and two river cruise ships in 7 countries, primarily under its Oberoi Hotels & Resorts and Trident brands.[3] The group also operates The Oberoi Centre for Learning and Development, which is regarded as one of Asia's top institutions for hospitality education.[4]. The foundations of the Oberoi Group date back to 1934 when The Rai Bahadur Mohan Singh Oberoi, the founder of the group, bought two properties: the Maidens in Delhi and the Clarke's in Shimla.[5] In the following years Oberoi, assisted by his two sons, Tilak Raj Singh Oberoi and Prithvi Raj Singh Oberoi (P.R.S. Oberoi
|
|
||||||
Thought 2: The Oberoi Group has its head office in New Delhi, India. I just need to finish with New Delhi.
|
|
||||||
Action 2: Finish[New Delhi]
|
|
||||||
Observation 2: Episode finished, reward = 0
|
|
||||||
|
|
||||||
Reflection: I appeared to have retrieved the correct information about The Oberoi Family and the location of it's head office, and provided a corresponding answer. However this answer does not exactly match the ground truth answer so I should try a different wording, such as Delhi.
|
|
||||||
|
|
||||||
Previous trial:
|
|
||||||
{trajectory}Reflection:'''
|
|
||||||
|
|
||||||
cot_prompt = '''
|
|
||||||
Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
After each observation, provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas. This took place from 1780 to 1650 million years ago (Mya), during the Paleoproterozoic (Statherian Period). It is recorded in the Colorado orogen, a >500-km-wide belt of oceanic arc rock that extends southward into New Mexico. The Colorado orogeny was likely part of the larger Yavapai orogeny.
|
|
||||||
Thought 2: It does not mention the eastern sector of the Colorado orogeny. I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Observation 2: (Result 1 / 1) The Colorado orogen, formerly called the Colorado province, is a >500-km-wide belt of oceanic arc rock (1.78\u20131.65 Ga) that extends southward into New Mexico and composes a major part of the Proterozoic provinces of southwestern United States. This transcontinental collisional event occurred during the Paleoproterozoic (Statherian Period).[1] The Wyoming sector of the Colorado orogeny was formerly called the Medicine Bow orogeny. The eastern sector extends into the High Plains and is called the Central Plains orogeny. The boundary between the Colorado orogeny and the Wyoming craton is the Cheyenne belt, a 5-km-wide mylonitic shear zone that verges northward. The Cheyenne belt transects and cuts off the south edge of the older Trans-Hudson orogeny.[2]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]Observation 3: High Plains refers to one of two distinct land regions:
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Observation 4: The High Plains are a subregion of the Great Plains, mainly in the Western United States, but also partly in the Midwest states of Nebraska, Kansas, and South Dakota, generally encompassing the western part of the Great Plains before the region reaches the Rocky Mountains. The High Plains are located in eastern Montana, southeastern Wyoming, southwestern South Dakota, western Nebraska, eastern Colorado, western Kansas, eastern New Mexico, western Oklahoma, and to just south of the Texas Panhandle.[4] The southern region of the Western High Plains ecology region contains the geological formation known as Llano Estacado which can be seen from a short distance or on satellite maps.[5] From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130 m).[3]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Guitars for Wounded Warriors is an album that was recorded in the village in which New York county?
|
|
||||||
Thought 1: I need to search Guitars for Wounded Warriors, then find in which New York county it was recorded.
|
|
||||||
Action 1: Search[Guitars for Wounded Warriors]
|
|
||||||
Observation 1: Guitars for Wounded Warriors is the sixth solo studio album by guitarist Brian Tarquin, released in August 2014 by Cleopatra Records. In 2016, it received a Best Compilation Album nomination by the Independent Music Awards. All tracks were recorded at Tarquin's Jungle Room Studios in New Paltz (village), New York. Being moved by the lack of support for returning veterans through his life Tarquin decided to compose a heavy instrumental rock album as a way to show his appreciation to all veterans. So once again he enlisted top notch players to guest with him on the album, Billy Sheehan on Black Hawk, Ron \"Bumblefoot\" Thal and Reb Beach on Taliban Terror, Steve Morse on Freedom, Chris Poland on Charlie Surfs and Hunting, Chuck Loeb on Escape Kabul, Hal Lindes on Sand & Blood, Gary Hoey on 5 Klicks To Hell and Baghdad, Alex De Rosso Dokken on Broken Arrow, and The Flyin' Ryan Brothers on Surgical Strike. The entire album was engineered, produced and composed by Tarquin especially for each one of the guest guitarists. Partial proceeds are donated to the Fisher House Foundation from sales.
|
|
||||||
Thought 2: Guitars for Wounded Warriors was recorded in New Paltz (village), New York. I need to search New Paltz (village), New York and find which county it is in
|
|
||||||
Action 2: Search[New Paltz (village), New York]
|
|
||||||
Observation 2: New Paltz is a village in Ulster County located in the U.S. state of New York. It is approximately 80 miles (130 km) north of New York City and 70 miles (110 km) south of Albany. The population was 7,324 at the 2020 census.[3]
|
|
||||||
Thought 3: New Paltz (village), New York is in Ulster County, so the answer is Ulster County.
|
|
||||||
Action 3: Finish[Ulster County]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
cot_prompt_short = '''
|
|
||||||
Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
After each observation, provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas. This took place from 1780 to 1650 million years ago (Mya), during the Paleoproterozoic (Statherian Period). It is recorded in the Colorado orogen, a >500-km-wide belt of oceanic arc rock that extends southward into New Mexico. The Colorado orogeny was likely part of the larger Yavapai orogeny.
|
|
||||||
Thought 2: It does not mention the eastern sector of the Colorado orogeny. I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Observation 2: (Result 1 / 1) The Colorado orogen, formerly called the Colorado province, is a >500-km-wide belt of oceanic arc rock (1.78\u20131.65 Ga) that extends southward into New Mexico and composes a major part of the Proterozoic provinces of southwestern United States. This transcontinental collisional event occurred during the Paleoproterozoic (Statherian Period).[1] The Wyoming sector of the Colorado orogeny was formerly called the Medicine Bow orogeny. The eastern sector extends into the High Plains and is called the Central Plains orogeny. The boundary between the Colorado orogeny and the Wyoming craton is the Cheyenne belt, a 5-km-wide mylonitic shear zone that verges northward. The Cheyenne belt transects and cuts off the south edge of the older Trans-Hudson orogeny.[2]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]Observation 3: High Plains refers to one of two distinct land regions:
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Observation 4: The High Plains are a subregion of the Great Plains, mainly in the Western United States, but also partly in the Midwest states of Nebraska, Kansas, and South Dakota, generally encompassing the western part of the Great Plains before the region reaches the Rocky Mountains. The High Plains are located in eastern Montana, southeastern Wyoming, southwestern South Dakota, western Nebraska, eastern Colorado, western Kansas, eastern New Mexico, western Oklahoma, and to just south of the Texas Panhandle.[4] The southern region of the Western High Plains ecology region contains the geological formation known as Llano Estacado which can be seen from a short distance or on satellite maps.[5] From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130 m).[3]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
cot_prompt_feedback_short = '''You are also an advanced reasoning agent that can improve based on self refection. Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
After each observation, provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas. This took place from 1780 to 1650 million years ago (Mya), during the Paleoproterozoic (Statherian Period). It is recorded in the Colorado orogen, a >500-km-wide belt of oceanic arc rock that extends southward into New Mexico. The Colorado orogeny was likely part of the larger Yavapai orogeny.
|
|
||||||
Thought 2: It does not mention the eastern sector of the Colorado orogeny. I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Observation 2: (Result 1 / 1) The Colorado orogen, formerly called the Colorado province, is a >500-km-wide belt of oceanic arc rock (1.78\u20131.65 Ga) that extends southward into New Mexico and composes a major part of the Proterozoic provinces of southwestern United States. This transcontinental collisional event occurred during the Paleoproterozoic (Statherian Period).[1] The Wyoming sector of the Colorado orogeny was formerly called the Medicine Bow orogeny. The eastern sector extends into the High Plains and is called the Central Plains orogeny. The boundary between the Colorado orogeny and the Wyoming craton is the Cheyenne belt, a 5-km-wide mylonitic shear zone that verges northward. The Cheyenne belt transects and cuts off the south edge of the older Trans-Hudson orogeny.[2]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]Observation 3: High Plains refers to one of two distinct land regions:
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Observation 4: The High Plains are a subregion of the Great Plains, mainly in the Western United States, but also partly in the Midwest states of Nebraska, Kansas, and South Dakota, generally encompassing the western part of the Great Plains before the region reaches the Rocky Mountains. The High Plains are located in eastern Montana, southeastern Wyoming, southwestern South Dakota, western Nebraska, eastern Colorado, western Kansas, eastern New Mexico, western Oklahoma, and to just south of the Texas Panhandle.[4] The southern region of the Western High Plains ecology region contains the geological formation known as Llano Estacado which can be seen from a short distance or on satellite maps.[5] From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130 m).[3]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
You have attempted to answer the following question before and failed. The following reflection(s) give a plan to avoid failing to answer the question in the same way you did previously. Use them to improve your strategy of correctly answering the given question.
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
cot_prompt_feedback = '''You are also an advanced reasoning agent that can improve based on self refection. Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
After each observation, provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas. This took place from 1780 to 1650 million years ago (Mya), during the Paleoproterozoic (Statherian Period). It is recorded in the Colorado orogen, a >500-km-wide belt of oceanic arc rock that extends southward into New Mexico. The Colorado orogeny was likely part of the larger Yavapai orogeny.
|
|
||||||
Thought 2: It does not mention the eastern sector of the Colorado orogeny. I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Observation 2: (Result 1 / 1) The Colorado orogen, formerly called the Colorado province, is a >500-km-wide belt of oceanic arc rock (1.78\u20131.65 Ga) that extends southward into New Mexico and composes a major part of the Proterozoic provinces of southwestern United States. This transcontinental collisional event occurred during the Paleoproterozoic (Statherian Period).[1] The Wyoming sector of the Colorado orogeny was formerly called the Medicine Bow orogeny. The eastern sector extends into the High Plains and is called the Central Plains orogeny. The boundary between the Colorado orogeny and the Wyoming craton is the Cheyenne belt, a 5-km-wide mylonitic shear zone that verges northward. The Cheyenne belt transects and cuts off the south edge of the older Trans-Hudson orogeny.[2]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]Observation 3: High Plains refers to one of two distinct land regions:
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Observation 4: The High Plains are a subregion of the Great Plains, mainly in the Western United States, but also partly in the Midwest states of Nebraska, Kansas, and South Dakota, generally encompassing the western part of the Great Plains before the region reaches the Rocky Mountains. The High Plains are located in eastern Montana, southeastern Wyoming, southwestern South Dakota, western Nebraska, eastern Colorado, western Kansas, eastern New Mexico, western Oklahoma, and to just south of the Texas Panhandle.[4] The southern region of the Western High Plains ecology region contains the geological formation known as Llano Estacado which can be seen from a short distance or on satellite maps.[5] From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130 m).[3]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Guitars for Wounded Warriors is an album that was recorded in the village in which New York county?
|
|
||||||
Thought 1: I need to search Guitars for Wounded Warriors, then find in which New York county it was recorded.
|
|
||||||
Action 1: Search[Guitars for Wounded Warriors]
|
|
||||||
Observation 1: Guitars for Wounded Warriors is the sixth solo studio album by guitarist Brian Tarquin, released in August 2014 by Cleopatra Records. In 2016, it received a Best Compilation Album nomination by the Independent Music Awards. All tracks were recorded at Tarquin's Jungle Room Studios in New Paltz (village), New York. Being moved by the lack of support for returning veterans through his life Tarquin decided to compose a heavy instrumental rock album as a way to show his appreciation to all veterans. So once again he enlisted top notch players to guest with him on the album, Billy Sheehan on Black Hawk, Ron \"Bumblefoot\" Thal and Reb Beach on Taliban Terror, Steve Morse on Freedom, Chris Poland on Charlie Surfs and Hunting, Chuck Loeb on Escape Kabul, Hal Lindes on Sand & Blood, Gary Hoey on 5 Klicks To Hell and Baghdad, Alex De Rosso Dokken on Broken Arrow, and The Flyin' Ryan Brothers on Surgical Strike. The entire album was engineered, produced and composed by Tarquin especially for each one of the guest guitarists. Partial proceeds are donated to the Fisher House Foundation from sales.
|
|
||||||
Thought 2: Guitars for Wounded Warriors was recorded in New Paltz (village), New York. I need to search New Paltz (village), New York and find which county it is in
|
|
||||||
Action 2: Search[New Paltz (village), New York]
|
|
||||||
Observation 2: New Paltz is a village in Ulster County located in the U.S. state of New York. It is approximately 80 miles (130 km) north of New York City and 70 miles (110 km) south of Albany. The population was 7,324 at the 2020 census.[3]
|
|
||||||
Thought 3: New Paltz (village), New York is in Ulster County, so the answer is Ulster County.
|
|
||||||
Action 3: Finish[Ulster County]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
You have attempted to answer the following question before and failed, either because your reasoning for the answer was incorrect or the phrasing of your response did not exactly match the answer. The following reflection(s) give a plan to avoid failing to answer the question in the same way you did previously. Use them to improve your strategy of correctly answering the given question.
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
When providing the thought and action for the current trial, that into account these failed trajectories and make sure not to repeat the same mistakes and incorrect answers.
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
vote_prompt = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by pairs of thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a list of trajectories, decide which trajectory is most promising. Analyze each trajectory in detail and consider possible errors, then conclude in the last line "The best trajectory is {s}", where s the integer id of the trajectory.
|
|
||||||
'''
|
|
||||||
|
|
||||||
compare_prompt = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by pairs of thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Briefly analyze the correctness of the following two trajectories. Conclude in the last line "The more correct trajectory is 1", "The more correct trajectory is 2", or "The two trajectories are similarly correct".
|
|
||||||
'''
|
|
||||||
|
|
||||||
score_prompt = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by pairs of thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, analyze the following trajectory, then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?
|
|
||||||
Thought 1: I need to search for the football manager who recruited David Beckham, and then find out the timeframe in which he managed Manchester United.
|
|
||||||
Action 1: Search[David Beckham's football manager]
|
|
||||||
Observation 1: Could not find David Beckham's football manager. Similar: ['David Beckham', 'Inter Miami CF (redirect from Miami Beckham United)', 'Victoria Beckham', 'The Beckham Experiment', 'Miami Freedom Park'].
|
|
||||||
Thought 2: I couldn't find the information on David Beckham's football manager. I need to search Manchester United and find out the timeframe of the manager.
|
|
||||||
Action 2: Search[Manchester United]
|
|
||||||
Observation 2: Manchester United Football Club, commonly referred to as Man United (often stylised as Man Utd), or simply United, is a professional football club based in Old Trafford, Greater Manchester, England. The club competes in the Premier League, the top division in the English football league system. Nicknamed the Red Devils, they were founded as Newton Heath LYR Football Club in 1878, but changed their name to Manchester United in 1902. After a spell playing in Clayton, Manchester, the club moved to their current stadium, Old Trafford, in 1910.. Domestically, Manchester United have won a record 20 league titles, 12 FA Cups, six League Cups and a record 21 FA Community Shields.
|
|
||||||
Thought 3: The information about David Beckham's football manager is not available. I will search for Manchester United's managers to find the timeframe.
|
|
||||||
Action 3: Search[Manchester United managers]
|
|
||||||
Observation 3: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
Thought 4: The information about David Beckham's football manager is not available. The question asks for the timeframe when the manager managed Manchester United, so I need to find a list of Manchester United managers.
|
|
||||||
Action 4: Search[list of Manchester United managers]
|
|
||||||
Observation 4: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
Thus the correctness score is 4
|
|
||||||
|
|
||||||
Question: Which magazine was started first Arthur's Magazine or First for Women?
|
|
||||||
Thought 1: I need to search Arthur's Magazine and First for Women, and find which was started first.
|
|
||||||
Action 1: Search[Arthur's Magazine]
|
|
||||||
Observation 1: Arthur's Magazine (1844\u00e2\u0080\u00931846) was an American literary periodical published in Philadelphia in the 19th century. Edited by Timothy Shay Arthur, it featured work by Edgar A. Poe, J.H. Ingraham, Sarah Josepha Hale, Thomas G. Spear, and others.[1][2] In May 1846 it was merged into Godey's Lady's Book.[3]
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt_feedback = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: Which magazine was started first Arthur's Magazine or First for Women?
|
|
||||||
Thought 1: I need to search Arthur's Magazine and First for Women, and find which was started first.
|
|
||||||
Action 1: Search[Arthur's Magazine]
|
|
||||||
Observation 1: Arthur's Magazine (1844\u00e2\u0080\u00931846) was an American literary periodical published in Philadelphia in the 19th century. Edited by Timothy Shay Arthur, it featured work by Edgar A. Poe, J.H. Ingraham, Sarah Josepha Hale, Thomas G. Spear, and others.[1][2] In May 1846 it was merged into Godey's Lady's Book.[3]
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt_reasoning = '''You are an advanced reasoning agent that can improve based on self refection. Analyze the trajectories of your previous solutions to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
This trajectory is incorrect as my search should be related to Mexican Formula One race car drivers, not winners of the Mexican Grand Prix, a seperate event. A better search would have been for the List of Formula One Grand Prix winners, as suggested.
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: Which magazine was started first Arthur's Magazine or First for Women?
|
|
||||||
Thought 1: I need to search Arthur's Magazine and First for Women, and find which was started first.
|
|
||||||
Action 1: Search[Arthur's Magazine]
|
|
||||||
Observation 1: Arthur's Magazine (1844\u00e2\u0080\u00931846) was an American literary periodical published in Philadelphia in the 19th century. Edited by Timothy Shay Arthur, it featured work by Edgar A. Poe, J.H. Ingraham, Sarah Josepha Hale, Thomas G. Spear, and others.[1][2] In May 1846 it was merged into Godey's Lady's Book.[3]
|
|
||||||
This trajectory is correct as it is reasonable to search for the first magazine provided in the question. It is also better to have simple searches corresponding to a single entity, making this the best action.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?
|
|
||||||
Thought 1: I need to search for the football manager who recruited David Beckham, and then find out the timeframe in which he managed Manchester United.
|
|
||||||
Action 1: Search[David Beckham's football manager]
|
|
||||||
Observation 1: Could not find David Beckham's football manager. Similar: ['David Beckham', 'Inter Miami CF (redirect from Miami Beckham United)', 'Victoria Beckham', 'The Beckham Experiment', 'Miami Freedom Park'].
|
|
||||||
Thought 2: I couldn't find the information on David Beckham's football manager. I need to search Manchester United and find out the timeframe of the manager.
|
|
||||||
Action 2: Search[Manchester United]
|
|
||||||
Observation 2: Manchester United Football Club, commonly referred to as Man United (often stylised as Man Utd), or simply United, is a professional football club based in Old Trafford, Greater Manchester, England. The club competes in the Premier League, the top division in the English football league system. Nicknamed the Red Devils, they were founded as Newton Heath LYR Football Club in 1878, but changed their name to Manchester United in 1902. After a spell playing in Clayton, Manchester, the club moved to their current stadium, Old Trafford, in 1910.. Domestically, Manchester United have won a record 20 league titles, 12 FA Cups, six League Cups and a record 21 FA Community Shields.
|
|
||||||
Thought 3: The information about David Beckham's football manager is not available. I will search for Manchester United's managers to find the timeframe.
|
|
||||||
Action 3: Search[Manchester United managers]
|
|
||||||
Observation 3: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
Thought 4: The information about David Beckham's football manager is not available. The question asks for the timeframe when the manager managed Manchester United, so I need to find a list of Manchester United managers.
|
|
||||||
Action 4: Search[list of Manchester United managers]
|
|
||||||
Observation 4: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
This trajectory is mostly incorrect as I was not able to retrieve the relevant information to answer the question. The searches are too broad and not relevant to the specific player recruited, David Beckham. The last two actions are also too similar. It would be better to first search for David Beckham and lookup information related to his manager.
|
|
||||||
Thus the correctness score is 4
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
This trajectory is correct as all of my thoughts and actions are correct. It makes sense to search for Milhouse first as it is the central subject of the question. It is also correct to directly look up the relevant information in the article, instead of trying another search.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt_reasoning_feedback = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
This trajectory is incorrect as my search should be related to Mexican Formula One race car drivers, not winners it is reasonable to checkof the Mexican Grand Prix, a seperate event. A better search would have been for the List of Formula One Grand Prix winners, as suggested.
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: Which magazine was started first Arthur's Magazine or First for Women?
|
|
||||||
Thought 1: I need to search Arthur's Magazine and First for Women, and find which was started first.
|
|
||||||
Action 1: Search[Arthur's Magazine]
|
|
||||||
Observation 1: Arthur's Magazine (1844\u00e2\u0080\u00931846) was an American literary periodical published in Philadelphia in the 19th century. Edited by Timothy Shay Arthur, it featured work by Edgar A. Poe, J.H. Ingraham, Sarah Josepha Hale, Thomas G. Spear, and others.[1][2] In May 1846 it was merged into Godey's Lady's Book.[3]
|
|
||||||
This trajectory is correct as it is reasonable to search for the first magazine provided in the question. It is also better to have simple searches corresponding to a single entity, making this the best action.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?
|
|
||||||
Thought 1: I need to search for the football manager who recruited David Beckham, and then find out the timeframe in which he managed Manchester United.
|
|
||||||
Action 1: Search[David Beckham's football manager]
|
|
||||||
Observation 1: Could not find David Beckham's football manager. Similar: ['David Beckham', 'Inter Miami CF (redirect from Miami Beckham United)', 'Victoria Beckham', 'The Beckham Experiment', 'Miami Freedom Park'].
|
|
||||||
Thought 2: I couldn't find the information on David Beckham's football manager. I need to search Manchester United and find out the timeframe of the manager.
|
|
||||||
Action 2: Search[Manchester United]
|
|
||||||
Observation 2: Manchester United Football Club, commonly referred to as Man United (often stylised as Man Utd), or simply United, is a professional football club based in Old Trafford, Greater Manchester, England. The club competes in the Premier League, the top division in the English football league system. Nicknamed the Red Devils, they were founded as Newton Heath LYR Football Club in 1878, but changed their name to Manchester United in 1902. After a spell playing in Clayton, Manchester, the club moved to their current stadium, Old Trafford, in 1910.. Domestically, Manchester United have won a record 20 league titles, 12 FA Cups, six League Cups and a record 21 FA Community Shields.
|
|
||||||
Thought 3: The information about David Beckham's football manager is not available. I will search for Manchester United's managers to find the timeframe.
|
|
||||||
Action 3: Search[Manchester United managers]
|
|
||||||
Observation 3: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
Thought 4: The information about David Beckham's football manager is not available. The question asks for the timeframe when the manager managed Manchester United, so I need to find a list of Manchester United managers.
|
|
||||||
Action 4: Search[list of Manchester United managers]
|
|
||||||
Observation 4: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
This trajectory is mostly incorrect as I was not able to retrieve the relevant information to answer the question. The searches are too broad and not relevant to the specific player recruited, David Beckham. The last two actions are also too similar. It would be better to first search for David Beckham and lookup information related to his manager.
|
|
||||||
Thus the correctness score is 4
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
This trajectory is correct as all of my thoughts and actions are correct. It makes sense to search for Milhouse first as it is the central subject of the question. It is also correct to directly look up the relevant information in the article, instead of trying another search.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt_reasoning_feedback_short = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
This trajectory is incorrect as my search should be related to Mexican Formula One race car drivers, not winners of the Mexican Grand Prix, a seperate event. A better search would have been for the List of Formula One Grand Prix winners, as suggested.
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
This trajectory is correct as all of my thoughts and actions are correct. It makes sense to search for Milhouse first as it is the central subject of the question. It is also correct to directly look up the relevant information in the article, instead of trying another search.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
rap_prompt = '''
|
|
||||||
Solve a question answering task with interleaving Thought and Action steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
Provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Thought 2: Then I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Guitars for Wounded Warriors is an album that was recorded in the village in which New York county?
|
|
||||||
Thought 1: I need to search Guitars for Wounded Warriors, then find in which New York county it was recorded.
|
|
||||||
Action 1: Search[Guitars for Wounded Warriors]
|
|
||||||
Thought 2: I need to search New Paltz (village), New York and find which county it is in
|
|
||||||
Action 2: Search[New Paltz (village), New York]
|
|
||||||
Thought 3: New Paltz (village), New York is in Ulster County, so the answer is Ulster County.
|
|
||||||
Action 3: Finish[Ulster County]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Thought 2: I can look up "named after" for finding the specific individual Milhouse is named after.
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
+12
-6
@@ -10,14 +10,20 @@ module YiemAgent
|
|||||||
include("type.jl")
|
include("type.jl")
|
||||||
using .type
|
using .type
|
||||||
|
|
||||||
include("util.jl")
|
include("utils.jl")
|
||||||
using .util
|
using .utils
|
||||||
|
|
||||||
include("llmfunction.jl")
|
include("toolRegistry.jl")
|
||||||
using .llmfunction
|
using .toolRegistry
|
||||||
|
|
||||||
include("interface.jl")
|
# include("llmfunction.jl")
|
||||||
using .interface
|
# using .llmfunction
|
||||||
|
|
||||||
|
include("agentCore.jl")
|
||||||
|
using .agentCore
|
||||||
|
|
||||||
|
include("api.jl")
|
||||||
|
using .api
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|||||||
+1154
File diff suppressed because it is too large
Load Diff
+401
@@ -0,0 +1,401 @@
|
|||||||
|
module api
|
||||||
|
|
||||||
|
export prompt
|
||||||
|
|
||||||
|
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||||
|
DataFrames
|
||||||
|
using GeneralUtils
|
||||||
|
using ..type, ..utils
|
||||||
|
|
||||||
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Send a message to the agent's input channel.
|
||||||
|
|
||||||
|
Blocks if the input channel buffer is full (capacity 16 by default).
|
||||||
|
The agent processes messages from `inputChannel` in the background task.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `agent::yiemAgent`: The agent instance to send a message to
|
||||||
|
- `msg`: The message to send (any type accepted by the agent's processing pipeline)
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- The same `agent` instance for chaining
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- Use `take_response(agent)` to receive the agent's response after sending a message.
|
||||||
|
- Use `follow_up(agent, msg)` to send messages while the agent is still processing.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> run_agent(agent, "Hello!")
|
||||||
|
yiemAgent(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function run_agent(agent::yiemAgent, msg)
|
||||||
|
put!(agent.inputChannel, msg)
|
||||||
|
return agent
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Take a response from the agent's output channel.
|
||||||
|
|
||||||
|
Blocks until the agent sends a response.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `agent::yiemAgent`: The agent instance to receive a response from
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- An `assistantMessage` instance representing the agent's response
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- Use `run_agent(agent, msg)` to send a message before calling this function.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> response = take_response(agent)
|
||||||
|
assistantMessage(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function take_response(agent::yiemAgent)
|
||||||
|
return take!(agent.outputChannel)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Send a follow-up message while the agent is still processing.
|
||||||
|
|
||||||
|
Follow-up messages are queued and processed after all `inputChannel` messages
|
||||||
|
and before any tool call results are sent.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `agent::yiemAgent`: The agent instance to send a follow-up message to
|
||||||
|
- `msg`: The follow-up message to send
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- The same `agent` instance for chaining
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- Use `run_agent(agent, msg)` for the primary message and `follow_up(agent, msg)` for additional
|
||||||
|
messages while the agent is processing.
|
||||||
|
- Follow-up messages are buffered in a separate channel (capacity 32 by default).
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> follow_up(agent, "Also consider red wines")
|
||||||
|
yiemAgent(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function follow_up(agent::yiemAgent, msg)
|
||||||
|
put!(agent.followUpChannel, msg)
|
||||||
|
return agent
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Gracefully stop the agent.
|
||||||
|
|
||||||
|
Sends a `:shutdown` signal to the input channel, waits for the background task to finish,
|
||||||
|
then closes all channels (`inputChannel`, `outputChannel`, `followUpChannel`).
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `agent::yiemAgent`: The agent instance to stop
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing`
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- After calling `stop_agent`, the agent is no longer usable. A new agent must be created
|
||||||
|
for further interaction.
|
||||||
|
- If the background task throws a `TaskFailedException`, it is rethrown.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> stop_agent(agent)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function stop_agent(agent::yiemAgent)
|
||||||
|
put!(agent.inputChannel, :shutdown)
|
||||||
|
try
|
||||||
|
fetch(agent._agent_loop)
|
||||||
|
catch e
|
||||||
|
if e isa TaskFailedException
|
||||||
|
rethrow(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
close(agent.inputChannel)
|
||||||
|
close(agent.outputChannel)
|
||||||
|
close(agent.followUpChannel)
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
""" Recursively convert dictionary-like variable (e.g. JSON.Object) into an OrderedDict.
|
||||||
|
|
||||||
|
The function walks any nested structure composed of `AbstractDict` (e.g., `JSON.Object`,
|
||||||
|
`Dict`, `OrderedDict`) and `AbstractArray` and produces a new tree where
|
||||||
|
every dictionary-like node is an `OrderedDict` and every array-like node is a `Vector{Any}`.
|
||||||
|
Scalar values (numbers, strings, booleans, `nothing`, etc.) are returned unchanged.
|
||||||
|
Does **not** mutate the input; it always allocates new containers.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `x`
|
||||||
|
Any Julia value. If `x` is an `AbstractDict` it will be converted to an `OrderedDict`;
|
||||||
|
if it is an `AbstractArray` its elements will be processed recursively.
|
||||||
|
|
||||||
|
# Keyword Arguments
|
||||||
|
- `keytype::Type=Any`
|
||||||
|
The key type for the output OrderedDict. Use `String` for `OrderedDict{String,Any}`,
|
||||||
|
`Symbol` for `OrderedDict{Symbol,Any}`, or `Any` to preserve original key types.
|
||||||
|
- `sort_order::Union{Nothing, Vector}=nothing`
|
||||||
|
Vector of keys specifying the desired order. Keys are arranged in the specified order
|
||||||
|
first, followed by any remaining keys.
|
||||||
|
|
||||||
|
# Return
|
||||||
|
- A newly allocated nested structure composed of `OrderedDict{keytype,Any}` and `Vector{Any}`
|
||||||
|
that mirrors the input shape but uses ordered Julia containers.
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- The function treats any `AbstractDict` as a mapping source, so it works with
|
||||||
|
`JSON.Object`, `Dict`, `OrderedDict`, etc.
|
||||||
|
- Arrays are returned as `Vector{Any}` with their elements processed recursively.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> using JSON, DataStructures
|
||||||
|
julia> d = Dict(
|
||||||
|
"a" => 4,
|
||||||
|
"b" => 6,
|
||||||
|
"c" => Dict(
|
||||||
|
"d"=>7,
|
||||||
|
:e=>Dict(
|
||||||
|
"f"=>"hey",
|
||||||
|
"g"=>Dict(
|
||||||
|
"world"=>[1, "2", 3, Dict(:dd=>4.7)]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
julia> jsonstring = JSON.json(d)
|
||||||
|
julia> A1 = JSON.parse(jsonstring) # A1 type is JSON.Object
|
||||||
|
julia> A2 = dictify(A1; keytype=String)
|
||||||
|
OrderedDict{String,Any} with 3 entries:
|
||||||
|
"a" => 4
|
||||||
|
"b" => 6
|
||||||
|
"c" => OrderedDict("d"=>7, "e"=>Dict("f"=>"hey", "g"=>Dict("world"=>[1, "2", 3, 4.7])))
|
||||||
|
|
||||||
|
julia> A3 = dictify(A1; keytype=Symbol)
|
||||||
|
OrderedDict{Symbol,Any} with 3 entries:
|
||||||
|
:a => 4
|
||||||
|
:b => 6
|
||||||
|
:c => OrderedDict(:d=>7, :e=>Dict("f"=>"hey", "g"=>Dict("world"=>[1, "2", 3, 4.7])))
|
||||||
|
|
||||||
|
julia> B1 = dictify(d; keytype=String)
|
||||||
|
OrderedDict{String, Any} with 3 entries:
|
||||||
|
```
|
||||||
|
|
||||||
|
**With sort_order:**
|
||||||
|
```jldoctest
|
||||||
|
julia> d = Dict("a"=>1, "b"=>2, "c"=>3)
|
||||||
|
julia> dictify(d; sort_order=["c", "a"])
|
||||||
|
OrderedDict{String,Int} with 3 entries:
|
||||||
|
"c" => 3
|
||||||
|
"a" => 1
|
||||||
|
"b" => 2
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function dictify(x::T; keytype::Type=Any, sort_order::Union{Nothing, Vector}=nothing
|
||||||
|
)::OrderedDict where {T<:AbstractDict}
|
||||||
|
|
||||||
|
# this function is example
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end # module interface
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-1563
File diff suppressed because it is too large
Load Diff
-1037
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,271 @@
|
|||||||
|
module toolRegistry
|
||||||
|
|
||||||
|
export toolStore, loadTools, registerTool, getTools, clearTools, listTool
|
||||||
|
|
||||||
|
using Dates
|
||||||
|
using JSON, DataStructures
|
||||||
|
using ..type
|
||||||
|
|
||||||
|
"""
|
||||||
|
Per-agent isolated tool storage.
|
||||||
|
|
||||||
|
Each agent gets its own `toolStore` so tool registration is independent —
|
||||||
|
`registerTool(store, tool)` only affects that agent's tool set.
|
||||||
|
|
||||||
|
# Fields
|
||||||
|
- `tools::OrderedDict{String, agentTool}` — keyed by name for O(1) lookup + ordered iteration
|
||||||
|
- `name::String` — identifier for debugging/logs
|
||||||
|
"""
|
||||||
|
struct toolStore
|
||||||
|
tools::OrderedDict{String, agentTool}
|
||||||
|
name::String
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
toolStore(; name="default") -> toolStore
|
||||||
|
|
||||||
|
Create a new empty tool store.
|
||||||
|
|
||||||
|
# Keyword Arguments
|
||||||
|
- `name::String`: Display name for logging (default: `"default"`)
|
||||||
|
|
||||||
|
# Example
|
||||||
|
```julia
|
||||||
|
julia> store = toolStore(name="agent1")
|
||||||
|
toolStore(OrderedDict{String, agentTool}(), "agent1")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function toolStore(; name::String="default")::toolStore
|
||||||
|
toolStore(OrderedDict{String, agentTool}(), name)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
listTool(store::toolStore) -> agentTool
|
||||||
|
|
||||||
|
Return an `agentTool` definition for listing registered tools.
|
||||||
|
|
||||||
|
Each call produces a **new** tool object that captures (closes over)
|
||||||
|
`store`. `loadTools` auto-registers one so the LLM can discover tools
|
||||||
|
at runtime.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `store`: The tool store whose tools will be listed when the tool runs
|
||||||
|
|
||||||
|
# Example
|
||||||
|
```julia
|
||||||
|
julia> store = toolStore(name="agent1");
|
||||||
|
|
||||||
|
julia> loadTools(store, "src/tools") # auto-registers listTools
|
||||||
|
[toolRegistry:agent1] Loaded tool: getWeather (Weather Lookup)
|
||||||
|
[toolRegistry:agent1] Registered tool: listTools
|
||||||
|
|
||||||
|
julia> tools = getTools(store)
|
||||||
|
OrderedDict{String, agentTool} with 4 entries:
|
||||||
|
"getWeather" => agentTool(...)
|
||||||
|
"getTime" => agentTool(...)
|
||||||
|
"writeTool" => agentTool(...)
|
||||||
|
"listTools" => agentTool(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function listTool(store::toolStore)::agentTool
|
||||||
|
return agentTool(
|
||||||
|
name = "listTools",
|
||||||
|
label = "List Tools",
|
||||||
|
description = "List all available tools with their names, labels, and descriptions. Use this before creating a new tool to check for name collisions.",
|
||||||
|
inputSchema = Dict{String,Any}(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict{String,Any}(),
|
||||||
|
"required" => Any[]
|
||||||
|
),
|
||||||
|
execute = (toolCallId, args, signal, onPartialResult) -> begin
|
||||||
|
tools = getTools(store)
|
||||||
|
if isempty(tools)
|
||||||
|
result_text = "No tools registered."
|
||||||
|
else
|
||||||
|
lines = String["- $(t.name): $(t.label) — $(t.description)" for (k, t) in tools]
|
||||||
|
result_text = "Available tools:\n" * join(lines, "\n")
|
||||||
|
end
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent(result_text)],
|
||||||
|
Dict{Any,Any}("count" => length(tools)),
|
||||||
|
nothing, false
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
prepareArguments = nothing,
|
||||||
|
validateRequiredArgs = nothing,
|
||||||
|
parallelToolExecute = false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Load `.jl` tool files from `dir` into `store`, then auto-register
|
||||||
|
`listTool` so the LLM can discover available tools at runtime.
|
||||||
|
|
||||||
|
Each `.jl` file must define `function getTool()::agentTool ... end`.
|
||||||
|
Files are sorted alphabetically for deterministic registration order.
|
||||||
|
Each file is loaded into its own Julia submodule to avoid name collisions.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `store`: Tool store to populate
|
||||||
|
- `dir`: Directory containing `.jl` tool files
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- The same `store.tools` dict (modified in place)
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
- Throws `ArgumentError` if `dir` does not exist or a file lacks `getTool()`
|
||||||
|
|
||||||
|
# Example
|
||||||
|
```julia
|
||||||
|
julia> store = toolStore(name="agent1");
|
||||||
|
|
||||||
|
julia> loadTools(store, "src/tools")
|
||||||
|
[toolRegistry:agent1] Loaded tool: getWeather (Weather Lookup)
|
||||||
|
[toolRegistry:agent1] Loaded tool: getTime (Time Lookup)
|
||||||
|
[toolRegistry:agent1] Registered tool: listTools
|
||||||
|
OrderedDict{String, agentTool} with 3 entries:
|
||||||
|
"getWeather" => agentTool(...)
|
||||||
|
"getTime" => agentTool(...)
|
||||||
|
"listTools" => agentTool(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function loadTools(store::toolStore, dir::String)::OrderedDict{String, agentTool}
|
||||||
|
if !isdir(dir)
|
||||||
|
throw(ArgumentError("Tool directory does not exist: $dir"))
|
||||||
|
end
|
||||||
|
|
||||||
|
jl_files = filter(f -> endswith(f, ".jl") && !occursin(r"(?i)registry", f), readdir(dir))
|
||||||
|
sort!(jl_files)
|
||||||
|
|
||||||
|
for filename in jl_files
|
||||||
|
filepath = joinpath(dir, filename)
|
||||||
|
|
||||||
|
# Derive a unique module name from the filename only (not full path).
|
||||||
|
# e.g. "getWeather.jl" -> "_tool_getWeather"
|
||||||
|
mod_name = Symbol("_tool_", replace(rstrip(filename, '.'), ".jl" => ""))
|
||||||
|
|
||||||
|
# Build the complete module as a string and eval the parsed code.
|
||||||
|
# Julia does not allow `module ... end` inside eval(quote ...),
|
||||||
|
# and constructing the module AST by hand is fragile.
|
||||||
|
# Instead, we generate the full module source as a string,
|
||||||
|
# parse it, and eval the resulting expression.
|
||||||
|
# Each tool file declares its own dependencies via `using` statements
|
||||||
|
# at the top of the file — the registry only injects `using ..type`
|
||||||
|
# to make core types (agentTool, textContent, etc.) available.
|
||||||
|
file_content = read(filepath, String)
|
||||||
|
module_code = """
|
||||||
|
module $(mod_name)
|
||||||
|
using ..type
|
||||||
|
$(file_content)
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
mod = eval(Meta.parse(module_code))
|
||||||
|
|
||||||
|
# Call getTool() via Core.eval in the submodule's scope.
|
||||||
|
# This evaluates getTool() entirely within the new module's world,
|
||||||
|
# completely avoiding world-age issues — no invokelatest needed.
|
||||||
|
# Note: all uses of `tool` must be inside the `try` block because
|
||||||
|
# Julia 1.12's SSA form doesn't track `tool` as definitely assigned
|
||||||
|
# after a `try-catch` where it's only assigned inside `try`.
|
||||||
|
try
|
||||||
|
tool = Core.eval(mod, :(getTool()))
|
||||||
|
if !(tool isa agentTool)
|
||||||
|
throw(ArgumentError(
|
||||||
|
"getTool() in $(filepath) did not return an agentTool instance, got: $(typeof(tool))"
|
||||||
|
))
|
||||||
|
end
|
||||||
|
store.tools[tool.name] = tool
|
||||||
|
println("[$(store.name)] Loaded tool: $(tool.name) ($(tool.label))")
|
||||||
|
catch e
|
||||||
|
if e isa UndefVarError || occursin("getTool", sprint(showerror, e))
|
||||||
|
throw(ArgumentError(
|
||||||
|
"Tool file $(filepath) does not define a `getTool()` function in module $(mod_name). " *
|
||||||
|
"Each tool file must define: function getTool()::agentTool ... end"
|
||||||
|
))
|
||||||
|
end
|
||||||
|
rethrow(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
registerTool(store, listTool(store))
|
||||||
|
return store.tools
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
registerTool(store::toolStore, tool::agentTool) -> OrderedDict{String, agentTool}
|
||||||
|
|
||||||
|
Add `tool` to `store`, overwriting any existing tool with the same name.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `store`: Tool store to modify
|
||||||
|
- `tool`: The `agentTool` to register
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- The same `store.tools` dict (modified in place)
|
||||||
|
|
||||||
|
# Example
|
||||||
|
```julia
|
||||||
|
julia> store = toolStore(name="agent1");
|
||||||
|
|
||||||
|
julia> registerTool(store, listTool(store))
|
||||||
|
[toolRegistry:agent1] Registered tool: listTools
|
||||||
|
OrderedDict{String, agentTool} with 1 entry:
|
||||||
|
"listTools" => agentTool(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function registerTool(store::toolStore, tool::agentTool)::OrderedDict{String, agentTool}
|
||||||
|
store.tools[tool.name] = tool
|
||||||
|
println("[$(store.name)] Registered tool: $(tool.name)")
|
||||||
|
return store.tools
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Return the tools registered in `store`.
|
||||||
|
|
||||||
|
The returned dict is the **same object** stored inside `store` — mutations
|
||||||
|
to it (e.g. via `registerTool`) are visible through subsequent calls.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `store`: Tool store to query
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `OrderedDict{String, agentTool}`: Tools keyed by name, in registration order
|
||||||
|
|
||||||
|
# Example
|
||||||
|
```julia
|
||||||
|
julia> tools = getTools(store)
|
||||||
|
OrderedDict{String, agentTool} with 2 entries:
|
||||||
|
"getWeather" => agentTool(...)
|
||||||
|
"getTime" => agentTool(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function getTools(store::toolStore)::OrderedDict{String, agentTool}
|
||||||
|
return store.tools
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Remove all tools from `store`.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `store`: Tool store to clear
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing`
|
||||||
|
|
||||||
|
# Example
|
||||||
|
```julia
|
||||||
|
julia> clearTools(store)
|
||||||
|
[toolRegistry:agent1] Registry cleared
|
||||||
|
nothing
|
||||||
|
|
||||||
|
julia> getTools(store)
|
||||||
|
OrderedDict{String, agentTool} with 0 entries
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function clearTools(store::toolStore)::Nothing
|
||||||
|
empty!(store.tools)
|
||||||
|
println("[$(store.name)] Registry cleared")
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
end # module
|
||||||
+1625
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
|||||||
|
using Dates
|
||||||
|
|
||||||
|
"""
|
||||||
|
Validate required arguments for the getTime tool.
|
||||||
|
|
||||||
|
Demonstrates custom validation beyond simple required-field checking:
|
||||||
|
- Ensures at least one time source (timezone or city) is provided
|
||||||
|
- Validates timezone is in IANA format if specified
|
||||||
|
- Validates city name is not empty if specified
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `args::Dict{String,Any}`: Arguments from the LLM
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing` if validation passes
|
||||||
|
- `String` error message if validation fails
|
||||||
|
"""
|
||||||
|
function validateRequiredArgs(args::Dict{String,Any})::Union{Nothing,String}
|
||||||
|
tz = get(args, "timezone", nothing)
|
||||||
|
city = get(args, "city", "")
|
||||||
|
|
||||||
|
hasTz = tz !== nothing && !isempty(tz)
|
||||||
|
hasCity = !isempty(city)
|
||||||
|
|
||||||
|
# At least one of timezone or city is required
|
||||||
|
if !hasTz && !hasCity
|
||||||
|
return "Missing required argument: provide at least one of 'timezone' or 'city'"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate timezone format (IANA tz database: "Continent/City" or "Continent/City/SubCity")
|
||||||
|
if hasTz
|
||||||
|
tz_str = string(tz)
|
||||||
|
if !occursin(r"^[A-Za-z]+\/[A-Za-z]+(/[A-Za-z]+)*$", tz_str)
|
||||||
|
return "Invalid timezone format: '$tz_str'. Use IANA format, e.g. 'America/New_York' or 'Asia/Tokyo'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Execute the getTime tool.
|
||||||
|
|
||||||
|
Returns mock time data for the given timezone or city.
|
||||||
|
"""
|
||||||
|
function executeTool(toolCallId::String, args::Dict{String,Any}, signal::Union{Nothing,abortSignal},
|
||||||
|
onPartialResult::Function)::agentToolResult
|
||||||
|
tz = get(args, "timezone", nothing)
|
||||||
|
city = get(args, "city", "")
|
||||||
|
if tz !== nothing
|
||||||
|
result = "Current time in $(tz): $(now())"
|
||||||
|
else
|
||||||
|
result = "Current time in $(city): $(now())"
|
||||||
|
end
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent(result)],
|
||||||
|
Dict{Any,Any}(), nothing, false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Define and return the getTime agentTool.
|
||||||
|
"""
|
||||||
|
function getTool()::agentTool
|
||||||
|
return agentTool(
|
||||||
|
name = "getTime",
|
||||||
|
label = "Time Lookup",
|
||||||
|
description = "Get current local time for a timezone or city.",
|
||||||
|
inputSchema = Dict{String,Any}(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict(
|
||||||
|
"timezone" => Dict("type" => "string", "description" => "IANA timezone, e.g. 'America/New_York'"),
|
||||||
|
"city" => Dict("type" => "string", "description" => "City name as fallback")
|
||||||
|
),
|
||||||
|
"required" => []
|
||||||
|
),
|
||||||
|
execute = executeTool,
|
||||||
|
prepareArguments = nothing,
|
||||||
|
validateRequiredArgs = validateRequiredArgs,
|
||||||
|
parallelToolExecute = false
|
||||||
|
)
|
||||||
|
end
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"""
|
||||||
|
Execute the getWeather tool.
|
||||||
|
|
||||||
|
Returns mock weather data for the given city and temperature units.
|
||||||
|
"""
|
||||||
|
function executeTool(toolCallId::String, args::Dict{String,Any}, signal::Union{Nothing,abortSignal},
|
||||||
|
onPartialResult::Function)::agentToolResult
|
||||||
|
city = get(args, "city", "")
|
||||||
|
units = get(args, "units", "celsius")
|
||||||
|
temp = units == "fahrenheit" ? "72" : "22"
|
||||||
|
unit_symbol = units == "celsius" ? "°C" : "°F"
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("Weather in $(city): Sunny, $(temp)$(unit_symbol)")],
|
||||||
|
Dict{Any,Any}(), nothing, false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Define and return the getWeather agentTool.
|
||||||
|
"""
|
||||||
|
function getTool()::agentTool
|
||||||
|
return agentTool(
|
||||||
|
name = "getWeather",
|
||||||
|
label = "Weather Lookup",
|
||||||
|
description = "Fetch current weather and forecast for a given city.",
|
||||||
|
inputSchema = Dict{String,Any}(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict(
|
||||||
|
"city" => Dict("type" => "string", "description" => "City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'"),
|
||||||
|
"units" => Dict("type" => "string", "enum" => ["celsius", "fahrenheit"], "default" => "celsius", "description" => "Temperature scale")
|
||||||
|
),
|
||||||
|
"required" => ["city"]
|
||||||
|
),
|
||||||
|
execute = executeTool,
|
||||||
|
prepareArguments = nothing,
|
||||||
|
validateRequiredArgs = nothing,
|
||||||
|
parallelToolExecute = false
|
||||||
|
)
|
||||||
|
end
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
using JSON
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tool that writes new Julia tool module files to disk.
|
||||||
|
|
||||||
|
The agent can use this tool when it encounters a task that no existing tool
|
||||||
|
can handle. Provide the tool's name, label, description, inputSchema, and
|
||||||
|
execute logic as Julia code. The tool is written to `src/tools/<name>.jl`.
|
||||||
|
|
||||||
|
After calling this tool, restart the agent so `loadTools(agent._tool_store, "src/tools")` picks
|
||||||
|
up the new file. The new tool is immediately available.
|
||||||
|
|
||||||
|
# Example
|
||||||
|
|
||||||
|
1. Agent calls writeTool with a spec for a "searchWine" tool
|
||||||
|
2. writeTool generates src/tools/searchWine.jl
|
||||||
|
3. Restart agent — loadTools() picks up the new file
|
||||||
|
4. Agent calls searchWine with args
|
||||||
|
|
||||||
|
# How It Works
|
||||||
|
|
||||||
|
writeTool is a **file writer**, not a code generator. The LLM provides the
|
||||||
|
tool logic as `executeCode`, and writeTool wraps it in Julia boilerplate:
|
||||||
|
- Converts `inputSchema` Dict into Julia `Dict{String,Any}(...)` string
|
||||||
|
- Indents `executeCode` with 4 spaces
|
||||||
|
- Wraps it inside `function executeTool(...)::agentToolResult ... end`
|
||||||
|
- Appends `getTool()` returning an `agentTool` struct
|
||||||
|
- Writes the combined string to `src/tools/<name>.jl`
|
||||||
|
|
||||||
|
# Important Notes
|
||||||
|
|
||||||
|
- The `executeCode` string is embedded literally into the generated tool.
|
||||||
|
Use `args["param_name"]` to access input parameters.
|
||||||
|
- The code string should be the function body (NOT wrapped in a function).
|
||||||
|
Lines will be indented with 4 spaces inside the execute function.
|
||||||
|
- Tool names must be valid Julia identifiers (lowercase letters, digits, underscores,
|
||||||
|
no leading digits or special characters).
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
Validate that a tool name is a valid Julia identifier.
|
||||||
|
"""
|
||||||
|
function validateToolName(name::String)::Union{Nothing,String}
|
||||||
|
if !occursin(r"^[a-zA-Z_][a-zA-Z0-9_!]*$", name)
|
||||||
|
return "Invalid tool name: '$name'. Tool names must be valid Julia identifiers (letters, digits, underscores, starting with a letter or underscore)."
|
||||||
|
end
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Indent a multi-line code string by the specified number of spaces.
|
||||||
|
"""
|
||||||
|
function indent_code(code::String, n::Int)::String
|
||||||
|
prefix = " "^n
|
||||||
|
lines = split(code, '\n')
|
||||||
|
result_lines = String[prefix * line for line in lines]
|
||||||
|
return join(result_lines, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a Julia Dict to a valid Julia Dict{String,Any}(...) literal string.
|
||||||
|
"""
|
||||||
|
function dict_to_julia_literal(d)::String
|
||||||
|
if d isa Dict
|
||||||
|
items = String[]
|
||||||
|
for (k, v) in d
|
||||||
|
key_str = json_string(k)
|
||||||
|
val_str = value_to_julia(v)
|
||||||
|
push!(items, "$key_str => $val_str")
|
||||||
|
end
|
||||||
|
return "Dict{String,Any}(" * join(items, ", ") * ")"
|
||||||
|
else
|
||||||
|
return value_to_julia(d)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function value_to_julia(v)::String
|
||||||
|
if v isa Dict
|
||||||
|
return dict_to_julia_literal(v)
|
||||||
|
elseif v isa Vector
|
||||||
|
items = [value_to_julia(x) for x in v]
|
||||||
|
return "[" * join(items, ", ") * "]"
|
||||||
|
elseif v isa String
|
||||||
|
escaped = replace(v, "\\" => "\\\\")
|
||||||
|
escaped = replace(escaped, "\"" => "\\\"")
|
||||||
|
return "\"$escaped\""
|
||||||
|
elseif v isa Number
|
||||||
|
return string(v)
|
||||||
|
elseif v isa Bool
|
||||||
|
return string(v)
|
||||||
|
elseif v === nothing
|
||||||
|
return "nothing"
|
||||||
|
else
|
||||||
|
return "\"$(v)\""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert any Julia value to a JSON string.
|
||||||
|
"""
|
||||||
|
function json_string(v)::String
|
||||||
|
return JSON.json(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Define and return the writeTool agentTool.
|
||||||
|
"""
|
||||||
|
function getTool()::agentTool
|
||||||
|
return agentTool(
|
||||||
|
name = "writeTool",
|
||||||
|
label = "Create Tool",
|
||||||
|
description = "Write a new Julia tool module file to src/tools/<name>.jl. The LLM provides the tool logic as executeCode; writeTool wraps it in Julia boilerplate and writes the file. Restart the agent to load the new tool.",
|
||||||
|
inputSchema = Dict{String,Any}(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict(
|
||||||
|
"name" => Dict("type" => "string", "description" => "Unique tool name (valid Julia identifier, no spaces or special chars)"),
|
||||||
|
"label" => Dict("type" => "string", "description" => "Human-readable tool name shown in tool descriptions"),
|
||||||
|
"description" => Dict("type" => "string", "description" => "What the tool does (shown to LLM for tool selection decisions)"),
|
||||||
|
"inputSchema" => Dict(
|
||||||
|
"type" => "object",
|
||||||
|
"description" => "JSON Schema describing tool parameters in MCP format"
|
||||||
|
),
|
||||||
|
"executeCode" => Dict("type" => "string", "description" => "Julia code for the execute function body. Use args[\"key\"] to access parameters. Do NOT wrap in a function definition."),
|
||||||
|
"validateCode" => Dict("type" => "string", "optional" => true, "description" => "Optional custom validation Julia code (runs before execute). Use args[\"key\"] to access parameters. Return nothing to pass, or a string error message to fail."),
|
||||||
|
"prepareCode" => Dict("type" => "string", "optional" => true, "description" => "Optional argument preparation code (runs before validation). Return modified args dict."),
|
||||||
|
"parallel" => Dict("type" => "boolean", "default" => false, "description" => "Whether this tool can run in parallel with other tools")
|
||||||
|
),
|
||||||
|
"required" => ["name", "label", "description", "inputSchema", "executeCode"]
|
||||||
|
),
|
||||||
|
execute = (toolCallId::String, args::Dict{String,Any}, signal::Union{Nothing,abortSignal}, onPartialResult::Function) -> begin
|
||||||
|
tool_name = get(args, "name", "")::String
|
||||||
|
tool_label = get(args, "label", tool_name)::String
|
||||||
|
tool_description = get(args, "description", "")::String
|
||||||
|
tool_schema = get(args, "inputSchema", Dict{String,Any}())::Dict{String,Any}
|
||||||
|
execute_code = get(args, "executeCode", "")::String
|
||||||
|
validate_code = get(args, "validateCode", nothing)::Union{String,Nothing}
|
||||||
|
prepare_code = get(args, "prepareCode", nothing)::Union{String,Nothing}
|
||||||
|
parallel = get(args, "parallel", false)::Bool
|
||||||
|
|
||||||
|
# Validate tool name
|
||||||
|
name_err = validateToolName(tool_name)
|
||||||
|
if name_err !== nothing
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent(name_err)],
|
||||||
|
Dict{Any,Any}(), nothing, false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate required fields
|
||||||
|
if isempty(tool_name)
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("Missing required field: 'name'")],
|
||||||
|
Dict{Any,Any}(), nothing, false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if isempty(tool_description)
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("Missing required field: 'description'")],
|
||||||
|
Dict{Any,Any}(), nothing, false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if isempty(execute_code)
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("Missing required field: 'executeCode'")],
|
||||||
|
Dict{Any,Any}(), nothing, false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
onPartialResult(Dict("status" => "Generating tool: $tool_name"))
|
||||||
|
|
||||||
|
# Build the tool file path
|
||||||
|
script_dir = dirname(@__FILE__)
|
||||||
|
tools_dir = dirname(script_dir)
|
||||||
|
filepath = joinpath(tools_dir, "$(tool_name).jl")
|
||||||
|
|
||||||
|
# Check for naming conflicts
|
||||||
|
if isfile(filepath)
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("Tool file already exists: $filepath. Rename the tool or delete the existing file first.")],
|
||||||
|
Dict{Any,Any}(), nothing, false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
onPartialResult(Dict("status" => "Writing file: $(basename(filepath))"))
|
||||||
|
|
||||||
|
# Convert schema Dict to a Julia Dict literal string
|
||||||
|
schema_literal = dict_to_julia_literal(tool_schema)
|
||||||
|
|
||||||
|
# Build optional validation function
|
||||||
|
validate_section = if validate_code !== nothing && !isempty(validate_code)
|
||||||
|
indented = indent_code(validate_code, 4)
|
||||||
|
"function validateRequiredArgs(args::Dict{String,Any})::Union{Nothing,String}\n$indented\n return nothing\nend\n"
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
|
# Build optional prepare function
|
||||||
|
prepare_section = if prepare_code !== nothing && !isempty(prepare_code)
|
||||||
|
indented = indent_code(prepare_code, 4)
|
||||||
|
"function prepareArguments(args::Dict{String,Any})::Dict{String,Any}\n$indented\n return args\nend\n"
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
|
# Indent user's execute code for embedding inside execute function body
|
||||||
|
indented_exec = indent_code(execute_code, 4)
|
||||||
|
|
||||||
|
# Escape description for Julia string literal
|
||||||
|
escaped_desc = replace(tool_description, "\\" => "\\\\")
|
||||||
|
escaped_desc = replace(escaped_desc, "\"" => "\\\"")
|
||||||
|
|
||||||
|
# Build the complete tool file content
|
||||||
|
parts = String[]
|
||||||
|
push!(parts, "# Auto-generated tool: $tool_name\n")
|
||||||
|
push!(parts, "# Generated by writeTool at $(now())\n\n")
|
||||||
|
if !isempty(validate_section)
|
||||||
|
push!(parts, validate_section)
|
||||||
|
push!(parts, "\n")
|
||||||
|
end
|
||||||
|
if !isempty(prepare_section)
|
||||||
|
push!(parts, prepare_section)
|
||||||
|
push!(parts, "\n")
|
||||||
|
end
|
||||||
|
push!(parts, "\n")
|
||||||
|
push!(parts, "# Execute function\n")
|
||||||
|
push!(parts, "function executeTool(toolCallId::String, args::Dict{String,Any}, signal::Union{Nothing,abortSignal}, onPartialResult::Function)::agentToolResult\n")
|
||||||
|
push!(parts, "$indented_exec\n")
|
||||||
|
push!(parts, "end\n\n")
|
||||||
|
push!(parts, "# Tool definition\n")
|
||||||
|
push!(parts, "function getTool()::agentTool\n")
|
||||||
|
push!(parts, " return agentTool(\n")
|
||||||
|
push!(parts, " name = \"$(tool_name)\",\n")
|
||||||
|
push!(parts, " label = \"$(tool_label)\",\n")
|
||||||
|
push!(parts, " description = \"$(escaped_desc)\",\n")
|
||||||
|
push!(parts, " inputSchema = $schema_literal,\n")
|
||||||
|
push!(parts, " execute = executeTool,\n")
|
||||||
|
if validate_code !== nothing && !isempty(validate_code)
|
||||||
|
push!(parts, " validateRequiredArgs = validateRequiredArgs,\n")
|
||||||
|
else
|
||||||
|
push!(parts, " validateRequiredArgs = nothing,\n")
|
||||||
|
end
|
||||||
|
if prepare_code !== nothing && !isempty(prepare_code)
|
||||||
|
push!(parts, " prepareArguments = prepareArguments,\n")
|
||||||
|
else
|
||||||
|
push!(parts, " prepareArguments = nothing,\n")
|
||||||
|
end
|
||||||
|
push!(parts, " parallelToolExecute = $parallel\n")
|
||||||
|
push!(parts, " )\n")
|
||||||
|
push!(parts, "end\n")
|
||||||
|
|
||||||
|
tool_code = join(parts)
|
||||||
|
|
||||||
|
# Write the file — tool is loaded on next agent restart via loadTools(store, "src/tools")
|
||||||
|
write(filepath, tool_code)
|
||||||
|
|
||||||
|
onPartialResult(Dict("status" => "Done"))
|
||||||
|
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("Tool '$(tool_name)' written to $filepath. Restart the agent so loadTools(agent._tool_store, \"src/tools\") picks it up, then call listTools to verify.")],
|
||||||
|
Dict{Any,Any}(
|
||||||
|
"file" => filepath,
|
||||||
|
"name" => tool_name,
|
||||||
|
"label" => tool_label,
|
||||||
|
"description" => tool_description,
|
||||||
|
),
|
||||||
|
nothing, false
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
prepareArguments = nothing,
|
||||||
|
validateRequiredArgs = nothing,
|
||||||
|
parallelToolExecute = false
|
||||||
|
)
|
||||||
|
end
|
||||||
+827
-176
File diff suppressed because it is too large
Load Diff
-496
@@ -1,496 +0,0 @@
|
|||||||
module util
|
|
||||||
|
|
||||||
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
|
|
||||||
availableWineToText
|
|
||||||
|
|
||||||
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
|
||||||
using GeneralUtils
|
|
||||||
using ..type
|
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
||||||
|
|
||||||
""" Clear agent chat history.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `a::agent`
|
|
||||||
an agent
|
|
||||||
|
|
||||||
# Return
|
|
||||||
- nothing
|
|
||||||
|
|
||||||
# Example
|
|
||||||
```jldoctest
|
|
||||||
julia> using YiemAgent, MQTTClient, GeneralUtils
|
|
||||||
julia> client, connection = MakeConnection("test.mosquitto.org", 1883)
|
|
||||||
julia> connect(client, connection)
|
|
||||||
julia> msgMeta = GeneralUtils.generate_msgMeta("testtopic")
|
|
||||||
julia> agentConfig = Dict(
|
|
||||||
:receiveprompt=>Dict(
|
|
||||||
:mqtttopic=> "testtopic/receive",
|
|
||||||
),
|
|
||||||
:receiveinternal=>Dict(
|
|
||||||
:mqtttopic=> "testtopic/internal",
|
|
||||||
),
|
|
||||||
:text2text=>Dict(
|
|
||||||
:mqtttopic=> "testtopic/text2text",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
julia> a = YiemAgent.sommelier(
|
|
||||||
client,
|
|
||||||
msgMeta,
|
|
||||||
agentConfig,
|
|
||||||
)
|
|
||||||
julia> YiemAgent.addNewMessage(a, "user", "hello")
|
|
||||||
julia> YiemAgent.clearhistory(a)
|
|
||||||
```
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
- [PENDING] clear memory
|
|
||||||
|
|
||||||
# Signature
|
|
||||||
"""
|
|
||||||
function clearhistory(a::T) where {T<:agent}
|
|
||||||
empty!(a.chathistory)
|
|
||||||
empty!(a.memory[:shortmem])
|
|
||||||
empty!(a.memory[:events])
|
|
||||||
a.memory[:chatbox] = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
""" Add new message to agent.
|
|
||||||
|
|
||||||
Arguments\n
|
|
||||||
-----
|
|
||||||
a::agent
|
|
||||||
an agent
|
|
||||||
role::String
|
|
||||||
message sender role i.e. system, user or assistant
|
|
||||||
text::String
|
|
||||||
message text
|
|
||||||
|
|
||||||
Return\n
|
|
||||||
-----
|
|
||||||
nothing
|
|
||||||
|
|
||||||
Example\n
|
|
||||||
-----
|
|
||||||
```jldoctest
|
|
||||||
julia> using YiemAgent, MQTTClient, GeneralUtils
|
|
||||||
julia> client, connection = MakeConnection("test.mosquitto.org", 1883)
|
|
||||||
julia> connect(client, connection)
|
|
||||||
julia> msgMeta = GeneralUtils.generate_msgMeta("testtopic")
|
|
||||||
julia> agentConfig = Dict(
|
|
||||||
:receiveprompt=>Dict(
|
|
||||||
:mqtttopic=> "testtopic/receive",
|
|
||||||
),
|
|
||||||
:receiveinternal=>Dict(
|
|
||||||
:mqtttopic=> "testtopic/internal",
|
|
||||||
),
|
|
||||||
:text2text=>Dict(
|
|
||||||
:mqtttopic=> "testtopic/text2text",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
julia> a = YiemAgent.sommelier(
|
|
||||||
client,
|
|
||||||
msgMeta,
|
|
||||||
agentConfig,
|
|
||||||
)
|
|
||||||
julia> YiemAgent.addNewMessage(a, "user", "hello")
|
|
||||||
```
|
|
||||||
|
|
||||||
Signature\n
|
|
||||||
-----
|
|
||||||
"""
|
|
||||||
function addNewMessage(a::T1, name::String, text::T2;
|
|
||||||
maximumMsg::Integer=20) where {T1<:agent, T2<:AbstractString}
|
|
||||||
if name ∉ ["system", "user", "assistant"] # guard against typo
|
|
||||||
error("name is not in agent.availableRole $(@__LINE__)")
|
|
||||||
end
|
|
||||||
|
|
||||||
#[PENDING] summarize the oldest 10 message
|
|
||||||
if length(a.chathistory) > maximumMsg
|
|
||||||
summarize(a.chathistory)
|
|
||||||
else
|
|
||||||
d = Dict(:name=> name, :text=> text, :timestamp=> Dates.now())
|
|
||||||
push!(a.chathistory, d)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
""" Converts a vector of dictionaries to a formatted string.
|
|
||||||
This function takes in a vector of dictionaries and outputs a single string where each dictionary's keys are prefixed by their values.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `vecd::Vector`
|
|
||||||
a vector of dictionaries
|
|
||||||
- `withkey::Bool`
|
|
||||||
whether to include the key in the output text. Default is true
|
|
||||||
|
|
||||||
# Return
|
|
||||||
a string with the formatted dictionaries
|
|
||||||
|
|
||||||
# Example
|
|
||||||
```jldoctest
|
|
||||||
julia> using Revise
|
|
||||||
julia> using GeneralUtils
|
|
||||||
julia> vecd = [Dict(:name => "John", :text => "Hello"), Dict(:name => "Jane", :text => "Goodbye")]
|
|
||||||
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
|
|
||||||
"John> Hello\nJane> Goodbye\n"
|
|
||||||
```
|
|
||||||
# Signature
|
|
||||||
"""
|
|
||||||
function chatHistoryToText(vecd::Vector; withkey=true)::String
|
|
||||||
# Initialize an empty string to hold the final text
|
|
||||||
text = ""
|
|
||||||
|
|
||||||
# Determine whether to include the key in the output text or not
|
|
||||||
if withkey
|
|
||||||
# Loop through each dictionary in the input vector
|
|
||||||
for d in vecd
|
|
||||||
# Extract the 'name' and 'text' keys from the dictionary
|
|
||||||
name = d[:name]
|
|
||||||
_text = d[:text]
|
|
||||||
|
|
||||||
# Append the formatted string to the text variable
|
|
||||||
text *= "$name> $_text \n"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# Loop through each dictionary in the input vector
|
|
||||||
for d in vecd
|
|
||||||
# Iterate over all key-value pairs in the dictionary
|
|
||||||
for (k, v) in d
|
|
||||||
# Append the formatted string to the text variable
|
|
||||||
text *= "$v \n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the final text
|
|
||||||
return text
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function availableWineToText(vecd::Vector)::String
|
|
||||||
# Initialize an empty string to hold the final text
|
|
||||||
rowtext = ""
|
|
||||||
# Loop through each dictionary in the input vector
|
|
||||||
for (i, d) in enumerate(vecd)
|
|
||||||
# Iterate over all key-value pairs in the dictionary
|
|
||||||
temp = []
|
|
||||||
for (k, v) in d
|
|
||||||
# Append the formatted string to the text variable
|
|
||||||
t = "$k:$v"
|
|
||||||
push!(temp, t)
|
|
||||||
end
|
|
||||||
_rowtext = join(temp, ',')
|
|
||||||
rowtext *= "$i) $_rowtext "
|
|
||||||
end
|
|
||||||
|
|
||||||
return rowtext
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function eventdict(;
|
|
||||||
event_description::Union{String, Nothing}=nothing,
|
|
||||||
timestamp::Union{DateTime, Nothing}=nothing,
|
|
||||||
subject::Union{String, Nothing}=nothing,
|
|
||||||
thought::Union{AbstractDict, Nothing}=nothing,
|
|
||||||
actionname::Union{String, Nothing}=nothing, # "CHAT", "CHECKINVENTORY", "PRESENTBOX", etc
|
|
||||||
actioninput::Union{String, Nothing}=nothing,
|
|
||||||
location::Union{String, Nothing}=nothing,
|
|
||||||
equipment_used::Union{String, Nothing}=nothing,
|
|
||||||
material_used::Union{String, Nothing}=nothing,
|
|
||||||
outcome::Union{String, Nothing}=nothing,
|
|
||||||
note::Union{String, Nothing}=nothing,
|
|
||||||
)
|
|
||||||
return Dict{Symbol, Any}(
|
|
||||||
:event_description=> event_description,
|
|
||||||
:timestamp=> timestamp,
|
|
||||||
:subject=> subject,
|
|
||||||
:thought=> thought,
|
|
||||||
:actionname=> actionname,
|
|
||||||
:actioninput=> actioninput,
|
|
||||||
:location=> location,
|
|
||||||
:equipment_used=> equipment_used,
|
|
||||||
:material_used=> material_used,
|
|
||||||
:outcome=> outcome,
|
|
||||||
:note=> note,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function createTimeline(memory::T1; skiprecent::Integer=0) where {T1<:AbstractVector}
|
|
||||||
events = memory[1:end-skiprecent]
|
|
||||||
|
|
||||||
timeline = ""
|
|
||||||
for (i, event) in enumerate(events)
|
|
||||||
if event[:outcome] === nothing
|
|
||||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput])\n"
|
|
||||||
else
|
|
||||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput]) $(event[:outcome])\n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return timeline
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# """ Convert a single chat dictionary into LLM model instruct format.
|
|
||||||
|
|
||||||
# # Llama 3 instruct format example
|
|
||||||
# <|system|>
|
|
||||||
# You are a helpful AI assistant.<|end|>
|
|
||||||
# <|user|>
|
|
||||||
# I am going to Paris, what should I see?<|end|>
|
|
||||||
# <|assistant|>
|
|
||||||
# Paris, the capital of France, is known for its stunning architecture, art museums."<|end|>
|
|
||||||
# <|user|>
|
|
||||||
# What is so great about #1?<|end|>
|
|
||||||
# <|assistant|>
|
|
||||||
|
|
||||||
|
|
||||||
# # Arguments
|
|
||||||
# - `name::T`
|
|
||||||
# message owner name e.f. "system", "user" or "assistant"
|
|
||||||
# - `text::T`
|
|
||||||
|
|
||||||
# # Return
|
|
||||||
# - `formattedtext::String`
|
|
||||||
# text formatted to model format
|
|
||||||
|
|
||||||
# # Example
|
|
||||||
# ```jldoctest
|
|
||||||
# julia> using Revise
|
|
||||||
# julia> using YiemAgent
|
|
||||||
# julia> d = Dict(:name=> "system",:text=> "You are a helpful, respectful and honest assistant.",)
|
|
||||||
# julia> formattedtext = YiemAgent.formatLLMtext_phi3instruct(d[:name], d[:text])
|
|
||||||
|
|
||||||
# ```
|
|
||||||
|
|
||||||
# Signature
|
|
||||||
# """
|
|
||||||
# function formatLLMtext_phi3instruct(name::T, text::T) where {T<:AbstractString}
|
|
||||||
# formattedtext =
|
|
||||||
# """
|
|
||||||
# <|$name|>
|
|
||||||
# $text<|end|>\n
|
|
||||||
# """
|
|
||||||
|
|
||||||
# return formattedtext
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# """ Convert a single chat dictionary into LLM model instruct format.
|
|
||||||
|
|
||||||
# # Llama 3 instruct format example
|
|
||||||
# <|begin_of_text|>
|
|
||||||
# <|start_header_id|>system<|end_header_id|>
|
|
||||||
# You are a helpful assistant.
|
|
||||||
# <|eot_id|>
|
|
||||||
# <|start_header_id|>user<|end_header_id|>
|
|
||||||
# Get me an icecream.
|
|
||||||
# <|eot_id|>
|
|
||||||
# <|start_header_id|>assistant<|end_header_id|>
|
|
||||||
# Go buy it yourself at 7-11.
|
|
||||||
# <|eot_id|>
|
|
||||||
|
|
||||||
# # Arguments
|
|
||||||
# - `name::T`
|
|
||||||
# message owner name e.f. "system", "user" or "assistant"
|
|
||||||
# - `text::T`
|
|
||||||
|
|
||||||
# # Return
|
|
||||||
# - `formattedtext::String`
|
|
||||||
# text formatted to model format
|
|
||||||
|
|
||||||
# # Example
|
|
||||||
# ```jldoctest
|
|
||||||
# julia> using Revise
|
|
||||||
# julia> using YiemAgent
|
|
||||||
# julia> d = Dict(:name=> "system",:text=> "You are a helpful, respectful and honest assistant.",)
|
|
||||||
# julia> formattedtext = YiemAgent.formatLLMtext_llama3instruct(d[:name], d[:text])
|
|
||||||
# "<|begin_of_text|>\n <|start_header_id|>system<|end_header_id|>\n You are a helpful, respectful and honest assistant.\n <|eot_id|>\n"
|
|
||||||
# ```
|
|
||||||
|
|
||||||
# Signature
|
|
||||||
# """
|
|
||||||
# function formatLLMtext_llama3instruct(name::T, text::T) where {T<:AbstractString}
|
|
||||||
# formattedtext =
|
|
||||||
# if name == "system"
|
|
||||||
# """
|
|
||||||
# <|begin_of_text|>
|
|
||||||
# <|start_header_id|>$name<|end_header_id|>
|
|
||||||
# $text
|
|
||||||
# <|eot_id|>
|
|
||||||
# """
|
|
||||||
# else
|
|
||||||
# """
|
|
||||||
# <|start_header_id|>$name<|end_header_id|>
|
|
||||||
# $text
|
|
||||||
# <|eot_id|>
|
|
||||||
# """
|
|
||||||
# end
|
|
||||||
|
|
||||||
# return formattedtext
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# """ Convert a chat messages in vector of dictionary into LLM model instruct format.
|
|
||||||
|
|
||||||
# # Arguments
|
|
||||||
# - `messages::Vector{Dict{Symbol, T}}`
|
|
||||||
# message owner name e.f. "system", "user" or "assistant"
|
|
||||||
# - `formatname::T`
|
|
||||||
# format name to be used
|
|
||||||
|
|
||||||
# # Return
|
|
||||||
# - `formattedtext::String`
|
|
||||||
# text formatted to model format
|
|
||||||
|
|
||||||
# # Example
|
|
||||||
# ```jldoctest
|
|
||||||
# julia> using Revise
|
|
||||||
# julia> using YiemAgent
|
|
||||||
# julia> chatmessage = [
|
|
||||||
# Dict(:name=> "system",:text=> "You are a helpful, respectful and honest assistant.",),
|
|
||||||
# Dict(:name=> "user",:text=> "list me all planets in our solar system.",),
|
|
||||||
# Dict(:name=> "assistant",:text=> "I'm sorry. I don't know. You tell me.",),
|
|
||||||
# ]
|
|
||||||
# julia> formattedtext = YiemAgent.formatLLMtext(chatmessage, "llama3instruct")
|
|
||||||
# "<|begin_of_text|>\n <|start_header_id|>system<|end_header_id|>\n You are a helpful, respectful and honest assistant.\n <|eot_id|>\n <|start_header_id|>user<|end_header_id|>\n list me all planets in our solar system.\n <|eot_id|>\n <|start_header_id|>assistant<|end_header_id|>\n I'm sorry. I don't know. You tell me.\n <|eot_id|>\n"
|
|
||||||
# ```
|
|
||||||
|
|
||||||
# # Signature
|
|
||||||
# """
|
|
||||||
# function formatLLMtext(messages::Vector{Dict{Symbol, T}},
|
|
||||||
# formatname::String="llama3instruct") where {T<:Any}
|
|
||||||
# f = if formatname == "llama3instruct"
|
|
||||||
# formatLLMtext_llama3instruct
|
|
||||||
# elseif formatname == "mistral"
|
|
||||||
# # not define yet
|
|
||||||
# elseif formatname == "phi3instruct"
|
|
||||||
# formatLLMtext_phi3instruct
|
|
||||||
# else
|
|
||||||
# error("$formatname template not define yet")
|
|
||||||
# end
|
|
||||||
|
|
||||||
# str = ""
|
|
||||||
# for t in messages
|
|
||||||
# str *= f(t[:name], t[:text])
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # add <|assistant|> so that the model don't generate it and I don't need to clean it up later
|
|
||||||
# if formatname == "phi3instruct"
|
|
||||||
# str *= "<|assistant|>\n"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# return str
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# """
|
|
||||||
|
|
||||||
# Arguments\n
|
|
||||||
# -----
|
|
||||||
|
|
||||||
# Return\n
|
|
||||||
# -----
|
|
||||||
|
|
||||||
# Example\n
|
|
||||||
# -----
|
|
||||||
# ```jldoctest
|
|
||||||
# julia>
|
|
||||||
# ```
|
|
||||||
|
|
||||||
# TODO\n
|
|
||||||
# -----
|
|
||||||
# [] update docstring
|
|
||||||
# [PENDING] implement the function
|
|
||||||
|
|
||||||
# Signature\n
|
|
||||||
# -----
|
|
||||||
# """
|
|
||||||
# function iterativeprompting(a::T, prompt::String, verification::Function) where {T<:agent}
|
|
||||||
# msgMeta = GeneralUtils.generate_msgMeta(
|
|
||||||
# a.config[:externalService][:text2textinstruct],
|
|
||||||
# senderName= "iterativeprompting",
|
|
||||||
# senderId= a.id,
|
|
||||||
# receiverName= "text2textinstruct",
|
|
||||||
# )
|
|
||||||
|
|
||||||
# outgoingMsg = Dict(
|
|
||||||
# :msgMeta=> msgMeta,
|
|
||||||
# :payload=> Dict(
|
|
||||||
# :text=> prompt,
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
|
|
||||||
# success = nothing
|
|
||||||
# result = nothing
|
|
||||||
# critique = ""
|
|
||||||
|
|
||||||
# # iteration loop
|
|
||||||
# while true
|
|
||||||
# # send prompt to LLM
|
|
||||||
# response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
|
||||||
# error("--> iterativeprompting")
|
|
||||||
# # check for correctness and get feedback
|
|
||||||
# success, _critique = verification(response)
|
|
||||||
|
|
||||||
# if success
|
|
||||||
# result = response
|
|
||||||
# break
|
|
||||||
# else
|
|
||||||
# # add critique to prompt
|
|
||||||
# critique *= _critique * "\n"
|
|
||||||
# replace!(prompt, "Critique: ..." => "Critique: $critique")
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# return (success=success, result=result)
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end # module util
|
|
||||||
+435
@@ -0,0 +1,435 @@
|
|||||||
|
module utils
|
||||||
|
|
||||||
|
export clearhistory, availableWineToText, prepareContext, formatMsgForLLM, validateRequiredArgs, validateToolArguments, _userMessageToOpenAI,
|
||||||
|
_assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks
|
||||||
|
|
||||||
|
using UUIDs, Dates, DataStructures, HTTP, JSON
|
||||||
|
using GeneralUtils
|
||||||
|
using ..type
|
||||||
|
|
||||||
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
|
"""
|
||||||
|
Clear agent chat history.
|
||||||
|
|
||||||
|
Empties the conversation history, short-term memory, events log, and chatbox.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `a::T`: An agent instance (subtype of `agent`)
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing`
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- Does not clear long-term memory; use `[PENDING] clear memory` when implemented.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> YiemAgent.clearhistory(agent)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function clearhistory(a::T) where {T<:agent}
|
||||||
|
# empty!(a.chathistory)
|
||||||
|
# empty!(a.memory["shortmem"])
|
||||||
|
# empty!(a.memory["events"])
|
||||||
|
# a.memory["chatbox"] = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a vector of wine dictionaries to a formatted text string.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `vecd::Vector`: A vector of dictionaries, each representing a wine with key-value pairs
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- A formatted string where each wine is numbered and each key-value pair is comma-separated
|
||||||
|
in the format: `"1) key1:value1,key2:value2 key3:value3 2) ..."`
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> vecd = [Dict("wine_name" => "Chateau A", "price" => "50")]
|
||||||
|
julia> YiemAgent.availableWineToText(vecd)
|
||||||
|
"1) wine_name:Chateau A,price:50 "
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function availableWineToText(vecd::Vector)::String
|
||||||
|
# Initialize an empty string to hold the final text
|
||||||
|
rowtext = ""
|
||||||
|
# Loop through each dictionary in the input vector
|
||||||
|
for (i, d) in enumerate(vecd)
|
||||||
|
# Iterate over all key-value pairs in the dictionary
|
||||||
|
temp = []
|
||||||
|
for (k, v) in d
|
||||||
|
# Append the formatted string to the text variable
|
||||||
|
t = "$k:$v"
|
||||||
|
push!(temp, t)
|
||||||
|
end
|
||||||
|
_rowtext = join(temp, ',')
|
||||||
|
rowtext *= "$i) $_rowtext "
|
||||||
|
end
|
||||||
|
|
||||||
|
return rowtext
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
prepareContext(state::agentState) -> agentContext
|
||||||
|
|
||||||
|
Prepares an `agentContext` from the given `agentState` for sending to
|
||||||
|
the LLM. By default, it deep copies the system prompt, messages, and
|
||||||
|
tools from `state` into a new `agentContext`.
|
||||||
|
|
||||||
|
Override this function to customize the context — such as filtering
|
||||||
|
tools based on the user's intent, modifying the system prompt, injecting
|
||||||
|
additional context (retrieved documents, current time, user preferences),
|
||||||
|
or pruning and reordering messages before formatting and calling the LLM.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `state::agentState`: The current agent state containing conversation history,
|
||||||
|
system prompt, tools, and other configuration
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `agentContext`: An `agentContext` containing the prepared system prompt,
|
||||||
|
messages, and tools to be sent to the LLM
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
# Default: returns an agentContext with deep copies of system prompt, messages, and tools
|
||||||
|
prepareContext(state).messages == deepcopy(state.messages)
|
||||||
|
|
||||||
|
# Override to filter tools and inject system context:
|
||||||
|
# function prepareContext(state::agentState)
|
||||||
|
# msgs = deepcopy(state.messages)
|
||||||
|
# sysPrompt = state.systemPrompt * "\\nCurrent time: $(now())"
|
||||||
|
# tools = filter(t -> contains(t.description, "wine"), state.tools)
|
||||||
|
# return agentContext(sysPrompt, msgs, tools)
|
||||||
|
# end
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function prepareContext(state::agentState)::agentContext
|
||||||
|
|
||||||
|
#TODO filter tools from state.tools based on user intend in user message and tool description
|
||||||
|
filteredTools = state.tools
|
||||||
|
|
||||||
|
#TODO add filtered tools to the current system prompt / modify systemPrompt here
|
||||||
|
preparedSystemPrompt = state.systemPrompt
|
||||||
|
|
||||||
|
#TODO add system prompt, adjust/modify and inject additional context into messages
|
||||||
|
preparedMessages = deepcopy(state.messages) # messages that will be send to LLM
|
||||||
|
|
||||||
|
agentCtx = agentContext(preparedSystemPrompt, preparedMessages, filteredTools)
|
||||||
|
|
||||||
|
return agentCtx
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
formatMsgForLLM(ctx::agentContext) -> Dict{String, Any}
|
||||||
|
|
||||||
|
Converts an `agentContext` into OpenAI-compatible message format
|
||||||
|
ready to be sent to the LLM. The system prompt is converted into
|
||||||
|
a system role message, followed by user, assistant, and tool result
|
||||||
|
messages.
|
||||||
|
|
||||||
|
This function can be overridden in `yiemAgent` to produce custom
|
||||||
|
LLM message formats for different APIs/providers.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `ctx::agentContext`: The prepared context containing system prompt,
|
||||||
|
messages, and tools
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `Dict{String, Any}`: A dictionary with `"messages"` key containing
|
||||||
|
an array of OpenAI-format message dicts
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
# Default output:
|
||||||
|
formatMsgForLLm(ctx) == Dict("messages" => [
|
||||||
|
Dict("role" => "system", "content" => [...]),
|
||||||
|
Dict("role" => "user", "content" => [...]),
|
||||||
|
Dict("role" => "assistant", "content" => [...]),
|
||||||
|
Dict("role" => "tool", "tool_call_id" => "...", "content" => [...]),
|
||||||
|
])
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function formatMsgForLLM(ctx::agentContext)::Dict{String, Any}
|
||||||
|
|
||||||
|
""" openai message format example
|
||||||
|
msg = Dict(
|
||||||
|
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
|
||||||
|
"messages" => [
|
||||||
|
Dict(
|
||||||
|
"role" => "system",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => systemmsg),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "Do you have something similar to the one in the image?"),
|
||||||
|
Dict(
|
||||||
|
"type" => "image_url",
|
||||||
|
"image_url" => Dict("url" => data_uri)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Dict(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "let me check."),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Dict(
|
||||||
|
"role" => "toolResult",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "name: Chateau Montelena ..."),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"temperature" => 0.7
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
messages = Vector{Dict{String, Any}}()
|
||||||
|
|
||||||
|
# System prompt as system message
|
||||||
|
if !isempty(ctx.systemPrompt)
|
||||||
|
push!(messages, Dict(
|
||||||
|
"role" => "system",
|
||||||
|
"content" => [Dict("type" => "text", "text" => ctx.systemPrompt)]
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Conversation messages
|
||||||
|
for msg in ctx.messages
|
||||||
|
if msg isa userMessage
|
||||||
|
push!(messages, _userMessageToOpenAI(msg))
|
||||||
|
elseif msg isa assistantMessage
|
||||||
|
push!(messages, _assistantMessageToOpenAI(msg))
|
||||||
|
elseif msg isa toolResultMessage
|
||||||
|
push!(messages, _toolResultMessageToOpenAI(msg))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Dict("messages" => messages)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a userMessage to OpenAI message format.
|
||||||
|
"""
|
||||||
|
function _userMessageToOpenAI(msg::userMessage)::Dict{String, Any}
|
||||||
|
return Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => _messageContentToBlocks(msg.content)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert an assistantMessage to OpenAI message format.
|
||||||
|
"""
|
||||||
|
function _assistantMessageToOpenAI(msg::assistantMessage)::Dict{String, Any}
|
||||||
|
return Dict(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => _messageContentToBlocks(msg.content)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a toolResultMessage to OpenAI message format.
|
||||||
|
"""
|
||||||
|
function _toolResultMessageToOpenAI(msg::toolResultMessage)::Dict{String, Any}
|
||||||
|
return Dict(
|
||||||
|
"role" => "tool",
|
||||||
|
"tool_call_id" => msg.toolCallId,
|
||||||
|
"content" => _messageContentToBlocks(msg.content)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a vector of messageContent to OpenAI content blocks.
|
||||||
|
|
||||||
|
Each textContent becomes a text block, each imageContent becomes
|
||||||
|
an image_url block.
|
||||||
|
"""
|
||||||
|
function _messageContentToBlocks(contents::Vector{messageContent})::Vector{Dict{String, Any}}
|
||||||
|
blocks = Vector{Dict{String, Any}}()
|
||||||
|
|
||||||
|
for c in contents
|
||||||
|
if c isa textContent
|
||||||
|
push!(blocks, Dict("type" => "text", "text" => c.text))
|
||||||
|
elseif c isa imageContent
|
||||||
|
push!(blocks, Dict(
|
||||||
|
"type" => "image_url",
|
||||||
|
"image_url" => Dict(
|
||||||
|
"url" => "data:$(c.mimeType);base64,$(c.data)"
|
||||||
|
)
|
||||||
|
))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
validateRequiredArgs(args::Dict{String,Any}, inputSchema::Dict{String,Any}) -> Union{Nothing,String}
|
||||||
|
|
||||||
|
Validates that all required fields listed in the tool's JSON Schema are present
|
||||||
|
in `args`. Returns `nothing` if validation passes, or a descriptive error string
|
||||||
|
listing the missing required fields.
|
||||||
|
|
||||||
|
This is the default `validateRequiredArgs` hook. Tool authors can override it
|
||||||
|
with a custom validation function that performs additional checks (e.g. type
|
||||||
|
coercion, format validation, cross-field constraints).
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `args::Dict{String,Any}`: The arguments provided by the LLM
|
||||||
|
- `inputSchema::Dict{String,Any}`: The tool's `inputSchema` (JSON Schema format)
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing` if all required args are present
|
||||||
|
- `String` error message listing missing fields otherwise
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
schema = Dict("required" => ["city"])
|
||||||
|
args = Dict{String,Any}()
|
||||||
|
validateRequiredArgs(args, schema) # => "Missing required arguments: city"
|
||||||
|
|
||||||
|
args2 = Dict("city" => "Tokyo")
|
||||||
|
validateRequiredArgs(args2, schema) # => nothing
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function validateRequiredArgs(args::Dict{String,Any}, inputSchema::Dict{String,Any})::Union{Nothing,String}
|
||||||
|
required = get(inputSchema, "required", Any[])
|
||||||
|
if isempty(required)
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
missing = String[]
|
||||||
|
for field in required
|
||||||
|
if !(field in keys(args))
|
||||||
|
push!(missing, field)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !isempty(missing)
|
||||||
|
return "Missing required arguments: $(join(missing, ", "))"
|
||||||
|
end
|
||||||
|
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
validateToolArguments(tool::agentTool, prepared::agentToolCall) -> Dict{String,Any}
|
||||||
|
|
||||||
|
Validates the prepared tool call arguments by calling the tool's
|
||||||
|
`validateRequiredArgs` hook (or the default implementation). If validation
|
||||||
|
fails, returns a modified `agentToolCall` with an empty arguments dict
|
||||||
|
so downstream code can detect the failure. If the hook exists on the tool
|
||||||
|
and returns an error string, that error is returned.
|
||||||
|
|
||||||
|
This runs **before** the `beforeToolCall` hook, allowing the agent to
|
||||||
|
reject invalid calls without invoking lifecycle callbacks or logging
|
||||||
|
false `toolExecutionStart` events.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `tool::agentTool`: The resolved tool definition
|
||||||
|
- `prepared::agentToolCall`: The prepared tool call with potentially transformed arguments
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `Dict{String,Any}`: The validated arguments if successful
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
- Throws `ArgumentError` if validation fails — this is caught by `prepareToolCall`
|
||||||
|
and converted to an `immediateOutcome`
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
# With validateRequiredArgs hook set on the tool
|
||||||
|
validateToolArguments(toolWithHook, tc) # => validated args or throws
|
||||||
|
|
||||||
|
# With default validation (nothing on tool)
|
||||||
|
validateToolArguments(toolDefault, tc) # => args or throws
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function validateToolArguments(tool::agentTool, prepared::agentToolCall)::Dict{String,Any}
|
||||||
|
# Use default (2-arg: args + schema) or tool-specific hook (1-arg: args only)
|
||||||
|
if isnothing(tool.validateRequiredArgs)
|
||||||
|
result = validateRequiredArgs(prepared.arguments, tool.inputSchema)
|
||||||
|
else
|
||||||
|
result = tool.validateRequiredArgs(prepared.arguments)
|
||||||
|
end
|
||||||
|
|
||||||
|
if result !== nothing
|
||||||
|
throw(ArgumentError(result))
|
||||||
|
end
|
||||||
|
|
||||||
|
return prepared.arguments
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end # module util
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,375 @@
|
|||||||
|
module type
|
||||||
|
|
||||||
|
export agent, sommelier, companion, virtualcustomer, agentcontext
|
||||||
|
|
||||||
|
using Dates, UUIDs, DataStructures, JSON, NATS
|
||||||
|
using GeneralUtils
|
||||||
|
|
||||||
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
mutable struct agentcontext
|
||||||
|
text2textInstructLLM::Function
|
||||||
|
getTextEmbedding::Function
|
||||||
|
executeSQL::Function
|
||||||
|
similarSQLVectorDB::Function
|
||||||
|
insertSQLVectorDB::Function
|
||||||
|
similarSommelierDecision::Function
|
||||||
|
insertSommelierDecision::Function
|
||||||
|
find_related_tables_for_user_question::Function
|
||||||
|
pg_conn_str::String
|
||||||
|
agentconfig::AbstractDict
|
||||||
|
end
|
||||||
|
|
||||||
|
abstract type agent end
|
||||||
|
|
||||||
|
mutable struct sommelier <: agent
|
||||||
|
name::String # agent name
|
||||||
|
id::String # agent id
|
||||||
|
retailername::String
|
||||||
|
retailerid::String
|
||||||
|
tools::Dict
|
||||||
|
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
|
||||||
|
chathistory::Vector{Dict{String, Any}}
|
||||||
|
memory::Dict{String, Any}
|
||||||
|
context::agentcontext
|
||||||
|
llmFormatName::String
|
||||||
|
end
|
||||||
|
|
||||||
|
""" A sommelier agent.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `context::agentcontext`
|
||||||
|
Application context containing shared functions for LLM, SQL, and vector database operations.
|
||||||
|
|
||||||
|
# Keyword Arguments
|
||||||
|
- `name::String`
|
||||||
|
Agent's name. Default: `"Assistant"`
|
||||||
|
- `id::String`
|
||||||
|
Agent's ID. Default: generated UUID string.
|
||||||
|
- `retailername::String`
|
||||||
|
Retailer name associated with the sommelier. Default: `"retailer_name"`
|
||||||
|
- `maxHistoryMsg::Integer`
|
||||||
|
Maximum history messages. Default: `20`
|
||||||
|
- `chathistory::Vector{Dict{String, String}}`
|
||||||
|
Chat history. Default: empty vector.
|
||||||
|
- `llmFormatName::String`
|
||||||
|
LLM format name. Default: `"granite3"`
|
||||||
|
|
||||||
|
# Return
|
||||||
|
- `sommelier`: An instantiated sommelier agent.
|
||||||
|
|
||||||
|
# Example
|
||||||
|
```julia
|
||||||
|
julia> using YiemAgent
|
||||||
|
julia> context = agentcontext(
|
||||||
|
text2textInstructLLM,
|
||||||
|
getTextEmbedding,
|
||||||
|
executeSQL,
|
||||||
|
similarSQLVectorDB,
|
||||||
|
insertSQLVectorDB,
|
||||||
|
similarSommelierDecision,
|
||||||
|
insertSommelierDecision
|
||||||
|
)
|
||||||
|
julia> agent = sommelier(context, name="WineExpert", id="123", retailername="MyWineShop")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function sommelier(
|
||||||
|
context::agentcontext, # agent functions, db connect and other context
|
||||||
|
;
|
||||||
|
name::String= "Assistant",
|
||||||
|
id::String= string(uuid4()),
|
||||||
|
retailername::String= "not specified",
|
||||||
|
retailerid::String= "not specified",
|
||||||
|
maxHistoryMsg::Integer= 20,
|
||||||
|
chathistory::Vector{Dict{String, Any}} = Vector{Dict{String, Any}}(),
|
||||||
|
llmFormatName::String= "granite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
tools = Dict( # update input format
|
||||||
|
"chatbox"=> Dict(
|
||||||
|
"description" => "<askbox tool description>Useful for when you need to ask the user for more context. Do not ask the user their own question.</askbox tool description>",
|
||||||
|
"input" => """<input>Input is a text in JSON format.</input><input example>{\"Q1\": \"How are you doing?\", \"Q2\": \"How may I help you?\"}</input example>""",
|
||||||
|
"output" => "" ,
|
||||||
|
),
|
||||||
|
"winestock"=> Dict(
|
||||||
|
"description" => "<winestock tool description>A handy tool for searching wine in your inventory that match the user preferences.</winestock tool description>",
|
||||||
|
"input" => """<input>Input is a JSON-formatted string that contains a detailed and precise search query.</input><input example>{\"wine type\": \"rose\", \"price\": \"max 35\", \"sweetness level\": \"sweet\", \"intensity level\": \"light bodied\", \"Tannin level\": \"low\", \"Acidity level\": \"low\"}</input example>""",
|
||||||
|
"output" => """<output>Output are wines that match the search query in JSON format.""",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
""" Memory
|
||||||
|
|
||||||
|
Chat history use openai format as follow:
|
||||||
|
|
||||||
|
image1_path = "test/large_image.png" ---
|
||||||
|
image1_bytes = read(image1_path) | this part must be done
|
||||||
|
image1_base64_string = base64encode(image1_bytes) | in frontend
|
||||||
|
mime_type = "image/png" | not in agent code
|
||||||
|
data1_uri = "data:<mime_type>;base64,<image1_base64_string>" ---
|
||||||
|
|
||||||
|
chathistory= [
|
||||||
|
Dict(
|
||||||
|
"role" => "system",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "You are a helpful assistant"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "<internal_context_for_assistant>
|
||||||
|
LLM context here...
|
||||||
|
</internal_context_for_assistant>
|
||||||
|
Do you know this wine? Just give me brief intro."
|
||||||
|
),
|
||||||
|
Dict(
|
||||||
|
"type" => "image_url",
|
||||||
|
"image_url" => Dict("url" => data1_uri)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
shortmem = Dict(
|
||||||
|
"1"=> Dict("plan"=> "...", "action_name"=> "...", "action_input"=> "...", "action_result"=> "..."),
|
||||||
|
"2"=> Dict("plan"=> "...", "action_name"=> "...", "action_input"=> "...", "action_result"=> "..."),
|
||||||
|
...
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
memory = Dict{String, Any}(
|
||||||
|
"shortmem"=> OrderedDict{String, Any}(),
|
||||||
|
"scratchpad"=> "",
|
||||||
|
"recap"=> OrderedDict{String, Any}(),
|
||||||
|
)
|
||||||
|
|
||||||
|
newAgent = sommelier(
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
retailername,
|
||||||
|
retailerid,
|
||||||
|
tools,
|
||||||
|
maxHistoryMsg,
|
||||||
|
chathistory,
|
||||||
|
memory,
|
||||||
|
context,
|
||||||
|
llmFormatName
|
||||||
|
)
|
||||||
|
systemmsg =
|
||||||
|
"""
|
||||||
|
# store_policy
|
||||||
|
- Generally speaking, the store inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know exactly until you check your inventory.
|
||||||
|
- If you found wines in the store's database, they are in stock.
|
||||||
|
- You can only recommend wines that are currently in our inventory
|
||||||
|
- Before searching the database for wine, ensure you have at least the following information: 1) budget, 2) wine type, and 3) occasion. Additional details are always helpful. If the user is unsure, provide relevant information and gather insights to make reasonable inferences.
|
||||||
|
- Ask the user one question at a time.
|
||||||
|
- Once the user has selected their wine, if you haven't already, ask the user whether they need any further assistance. Do not offer any additional services.
|
||||||
|
- Only end the conversation when the user explicitly intends to do so. When ending, ensure a polite farewell and an invitation to return in the future.
|
||||||
|
- Spicy foods should be paired only with light red wines.
|
||||||
|
- We do not sell organic, sustainable, gluten-free, and sulfite-free wine. Inform the user imediately if they are looking for these types of wines. Do not sell our wines as such.
|
||||||
|
- Gift box, gift card, and custom messages are available. Inform the user to contact our sales team.
|
||||||
|
|
||||||
|
# store_guidelines
|
||||||
|
- Greeting the customer warmly by ask them how could you help. Do not ask any other questions during this greeting.
|
||||||
|
- Customer may provide images for you to look up.
|
||||||
|
- Encourage the customer to explore different options and try new things.
|
||||||
|
- If you are unable to locate the desired item in the database after 2 attempts, it may not be available in your inventory. In such cases, inform the user that the item is unavailable and suggest an alternative instead.
|
||||||
|
- Your store carries only wine.
|
||||||
|
- Vintage 0 means non-vintage.
|
||||||
|
- Start searching the database as broadly as possible within the given information boundary to maximize the chances of finding. Avoid unnecessary parameters unless specified by the user. Refine the search subsequently.
|
||||||
|
- User usually ask for something similar. This means you should use the search term based on the profile they like.
|
||||||
|
|
||||||
|
# situation
|
||||||
|
You are having conversation with a customer.
|
||||||
|
|
||||||
|
# your role
|
||||||
|
Your name is $(newAgent.name). You are a helpful sommelier for website-based $(newAgent.retailername)'s wine store.
|
||||||
|
|
||||||
|
# objective
|
||||||
|
- Establish a connection with the customer by talking to them politely and showing your enthusiasm for their wine preferences.
|
||||||
|
- Provide relevant information and guide them to select the best wines only from your store's inventory that align with their preferences.
|
||||||
|
|
||||||
|
# your responsibility includes
|
||||||
|
- According to the store's policy and guidelines, and make an informed decision about what available_actions you need to use to achieve the objective.
|
||||||
|
- Keep the conversation with the customer going smoothly
|
||||||
|
|
||||||
|
# your responsibility does NOT includes
|
||||||
|
- Requesting the user to place an order, make a purchase, or confirm the order. These are the job of our sales team at the store.
|
||||||
|
- Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
|
||||||
|
- Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
|
||||||
|
|
||||||
|
# you should then respond to the user with interleaving plan, action_name, action_input in JSON format
|
||||||
|
1) "plan", Based on the current situation, state a complete action plan to complete the task and rationale. Be specific.
|
||||||
|
2) "action_name", (Typically corresponds to the execution of the first step in your plan) Can be one of the available_actions name
|
||||||
|
3) "action_input", The input to the action you are about to perform according to your plan.
|
||||||
|
After the action is executed you gets "action_result". It is the output from the action you selected.
|
||||||
|
|
||||||
|
# available actions
|
||||||
|
"CHAT_BOX", which you can use to talk with the user. The input is dialogue you want to chat with the user according to your plan.
|
||||||
|
"SEARCH_WINE_DATABASE", allows you to search information about wines you want in your inventory's database. The input is strictly supported search term including: retailer_name, wine price, winery, name, vintage, region, country, type of wine, grape varietal, tasting notes, occasion, food pairing, intensity, tannin, sweetness, and acidity.
|
||||||
|
Example query 1: "Dry, full-bodied red wine from Burgundy, France. Grape varietal could be Merlot or Syrah. price 100 to 1000 USD."
|
||||||
|
Example query 2: "Red or white wine, medium tannin, price under 700 USD"
|
||||||
|
Example query 3: "white wine from Tuscany, Italy or Bordeaux, France
|
||||||
|
"WINE_PRESENTATION_GUIDELINE", which you can use to check the store guidelines about how to present wines you have found to the user. The input is "nothing" keyword. The output is the guidelines that you can follow.
|
||||||
|
"END_CONVER_GUIDELINE", which you can use to check the store guidelines about how to end the conversation with the user. The input is "nothing" keyword. The output is the guidelines that you can follow.
|
||||||
|
"""
|
||||||
|
|
||||||
|
system_msg = Dict(
|
||||||
|
"role" => "system",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => systemmsg),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
push!(newAgent.chathistory, system_msg)
|
||||||
|
|
||||||
|
return newAgent
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
mutable struct virtualcustomer <: agent
|
||||||
|
name::String # agent name
|
||||||
|
id::String # agent id
|
||||||
|
systemmsg::String # system message
|
||||||
|
tools::Dict
|
||||||
|
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
|
||||||
|
chathistory::Vector{Dict{String, Any}}
|
||||||
|
memory::Dict{String, Any}
|
||||||
|
context # NamedTuple of functions
|
||||||
|
llmFormatName::String
|
||||||
|
end
|
||||||
|
|
||||||
|
function virtualcustomer(
|
||||||
|
context, # NamedTuple of functions
|
||||||
|
;
|
||||||
|
name::String= "Assistant",
|
||||||
|
id::String= string(uuid4()),
|
||||||
|
maxHistoryMsg::Integer= 20,
|
||||||
|
chathistory::Vector{Dict{String, String}} = Vector{Dict{String, String}}(),
|
||||||
|
llmFormatName::String= "granite3",
|
||||||
|
systemmsg::String=
|
||||||
|
"""
|
||||||
|
Your name: $name
|
||||||
|
Your sex: Female
|
||||||
|
Your role: You are a helpful assistant.
|
||||||
|
You should follow the following guidelines:
|
||||||
|
- Focus on the latest conversation.
|
||||||
|
- Your like to be short and concise.
|
||||||
|
|
||||||
|
Let's begin!
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
tools = Dict( # update input format
|
||||||
|
"chatbox"=> Dict(
|
||||||
|
"description" => "<askbox tool description>Useful for when you need to ask the user for more context. Do not ask the user their own question.</askbox tool description>",
|
||||||
|
"input" => """<input>Input is a text in JSON format.</input><input example>{\"Q1\": \"How are you doing?\", \"Q2\": \"How may I help you?\"}</input example>""",
|
||||||
|
"output" => "" ,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
""" Memory
|
||||||
|
Ref: Chat prompt format is openai
|
||||||
|
chathistory = [
|
||||||
|
Dict(
|
||||||
|
"role" => "system",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => system_msg),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "Do you know this wine? Just give me brief intro."),
|
||||||
|
Dict(
|
||||||
|
"type" => "image_url",
|
||||||
|
"image_url" => Dict("url" => data1_uri)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
memory = Dict{String, Any}(
|
||||||
|
"shortmem"=> OrderedDict{String, Any}(
|
||||||
|
),
|
||||||
|
"scratchpad"=> "",
|
||||||
|
"events"=> Vector{Dict{String, Any}}(),
|
||||||
|
"state"=> Dict{String, Any}(
|
||||||
|
),
|
||||||
|
"recap"=> OrderedDict{String, Any}(),
|
||||||
|
)
|
||||||
|
|
||||||
|
newAgent = virtualcustomer(
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
systemmsg,
|
||||||
|
tools,
|
||||||
|
maxHistoryMsg,
|
||||||
|
chathistory,
|
||||||
|
memory,
|
||||||
|
context,
|
||||||
|
llmFormatName
|
||||||
|
)
|
||||||
|
|
||||||
|
return newAgent
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end # module type
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,56 +0,0 @@
|
|||||||
{
|
|
||||||
"mqttServerInfo": {
|
|
||||||
"description": "mqtt server info",
|
|
||||||
"port": 1883,
|
|
||||||
"broker": "mqtt.yiem.cc"
|
|
||||||
},
|
|
||||||
"testingOrProduction": {
|
|
||||||
"value": "testing",
|
|
||||||
"description": "agent status, couldbe testing or production"
|
|
||||||
},
|
|
||||||
"agentid": {
|
|
||||||
"value": "2b74b87a-5413-4fe2-a4d3-405891051680",
|
|
||||||
"description": "a unique id for this agent"
|
|
||||||
},
|
|
||||||
"agentCentralConfigTopic": {
|
|
||||||
"mqtttopic": "/yiem_branch_1/agent/sommelier/backend/config/api/v1.1",
|
|
||||||
"description": "a central agent server's topic to get this agent config"
|
|
||||||
},
|
|
||||||
"servicetopic": {
|
|
||||||
"mqtttopic": [
|
|
||||||
"/yiem/hq/agent/sommelier/backend/prompt/api_v1/testing"
|
|
||||||
],
|
|
||||||
"description": "a topic this agent are waiting for service request"
|
|
||||||
},
|
|
||||||
"role": {
|
|
||||||
"value": "sommelier",
|
|
||||||
"description": "agent role"
|
|
||||||
},
|
|
||||||
"organization": {
|
|
||||||
"value": "yiem_hq",
|
|
||||||
"description": "organization name"
|
|
||||||
},
|
|
||||||
"externalservice": {
|
|
||||||
"text2textinstruct": {
|
|
||||||
"mqtttopic": "/loadbalancer/requestingservice",
|
|
||||||
"description": "text to text service with instruct LLM",
|
|
||||||
"llminfo": {
|
|
||||||
"name": "llama3instruct"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"virtualWineCustomer_1": {
|
|
||||||
"mqtttopic": "/virtualenvironment/winecustomer",
|
|
||||||
"description": "text to text service with instruct LLM that act as wine customer",
|
|
||||||
"llminfo": {
|
|
||||||
"name": "llama3instruct"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"text2textchat": {
|
|
||||||
"mqtttopic": "/loadbalancer/requestingservice",
|
|
||||||
"description": "text to text service with instruct LLM",
|
|
||||||
"llminfo": {
|
|
||||||
"name": "llama3instruct"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using GeneralUtils
|
|
||||||
|
|
||||||
response = "trajectory_evaluation:\nThe trajectory is correct so far. The thought accurately reflects the user's question, and the action taken is a valid attempt to retrieve data from the database that matches the specified criteria.\n\nanswer_evaluation:\nThe observation provides information about two red wines from Bordeaux rive droite in France, which partially answers the question. However, it does not provide a complete answer as it only lists the wine names and characteristics, but does not explicitly state whether there are any other wines that match the criteria.\n\naccepted_as_answer: No\n\nscore: 6\nThe trajectory is mostly correct, but the observation does not fully address the question.\n\nsuggestion: Consider adding more filters or parameters to the database query to retrieve a complete list of wines that match the specified criteria."
|
|
||||||
|
|
||||||
responsedict = GeneralUtils.textToDict(response,
|
|
||||||
["trajectory_evaluation", "answer_evaluation", "accepted_as_answer", "score", "suggestion"],
|
|
||||||
rightmarker=":", symbolkey=true)
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
@@ -1,159 +0,0 @@
|
|||||||
using Revise
|
|
||||||
using YiemAgent, GeneralUtils, JSON3, DataStructures
|
|
||||||
|
|
||||||
thoughtDict = OrderedDict(
|
|
||||||
:Question=> "Hello, I would like a get a bottle of wine",
|
|
||||||
:Thought_1=> "The customer wants to buy a bottle of wine, but we need more information about their preferences.",
|
|
||||||
:Action_1=> Dict(
|
|
||||||
:name=> "chatbox",
|
|
||||||
:input=> "What occasion are you buying the wine for?",
|
|
||||||
),
|
|
||||||
:Observation_1=> "We are having a wedding pary this weekend.",
|
|
||||||
|
|
||||||
:Thought_2=> "A wedding party is a great occasion to have a good bottle of wine.",
|
|
||||||
:Action_2=> Dict(
|
|
||||||
:name=> "chatbox",
|
|
||||||
:input=> "What type of food will you be serving with the wine?",
|
|
||||||
),
|
|
||||||
:Observation_2=> "I think it is Thai dishes",
|
|
||||||
|
|
||||||
:Thought_3=> "Now that I know the occasion and food, I need to ask about the budget.",
|
|
||||||
:Action_3=> Dict(
|
|
||||||
:name=> "chatbox",
|
|
||||||
:input=> "What is your budget for this wine?",
|
|
||||||
),
|
|
||||||
:Observation_3=> "50 bucks",
|
|
||||||
|
|
||||||
:Thought_4=> "With a budget of \$50, we have a wide range of options. Now that I know it's a wedding party and Thai dishes, I need to ask about the type of wine they prefer.",
|
|
||||||
:Action_4=> Dict(
|
|
||||||
:name=> "chatbox",
|
|
||||||
:input=> "What type of wine are you looking for? (Red, White, Sparkling, Rose, Dessert, Fortified)",
|
|
||||||
),
|
|
||||||
:Observation_4=> "Sparkling please.",
|
|
||||||
|
|
||||||
:Thought_5=> "Now that I know the occasion, food, budget and preferred type of wine, it's time to check our inventory for the best matching wine.",
|
|
||||||
:Action_5=> Dict(
|
|
||||||
:name=> "winestock",
|
|
||||||
:input=> "wine with budget \$50, Thai dishes, sparkling, wedding party",
|
|
||||||
),
|
|
||||||
:Observation_5=> "I found the following wine in stock {1 : Zena Crown Vista, 2 : Schrader Cabernet Sauvignon}",
|
|
||||||
|
|
||||||
:Thought_6=> "Now that I have all the information, it's time to recommend a wine that fits their preferences.",
|
|
||||||
:Action_6=> Dict(
|
|
||||||
:name=> "recommendation",
|
|
||||||
:input=> "I recommend Zena Crown Vista for its sparkling and affordable price.",
|
|
||||||
),
|
|
||||||
:Observation_6=> "I don't like it. Do you have another option?",
|
|
||||||
)
|
|
||||||
|
|
||||||
_thoughtJsonStr = JSON3.write(thoughtDict)
|
|
||||||
thoughtJsonStr = _thoughtJsonStr[1:end-1] # remove } at the end
|
|
||||||
# @show thoughtJsonStr
|
|
||||||
|
|
||||||
_, latestThoughtIndice = GeneralUtils.findHighestIndexKey(thoughtDict, "Thought")
|
|
||||||
nextThoughtIndice = latestThoughtIndice + 1
|
|
||||||
|
|
||||||
_prompt =
|
|
||||||
"""
|
|
||||||
You are a helpful sommelier working for a wine store.
|
|
||||||
Your goal is to reccommend the best wine from your inventory that match the user preferences.
|
|
||||||
|
|
||||||
You must follow the following criteria:
|
|
||||||
1) Get to know what occasion the user is buying wine for
|
|
||||||
2) Get to know what food the user will have with wine
|
|
||||||
3) Get to know how much the user willing to spend
|
|
||||||
4) Get to know type of wine the user is looking for e.g. Red, White, Sparkling, Rose, Dessert, Fortified
|
|
||||||
5) Get to know what characteristics of wine the user is looking for
|
|
||||||
e.g. tannin, sweetness, intensity, acidity
|
|
||||||
6) Check your inventory for the best wine that match the user preference
|
|
||||||
7) Recommend wine to the user
|
|
||||||
|
|
||||||
You should only respond with interleaving Thought, Action, Observation steps.
|
|
||||||
Thought can reason about the current situation, and Action can be three types:
|
|
||||||
1) winestock[query], which you can use to find wine in your inventory. The more input data the better.
|
|
||||||
2) chatbox[text], which you can use to interact with the user.
|
|
||||||
3) recommendation[answer], which returns your wine reccommendation to the user.
|
|
||||||
|
|
||||||
You should only respond in JSON format as describe below:
|
|
||||||
{
|
|
||||||
"Thought": "your reasoning",
|
|
||||||
"Action": {"name": "action to take", "input": "Action input"},
|
|
||||||
"Observation": "result of the action"
|
|
||||||
}
|
|
||||||
|
|
||||||
Here are some examples:
|
|
||||||
{
|
|
||||||
"Question": "I would like to buy a sedan with 8 seats.",
|
|
||||||
"Thought_1": "Our showroom carries various vehicle model. But I'm not sure whether we have a models that fits the user demand, I need to check our inventory.",
|
|
||||||
"Action_1": {"name": "inventory", "input": "sedan with 8 seats."},
|
|
||||||
"Observation_1": "Several model has 8 seats. Available color are black, red green"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"Thought_2": "I have to ask the user what color he likes.",
|
|
||||||
"Action_2": {"name": "chatbox", "input": "Which color do you like?"}
|
|
||||||
"Observation_2": "I'll take black."
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"Thought_3": "There is only one model that fits the user preference. It's Yiem model A",
|
|
||||||
"Action_3": {"name": "recommendation", "input": "I recommend a Yiem model A"}
|
|
||||||
}
|
|
||||||
|
|
||||||
Let's begin!
|
|
||||||
|
|
||||||
$(JSON3.write(thoughtDict))
|
|
||||||
{Thought_$nextThoughtIndice
|
|
||||||
"""
|
|
||||||
|
|
||||||
prompt = YiemAgent.formatLLMtext_llama3instruct("system", _prompt)
|
|
||||||
@show prompt
|
|
||||||
msgMeta = Dict(:requestResponse => nothing,
|
|
||||||
:msgPurpose => nothing,
|
|
||||||
:receiverId => nothing,
|
|
||||||
:getPost => nothing,
|
|
||||||
:msgId => "4c7111e0-c30e-44c3-8f85-1c8b3f03a8be",
|
|
||||||
:acknowledgestatus => nothing,
|
|
||||||
:replyToMsgId => nothing,
|
|
||||||
:msgFormatVersion => nothing,
|
|
||||||
:mqttServerInfo => Dict(:port => 1883, :broker => "mqtt.yiem.cc"),
|
|
||||||
:sendTopic => "/loadbalancer/requestingservice",
|
|
||||||
:receiverName => "text2textinstruct",
|
|
||||||
:replyTopic => nothing,
|
|
||||||
:senderName => "decisionMaker",
|
|
||||||
:senderSelfnote => nothing,
|
|
||||||
:senderId => "testingSessionID",
|
|
||||||
:timeStamp => "2024-05-04T08:06:23.561"
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
|
||||||
:msgMeta=> msgMeta,
|
|
||||||
:payload=> Dict(
|
|
||||||
:text=> prompt,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
|
||||||
thoughtJsonStr = _response[:response][:text]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
using Revise # remove when this package is completed
|
|
||||||
using YiemAgent, GeneralUtils, JSON3, MQTTClient, Dates, UUIDs, DataStructures
|
|
||||||
using Base.Threads
|
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
||||||
|
|
||||||
config = copy(JSON3.read("config.json"))
|
|
||||||
|
|
||||||
instanceInternalTopic = config[:serviceInternalTopic][:mqtttopic] * "/1"
|
|
||||||
|
|
||||||
client, connection = MakeConnection(config[:mqttServerInfo][:broker],
|
|
||||||
config[:mqttServerInfo][:port])
|
|
||||||
|
|
||||||
receiveUserMsgChannel = Channel{Dict}(4)
|
|
||||||
receiveInternalMsgChannel = Channel{Dict}(4)
|
|
||||||
|
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
|
||||||
"N/A",
|
|
||||||
replyTopic = config[:servicetopic][:mqtttopic] # ask frontend reply to this instance_chat_topic
|
|
||||||
)
|
|
||||||
|
|
||||||
agentConfig = Dict(
|
|
||||||
:mqttServerInfo=> config[:mqttServerInfo],
|
|
||||||
:receivemsg=> Dict(
|
|
||||||
:prompt=> config[:servicetopic][:mqtttopic], # topic to receive prompt i.e. frontend send msg to this topic
|
|
||||||
:internal=> instanceInternalTopic,
|
|
||||||
),
|
|
||||||
:externalservice=> config[:externalservice],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Instantiate an agent
|
|
||||||
tools=Dict( # update input format
|
|
||||||
"askbox"=> Dict(
|
|
||||||
:description => "<askbox tool description>Useful for when you need to ask the user for more context. Do not ask the user their own question.</askbox tool description>",
|
|
||||||
:input => """<input>Input is a text in JSON format.</input><input example>{\"Q1\": \"How are you doing?\", \"Q2\": \"How may I help you?\"}</input example>""",
|
|
||||||
:output => "" ,
|
|
||||||
:func => nothing,
|
|
||||||
),
|
|
||||||
# "winestock"=> Dict(
|
|
||||||
# :description => "<winestock tool description>A handy tool for searching wine in your inventory that match the user preferences.</winestock tool description>",
|
|
||||||
# :input => """<input>Input is a JSON-formatted string that contains a detailed and precise search query.</input><input example>{\"wine type\": \"rose\", \"price\": \"max 35\", \"sweetness level\": \"sweet\", \"intensity level\": \"light bodied\", \"Tannin level\": \"low\", \"Acidity level\": \"low\"}</input example>""",
|
|
||||||
# :output => """<output>Output are wines that match the search query in JSON format.""",
|
|
||||||
# :func => ChatAgent.winestock,
|
|
||||||
# ),
|
|
||||||
"finalanswer"=> Dict(
|
|
||||||
:description => "<tool description>Useful for when you are ready to recommend wines to the user.</tool description>",
|
|
||||||
:input => """<input format>{\"finalanswer\": \"some text\"}.</input format><input example>{\"finalanswer\": \"I recommend Zena Crown Vista\"}</input example>""",
|
|
||||||
:output => "" ,
|
|
||||||
:func => nothing,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
a = YiemAgent.sommelier(
|
|
||||||
receiveUserMsgChannel,
|
|
||||||
receiveInternalMsgChannel,
|
|
||||||
agentConfig,
|
|
||||||
name= "assistant",
|
|
||||||
id= "testingSessionID", # agent instance id
|
|
||||||
tools=tools,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
input =
|
|
||||||
OrderedDict{Symbol, Any}(:question => "Hello, I would like a get a bottle of wine", :thought_1 => "It's great that the user is looking for a bottle of wine. To give them a personalized recommendation, I need to know more about their preferences.", :action_1 => Dict{Symbol, Any}(:name => "chatbox", :input => "What occasion are you planning to use this wine for?"), :observation_1 => "We are holding a wedding party", :thought_2 => "A wedding party is a great occasion for a special bottle of wine. I need to know what type of food will be served, and how much the user is willing to spend.", :action_2 => Dict{Symbol, Any}(:name => "chatbox", :input => "What type of food will you be serving at the wedding?"), :observation_2 => "It will be Thai dishes.", :thought_3 => "The type of wine that pairs well with Thai dishes is usually a crisp and refreshing white wine, but I also need to consider the budget and personal preferences.", :action_3 => Dict{Symbol, Any}(:name => "chatbox", :input => "How much are you willing to spend on this bottle of wine?"), :observation_3 => "I would spend up to 50 bucks.", :thought_4 => "I have a good idea of the occasion, food, and budget. Now I need to know what type of wine the user is looking for.", :action_4 => Dict{Symbol, Any}(:name => "chatbox", :input => "What type of wine are you usually looking for? Red, White, Sparkling, Rose, Dessert or Fortified?"), :observation_4 => "I like full-bodied Red wine with low tannin.", :thought_5 => "Now that I have all the necessary information, I can start searching for a suitable wine in our inventory.", :action_5 => Dict{Symbol, Any}(:name => "winestock", :input => "red wine with low tannins"), :observation_5 => "I found the following wines in our stock: \n{\n 1: El Enemigo Cabernet Franc 2019\n2: Tantara Chardonnay 2017\n\n}\n", :thought_6 => "Now that I have the information about the wine, it's time to make a recommendation.", :action_6 => Dict{Symbol, Any}(:name => "recommendbox", :input => "El Enemigo Cabernet Franc 2019"), :observation_6 => "I don't like the one you recommend. I want dry wine.")
|
|
||||||
|
|
||||||
|
|
||||||
result = YiemAgent.jsoncorrection(a, input)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
using Revise
|
|
||||||
using YiemAgent, GeneralUtils, JSON3, DataStructures, LibPQ
|
|
||||||
using SQLLLM
|
|
||||||
|
|
||||||
|
|
||||||
# _prompt =
|
|
||||||
# """
|
|
||||||
# You are a helpful assistant.
|
|
||||||
# answer the following question:
|
|
||||||
# From the following CSV text:
|
|
||||||
# "{\"tabledescription\":[\"The customer table stores information about customers. It includes details such as first name, last name, display name, username, password, gender, country, telephone number, email, birthdate, additional_search_term, other attributes (in JSON format) and a description.\",\"The wine table stores information about different wines. It includes details namely id, name, brand, manufacturer, region, country, wine_type, grape_variety, serving_temperature, intensity, sweetness, tannin, acidity, fizziness, additional_search_term, other attributes (in JSON format) and a description.\",\"The wine_food table represents the association between wines and food items. It estab" ⋯ 477 bytes ⋯ "ed to retailer names, usernames, passwords, addresses, contact persons, telephone numbers, email addresses, additional_search_term, other attributes (in JSON format) and a description.\",\"The retailer_wine table represents the relationship between retailers and wines. It stores information about the wines available from which retailers, including vintage, their price, and the currency.\",\"The retailer_food table represents the relationship between retailers and food items. It stores information about the food items available from which retailers, including their price and the currency.\"],\"tablename\":[\"customer\",\"wine\",\"wine_food\",\"food\",\"retailer\",\"retailer_wine\",\"retailer_food\"]}"
|
|
||||||
# What is the description of table wine?
|
|
||||||
# """
|
|
||||||
|
|
||||||
# prompt = YiemAgent.formatLLMtext_llama3instruct("system", _prompt)
|
|
||||||
# @show prompt
|
|
||||||
# msgMeta = Dict(:requestResponse => nothing,
|
|
||||||
# :msgPurpose => nothing,
|
|
||||||
# :receiverId => nothing,
|
|
||||||
# :getPost => nothing,
|
|
||||||
# :msgId => "4c7111e0-c30e-44c3-8f85-1c8b3f03a8be",
|
|
||||||
# :acknowledgestatus => nothing,
|
|
||||||
# :replyToMsgId => nothing,
|
|
||||||
# :msgFormatVersion => nothing,
|
|
||||||
# :mqttServerInfo => Dict(:port => 1883, :broker => "mqtt.yiem.cc"),
|
|
||||||
# :sendTopic => "/loadbalancer/requestingservice",
|
|
||||||
# :receiverName => "text2textinstruct",
|
|
||||||
# :replyTopic => nothing,
|
|
||||||
# :senderName => "decisionMaker",
|
|
||||||
# :senderSelfnote => nothing,
|
|
||||||
# :senderId => "testingSessionID",
|
|
||||||
# :timeStamp => "2024-05-04T08:06:23.561"
|
|
||||||
# )
|
|
||||||
|
|
||||||
# outgoingMsg = Dict(
|
|
||||||
# :msgMeta=> msgMeta,
|
|
||||||
# :payload=> Dict(
|
|
||||||
# :text=> prompt,
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# _response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
|
||||||
# result = _response[:response][:text]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DBconnection = LibPQ.Connection("host=192.168.88.12 port=5432 dbname=yiem_wine_assistant user=yiem password=yiem@Postgres_0.0")
|
|
||||||
|
|
||||||
tableinfo, df1, df2, df3 = SQLLLM.tableinfo(DBconnection, "wine")
|
|
||||||
|
|
||||||
|
|
||||||
_prompt =
|
|
||||||
"""
|
|
||||||
You are a helpful assistant helping to answer user question from a database table.
|
|
||||||
|
|
||||||
$tableinfo
|
|
||||||
|
|
||||||
Are there any chardonnay?
|
|
||||||
"""
|
|
||||||
|
|
||||||
prompt = YiemAgent.formatLLMtext_llama3instruct("system", _prompt)
|
|
||||||
@show prompt
|
|
||||||
msgMeta = Dict(:requestResponse => nothing,
|
|
||||||
:msgPurpose => nothing,
|
|
||||||
:receiverId => nothing,
|
|
||||||
:getPost => nothing,
|
|
||||||
:msgId => "4c7111e0-c30e-44c3-8f85-1c8b3f03a8be",
|
|
||||||
:acknowledgestatus => nothing,
|
|
||||||
:replyToMsgId => nothing,
|
|
||||||
:msgFormatVersion => nothing,
|
|
||||||
:mqttServerInfo => Dict(:port => 1883, :broker => "mqtt.yiem.cc"),
|
|
||||||
:sendTopic => "/loadbalancer/requestingservice",
|
|
||||||
:receiverName => "text2textinstruct",
|
|
||||||
:replyTopic => nothing,
|
|
||||||
:senderName => "decisionMaker",
|
|
||||||
:senderSelfnote => nothing,
|
|
||||||
:senderId => "testingSessionID",
|
|
||||||
:timeStamp => "2024-05-04T08:06:23.561"
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
|
||||||
:msgMeta=> msgMeta,
|
|
||||||
:payload=> Dict(
|
|
||||||
:text=> prompt,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
|
||||||
result2 = _response[:response][:text]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+323
-194
@@ -1,227 +1,356 @@
|
|||||||
using Revise
|
using JSON, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames, DataStructures, HTTP, Base64,
|
||||||
using JSON, JSON3, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames
|
NATS, Base.Threads
|
||||||
using YiemAgent, GeneralUtils
|
using YiemAgent, GeneralUtils, msghandler
|
||||||
using Base.Threads
|
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
function text2text_instruct_llm(sender_id::String, openai_msg::Dict{String, Any})
|
||||||
|
payloads = [("msg", openai_msg, "dictionary")] # List of tuples
|
||||||
|
_, msg_envelope_json_str = msghandler.smartpack(
|
||||||
|
config["externalservice"]["servicesloadbalancer"]["nats"],
|
||||||
|
payloads;
|
||||||
|
sender_id=sender_id,
|
||||||
|
msg_purpose="text2text",
|
||||||
|
broker_url=config["nats_server_info"]["url"],
|
||||||
|
fileserver_url=config["externalservice"]["fileserver"]["url"])
|
||||||
|
|
||||||
|
reply = NATS.request(agent_conn,
|
||||||
|
config["externalservice"]["servicesloadbalancer"]["nats"],
|
||||||
|
msg_envelope_json_str, timeout=120)
|
||||||
|
|
||||||
|
incoming_env_json_str = String(reply.payload)
|
||||||
|
incoming_env = msghandler.smartunpack(incoming_env_json_str)
|
||||||
|
_llm_response = incoming_env["payloads"][1][2]
|
||||||
|
llm_response = _llm_response["choices"][1]["message"]["content"]
|
||||||
|
return llm_response
|
||||||
|
end
|
||||||
|
|
||||||
# load config
|
""" get a single text embedding from a LLM service
|
||||||
config = JSON3.read("./test/config.json")
|
Example
|
||||||
# config = copy(JSON3.read("../mountvolume/config.json"))
|
text = ["hello"]
|
||||||
|
embedding = get_embedding(text)
|
||||||
|
"""
|
||||||
|
function get_embedding(text::AbstractArray{String})
|
||||||
|
documents_dict = Dict("documents" => text)
|
||||||
|
payloads = [("documents", documents_dict, "dictionary")]
|
||||||
|
_, msg_envelope_json_str = msghandler.smartpack(
|
||||||
|
config["externalservice"]["servicesloadbalancer"]["nats"],
|
||||||
|
payloads;
|
||||||
|
msg_purpose="embedding",
|
||||||
|
broker_url=config["nats_server_info"]["url"],
|
||||||
|
fileserver_url=config["externalservice"]["fileserver"]["url"])
|
||||||
|
|
||||||
|
reply = NATS.request(agent_conn,
|
||||||
|
config["externalservice"]["servicesloadbalancer"]["nats"],
|
||||||
|
msg_envelope_json_str, timeout=120)
|
||||||
|
incoming_env_json_str = String(reply.payload)
|
||||||
|
incoming_env = msghandler.smartunpack(incoming_env_json_str)
|
||||||
|
embedding_response = incoming_env["payloads"][1][2]
|
||||||
|
|
||||||
function executeSQL(sql::T) where {T<:AbstractString}
|
return embedding_response
|
||||||
DBconnection = LibPQ.Connection("host=192.168.88.12 port=10201 dbname=wineDB user=yiemtechnologies password=yiemtechnologies@Postgres_0.0")
|
end
|
||||||
result = LibPQ.execute(DBconnection, sql)
|
|
||||||
close(DBconnection)
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function executeSQLVectorDB(sql)
|
""" sql = "SELECT * FROM wine;"
|
||||||
DBconnection = LibPQ.Connection("host=192.168.88.12 port=10203 dbname=SQLVectorDB user=yiemtechnologies password=yiemtechnologies@Postgres_0.0")
|
result = execute_sql_winedb(sql)
|
||||||
result = LibPQ.execute(DBconnection, sql)
|
"""
|
||||||
close(DBconnection)
|
function execute_sql_winedb(sql::T) where {T<:AbstractString}
|
||||||
return result
|
host_url, _port = split(config["externalservice"]["sommpanion_db"]["url"], ':')
|
||||||
end
|
port = parse(Int, _port)
|
||||||
|
dbname = "winedb"
|
||||||
|
user = config["externalservice"]["sommpanion_db"]["user"]
|
||||||
|
password = config["externalservice"]["sommpanion_db"]["password"]
|
||||||
|
db_connection = LibPQ.Connection("host=$host_url port=$port dbname=$dbname user=$user password=$password")
|
||||||
|
result = nothing
|
||||||
|
try
|
||||||
|
result = LibPQ.execute(db_connection, sql)
|
||||||
|
catch e
|
||||||
|
LibPQ.close(db_connection)
|
||||||
|
end
|
||||||
|
|
||||||
function text2textInstructLLM(prompt::String)
|
LibPQ.close(db_connection)
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
return result
|
||||||
config[:externalservice][:text2textinstruct][:mqtttopic];
|
end
|
||||||
msgPurpose="inference",
|
|
||||||
senderName="yiemagent",
|
|
||||||
senderId=string(uuid4()),
|
|
||||||
receiverName="text2textinstruct",
|
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
""" find similar sql from vector database
|
||||||
:msgMeta => msgMeta,
|
sql = "SELECT * FROM wine;"
|
||||||
:payload => Dict(
|
result, distance = similar_sql_vectordb(sql)
|
||||||
:text => prompt,
|
"""
|
||||||
:kwargs => Dict(
|
function similar_sql_vectordb(sql::T; maxdistance::Number=0.2) where {T<:AbstractString}
|
||||||
:num_ctx => 16384,
|
tablename = "sqlllm_decision_repository"
|
||||||
:temperature => 0.2,
|
# get embedding of the query
|
||||||
)
|
df = find_similar_text_from_vectordb(sql, tablename,
|
||||||
)
|
"function_input_embedding", execute_sql_vectordb)
|
||||||
)
|
# println(df[1, [:id, :function_output]])
|
||||||
|
row, col = size(df)
|
||||||
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
|
if row != 0 && distance < maxdistance
|
||||||
|
# if there is usable SQL, return it.
|
||||||
|
output_b64 = df[1, :function_output_base64] # pick the closest match
|
||||||
|
output_str = String(base64decode(output_b64))
|
||||||
|
rowid = df[1, :id]
|
||||||
|
println("\n--| similar sql found. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
pprintln(output_str)
|
||||||
|
return (result=output_str, distance=distance)
|
||||||
|
else
|
||||||
|
println("\n--| similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
return (result=nothing, distance=nothing)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=6000)
|
""" insert query and sql into vector database
|
||||||
response = _response[:response][:text]
|
query = "get all wines from wine table"
|
||||||
|
sql = "SELECT * FROM wine;"
|
||||||
|
insert_sql_vectordb(query, sql)
|
||||||
|
"""
|
||||||
|
function insert_sql_vectordb(query::T1, SQL::T2; maxdistance::Number=3
|
||||||
|
) where {T1<:AbstractString, T2<:AbstractString}
|
||||||
|
|
||||||
return response
|
tablename = "sqlllm_decision_repository"
|
||||||
end
|
# get embedding of the query
|
||||||
|
# query = state[:thoughtHistory][:question]
|
||||||
|
df = find_similar_text_from_vectordb(query, tablename,
|
||||||
|
"function_input_embedding", execute_sql_vectordb)
|
||||||
|
row, col = size(df)
|
||||||
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
|
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
||||||
|
_query_embedding = get_embedding([query])
|
||||||
|
_query_embedding = GeneralUtils.dictify(_query_embedding)
|
||||||
|
# println("\n--- _query_embedding() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
# println(_query_embedding)
|
||||||
|
# println("---\n")
|
||||||
|
query_embedding = _query_embedding["data"][1]["embedding"]
|
||||||
|
query = replace(query, "'" => "")
|
||||||
|
sql_base64 = base64encode(SQL)
|
||||||
|
sql_ = replace(SQL, "'" => "")
|
||||||
|
|
||||||
# get text embedding from a LLM service
|
sql =
|
||||||
function getEmbedding(text::T) where {T<:AbstractString}
|
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
|
||||||
config[:externalservice][:text2textinstruct][:mqtttopic];
|
|
||||||
msgPurpose="embedding",
|
|
||||||
senderName="yiemagent",
|
|
||||||
senderId=string(uuid4()),
|
|
||||||
receiverName="text2textinstruct",
|
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
|
||||||
:msgMeta => msgMeta,
|
|
||||||
:payload => Dict(
|
|
||||||
:text => [text] # must be a vector of string
|
|
||||||
)
|
|
||||||
)
|
|
||||||
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=6000)
|
|
||||||
embedding = response[:response][:embeddings]
|
|
||||||
return embedding
|
|
||||||
end
|
|
||||||
|
|
||||||
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
|
|
||||||
vectorDB::Function; limit::Integer=1
|
|
||||||
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
|
|
||||||
|
|
||||||
# get embedding from LLM service
|
|
||||||
embedding = getEmbedding(text)[1]
|
|
||||||
|
|
||||||
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
|
|
||||||
sql = """
|
|
||||||
SELECT *, $embeddingColumnName <-> '$embedding' as distance
|
|
||||||
FROM $tablename
|
|
||||||
ORDER BY distance LIMIT $limit;
|
|
||||||
"""
|
"""
|
||||||
response = vectorDB(sql)
|
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
|
||||||
df = DataFrame(response)
|
"""
|
||||||
return df
|
# println("\n--| added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
end
|
# println(sql)
|
||||||
|
_ = execute_sql_vectordb(sql)
|
||||||
|
end
|
||||||
function similarSQLVectorDB(query; maxdistance::Integer=100)
|
|
||||||
tablename = "sqlllm_decision_repository"
|
|
||||||
# get embedding of the query
|
|
||||||
df = findSimilarTextFromVectorDB(query, tablename,
|
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
if row != 0 && distance < maxdistance
|
|
||||||
# if there is usable SQL, return it.
|
|
||||||
output_b64 = df[1, :function_output_base64] # pick the closest match
|
|
||||||
output_str = String(base64decode(output_b64))
|
|
||||||
rowid = df[1, :id]
|
|
||||||
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
|
|
||||||
return (dict=output_str, distance=distance)
|
|
||||||
else
|
|
||||||
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
|
|
||||||
return (dict=nothing, distance=nothing)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
|
""" execute sql against vectordb
|
||||||
|
sql = "SELECT * FROM wine;"
|
||||||
|
result = execute_sql_vectordb(sql)
|
||||||
|
"""
|
||||||
|
function execute_sql_vectordb(sql::T) where {T<:AbstractString}
|
||||||
|
host_url, _port = split(config["externalservice"]["sommpanion_vectordb"]["url"], ':')
|
||||||
|
port = parse(Int, _port)
|
||||||
|
dbname = config["externalservice"]["sommpanion_vectordb"]["dbname"]
|
||||||
|
user = config["externalservice"]["sommpanion_vectordb"]["user"]
|
||||||
|
password = config["externalservice"]["sommpanion_vectordb"]["password"]
|
||||||
|
DBconnection = LibPQ.Connection("host=$host_url port=$port dbname=$dbname user=$user password=$password")
|
||||||
|
result = LibPQ.execute(DBconnection, sql)
|
||||||
|
close(DBconnection)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=1) where {T1<:AbstractString, T2<:AbstractString}
|
""" search similar decision llm made from vectordb
|
||||||
tablename = "sqlllm_decision_repository"
|
"""
|
||||||
# get embedding of the query
|
function similar_sommelier_decision(recentevents::T1; maxdistance::Integer=3
|
||||||
# query = state[:thoughtHistory][:question]
|
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
|
||||||
df = findSimilarTextFromVectorDB(query, tablename,
|
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
|
||||||
query_embedding = getEmbedding(query)[1]
|
|
||||||
query = replace(query, "'" => "")
|
|
||||||
sql_base64 = base64encode(SQL)
|
|
||||||
sql_ = replace(SQL, "'" => "")
|
|
||||||
|
|
||||||
|
tablename = "sommelier_decision_repository"
|
||||||
|
# find similar
|
||||||
|
df = find_similar_text_from_vectordb(recentevents, tablename,
|
||||||
|
"function_input_embedding", execute_sql_vectordb)
|
||||||
|
row, col = size(df)
|
||||||
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
|
if row != 0 && distance < maxdistance
|
||||||
|
# if there is usable decision, return it.
|
||||||
|
rowid = df[1, :id]
|
||||||
|
println("\n--| found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
|
||||||
|
output_b64 = df[1, :function_output_base64] # pick the closest match
|
||||||
|
_output_str = String(base64decode(output_b64))
|
||||||
|
output = copy(JSON.read(_output_str))
|
||||||
|
return output
|
||||||
|
else
|
||||||
|
println("\n--| similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
""" search similar text from vectordb
|
||||||
|
"""
|
||||||
|
function find_similar_text_from_vectordb(text::T1, tablename::T2, embeddingColumnName::T3,
|
||||||
|
vectorDB::Function; limit::Integer=1
|
||||||
|
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
|
||||||
|
# get embedding from LLM service
|
||||||
|
_embedding = get_embedding([text])
|
||||||
|
_embedding = _embedding["data"][1]["embedding"]
|
||||||
|
_embedding = "$_embedding"
|
||||||
|
|
||||||
|
embedding = _embedding[4:end]
|
||||||
|
|
||||||
|
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
|
||||||
sql = """
|
sql = """
|
||||||
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
|
SELECT *, $embeddingColumnName <-> '$embedding' as distance
|
||||||
|
FROM $tablename
|
||||||
|
ORDER BY distance LIMIT $limit;
|
||||||
"""
|
"""
|
||||||
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
|
response = vectorDB(sql)
|
||||||
println(sql)
|
df = DataFrame(response)
|
||||||
_ = executeSQLVectorDB(sql)
|
|
||||||
|
return df
|
||||||
end
|
end
|
||||||
|
|
||||||
|
""" insert decision llm made to vectordb
|
||||||
|
"""
|
||||||
|
function insert_sommelier_decision(recentevents::T1, decision::T2; maxdistance::Integer=5
|
||||||
|
) where {T1<:AbstractString, T2<:AbstractDict}
|
||||||
|
tablename = "sommelier_decision_repository"
|
||||||
|
# find similar
|
||||||
|
df = find_similar_text_from_vectordb(recentevents, tablename,
|
||||||
|
"function_input_embedding", execute_sql_vectordb)
|
||||||
|
row, col = size(df)
|
||||||
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
|
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
||||||
|
_embedding = get_embedding([recentevents])[1]
|
||||||
|
recentevents_embedding = _embedding["data"][1]["embedding"]
|
||||||
|
recentevents = replace(recentevents, "'" => "")
|
||||||
|
decision_json = JSON.json(decision)
|
||||||
|
decision_base64 = base64encode(decision_json)
|
||||||
|
decision = replace(decision_json, "'" => "")
|
||||||
|
|
||||||
|
sql =
|
||||||
|
"""
|
||||||
|
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$recentevents', '$decision', '$decision_base64', '$recentevents_embedding');
|
||||||
|
"""
|
||||||
|
println("\n--| added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
|
||||||
|
println(sql)
|
||||||
|
_ = execute_sql_vectordb(sql)
|
||||||
|
else
|
||||||
|
println("--| similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
config = JSON.parsefile("./appconfig.json")
|
||||||
|
sessionId = "0"
|
||||||
|
backend_session_topic = "sommpanion.testsubject"
|
||||||
|
agent_ch = Channel(8)
|
||||||
|
agent_conn = NATS.connect(config["nats_server_info"]["url"])
|
||||||
|
|
||||||
|
sub2 = NATS.subscribe(agent_conn, backend_session_topic) do msg
|
||||||
|
put!(agent_ch, msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
agent_context = YiemAgent.agentcontext(
|
||||||
|
text2text_instruct_llm,
|
||||||
|
get_embedding,
|
||||||
|
execute_sql_winedb,
|
||||||
|
similar_sql_vectordb,
|
||||||
|
insert_sql_vectordb,
|
||||||
|
similar_sommelier_decision,
|
||||||
|
insert_sommelier_decision
|
||||||
|
)
|
||||||
|
|
||||||
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=5
|
# can't instantiate
|
||||||
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
|
agent = YiemAgent.sommelier(
|
||||||
tablename = "sommelier_decision_repository"
|
agent_context;
|
||||||
# find similar
|
name="Janie",
|
||||||
println("\n~~~ search vectorDB for this: $recentevents ", @__FILE__, " ", @__LINE__)
|
id=sessionId, # agent instance id
|
||||||
df = findSimilarTextFromVectorDB(recentevents, tablename,
|
retailername="Yiem Wine Ltd.",
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
llmFormatName=""
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
if row != 0 && distance < maxdistance
|
|
||||||
# if there is usable decision, return it.
|
|
||||||
rowid = df[1, :id]
|
|
||||||
println("\n~~~ found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
|
|
||||||
output_b64 = df[1, :function_output_base64] # pick the closest match
|
|
||||||
_output_str = String(base64decode(output_b64))
|
|
||||||
output = copy(JSON3.read(_output_str))
|
|
||||||
return output
|
|
||||||
else
|
|
||||||
println("\n~~~ similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
|
|
||||||
return nothing
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function insertSommelierDecision(recentevents::T1, decision::T2; maxdistance::Integer=5
|
|
||||||
) where {T1<:AbstractString, T2<:AbstractDict}
|
|
||||||
tablename = "sommelier_decision_repository"
|
|
||||||
# find similar
|
|
||||||
df = findSimilarTextFromVectorDB(recentevents, tablename,
|
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
|
||||||
recentevents_embedding = a.func[:getEmbedding](recentevents)[1]
|
|
||||||
recentevents = replace(recentevents, "'" => "")
|
|
||||||
decision_json = JSON3.write(decision)
|
|
||||||
decision_base64 = base64encode(decision_json)
|
|
||||||
decision = replace(decision_json, "'" => "")
|
|
||||||
|
|
||||||
sql = """
|
|
||||||
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$recentevents', '$decision', '$decision_base64', '$recentevents_embedding');
|
|
||||||
"""
|
|
||||||
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
|
|
||||||
println(sql)
|
|
||||||
_ = executeSQLVectorDB(sql)
|
|
||||||
else
|
|
||||||
println("~~~ similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
sessionId = "12345"
|
|
||||||
|
|
||||||
externalFunction = (
|
|
||||||
getEmbedding=getEmbedding,
|
|
||||||
text2textInstructLLM=text2textInstructLLM,
|
|
||||||
executeSQL=executeSQL,
|
|
||||||
similarSQLVectorDB=similarSQLVectorDB,
|
|
||||||
insertSQLVectorDB=insertSQLVectorDB,
|
|
||||||
similarSommelierDecision=similarSommelierDecision,
|
|
||||||
insertSommelierDecision=insertSommelierDecision,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
image1_path = "test/large_image.png"
|
||||||
|
image1_bytes = read(image1_path)
|
||||||
|
image1_base64_string = base64encode(image1_bytes)
|
||||||
|
mime_type = "image/png"
|
||||||
|
data1_uri = "data:$(mime_type);base64,$(image1_base64_string)"
|
||||||
|
|
||||||
|
# 1. Read local file and encode to base64 string
|
||||||
|
image2_path = "test/small_image.png"
|
||||||
|
image2_bytes = read(image2_path)
|
||||||
|
image2_base64_string = base64encode(image2_bytes)
|
||||||
|
mime_type = "image/png"
|
||||||
|
data2_uri = "data:$(mime_type);base64,$(image2_base64_string)"
|
||||||
|
|
||||||
|
# 3. Construct payload with the Data URI
|
||||||
|
message = Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "Do you know type of wine in the image?"),
|
||||||
|
Dict(
|
||||||
|
"type" => "image_url",
|
||||||
|
"image_url" => Dict("url" => data1_uri)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
result = YiemAgent.conversation(agent; userinput=message)
|
||||||
|
println("\n$result")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# message = Dict(
|
||||||
|
# "role" => "user",
|
||||||
|
# "content" => [
|
||||||
|
# Dict("type" => "text", "text" =>
|
||||||
|
# "
|
||||||
|
# เป็นงานเลี้ยงทั่วไป
|
||||||
|
# "),
|
||||||
|
# ]
|
||||||
|
# )
|
||||||
|
|
||||||
|
# result = YiemAgent.conversation(agent; userinput=message)
|
||||||
|
# println("\n$result")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# message = Dict(
|
||||||
|
# "role" => "user",
|
||||||
|
# "content" => [
|
||||||
|
# Dict("type" => "text", "text" => "no thanks. that's all"),
|
||||||
|
# ]
|
||||||
|
# )
|
||||||
|
|
||||||
|
# result = YiemAgent.conversation(agent; userinput=message)
|
||||||
|
# println("\n$result")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# message = Dict(
|
||||||
|
# "role" => "user",
|
||||||
|
# "content" => [
|
||||||
|
# Dict("type" => "text", "text" => "What about this wine?"),
|
||||||
|
# Dict(
|
||||||
|
# "type" => "image_url",
|
||||||
|
# "image_url" => Dict("url" => data2_uri)
|
||||||
|
# )
|
||||||
|
# ]
|
||||||
|
# )
|
||||||
|
|
||||||
|
# result = YiemAgent.conversation(agent; userinput=message)
|
||||||
|
# println("\n$result")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
a = YiemAgent.sommelier(
|
|
||||||
externalFunction;
|
|
||||||
name="Ton",
|
|
||||||
id=sessionId, # agent instance id
|
|
||||||
retailername="Yiem",
|
|
||||||
)
|
|
||||||
|
|
||||||
while true
|
|
||||||
println("your respond: ")
|
|
||||||
user_answer = readline()
|
|
||||||
response = YiemAgent.conversation(a, Dict(:text=> user_answer))
|
|
||||||
println("\n$response")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,212 @@
|
|||||||
|
using Test
|
||||||
|
using YiemAgent
|
||||||
|
using YiemAgent.toolRegistry
|
||||||
|
using YiemAgent.type
|
||||||
|
|
||||||
|
# Path to the real tools directory
|
||||||
|
TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
|
||||||
|
|
||||||
|
@testset "loadTools with toolStore" begin
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 1. loadTools throws on non-existent directory #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
store = toolStore(name="test1")
|
||||||
|
@test_throws ArgumentError loadTools(store, "/nonexistent/dir/that/does/not/exist")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 2. loadTools throws if a .jl file does not define getTool() #
|
||||||
|
# Must run BEFORE any other loadTools call (getTool binding #
|
||||||
|
# persists in module scope after include()). #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
bad_dir = mktempdir()
|
||||||
|
write(joinpath(bad_dir, "noTool.jl"), "x = 42\n")
|
||||||
|
@test_throws ArgumentError loadTools(store, bad_dir)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 3. loadTools loads actual tool files from src/tools/ #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
store2 = toolStore(name="test2")
|
||||||
|
loaded = loadTools(store2, TOOLS_DIR)
|
||||||
|
@test !isempty(loaded)
|
||||||
|
@test length(loaded) == 4 # 3 files + auto-registered listTools
|
||||||
|
|
||||||
|
names = [k for k in keys(loaded)]
|
||||||
|
@test "getTime" in names
|
||||||
|
@test "getWeather" in names
|
||||||
|
@test "writeTool" in names
|
||||||
|
@test "listTools" in names
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 4. loadTools returns tools sorted alphabetically by filename #
|
||||||
|
# (getTime.jl < getWeather.jl < writeTool.jl) + listTools at end #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
@test collect(keys(loaded))[1] == "getTime"
|
||||||
|
@test collect(keys(loaded))[2] == "getWeather"
|
||||||
|
@test collect(keys(loaded))[3] == "writeTool"
|
||||||
|
@test collect(keys(loaded))[4] == "listTools"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 5. Verify loaded tool fields are correct #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# getTime
|
||||||
|
time_tool = loaded["getTime"]
|
||||||
|
@test time_tool.name == "getTime"
|
||||||
|
@test time_tool.label == "Time Lookup"
|
||||||
|
@test time_tool.validateRequiredArgs !== nothing
|
||||||
|
@test time_tool.parallelToolExecute == false
|
||||||
|
@test time_tool.inputSchema["required"] == Any[]
|
||||||
|
|
||||||
|
# getWeather
|
||||||
|
weather = loaded["getWeather"]
|
||||||
|
@test weather.name == "getWeather"
|
||||||
|
@test weather.label == "Weather Lookup"
|
||||||
|
@test weather.execute !== nothing
|
||||||
|
@test weather.parallelToolExecute == false
|
||||||
|
@test weather.inputSchema["required"] == ["city"]
|
||||||
|
|
||||||
|
# writeTool
|
||||||
|
wt = loaded["writeTool"]
|
||||||
|
@test wt.name == "writeTool"
|
||||||
|
@test wt.label == "Create Tool"
|
||||||
|
@test wt.execute !== nothing
|
||||||
|
@test "name" in wt.inputSchema["required"]
|
||||||
|
@test "executeCode" in wt.inputSchema["required"]
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 6. Tool execution returns valid results #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
sig = nothing
|
||||||
|
op = x -> x # no-op partial result callback
|
||||||
|
|
||||||
|
# execute getTime
|
||||||
|
result_t = time_tool.execute("call-1", Dict{String,Any}("city" => "Tokyo"), sig, op)
|
||||||
|
@test result_t isa agentToolResult
|
||||||
|
@test result_t.content[1] isa textContent
|
||||||
|
@test occursin("Tokyo", result_t.content[1].text)
|
||||||
|
|
||||||
|
# execute getTime with timezone
|
||||||
|
result_tz = time_tool.execute("call-2", Dict{String,Any}("timezone" => "America/New_York"), sig, op)
|
||||||
|
@test result_tz isa agentToolResult
|
||||||
|
@test occursin("America/New_York", result_tz.content[1].text)
|
||||||
|
|
||||||
|
# execute getWeather
|
||||||
|
result_w = weather.execute("call-3", Dict{String,Any}("city" => "Bangkok"), sig, op)
|
||||||
|
@test result_w isa agentToolResult
|
||||||
|
@test result_w.content[1] isa textContent
|
||||||
|
@test occursin("Bangkok", result_w.content[1].text)
|
||||||
|
|
||||||
|
# execute getWeather with units
|
||||||
|
result_w2 = weather.execute("call-4", Dict{String,Any}("city" => "London", "units" => "fahrenheit"), sig, op)
|
||||||
|
@test occursin("72°F", result_w2.content[1].text)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 7. getTools / registerTool / clearTools (per-store isolation) #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
store3 = toolStore(name="test3")
|
||||||
|
registry_tools = getTools(store3)
|
||||||
|
@test isempty(registry_tools)
|
||||||
|
|
||||||
|
# listTool is not auto-registered anymore — each store starts empty
|
||||||
|
# Register tools manually
|
||||||
|
registerTool(store3, loaded["getTime"])
|
||||||
|
registerTool(store3, loaded["getWeather"])
|
||||||
|
registerTool(store3, loaded["writeTool"])
|
||||||
|
|
||||||
|
reg = getTools(store3)
|
||||||
|
@test !isempty(reg)
|
||||||
|
@test "getTime" in keys(reg)
|
||||||
|
@test "getWeather" in keys(reg)
|
||||||
|
@test "writeTool" in keys(reg)
|
||||||
|
@test collect(keys(reg))[1] == "getTime"
|
||||||
|
@test collect(keys(reg))[2] == "getWeather"
|
||||||
|
@test collect(keys(reg))[3] == "writeTool"
|
||||||
|
|
||||||
|
clearTools(store3)
|
||||||
|
@test isempty(getTools(store3))
|
||||||
|
|
||||||
|
test_tool = agentTool(
|
||||||
|
name = "manualTool",
|
||||||
|
label = "Manual Tool",
|
||||||
|
description = "Registered manually",
|
||||||
|
inputSchema = Dict{String,Any}("type" => "object", "properties" => Dict{String,Any}(), "required" => Any[]),
|
||||||
|
execute = (toolCallId, args, signal, onPartialResult) ->
|
||||||
|
agentToolResult([textContent("manual")], Dict{Any,Any}(), nothing, false),
|
||||||
|
prepareArguments = nothing,
|
||||||
|
validateRequiredArgs = nothing,
|
||||||
|
parallelToolExecute = true
|
||||||
|
)
|
||||||
|
registerTool(store3, test_tool)
|
||||||
|
reg = getTools(store3)
|
||||||
|
@test haskey(reg, "manualTool")
|
||||||
|
@test length(reg) == 1
|
||||||
|
@test reg["manualTool"].parallelToolExecute == true
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 8. getTools returns direct reference (mutations affect registry) #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
copy1 = getTools(store3)
|
||||||
|
copy2 = getTools(store3)
|
||||||
|
@test copy1 === copy2 # same reference, not a deep copy
|
||||||
|
empty!(copy1)
|
||||||
|
@test isempty(getTools(store3)) # mutation propagates
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 9. Per-store isolation — two stores don't share tools #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
storeA = toolStore(name="isolationA")
|
||||||
|
storeB = toolStore(name="isolationB")
|
||||||
|
|
||||||
|
registerTool(storeA, loaded["getTime"])
|
||||||
|
registerTool(storeB, loaded["getWeather"])
|
||||||
|
|
||||||
|
regA = getTools(storeA)
|
||||||
|
regB = getTools(storeB)
|
||||||
|
|
||||||
|
@test "getTime" in keys(regA)
|
||||||
|
@test "getWeather" ∉ keys(regA)
|
||||||
|
@test "getWeather" in keys(regB)
|
||||||
|
@test "getTime" ∉ keys(regB)
|
||||||
|
|
||||||
|
clearTools(storeA)
|
||||||
|
@test isempty(getTools(storeA))
|
||||||
|
@test !isempty(getTools(storeB)) # storeB unaffected
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "listTool" begin
|
||||||
|
store = toolStore(name="test_list")
|
||||||
|
loaded = loadTools(store, TOOLS_DIR) # auto-registers getWeather, getTime, writeTool + listTools
|
||||||
|
|
||||||
|
# loadTools auto-registers listTool
|
||||||
|
@test "listTools" in keys(loaded)
|
||||||
|
|
||||||
|
# listTool returns an agentTool, not a string or array
|
||||||
|
list_t = listTool(store)
|
||||||
|
@test list_t isa agentTool
|
||||||
|
@test list_t.name == "listTools"
|
||||||
|
@test list_t.label == "List Tools"
|
||||||
|
@test isempty(list_t.inputSchema["required"])
|
||||||
|
|
||||||
|
# Verify all tools appear (3 loaded + listTools = 4)
|
||||||
|
result = list_t.execute("call-1", Dict{String,Any}(), nothing, x -> x)
|
||||||
|
@test result isa agentToolResult
|
||||||
|
@test result.content[1] isa textContent
|
||||||
|
@test occursin("listTools", result.content[1].text)
|
||||||
|
@test occursin("getWeather", result.content[1].text)
|
||||||
|
@test occursin("getTime", result.content[1].text)
|
||||||
|
@test occursin("writeTool", result.content[1].text)
|
||||||
|
@test result.details["count"] == 4
|
||||||
|
|
||||||
|
# Each listTool call creates an independent closure
|
||||||
|
storeB = toolStore(name="test_listB")
|
||||||
|
registerTool(storeB, loaded["getWeather"])
|
||||||
|
list_tB = listTool(storeB)
|
||||||
|
|
||||||
|
resultA = list_t.execute("call-3", Dict{String,Any}(), nothing, x -> x)
|
||||||
|
resultB = list_tB.execute("call-4", Dict{String,Any}(), nothing, x -> x)
|
||||||
|
|
||||||
|
@test occursin("getWeather", resultA.content[1].text)
|
||||||
|
@test occursin("getWeather", resultB.content[1].text)
|
||||||
|
@test occursin("getTime", resultA.content[1].text)
|
||||||
|
@test occursin("getTime", resultB.content[1].text) == false # storeB only has getWeather
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user