Skip to content

Commit 19efb32

Browse files
authored
Merge pull request #29 from lilfetz22/feat/allow-arbitrary-field
2 parents 6b6471c + 56a8e23 commit 19efb32

4 files changed

Lines changed: 158 additions & 182 deletions

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
9595
This is required for venv activation and Python script execution.
9696

9797
### Standard Workflow
98-
1. **First-time setup**: Prompt user to select field (Data Science or Gen AI), save to `config.json`
98+
1. **First-time setup**: Prompt user to enter their field of expertise (accepts any custom field), save to `config.json`
9999
2. **Execution**: `python main.py` triggers orchestrator, creates unique run directory
100100
3. **Debugging**: Check `events.jsonl` and per-run `run_failed.json` for failures
101101
4. **Testing**: Validate retry logic, artifact integrity, character limit loop, fallback activation

README.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,13 @@ Each execution follows this sequence:
253253

254254
5. **First-time setup**
255255

256-
On first run, you'll be prompted to select your field of expertise:
256+
On first run, you'll be prompted to enter your field of expertise. You can enter any field you specialize in:
257257
- Data Science (Optimizations & Time-Series Analysis)
258258
- Generative AI & AI Agents
259+
- Software Engineering (Cloud Architecture)
260+
- DevOps & Infrastructure
261+
- Machine Learning Operations
262+
- Or any custom field of your choice
259263

260264
Your selection is saved in `config.json`.
261265

@@ -614,13 +618,29 @@ This will execute all logic up to the first LLM call, allowing you to:
614618

615619
### Field Selection
616620

617-
Edit `config.json` to change your field:
621+
Edit `config.json` to change your field. You can use any field of expertise you wish:
618622
```json
619623
{
620-
"field": "Data Science (Optimizations & Time-Series Analysis)"
624+
"field": "Your custom field here (e.g., Data Science, Machine Learning, Cloud Architecture)"
621625
}
622626
```
623627

628+
**Example fields:**
629+
- `"Data Science (Optimizations & Time-Series Analysis)"`
630+
- `"Generative AI & AI Agents"`
631+
- `"Software Engineering (Cloud Architecture)"`
632+
- `"DevOps & Infrastructure"`
633+
- `"Machine Learning Operations"`
634+
- Any other field you specialize in
635+
636+
**How field choice affects topic selection and cost**
637+
638+
- The two pre-seeded fields
639+
- `"Data Science (Optimizations & Time-Series Analysis)"`
640+
- `"Generative AI & AI Agents"`
641+
use the `potential_topics` table in `database/topics.db` for topic selection. For these, the Topic Agent selects topics via SQLite queries only (no extra LLM call for topic selection).
642+
- When you use any other custom field (for example, `"Software Engineering (Cloud Architecture)"` or your own niche), the Topic Agent typically will not find matching topics in the database and will **fall back to LLM-based topic generation**. This requires an additional LLM API call and therefore increases cost for each run that needs a new topic.
643+
- If you want to use a custom field **without** paying the extra LLM cost for topic selection, you can seed your own topics into the database (for your custom field value) using `database/init_db.py` or a similar initialization step. Once seeded, your custom field behaves like the pre-seeded ones and uses database queries first.
624644
### Character Limits
625645

626646
The system enforces a 3000-character limit for LinkedIn posts. If a post exceeds this, it's automatically sent back to the Writer Agent for shortening.

main.py

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,72 +35,84 @@
3535
from orchestrator import Orchestrator
3636

3737

