Spaces:
Configuration error
Configuration error
EZTIME2025 commited on
Commit ·
21fb2c3
1
Parent(s): 9e94e66
update response parser
Browse files- .github/instructions/WORK_PLAN.md +19 -11
- examples/ai_testing/my_games/current_state.json +12 -6
- examples/ai_testing/my_games/current_state_optimized.txt +2 -2
- examples/ai_testing/my_games/prompts/prompt_player_a.json +6 -5
- examples/ai_testing/my_games/prompts/prompt_player_a.txt +7 -6
- examples/ai_testing/my_games/prompts/prompt_player_b.json +2 -2
- examples/ai_testing/my_games/prompts/prompt_player_b.txt +3 -3
- examples/ai_testing/my_games/prompts/prompt_player_c.json +2 -2
- examples/ai_testing/my_games/prompts/prompt_player_c.txt +3 -3
- pycatan/ai/response_parser.py +349 -0
- pycatan/ai/schemas.py +233 -0
.github/instructions/WORK_PLAN.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
# 🗺️ AI Agent Development Work Plan
|
| 2 |
|
| 3 |
**Date:** January 3, 2026
|
| 4 |
-
**Status:** ✅ Phase 1 - Foundation & Infrastructure (
|
| 5 |
-
**Current Task:**
|
| 6 |
|
| 7 |
## 🎯 Project Goal
|
| 8 |
|
|
@@ -90,16 +90,24 @@ Build a fully functional LLM-based AI agent that can play Settlers of Catan auto
|
|
| 90 |
|
| 91 |
---
|
| 92 |
|
| 93 |
-
#### 1.3 Response Parser
|
| 94 |
-
- [
|
| 95 |
-
- [
|
| 96 |
-
- [
|
| 97 |
-
- [
|
| 98 |
-
- [
|
| 99 |
|
| 100 |
-
**Files
|
| 101 |
-
- `pycatan/ai/response_parser.py` - Parse and validate LLM responses
|
| 102 |
-
- `pycatan/ai/schemas.py` - JSON schemas for requests/responses
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
---
|
| 105 |
|
|
|
|
| 1 |
# 🗺️ AI Agent Development Work Plan
|
| 2 |
|
| 3 |
**Date:** January 3, 2026
|
| 4 |
+
**Status:** ✅ Phase 1 - Foundation & Infrastructure (100% Complete)
|
| 5 |
+
**Current Task:** Phase 3 - Core AI Agent (3.1) - **NEXT**
|
| 6 |
|
| 7 |
## 🎯 Project Goal
|
| 8 |
|
|
|
|
| 90 |
|
| 91 |
---
|
| 92 |
|
| 93 |
+
#### 1.3 Response Parser ✅ **COMPLETED**
|
| 94 |
+
- [x] Define structured response format (JSON schema)
|
| 95 |
+
- [x] Build response parser and validator
|
| 96 |
+
- [x] Implement error handling for malformed responses
|
| 97 |
+
- [x] Create fallback mechanisms for parsing failures
|
| 98 |
+
- [x] Add response logging for debugging
|
| 99 |
|
| 100 |
+
**Files created:**
|
| 101 |
+
- ✅ `pycatan/ai/response_parser.py` - Parse and validate LLM responses
|
| 102 |
+
- ✅ `pycatan/ai/schemas.py` - JSON schemas for requests/responses
|
| 103 |
+
|
| 104 |
+
**Key features:**
|
| 105 |
+
- 🎯 Dual schema support: Active turn (with action) & Observing (no action)
|
| 106 |
+
- 🛡️ Error handling: Invalid JSON, missing fields, type validation
|
| 107 |
+
- 🔧 Fallback mechanisms: JSON repair, structure repair, default values
|
| 108 |
+
- 📊 Parse statistics tracking
|
| 109 |
+
- 🔍 Flexible parsing: Handles markdown code blocks, extra text
|
| 110 |
+
- ✅ Action parameter validation against expected schemas
|
| 111 |
|
| 112 |
---
|
| 113 |
|
examples/ai_testing/my_games/current_state.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
-
"timestamp": "2026-01-03T22:
|
| 3 |
-
"state_number":
|
| 4 |
"state": {
|
| 5 |
"hexes": [
|
| 6 |
{
|
|
@@ -118,7 +118,13 @@
|
|
| 118 |
"has_robber": false
|
| 119 |
}
|
| 120 |
],
|
| 121 |
-
"settlements": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
"cities": [],
|
| 123 |
"roads": [],
|
| 124 |
"harbors": [
|
|
@@ -190,11 +196,11 @@
|
|
| 190 |
{
|
| 191 |
"id": 0,
|
| 192 |
"name": "a",
|
| 193 |
-
"victory_points":
|
| 194 |
"total_cards": 0,
|
| 195 |
"cards_list": [],
|
| 196 |
"dev_cards_list": [],
|
| 197 |
-
"settlements":
|
| 198 |
"cities": 0,
|
| 199 |
"roads": 0,
|
| 200 |
"longest_road": 0,
|
|
@@ -244,7 +250,7 @@
|
|
| 244 |
],
|
| 245 |
"dice_result": null,
|
| 246 |
"allowed_actions": [
|
| 247 |
-
"
|
| 248 |
],
|
| 249 |
"points": [
|
| 250 |
{
|
|
|
|
| 1 |
{
|
| 2 |
+
"timestamp": "2026-01-03T22:34:02.690022",
|
| 3 |
+
"state_number": 3,
|
| 4 |
"state": {
|
| 5 |
"hexes": [
|
| 6 |
{
|
|
|
|
| 118 |
"has_robber": false
|
| 119 |
}
|
| 120 |
],
|
| 121 |
+
"settlements": [
|
| 122 |
+
{
|
| 123 |
+
"id": "b_20",
|
| 124 |
+
"vertex": 20,
|
| 125 |
+
"player": 1
|
| 126 |
+
}
|
| 127 |
+
],
|
| 128 |
"cities": [],
|
| 129 |
"roads": [],
|
| 130 |
"harbors": [
|
|
|
|
| 196 |
{
|
| 197 |
"id": 0,
|
| 198 |
"name": "a",
|
| 199 |
+
"victory_points": 1,
|
| 200 |
"total_cards": 0,
|
| 201 |
"cards_list": [],
|
| 202 |
"dev_cards_list": [],
|
| 203 |
+
"settlements": 1,
|
| 204 |
"cities": 0,
|
| 205 |
"roads": 0,
|
| 206 |
"longest_road": 0,
|
|
|
|
| 250 |
],
|
| 251 |
"dice_result": null,
|
| 252 |
"allowed_actions": [
|
| 253 |
+
"PLACE_STARTING_ROAD"
|
| 254 |
],
|
| 255 |
"points": [
|
| 256 |
{
|
examples/ai_testing/my_games/current_state_optimized.txt
CHANGED
|
@@ -20,6 +20,6 @@ JSON:
|
|
| 20 |
"meta":{"curr":"a","phase":"SETUP_FIRST_ROUND","robber":10,"dice":null},
|
| 21 |
"H":["","W12","S5","W4","S8","B6","W3","Wh8","B10","W11","D","O3","S4","B10","Wh9","Wh6","S11","O5","Wh9","O2"],
|
| 22 |
"N":[null,[[2,9],[1]],[[1,3],[1],"Wh2"],[[2,4,11],[2,1],"Wh2"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],"B2"],[[6,15],[3],"B2"],[[9,18],[4],"?3"],[[8,10,1],[4,1],"?3"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],"O2"],[[18,28],[8],"S2"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],"O2"],[[26,38],[12]],[[29,17],[8],"S2"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],"?3"],[[37,27],[12],"?3"],[[40,29],[13]],[[39,41,48],[17,13],"W2"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],"W2"],[[48,50],[17]],[[49,51,42],[18,17],"?3"],[[50,52],[18],"?3"],[[51,53,44],[19,18]],[[52,54],[19],"?3"],[[53,46],[19],"?3"]],
|
| 23 |
-
"state":{"bld":[],"rds":[]},
|
| 24 |
-
"players":{"a":{"vp":
|
| 25 |
}
|
|
|
|
| 20 |
"meta":{"curr":"a","phase":"SETUP_FIRST_ROUND","robber":10,"dice":null},
|
| 21 |
"H":["","W12","S5","W4","S8","B6","W3","Wh8","B10","W11","D","O3","S4","B10","Wh9","Wh6","S11","O5","Wh9","O2"],
|
| 22 |
"N":[null,[[2,9],[1]],[[1,3],[1],"Wh2"],[[2,4,11],[2,1],"Wh2"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],"B2"],[[6,15],[3],"B2"],[[9,18],[4],"?3"],[[8,10,1],[4,1],"?3"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],"O2"],[[18,28],[8],"S2"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],"O2"],[[26,38],[12]],[[29,17],[8],"S2"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],"?3"],[[37,27],[12],"?3"],[[40,29],[13]],[[39,41,48],[17,13],"W2"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],"W2"],[[48,50],[17]],[[49,51,42],[18,17],"?3"],[[50,52],[18],"?3"],[[51,53,44],[19,18]],[[52,54],[19],"?3"],[[53,46],[19],"?3"]],
|
| 23 |
+
"state":{"bld":[[20,"a","S"]],"rds":[]},
|
| 24 |
+
"players":{"a":{"vp":1,"res":{}},"b":{"vp":0,"res":{}},"c":{"vp":0,"res":{}}}
|
| 25 |
}
|
examples/ai_testing/my_games/prompts/prompt_player_a.json
CHANGED
|
@@ -76,18 +76,19 @@
|
|
| 76 |
"role": "You are player 'a'. Play strategically to win."
|
| 77 |
},
|
| 78 |
"task_context": {
|
| 79 |
-
"what_just_happened": "
|
| 80 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. You have 2 possible actions. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 81 |
},
|
| 82 |
-
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[],\"rds\":[]},\"players\":{\"a\":{\"vp\":
|
| 83 |
"constraints": {
|
| 84 |
"usage_instructions": "Choose one action type from the list below. Populate the 'parameters' field in your response strictly according to the 'example_parameters' structure provided.",
|
| 85 |
"allowed_actions": [
|
| 86 |
{
|
| 87 |
-
"action": "
|
| 88 |
-
"description": "Place your starting
|
| 89 |
"example_parameters": {
|
| 90 |
-
"
|
|
|
|
| 91 |
}
|
| 92 |
},
|
| 93 |
{
|
|
|
|
| 76 |
"role": "You are player 'a'. Play strategically to win."
|
| 77 |
},
|
| 78 |
"task_context": {
|
| 79 |
+
"what_just_happened": "You placed your first settlement at node 20. Now place a road connecting to it.",
|
| 80 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. You have 2 possible actions. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 81 |
},
|
| 82 |
+
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[[20,\"a\",\"S\"]],\"rds\":[]},\"players\":{\"a\":{\"vp\":1,\"res\":{}},\"b\":{\"vp\":0,\"res\":{}},\"c\":{\"vp\":0,\"res\":{}}}}",
|
| 83 |
"constraints": {
|
| 84 |
"usage_instructions": "Choose one action type from the list below. Populate the 'parameters' field in your response strictly according to the 'example_parameters' structure provided.",
|
| 85 |
"allowed_actions": [
|
| 86 |
{
|
| 87 |
+
"action": "build_road",
|
| 88 |
+
"description": "Place your starting road connecting to your settlement",
|
| 89 |
"example_parameters": {
|
| 90 |
+
"from": "20",
|
| 91 |
+
"to": "21"
|
| 92 |
}
|
| 93 |
},
|
| 94 |
{
|
examples/ai_testing/my_games/prompts/prompt_player_a.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
================================================================================
|
| 2 |
AI AGENT PROMPT - PLAYER A
|
| 3 |
-
Generated: 2026-01-03 22:
|
| 4 |
================================================================================
|
| 5 |
|
| 6 |
📋 RESPONSE SCHEMA (Expected LLM Output Format)
|
|
@@ -86,18 +86,19 @@ Generated: 2026-01-03 22:25:23
|
|
| 86 |
"role": "You are player 'a'. Play strategically to win."
|
| 87 |
},
|
| 88 |
"task_context": {
|
| 89 |
-
"what_just_happened": "
|
| 90 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. You have 2 possible actions. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 91 |
},
|
| 92 |
-
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[],\"rds\":[]},\"players\":{\"a\":{\"vp\":
|
| 93 |
"constraints": {
|
| 94 |
"usage_instructions": "Choose one action type from the list below. Populate the 'parameters' field in your response strictly according to the 'example_parameters' structure provided.",
|
| 95 |
"allowed_actions": [
|
| 96 |
{
|
| 97 |
-
"action": "
|
| 98 |
-
"description": "Place your starting
|
| 99 |
"example_parameters": {
|
| 100 |
-
"
|
|
|
|
| 101 |
}
|
| 102 |
},
|
| 103 |
{
|
|
|
|
| 1 |
================================================================================
|
| 2 |
AI AGENT PROMPT - PLAYER A
|
| 3 |
+
Generated: 2026-01-03 22:34:03
|
| 4 |
================================================================================
|
| 5 |
|
| 6 |
📋 RESPONSE SCHEMA (Expected LLM Output Format)
|
|
|
|
| 86 |
"role": "You are player 'a'. Play strategically to win."
|
| 87 |
},
|
| 88 |
"task_context": {
|
| 89 |
+
"what_just_happened": "You placed your first settlement at node 20. Now place a road connecting to it.",
|
| 90 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. You have 2 possible actions. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 91 |
},
|
| 92 |
+
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[[20,\"a\",\"S\"]],\"rds\":[]},\"players\":{\"a\":{\"vp\":1,\"res\":{}},\"b\":{\"vp\":0,\"res\":{}},\"c\":{\"vp\":0,\"res\":{}}}}",
|
| 93 |
"constraints": {
|
| 94 |
"usage_instructions": "Choose one action type from the list below. Populate the 'parameters' field in your response strictly according to the 'example_parameters' structure provided.",
|
| 95 |
"allowed_actions": [
|
| 96 |
{
|
| 97 |
+
"action": "build_road",
|
| 98 |
+
"description": "Place your starting road connecting to your settlement",
|
| 99 |
"example_parameters": {
|
| 100 |
+
"from": "20",
|
| 101 |
+
"to": "21"
|
| 102 |
}
|
| 103 |
},
|
| 104 |
{
|
examples/ai_testing/my_games/prompts/prompt_player_b.json
CHANGED
|
@@ -34,9 +34,9 @@
|
|
| 34 |
"role": "You are player 'b'. Play strategically to win."
|
| 35 |
},
|
| 36 |
"task_context": {
|
| 37 |
-
"what_just_happened": "Player a
|
| 38 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 39 |
},
|
| 40 |
-
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[],\"rds\":[]},\"players\":{\"a\":{\"vp\":
|
| 41 |
}
|
| 42 |
}
|
|
|
|
| 34 |
"role": "You are player 'b'. Play strategically to win."
|
| 35 |
},
|
| 36 |
"task_context": {
|
| 37 |
+
"what_just_happened": "Player a placed their first settlement at node 20 and is now choosing where to place their starting road.",
|
| 38 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 39 |
},
|
| 40 |
+
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[[20,\"a\",\"S\"]],\"rds\":[]},\"players\":{\"a\":{\"vp\":1,\"res\":{}},\"b\":{\"vp\":0,\"res\":{}},\"c\":{\"vp\":0,\"res\":{}}}}"
|
| 41 |
}
|
| 42 |
}
|
examples/ai_testing/my_games/prompts/prompt_player_b.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
================================================================================
|
| 2 |
AI AGENT PROMPT - PLAYER B
|
| 3 |
-
Generated: 2026-01-03 22:
|
| 4 |
================================================================================
|
| 5 |
|
| 6 |
📋 RESPONSE SCHEMA (Expected LLM Output Format)
|
|
@@ -44,10 +44,10 @@ Generated: 2026-01-03 22:25:23
|
|
| 44 |
"role": "You are player 'b'. Play strategically to win."
|
| 45 |
},
|
| 46 |
"task_context": {
|
| 47 |
-
"what_just_happened": "Player a
|
| 48 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 49 |
},
|
| 50 |
-
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[],\"rds\":[]},\"players\":{\"a\":{\"vp\":
|
| 51 |
}
|
| 52 |
|
| 53 |
================================================================================
|
|
|
|
| 1 |
================================================================================
|
| 2 |
AI AGENT PROMPT - PLAYER B
|
| 3 |
+
Generated: 2026-01-03 22:34:03
|
| 4 |
================================================================================
|
| 5 |
|
| 6 |
📋 RESPONSE SCHEMA (Expected LLM Output Format)
|
|
|
|
| 44 |
"role": "You are player 'b'. Play strategically to win."
|
| 45 |
},
|
| 46 |
"task_context": {
|
| 47 |
+
"what_just_happened": "Player a placed their first settlement at node 20 and is now choosing where to place their starting road.",
|
| 48 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 49 |
},
|
| 50 |
+
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[[20,\"a\",\"S\"]],\"rds\":[]},\"players\":{\"a\":{\"vp\":1,\"res\":{}},\"b\":{\"vp\":0,\"res\":{}},\"c\":{\"vp\":0,\"res\":{}}}}"
|
| 51 |
}
|
| 52 |
|
| 53 |
================================================================================
|
examples/ai_testing/my_games/prompts/prompt_player_c.json
CHANGED
|
@@ -34,9 +34,9 @@
|
|
| 34 |
"role": "You are player 'c'. Play strategically to win."
|
| 35 |
},
|
| 36 |
"task_context": {
|
| 37 |
-
"what_just_happened": "Player a
|
| 38 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 39 |
},
|
| 40 |
-
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[],\"rds\":[]},\"players\":{\"a\":{\"vp\":
|
| 41 |
}
|
| 42 |
}
|
|
|
|
| 34 |
"role": "You are player 'c'. Play strategically to win."
|
| 35 |
},
|
| 36 |
"task_context": {
|
| 37 |
+
"what_just_happened": "Player a placed their first settlement at node 20 and is now choosing where to place their starting road.",
|
| 38 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 39 |
},
|
| 40 |
+
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[[20,\"a\",\"S\"]],\"rds\":[]},\"players\":{\"a\":{\"vp\":1,\"res\":{}},\"b\":{\"vp\":0,\"res\":{}},\"c\":{\"vp\":0,\"res\":{}}}}"
|
| 41 |
}
|
| 42 |
}
|
examples/ai_testing/my_games/prompts/prompt_player_c.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
================================================================================
|
| 2 |
AI AGENT PROMPT - PLAYER C
|
| 3 |
-
Generated: 2026-01-03 22:
|
| 4 |
================================================================================
|
| 5 |
|
| 6 |
📋 RESPONSE SCHEMA (Expected LLM Output Format)
|
|
@@ -44,10 +44,10 @@ Generated: 2026-01-03 22:25:23
|
|
| 44 |
"role": "You are player 'c'. Play strategically to win."
|
| 45 |
},
|
| 46 |
"task_context": {
|
| 47 |
-
"what_just_happened": "Player a
|
| 48 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 49 |
},
|
| 50 |
-
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[],\"rds\":[]},\"players\":{\"a\":{\"vp\":
|
| 51 |
}
|
| 52 |
|
| 53 |
================================================================================
|
|
|
|
| 1 |
================================================================================
|
| 2 |
AI AGENT PROMPT - PLAYER C
|
| 3 |
+
Generated: 2026-01-03 22:34:03
|
| 4 |
================================================================================
|
| 5 |
|
| 6 |
📋 RESPONSE SCHEMA (Expected LLM Output Format)
|
|
|
|
| 44 |
"role": "You are player 'c'. Play strategically to win."
|
| 45 |
},
|
| 46 |
"task_context": {
|
| 47 |
+
"what_just_happened": "Player a placed their first settlement at node 20 and is now choosing where to place their starting road.",
|
| 48 |
"instructions": "Analyze the game state and select the optimal move from 'allowed_actions'. If you wish to negotiate or wait for other players, select the 'wait_for_response' action."
|
| 49 |
},
|
| 50 |
+
"game_state": "\n 1. LOOKUP TABLES:\n • \"H\" (Hexes): Array where Index = HexID. Value = Resource+Num.\n Example: H[1]=\"W12\" -> Hex 1 is Wood 12.\n • \"N\" (Nodes): Array where Index = NodeID.\n Format: [ [Neighbors], [HexIDs], Port? ]\n Logic: To find yield of Node 10, check N[10]. Get HexIDs (e.g. [1,5]). Look up H[1] and H[5].\n\n2. CODES: W=Wood, B=Brick, S=Sheep, Wh=Wheat, O=Ore, D=Desert.\n ?3=Any 3:1 port, X2=Specific Resource 2:1 port.\n\n3. STATE: \"bld\"=[NodeID, Owner, Type], \"rds\"=[[From,To], Owner].\n\n4. PLAYERS: \"res\"={Resource:Count}, \"dev\"={\"h\":[Hidden Cards], \"r\":[Revealed] (K=Knight)}, \n \"stat\"=[\"LR\" (Longest Road), \"LA\" (Largest Army)].\n\n5. ROBBER: Located at HexID specified in \"meta.robber\". H[id] is blocked.\n\nJSON:\n{\"meta\":{\"curr\":\"a\",\"phase\":\"SETUP_FIRST_ROUND\",\"robber\":10,\"dice\":null},\"H\":[\"\",\"W12\",\"S5\",\"W4\",\"S8\",\"B6\",\"W3\",\"Wh8\",\"B10\",\"W11\",\"D\",\"O3\",\"S4\",\"B10\",\"Wh9\",\"Wh6\",\"S11\",\"O5\",\"Wh9\",\"O2\"],\"N\":[null,[[2,9],[1]],[[1,3],[1],\"Wh2\"],[[2,4,11],[2,1],\"Wh2\"],[[3,5],[2]],[[4,6,13],[3,2]],[[5,7],[3],\"B2\"],[[6,15],[3],\"B2\"],[[9,18],[4],\"?3\"],[[8,10,1],[4,1],\"?3\"],[[9,11,20],[5,4,1]],[[10,12,3],[5,2,1]],[[11,13,22],[6,5,2]],[[12,14,5],[6,3,2]],[[13,15,24],[7,6,3]],[[14,16,7],[7,3]],[[15,26],[7],\"O2\"],[[18,28],[8],\"S2\"],[[17,19,8],[8,4]],[[18,20,30],[9,8,4]],[[19,21,10],[9,5,4]],[[20,22,32],[10,9,5]],[[21,23,12],[10,6,5]],[[22,24,34],[11,10,6]],[[23,25,14],[11,7,6]],[[24,26,36],[12,11,7]],[[25,27,16],[12,7],\"O2\"],[[26,38],[12]],[[29,17],[8],\"S2\"],[[28,30,39],[13,8]],[[29,31,19],[13,9,8]],[[30,32,41],[14,13,9]],[[31,33,21],[14,10,9]],[[32,34,43],[15,14,10]],[[33,35,23],[15,11,10]],[[34,36,45],[16,15,11]],[[35,37,25],[16,12,11]],[[36,38,47],[16,12],\"?3\"],[[37,27],[12],\"?3\"],[[40,29],[13]],[[39,41,48],[17,13],\"W2\"],[[40,42,31],[17,14,13]],[[41,43,50],[18,17,14]],[[42,44,33],[18,15,14]],[[43,45,52],[19,18,15]],[[44,46,35],[19,16,15]],[[45,47,54],[19,16]],[[46,37],[16]],[[49,40],[17],\"W2\"],[[48,50],[17]],[[49,51,42],[18,17],\"?3\"],[[50,52],[18],\"?3\"],[[51,53,44],[19,18]],[[52,54],[19],\"?3\"],[[53,46],[19],\"?3\"]],\"state\":{\"bld\":[[20,\"a\",\"S\"]],\"rds\":[]},\"players\":{\"a\":{\"vp\":1,\"res\":{}},\"b\":{\"vp\":0,\"res\":{}},\"c\":{\"vp\":0,\"res\":{}}}}"
|
| 51 |
}
|
| 52 |
|
| 53 |
================================================================================
|
pycatan/ai/response_parser.py
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Response parser for AI agent LLM responses.
|
| 3 |
+
|
| 4 |
+
This module handles:
|
| 5 |
+
1. Parsing JSON responses from LLM
|
| 6 |
+
2. Validating response structure against schemas
|
| 7 |
+
3. Error handling and recovery
|
| 8 |
+
4. Fallback mechanisms for malformed responses
|
| 9 |
+
5. Logging all parsing attempts
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import json
|
| 13 |
+
import logging
|
| 14 |
+
import re
|
| 15 |
+
from typing import Dict, Any, Optional, Tuple
|
| 16 |
+
from dataclasses import dataclass
|
| 17 |
+
from enum import Enum
|
| 18 |
+
|
| 19 |
+
from pycatan.ai.schemas import (
|
| 20 |
+
ResponseType,
|
| 21 |
+
get_schema_for_response_type,
|
| 22 |
+
validate_action_parameters,
|
| 23 |
+
ACTIVE_TURN_RESPONSE_SCHEMA,
|
| 24 |
+
OBSERVING_RESPONSE_SCHEMA
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# Set up logging
|
| 29 |
+
logger = logging.getLogger(__name__)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class ParseError(Enum):
|
| 33 |
+
"""Types of parsing errors."""
|
| 34 |
+
INVALID_JSON = "invalid_json"
|
| 35 |
+
MISSING_REQUIRED_FIELD = "missing_required_field"
|
| 36 |
+
INVALID_FIELD_TYPE = "invalid_field_type"
|
| 37 |
+
INVALID_ACTION = "invalid_action"
|
| 38 |
+
VALIDATION_ERROR = "validation_error"
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class ParseResult:
|
| 43 |
+
"""Result of parsing an LLM response."""
|
| 44 |
+
success: bool
|
| 45 |
+
data: Optional[Dict[str, Any]] = None
|
| 46 |
+
error_type: Optional[ParseError] = None
|
| 47 |
+
error_message: Optional[str] = None
|
| 48 |
+
raw_response: Optional[str] = None
|
| 49 |
+
fallback_used: bool = False
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class ResponseParser:
|
| 53 |
+
"""
|
| 54 |
+
Parser for AI agent LLM responses with error handling and fallback mechanisms.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
def __init__(self, enable_fallbacks: bool = True, strict_mode: bool = False):
|
| 58 |
+
"""
|
| 59 |
+
Initialize the response parser.
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
enable_fallbacks: Whether to use fallback mechanisms for parsing errors
|
| 63 |
+
strict_mode: If True, fail on any validation error. If False, be lenient.
|
| 64 |
+
"""
|
| 65 |
+
self.enable_fallbacks = enable_fallbacks
|
| 66 |
+
self.strict_mode = strict_mode
|
| 67 |
+
self.parse_attempts = 0
|
| 68 |
+
self.successful_parses = 0
|
| 69 |
+
self.failed_parses = 0
|
| 70 |
+
|
| 71 |
+
def parse(self,
|
| 72 |
+
raw_response: str,
|
| 73 |
+
response_type: ResponseType,
|
| 74 |
+
allowed_actions: Optional[list] = None) -> ParseResult:
|
| 75 |
+
"""
|
| 76 |
+
Parse and validate an LLM response.
|
| 77 |
+
|
| 78 |
+
Args:
|
| 79 |
+
raw_response: Raw string response from LLM
|
| 80 |
+
response_type: Expected response type (active turn or observing)
|
| 81 |
+
allowed_actions: List of allowed action types (for validation)
|
| 82 |
+
|
| 83 |
+
Returns:
|
| 84 |
+
ParseResult with success status and parsed data or error info
|
| 85 |
+
"""
|
| 86 |
+
self.parse_attempts += 1
|
| 87 |
+
|
| 88 |
+
logger.info(f"Parsing response (attempt #{self.parse_attempts})")
|
| 89 |
+
logger.debug(f"Raw response: {raw_response[:200]}...")
|
| 90 |
+
|
| 91 |
+
# Step 1: Extract JSON from response
|
| 92 |
+
json_str = self._extract_json(raw_response)
|
| 93 |
+
if json_str is None:
|
| 94 |
+
self.failed_parses += 1
|
| 95 |
+
return ParseResult(
|
| 96 |
+
success=False,
|
| 97 |
+
error_type=ParseError.INVALID_JSON,
|
| 98 |
+
error_message="Could not find valid JSON in response",
|
| 99 |
+
raw_response=raw_response
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
# Step 2: Parse JSON
|
| 103 |
+
try:
|
| 104 |
+
data = json.loads(json_str)
|
| 105 |
+
except json.JSONDecodeError as e:
|
| 106 |
+
logger.error(f"JSON decode error: {e}")
|
| 107 |
+
|
| 108 |
+
if self.enable_fallbacks:
|
| 109 |
+
# Try to fix common JSON errors
|
| 110 |
+
fixed_data = self._try_fix_json(json_str)
|
| 111 |
+
if fixed_data is not None:
|
| 112 |
+
logger.warning("Used fallback JSON repair mechanism")
|
| 113 |
+
data = fixed_data
|
| 114 |
+
else:
|
| 115 |
+
self.failed_parses += 1
|
| 116 |
+
return ParseResult(
|
| 117 |
+
success=False,
|
| 118 |
+
error_type=ParseError.INVALID_JSON,
|
| 119 |
+
error_message=f"JSON parse error: {str(e)}",
|
| 120 |
+
raw_response=raw_response
|
| 121 |
+
)
|
| 122 |
+
else:
|
| 123 |
+
self.failed_parses += 1
|
| 124 |
+
return ParseResult(
|
| 125 |
+
success=False,
|
| 126 |
+
error_type=ParseError.INVALID_JSON,
|
| 127 |
+
error_message=f"JSON parse error: {str(e)}",
|
| 128 |
+
raw_response=raw_response
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
# Step 3: Validate structure
|
| 132 |
+
validation_result = self._validate_structure(data, response_type)
|
| 133 |
+
if not validation_result[0]:
|
| 134 |
+
if self.enable_fallbacks and not self.strict_mode:
|
| 135 |
+
# Try to repair structure
|
| 136 |
+
data = self._try_repair_structure(data, response_type)
|
| 137 |
+
if data is None:
|
| 138 |
+
self.failed_parses += 1
|
| 139 |
+
return ParseResult(
|
| 140 |
+
success=False,
|
| 141 |
+
error_type=ParseError.VALIDATION_ERROR,
|
| 142 |
+
error_message=validation_result[1],
|
| 143 |
+
raw_response=raw_response,
|
| 144 |
+
data=data
|
| 145 |
+
)
|
| 146 |
+
logger.warning("Used fallback structure repair mechanism")
|
| 147 |
+
else:
|
| 148 |
+
self.failed_parses += 1
|
| 149 |
+
return ParseResult(
|
| 150 |
+
success=False,
|
| 151 |
+
error_type=ParseError.VALIDATION_ERROR,
|
| 152 |
+
error_message=validation_result[1],
|
| 153 |
+
raw_response=raw_response,
|
| 154 |
+
data=data
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
# Step 4: Validate action if present
|
| 158 |
+
if response_type == ResponseType.ACTIVE_TURN and "action" in data:
|
| 159 |
+
action_validation = self._validate_action(data["action"], allowed_actions)
|
| 160 |
+
if not action_validation[0]:
|
| 161 |
+
if self.strict_mode:
|
| 162 |
+
self.failed_parses += 1
|
| 163 |
+
return ParseResult(
|
| 164 |
+
success=False,
|
| 165 |
+
error_type=ParseError.INVALID_ACTION,
|
| 166 |
+
error_message=action_validation[1],
|
| 167 |
+
raw_response=raw_response,
|
| 168 |
+
data=data
|
| 169 |
+
)
|
| 170 |
+
else:
|
| 171 |
+
logger.warning(f"Action validation warning: {action_validation[1]}")
|
| 172 |
+
|
| 173 |
+
# Success!
|
| 174 |
+
self.successful_parses += 1
|
| 175 |
+
logger.info("Successfully parsed and validated response")
|
| 176 |
+
|
| 177 |
+
return ParseResult(
|
| 178 |
+
success=True,
|
| 179 |
+
data=data,
|
| 180 |
+
raw_response=raw_response
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
def _extract_json(self, text: str) -> Optional[str]:
|
| 184 |
+
"""
|
| 185 |
+
Extract JSON from text that may contain additional content.
|
| 186 |
+
|
| 187 |
+
Handles cases where LLM returns JSON wrapped in markdown code blocks
|
| 188 |
+
or with additional text before/after.
|
| 189 |
+
"""
|
| 190 |
+
# Try to find JSON in code blocks first
|
| 191 |
+
code_block_pattern = r'```(?:json)?\s*(\{.*?\})\s*```'
|
| 192 |
+
matches = re.findall(code_block_pattern, text, re.DOTALL)
|
| 193 |
+
if matches:
|
| 194 |
+
return matches[0]
|
| 195 |
+
|
| 196 |
+
# Try to find raw JSON object
|
| 197 |
+
json_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'
|
| 198 |
+
matches = re.findall(json_pattern, text, re.DOTALL)
|
| 199 |
+
if matches:
|
| 200 |
+
# Return the longest match (most likely to be complete)
|
| 201 |
+
return max(matches, key=len)
|
| 202 |
+
|
| 203 |
+
# If text looks like pure JSON, return as is
|
| 204 |
+
stripped = text.strip()
|
| 205 |
+
if stripped.startswith('{') and stripped.endswith('}'):
|
| 206 |
+
return stripped
|
| 207 |
+
|
| 208 |
+
return None
|
| 209 |
+
|
| 210 |
+
def _try_fix_json(self, json_str: str) -> Optional[Dict[str, Any]]:
|
| 211 |
+
"""
|
| 212 |
+
Attempt to fix common JSON errors.
|
| 213 |
+
|
| 214 |
+
Common issues:
|
| 215 |
+
- Missing closing quotes
|
| 216 |
+
- Trailing commas
|
| 217 |
+
- Single quotes instead of double quotes
|
| 218 |
+
"""
|
| 219 |
+
fixes = [
|
| 220 |
+
# Replace single quotes with double quotes (careful with apostrophes)
|
| 221 |
+
lambda s: s.replace("'", '"'),
|
| 222 |
+
# Remove trailing commas
|
| 223 |
+
lambda s: re.sub(r',\s*}', '}', s),
|
| 224 |
+
lambda s: re.sub(r',\s*]', ']', s),
|
| 225 |
+
]
|
| 226 |
+
|
| 227 |
+
for fix in fixes:
|
| 228 |
+
try:
|
| 229 |
+
fixed = fix(json_str)
|
| 230 |
+
return json.loads(fixed)
|
| 231 |
+
except (json.JSONDecodeError, Exception):
|
| 232 |
+
continue
|
| 233 |
+
|
| 234 |
+
return None
|
| 235 |
+
|
| 236 |
+
def _validate_structure(self, data: Dict[str, Any], response_type: ResponseType) -> Tuple[bool, Optional[str]]:
|
| 237 |
+
"""
|
| 238 |
+
Validate response structure against schema.
|
| 239 |
+
|
| 240 |
+
Returns:
|
| 241 |
+
Tuple of (is_valid, error_message)
|
| 242 |
+
"""
|
| 243 |
+
schema = get_schema_for_response_type(response_type)
|
| 244 |
+
|
| 245 |
+
# Check required fields
|
| 246 |
+
for field in schema.get("required", []):
|
| 247 |
+
if field not in data:
|
| 248 |
+
return False, f"Missing required field: '{field}'"
|
| 249 |
+
|
| 250 |
+
# Validate field types and constraints
|
| 251 |
+
for field, value in data.items():
|
| 252 |
+
if field in schema["properties"]:
|
| 253 |
+
field_schema = schema["properties"][field]
|
| 254 |
+
|
| 255 |
+
# Check type
|
| 256 |
+
expected_type = field_schema.get("type")
|
| 257 |
+
if expected_type == "string" and not isinstance(value, str):
|
| 258 |
+
return False, f"Field '{field}' must be a string"
|
| 259 |
+
elif expected_type == "object" and not isinstance(value, dict):
|
| 260 |
+
return False, f"Field '{field}' must be an object"
|
| 261 |
+
|
| 262 |
+
# Check string constraints
|
| 263 |
+
if isinstance(value, str):
|
| 264 |
+
min_length = field_schema.get("minLength")
|
| 265 |
+
max_length = field_schema.get("maxLength")
|
| 266 |
+
if min_length and len(value) < min_length:
|
| 267 |
+
return False, f"Field '{field}' must be at least {min_length} characters"
|
| 268 |
+
if max_length and len(value) > max_length:
|
| 269 |
+
return False, f"Field '{field}' must be at most {max_length} characters"
|
| 270 |
+
|
| 271 |
+
return True, None
|
| 272 |
+
|
| 273 |
+
def _validate_action(self, action: Dict[str, Any], allowed_actions: Optional[list]) -> Tuple[bool, Optional[str]]:
|
| 274 |
+
"""
|
| 275 |
+
Validate action structure and parameters.
|
| 276 |
+
|
| 277 |
+
Returns:
|
| 278 |
+
Tuple of (is_valid, error_message)
|
| 279 |
+
"""
|
| 280 |
+
if not isinstance(action, dict):
|
| 281 |
+
return False, "Action must be an object"
|
| 282 |
+
|
| 283 |
+
if "type" not in action:
|
| 284 |
+
return False, "Action missing 'type' field"
|
| 285 |
+
|
| 286 |
+
if "parameters" not in action:
|
| 287 |
+
return False, "Action missing 'parameters' field"
|
| 288 |
+
|
| 289 |
+
action_type = action["type"]
|
| 290 |
+
|
| 291 |
+
# Check if action is in allowed list
|
| 292 |
+
if allowed_actions:
|
| 293 |
+
if action_type not in allowed_actions:
|
| 294 |
+
return False, f"Action type '{action_type}' not in allowed actions: {allowed_actions}"
|
| 295 |
+
|
| 296 |
+
# Validate parameters
|
| 297 |
+
parameters = action["parameters"]
|
| 298 |
+
if not isinstance(parameters, dict):
|
| 299 |
+
return False, "Action parameters must be an object"
|
| 300 |
+
|
| 301 |
+
# Validate parameter schema
|
| 302 |
+
param_valid, param_error = validate_action_parameters(action_type, parameters)
|
| 303 |
+
if not param_valid:
|
| 304 |
+
return False, param_error
|
| 305 |
+
|
| 306 |
+
return True, None
|
| 307 |
+
|
| 308 |
+
def _try_repair_structure(self, data: Dict[str, Any], response_type: ResponseType) -> Optional[Dict[str, Any]]:
|
| 309 |
+
"""
|
| 310 |
+
Attempt to repair missing or invalid fields.
|
| 311 |
+
|
| 312 |
+
Strategies:
|
| 313 |
+
- Add default values for missing optional fields
|
| 314 |
+
- Convert types if possible
|
| 315 |
+
- Use empty objects/strings as fallbacks
|
| 316 |
+
"""
|
| 317 |
+
schema = get_schema_for_response_type(response_type)
|
| 318 |
+
repaired = data.copy()
|
| 319 |
+
|
| 320 |
+
# Add missing required fields with defaults
|
| 321 |
+
for field in schema.get("required", []):
|
| 322 |
+
if field not in repaired:
|
| 323 |
+
if field == "internal_thinking":
|
| 324 |
+
repaired[field] = "[No reasoning provided]"
|
| 325 |
+
elif field == "action":
|
| 326 |
+
repaired[field] = {"type": "wait_for_response", "parameters": {}}
|
| 327 |
+
else:
|
| 328 |
+
return None # Can't repair
|
| 329 |
+
|
| 330 |
+
# Try to fix internal_thinking if too short
|
| 331 |
+
if "internal_thinking" in repaired:
|
| 332 |
+
min_length = schema["properties"]["internal_thinking"].get("minLength", 0)
|
| 333 |
+
if len(repaired["internal_thinking"]) < min_length:
|
| 334 |
+
repaired["internal_thinking"] += " [Response was too brief]"
|
| 335 |
+
|
| 336 |
+
return repaired
|
| 337 |
+
|
| 338 |
+
def get_statistics(self) -> Dict[str, Any]:
|
| 339 |
+
"""Get parser statistics."""
|
| 340 |
+
return {
|
| 341 |
+
"total_attempts": self.parse_attempts,
|
| 342 |
+
"successful": self.successful_parses,
|
| 343 |
+
"failed": self.failed_parses,
|
| 344 |
+
"success_rate": (
|
| 345 |
+
self.successful_parses / self.parse_attempts
|
| 346 |
+
if self.parse_attempts > 0
|
| 347 |
+
else 0.0
|
| 348 |
+
)
|
| 349 |
+
}
|
pycatan/ai/schemas.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
JSON Schema definitions for AI agent requests and responses.
|
| 3 |
+
|
| 4 |
+
This module contains all schema definitions used for:
|
| 5 |
+
1. Validating LLM responses
|
| 6 |
+
2. Generating prompts with schema documentation
|
| 7 |
+
3. Type checking and validation
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from typing import Dict, Any, List, Optional
|
| 11 |
+
from enum import Enum
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class ResponseType(Enum):
|
| 15 |
+
"""Types of responses expected from AI agents."""
|
| 16 |
+
ACTIVE_TURN = "active_turn" # When it's the agent's turn to act
|
| 17 |
+
OBSERVING = "observing" # When agent is observing other players
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Schema for when it's the agent's turn (must take action)
|
| 21 |
+
ACTIVE_TURN_RESPONSE_SCHEMA = {
|
| 22 |
+
"type": "object",
|
| 23 |
+
"required": ["internal_thinking", "action"],
|
| 24 |
+
"properties": {
|
| 25 |
+
"internal_thinking": {
|
| 26 |
+
"type": "string",
|
| 27 |
+
"description": "Private strategy. What's your plan and why?",
|
| 28 |
+
"minLength": 50
|
| 29 |
+
},
|
| 30 |
+
"note_to_self": {
|
| 31 |
+
"type": "string",
|
| 32 |
+
"description": "Important facts for when it's your turn. Use only if essential for clarity or direct user query. Omit otherwise.",
|
| 33 |
+
"maxLength": 100
|
| 34 |
+
},
|
| 35 |
+
"say_outloud": {
|
| 36 |
+
"type": "string",
|
| 37 |
+
"description": "A short message to other players (max 100 chars). Use for negotiation, threats, or table talk. Keep in mind you pay for speak outload.",
|
| 38 |
+
"maxLength": 100
|
| 39 |
+
},
|
| 40 |
+
"action": {
|
| 41 |
+
"type": "object",
|
| 42 |
+
"required": ["type", "parameters"],
|
| 43 |
+
"properties": {
|
| 44 |
+
"type": {
|
| 45 |
+
"type": "string",
|
| 46 |
+
"description": "The action type (must match one from allowed_actions in constraints)"
|
| 47 |
+
},
|
| 48 |
+
"parameters": {
|
| 49 |
+
"type": "object",
|
| 50 |
+
"description": "Action-specific parameters. If no parameters are needed, provide an empty object.",
|
| 51 |
+
"additionalProperties": True # Allow flexible parameters based on action
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
"propertyOrdering": [
|
| 57 |
+
"internal_thinking",
|
| 58 |
+
"note_to_self",
|
| 59 |
+
"say_outloud",
|
| 60 |
+
"action"
|
| 61 |
+
]
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
# Schema for when agent is observing (not their turn)
|
| 66 |
+
OBSERVING_RESPONSE_SCHEMA = {
|
| 67 |
+
"type": "object",
|
| 68 |
+
"required": ["internal_thinking"],
|
| 69 |
+
"properties": {
|
| 70 |
+
"internal_thinking": {
|
| 71 |
+
"type": "string",
|
| 72 |
+
"description": "Track what's happening. What are opponents doing? What's your strategy for next turn?",
|
| 73 |
+
"minLength": 30
|
| 74 |
+
},
|
| 75 |
+
"note_to_self": {
|
| 76 |
+
"type": "string",
|
| 77 |
+
"description": "Important facts for when it's your turn. Use only if essential for clarity or direct user query. Omit otherwise.",
|
| 78 |
+
"maxLength": 100
|
| 79 |
+
},
|
| 80 |
+
"say_outloud": {
|
| 81 |
+
"type": "string",
|
| 82 |
+
"description": "Optional message to other players (max 100 chars). Propose trades or negotiate. You pay for this.",
|
| 83 |
+
"maxLength": 100
|
| 84 |
+
}
|
| 85 |
+
},
|
| 86 |
+
"propertyOrdering": [
|
| 87 |
+
"internal_thinking",
|
| 88 |
+
"note_to_self",
|
| 89 |
+
"say_outloud"
|
| 90 |
+
]
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def get_schema_for_response_type(response_type: ResponseType) -> Dict[str, Any]:
|
| 95 |
+
"""
|
| 96 |
+
Get the appropriate schema based on response type.
|
| 97 |
+
|
| 98 |
+
Args:
|
| 99 |
+
response_type: Type of response expected (active turn or observing)
|
| 100 |
+
|
| 101 |
+
Returns:
|
| 102 |
+
JSON schema dictionary
|
| 103 |
+
"""
|
| 104 |
+
if response_type == ResponseType.ACTIVE_TURN:
|
| 105 |
+
return ACTIVE_TURN_RESPONSE_SCHEMA
|
| 106 |
+
elif response_type == ResponseType.OBSERVING:
|
| 107 |
+
return OBSERVING_RESPONSE_SCHEMA
|
| 108 |
+
else:
|
| 109 |
+
raise ValueError(f"Unknown response type: {response_type}")
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def get_schema_description(response_type: ResponseType) -> str:
|
| 113 |
+
"""
|
| 114 |
+
Get a human-readable description of what the schema expects.
|
| 115 |
+
|
| 116 |
+
Args:
|
| 117 |
+
response_type: Type of response expected
|
| 118 |
+
|
| 119 |
+
Returns:
|
| 120 |
+
Description string
|
| 121 |
+
"""
|
| 122 |
+
if response_type == ResponseType.ACTIVE_TURN:
|
| 123 |
+
return (
|
| 124 |
+
"Response must include:\n"
|
| 125 |
+
"- internal_thinking: Your strategy (min 50 chars)\n"
|
| 126 |
+
"- action: {type: action_name, parameters: {...}}\n"
|
| 127 |
+
"Optional:\n"
|
| 128 |
+
"- note_to_self: Important reminder (max 100 chars)\n"
|
| 129 |
+
"- say_outloud: Message to other players (max 100 chars)"
|
| 130 |
+
)
|
| 131 |
+
elif response_type == ResponseType.OBSERVING:
|
| 132 |
+
return (
|
| 133 |
+
"Response must include:\n"
|
| 134 |
+
"- internal_thinking: What you observed (min 30 chars)\n"
|
| 135 |
+
"Optional:\n"
|
| 136 |
+
"- note_to_self: Important reminder (max 100 chars)\n"
|
| 137 |
+
"- say_outloud: Message to other players (max 100 chars)"
|
| 138 |
+
)
|
| 139 |
+
else:
|
| 140 |
+
return "Unknown response type"
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
# Common action parameter schemas for validation
|
| 144 |
+
ACTION_PARAMETER_SCHEMAS = {
|
| 145 |
+
"build_road": {
|
| 146 |
+
"required": ["from", "to"],
|
| 147 |
+
"properties": {
|
| 148 |
+
"from": {"type": "string", "description": "Starting node ID"},
|
| 149 |
+
"to": {"type": "string", "description": "Ending node ID"}
|
| 150 |
+
}
|
| 151 |
+
},
|
| 152 |
+
"build_settlement": {
|
| 153 |
+
"required": ["node"],
|
| 154 |
+
"properties": {
|
| 155 |
+
"node": {"type": "string", "description": "Node ID where to build"}
|
| 156 |
+
}
|
| 157 |
+
},
|
| 158 |
+
"build_city": {
|
| 159 |
+
"required": ["node"],
|
| 160 |
+
"properties": {
|
| 161 |
+
"node": {"type": "string", "description": "Node ID where settlement exists"}
|
| 162 |
+
}
|
| 163 |
+
},
|
| 164 |
+
"buy_dev_card": {
|
| 165 |
+
"required": [],
|
| 166 |
+
"properties": {}
|
| 167 |
+
},
|
| 168 |
+
"play_knight": {
|
| 169 |
+
"required": ["hex", "victim"],
|
| 170 |
+
"properties": {
|
| 171 |
+
"hex": {"type": "string", "description": "Hex ID to move robber"},
|
| 172 |
+
"victim": {"type": "string", "description": "Player to steal from (or 'none')"}
|
| 173 |
+
}
|
| 174 |
+
},
|
| 175 |
+
"trade_with_bank": {
|
| 176 |
+
"required": ["give", "receive"],
|
| 177 |
+
"properties": {
|
| 178 |
+
"give": {"type": "string", "description": "Resource to give"},
|
| 179 |
+
"receive": {"type": "string", "description": "Resource to receive"}
|
| 180 |
+
}
|
| 181 |
+
},
|
| 182 |
+
"propose_trade": {
|
| 183 |
+
"required": ["offer", "request"],
|
| 184 |
+
"properties": {
|
| 185 |
+
"offer": {"type": "object", "description": "Resources offered"},
|
| 186 |
+
"request": {"type": "object", "description": "Resources requested"}
|
| 187 |
+
}
|
| 188 |
+
},
|
| 189 |
+
"wait_for_response": {
|
| 190 |
+
"required": [],
|
| 191 |
+
"properties": {}
|
| 192 |
+
},
|
| 193 |
+
"end_turn": {
|
| 194 |
+
"required": [],
|
| 195 |
+
"properties": {}
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def validate_action_parameters(action_type: str, parameters: Dict[str, Any]) -> tuple[bool, Optional[str]]:
|
| 201 |
+
"""
|
| 202 |
+
Validate that action parameters match expected schema.
|
| 203 |
+
|
| 204 |
+
Args:
|
| 205 |
+
action_type: Type of action (e.g., "build_road")
|
| 206 |
+
parameters: Parameters provided by the agent
|
| 207 |
+
|
| 208 |
+
Returns:
|
| 209 |
+
Tuple of (is_valid, error_message)
|
| 210 |
+
"""
|
| 211 |
+
if action_type not in ACTION_PARAMETER_SCHEMAS:
|
| 212 |
+
# Unknown action type - let game manager handle validation
|
| 213 |
+
return True, None
|
| 214 |
+
|
| 215 |
+
schema = ACTION_PARAMETER_SCHEMAS[action_type]
|
| 216 |
+
|
| 217 |
+
# Check required parameters
|
| 218 |
+
for required_param in schema["required"]:
|
| 219 |
+
if required_param not in parameters:
|
| 220 |
+
return False, f"Missing required parameter '{required_param}' for action '{action_type}'"
|
| 221 |
+
|
| 222 |
+
# Check parameter types if defined
|
| 223 |
+
for param_name, param_value in parameters.items():
|
| 224 |
+
if param_name in schema["properties"]:
|
| 225 |
+
expected_type = schema["properties"][param_name].get("type")
|
| 226 |
+
if expected_type == "string" and not isinstance(param_value, str):
|
| 227 |
+
return False, f"Parameter '{param_name}' should be a string"
|
| 228 |
+
elif expected_type == "number" and not isinstance(param_value, (int, float)):
|
| 229 |
+
return False, f"Parameter '{param_name}' should be a number"
|
| 230 |
+
elif expected_type == "object" and not isinstance(param_value, dict):
|
| 231 |
+
return False, f"Parameter '{param_name}' should be an object"
|
| 232 |
+
|
| 233 |
+
return True, None
|