38-
ALLOWED_FIELDS = [
38+
# Example fields (users can enter any field they wish)
39+
EXAMPLE_FIELDS = [
3940
"Data Science (Optimizations & Time-Series Analysis)",
4041
"Generative AI & AI Agents",
42+
"Software Engineering (Cloud Architecture)",
43+
"DevOps & Infrastructure",
4144
]
4245

4346

4447
def validate_field(value: str) -> str:
4548
"""
46-
Validate that the provided field value is in ALLOWED_FIELDS.
49+
Validate that the provided field value is a non-empty string.
4750
4851
Args:
49-
value: Field string to validate against ALLOWED_FIELDS list
52+
value: Field string to validate (any non-empty string is accepted)
5053
5154
Returns:
5255
The validated field string (same as input if valid)
5356
5457
Raises:
55-
ValidationError: If value is not in ALLOWED_FIELDS
58+
ValidationError: If value is empty or only whitespace
5659
5760
Example:
5861
>>> validate_field("Data Science (Optimizations & Time-Series Analysis)")
5962
"Data Science (Optimizations & Time-Series Analysis)"
60-
>>> validate_field("Invalid Field")
61-
ValidationError: Invalid field. Choose one of: ...
63+
>>> validate_field("Custom Machine Learning Field")
64+
"Custom Machine Learning Field"
65+
>>> validate_field("")
66+
ValidationError: Field cannot be empty
6267
"""
63-
if value not in ALLOWED_FIELDS:
64-
raise ValidationError(
65-
"Invalid field. Choose one of: " + ", ".join(ALLOWED_FIELDS)
66-
)
67-
return value
68+
if not value or not value.strip():
69+
raise ValidationError("Field cannot be empty")
70+
return value.strip()
6871

6972

7073
def prompt_select_field() -> str:
7174
"""
72-
Interactively prompt user to select a field from ALLOWED_FIELDS.
75+
Interactively prompt user to enter their field of expertise.
7376
74-
Displays numbered menu of available fields and validates user input.
75-
Continues prompting until valid selection is made.
77+
Displays example fields and accepts any non-empty string as input.
78+
Continues prompting until valid input is provided.
7679
7780
Returns:
78-
Selected field string from ALLOWED_FIELDS
81+
User-entered field string (non-empty)
7982
8083
Notes:
8184
This function implements an infinite retry loop, prompting
82-
repeatedly until user provides valid numeric input within
83-
the allowed range (1 to len(ALLOWED_FIELDS)).
85+
repeatedly until user provides non-empty input. Users can
86+
enter any custom field they wish.
8487
8588
Example:
8689
>>> field = prompt_select_field()
87-
Select your field of expertise:
88-
1. Data Science (Optimizations & Time-Series Analysis)
89-
2. Generative AI & AI Agents
90-
Enter number (1-2): 1
90+
Enter your field of expertise.
91+
Examples:
92+
- Data Science (Optimizations & Time-Series Analysis)
93+
- Generative AI & AI Agents
94+
- Software Engineering (Cloud Architecture)
95+
- DevOps & Infrastructure
96+
97+
Your field: Machine Learning Operations
9198
>>> print(field)
92-
"Data Science (Optimizations & Time-Series Analysis)"
99+
"Machine Learning Operations"
93100
"""
94-
print("Select your field of expertise:")
95-
for idx, val in enumerate(ALLOWED_FIELDS, start=1):
96-
print(f" {idx}. {val}")
101+
print("\nEnter your field of expertise.")
102+
print("Examples:")
103+
for example in EXAMPLE_FIELDS:
104+
print(f" - {example}")
105+
print()
106+
97107
while True:
98-
raw = input("Enter number (1-{}): ".format(len(ALLOWED_FIELDS))).strip()
99-
if raw.isdigit():
100-
i = int(raw)
101-
if 1 <= i <= len(ALLOWED_FIELDS):
102-
return ALLOWED_FIELDS[i - 1]
103-
print("Invalid selection. Please try again.")
108+
raw = input("Your field: ").strip()
109+
if raw:
110+
try:
111+
return validate_field(raw)
112+
except ValidationError as e:
113+
print(f"Error: {e}. Please try again.")
114+
else:
115+
print("Field cannot be empty. Please try again.")
104116

105117

106118
def config_path(root: Path) -> Path:
@@ -126,7 +138,7 @@ def load_config(root: Path) -> Optional[dict]:
126138
Load and validate configuration from config.json.
127139
128140
Reads config.json from the project root, validates its structure,
129-
and ensures the 'field' value is in ALLOWED_FIELDS.
141+
and ensures the 'field' value is a non-empty string.
130142
131143
Args:
132144
root: Project root directory path
@@ -137,7 +149,7 @@ def load_config(root: Path) -> Optional[dict]:
137149
138150
Raises:
139151
ValidationError: If config.json exists but is missing 'field' key
140-
or field value is not in ALLOWED_FIELDS
152+
or field value is empty
141153
CorruptionError: If config.json contains invalid JSON that cannot be parsed
142154
143155
Example:
@@ -347,8 +359,8 @@ def print_summary(result: dict) -> None:
347359
if next_steps:
348360
print()
349361
print(f"Next LLM Call: {next_steps.get('first_llm_call', 'N/A')}")
350-
model = next_steps.get('model', 'N/A')
351-
temp = next_steps.get('temperature', 'N/A')
362+
model = next_steps.get("model", "N/A")
363+
temp = next_steps.get("temperature", "N/A")
352364
print(f"Model: {model} (temperature: {temp})")
353365

354366
print()

0 commit comments

Comments
 (0)