🎥 Watch me develop this live! Follow along as I build Chatfield in real-time: YouTube Development Streams
Chatfield is a library to collect information using conversation rather than forms.
- Chatfield supports Python and TypeScript as well as JavaScript
- Chatfield supports Server and Browser operation
- Chatfield works in any User Interface because it computes what to say, now how to present
import { chatfield } from 'chatfield' // Or Python: from chatfield import chatfield
const trip = chatfield() // Identical API in Python and TypeScript/JavaScript
.field('destination')
.desc('Where would you like to go?')
.field('budget')
.desc('What is your budget?')
.build()
With Chatfield, your application can easily do:
- Natural Conversation: You tell Chatfield the user input. Chatfield tells you what to say in response.
- LLM-Powered Validation: Write rules in natural language describing valid or invalid field values.
- LLM-powered Data Transformation: "Cast" user input into any additional representation you want:
- Convert to data:
"5k"
becomes5000
."Yes"
becomestrue
."50/50"
becomes0.5
- Convert to object:
"I am Sam age 20"
becomes{"name":"Sam", "age":20}
- Translate language:
"Hello"
becomes"Bonjour"
- Classify user input into categories you choose
- Convert to data:
Chatfield works in any framework. Internally, Chatfield is built on LangGraph and LangChain, supporting all LLMs which LangChain supports.
Using the simple travel form defined above, here's how to run a conversation:
Python:
from chatfield import chatfield, Interviewer
# Define the travel planner (from above)
trip = (chatfield()
.field("destination")
.desc("Where would you like to go?")
.field("budget")
.desc("What is your budget?")
.build())
# Run the conversation
interviewer = Interviewer(trip)
user_input = None
while not trip._done:
message = interviewer.go(user_input)
print(message)
user_input = input("> ")
# Access collected data
print(f"Destination: {trip.destination}")
print(f"Budget: {trip.budget}")
TypeScript:
import { chatfield, Interviewer } from 'chatfield'
// Define the travel planner (from above)
const trip = chatfield()
.field('destination')
.desc('Where would you like to go?')
.field('budget')
.desc('What is your budget?')
.build()
// Run the conversation
const interviewer = new Interviewer(trip)
let userInput: string | null = null
while (!trip._done) {
const message = await interviewer.go(userInput)
console.log(message)
userInput = await getUserInput() // Your input method
}
// Access collected data
console.log(`Destination: ${trip.destination}`)
console.log(`Budget: ${trip.budget}`)
Chatfield transforms traditional form fields into conversational topics. The AI interviewer guides users through data collection naturally, validating responses and transforming them into structured data.
Fields are the basic building blocks. Each field represents a piece of data to collect:
Python:
trip = (chatfield()
.field("destination")
.desc("Where would you like to go?")
.field("travel_style")
.desc("What's your travel style?")
.field("group_size")
.desc("How many people are traveling?")
.build())
TypeScript:
const trip = chatfield()
.field('destination')
.desc('Where would you like to go?')
.field('travelStyle')
.desc('What\'s your travel style?')
.field('groupSize')
.desc('How many people are traveling?')
.build()
Provide context about your interview:
Python:
vacation_planner = (chatfield()
.type("Vacation Planning")
.desc("Plan your perfect vacation")
.field("destination")
.desc("Where would you like to go?")
.field("duration")
.desc("How many days?")
.field("interests")
.desc("What are you interested in?")
.build())
TypeScript:
const vacationPlanner = chatfield()
.type('Vacation Planning')
.desc('Plan your perfect vacation')
.field('destination')
.desc('Where would you like to go?')
.field('duration')
.desc('How many days?')
.field('interests')
.desc('What are you interested in?')
.build()
The basic travel planner above can be enhanced with validation rules:
Python:
trip = (chatfield()
.field("destination")
.desc("Where would you like to go?")
.must("be a real place")
.reject("generic answers like 'anywhere'")
.field("budget")
.desc("What is your budget?")
.must("include a specific amount")
.hint("Per person or total is fine")
.build())
TypeScript:
const trip = chatfield()
.field('destination')
.desc('Where would you like to go?')
.must('be a real place')
.reject("generic answers like 'anywhere'")
.field('budget')
.desc('What is your budget?')
.must('include a specific amount')
.hint('Per person or total is fine')
.build()
Ensure data quality with validation rules that guide the conversation:
For more complex validation scenarios:
Python:
advanced_trip = (chatfield()
.field("dates")
.desc("Travel dates")
.must("be at least 7 days in the future")
.must("not exceed 30 days duration")
.reject("peak holiday periods if avoiding crowds")
.hint("Consider shoulder season for better rates")
.field("budget")
.desc("Total trip budget")
.must("be realistic for destination")
.must("include all travelers")
.reject("vague amounts like 'whatever it takes'")
.hint("Include flights, hotels, food, and activities")
.build())
TypeScript:
const advancedTrip = chatfield()
.field('dates')
.desc('Travel dates')
.must('be at least 7 days in the future')
.must('not exceed 30 days duration')
.reject('peak holiday periods if avoiding crowds')
.hint('Consider shoulder season for better rates')
.field('budget')
.desc('Total trip budget')
.must('be realistic for destination')
.must('include all travelers')
.reject("vague amounts like 'whatever it takes'")
.hint('Include flights, hotels, food, and activities')
.build()
The basic budget field can be enhanced to convert text into structured data:
Python:
trip = (chatfield()
.field("destination")
.desc("Where would you like to go?")
.field("budget")
.desc("What is your budget?")
.as_float() # "2.5k" → 2500.0
.build())
TypeScript:
const trip = chatfield()
.field('destination')
.desc('Where would you like to go?')
.field('budget')
.desc('What is your budget?')
.as_float() // "2.5k" → 2500.0
.build()
More transformation examples:
Python:
booking = (chatfield()
.field("group_size")
.desc("How many travelers?")
.as_int() # "family of four" → 4
.field("trip_budget")
.desc("Trip budget per person")
.as_float() # "2.5k" → 2500.0
.field("all_inclusive")
.desc("Want all-inclusive package?")
.as_bool() # "yes please" → True
.field("destinations")
.desc("Countries to visit")
.as_list() # "France, Italy, Greece" → ["France", "Italy", "Greece"]
.field("flexibility")
.desc("How flexible are your dates?")
.as_percent() # "very flexible" → 0.85
.build())
# Access transformed values
travelers = booking.group_size.as_int # integer
budget = booking.trip_budget.as_float # float
inclusive = booking.all_inclusive.as_bool # boolean
countries = booking.destinations.as_list # list
flex = booking.flexibility.as_percent # float
TypeScript:
const booking = chatfield()
.field('groupSize')
.desc('How many travelers?')
.as_int() // "family of four" → 4
.field('tripBudget')
.desc('Trip budget per person')
.as_float() // "2.5k" → 2500.0
.field('allInclusive')
.desc('Want all-inclusive package?')
.as_bool() // "yes please" → true
.field('destinations')
.desc('Countries to visit')
.as_list() // "France, Italy, Greece" → ["France", "Italy", "Greece"]
.field('flexibility')
.desc('How flexible are your dates?')
.as_percent() // "very flexible" → 0.85
.build()
// Access transformed values
const travelers = booking.groupSize.as_int // number
const budget = booking.tripBudget.as_float // number
const inclusive = booking.allInclusive.as_bool // boolean
const countries = booking.destinations.as_list // array
const flex = booking.flexibility.as_percent // number
Multiple transformations on the same field:
Python:
international_trip = (chatfield()
.field("budget")
.desc("Your travel budget")
.as_float()
.as_lang('es') # Spanish translation
.as_lang('jp') # Japanese translation
.as_bool('luxury', 'True if > 5000 per person')
.as_str('formatted', 'With currency symbol like $5,000')
.as_percent('of_annual', 'As percentage of $50k annual travel budget')
.build())
# After user says "three thousand dollars":
budget = international_trip.budget # "3000"
budget_float = international_trip.budget.as_float # 3000.0
budget_es = international_trip.budget.as_lang_es # "tres mil dólares"
budget_jp = international_trip.budget.as_lang_jp # "三千ドル"
is_luxury = international_trip.budget.as_bool_luxury # False
formatted = international_trip.budget.as_str_formatted # "$3,000"
of_annual = international_trip.budget.as_percent_of_annual # 0.06
TypeScript:
const internationalTrip = chatfield()
.field('budget')
.desc('Your travel budget')
.as_float()
.as_lang('es') // Spanish translation
.as_lang('jp') // Japanese translation
.as_bool('luxury', 'True if > 5000 per person')
.as_str('formatted', 'With currency symbol like $5,000')
.as_percent('ofAnnual', 'As percentage of $50k annual travel budget')
.build()
// After user says "three thousand dollars":
const budget = internationalTrip.budget // "3000"
const budgetFloat = internationalTrip.budget.as_float // 3000.0
const budgetEs = internationalTrip.budget.as_lang_es // "tres mil dólares"
const budgetJp = internationalTrip.budget.as_lang_jp // "三千ドル"
const isLuxury = internationalTrip.budget.as_bool_luxury // false
const formatted = internationalTrip.budget.as_str_formatted // "$3,000"
const ofAnnual = internationalTrip.budget.as_percent_ofAnnual // 0.06
Python:
multilingual_booking = (chatfield()
.field("special_request")
.desc("Any special requests for your trip?")
.as_lang('es') # Spanish
.as_lang('fr') # French
.as_lang('de') # German
.as_lang('ja') # Japanese
.as_lang('zh') # Chinese
.build())
# User: "I need a quiet room away from the elevator"
request_es = multilingual_booking.special_request.as_lang_es # "Necesito una habitación tranquila lejos del ascensor"
request_fr = multilingual_booking.special_request.as_lang_fr # "J'ai besoin d'une chambre calme loin de l'ascenseur"
request_de = multilingual_booking.special_request.as_lang_de # "Ich brauche ein ruhiges Zimmer weit vom Aufzug"
request_ja = multilingual_booking.special_request.as_lang_ja # "エレベーターから離れた静かな部屋が必要です"
request_zh = multilingual_booking.special_request.as_lang_zh # "我需要一个远离电梯的安静房间"
TypeScript:
const multilingualBooking = chatfield()
.field('specialRequest')
.desc('Any special requests for your trip?')
.as_lang('es') // Spanish
.as_lang('fr') // French
.as_lang('de') // German
.as_lang('ja') // Japanese
.as_lang('zh') // Chinese
.build()
// User: "I need a quiet room away from the elevator"
const requestEs = multilingualBooking.specialRequest.as_lang_es // "Necesito una habitación tranquila lejos del ascensor"
const requestFr = multilingualBooking.specialRequest.as_lang_fr // "J'ai besoin d'une chambre calme loin de l'ascenseur"
const requestDe = multilingualBooking.specialRequest.as_lang_de // "Ich brauche ein ruhiges Zimmer weit vom Aufzug"
const requestJa = multilingualBooking.specialRequest.as_lang_ja // "エレベーターから離れた静かな部屋が必要です"
const requestZh = multilingualBooking.specialRequest.as_lang_zh // "我需要一个远离电梯的安静房间"
Python:
api_form = (chatfield()
.field("config")
.desc("Paste your configuration")
.as_dict() # Parses JSON/dict
.field("metadata")
.desc("Additional metadata")
.as_obj() # Alternative name for dict
.build())
# User: "timeout: 30, retries: 3, debug: true"
config = api_form.config.as_dict # {'timeout': 30, 'retries': 3, 'debug': True}
TypeScript:
const apiForm = chatfield()
.field('config')
.desc('Paste your configuration')
.as_dict() // Parses JSON/dict
.field('metadata')
.desc('Additional metadata')
.as_obj() // Alternative name for dict
.build()
// User: "timeout: 30, retries: 3, debug: true"
const config = apiForm.config.as_dict // {timeout: 30, retries: 3, debug: true}
Python:
analysis = (chatfield()
.field("tags")
.desc("Relevant tags")
.as_set() # Unique values only
.as_set('keywords', 'Extract main keywords')
.as_set('entities', 'Named entities')
.build())
# User: "machine learning, AI, machine learning, neural networks"
tags = analysis.tags.as_set # {'machine learning', 'AI', 'neural networks'}
keywords = analysis.tags.as_set_keywords # Custom set extraction
entities = analysis.tags.as_set_entities # Named entities from text
TypeScript:
const analysis = chatfield()
.field('tags')
.desc('Relevant tags')
.as_set() // Unique values only
.as_set('keywords', 'Extract main keywords')
.as_set('entities', 'Named entities')
.build()
// User: "machine learning, AI, machine learning, neural networks"
const tags = analysis.tags.as_set // Set {'machine learning', 'AI', 'neural networks'}
const keywords = analysis.tags.as_set_keywords // Custom set extraction
const entities = analysis.tags.as_set_entities // Named entities from text
Control how many options can be selected from a list:
Python:
preferences = (chatfield()
# Exactly one choice required
.field("department")
.desc("Your department")
.as_one('dept', 'Engineering', 'Sales', 'Marketing', 'Support')
# Zero or one choice (optional)
.field("mentor")
.desc("Would you like a mentor?")
.as_maybe('person', 'Alice', 'Bob', 'Charlie')
# One or more choices (at least one required)
.field("languages")
.desc("Programming languages you know")
.as_multi('langs', 'Python', 'JavaScript', 'Go', 'Rust', 'Java')
# Zero or more choices (completely optional)
.field("interests")
.desc("Technical interests")
.as_any('topics', 'ML', 'Web', 'Mobile', 'DevOps', 'Security')
.build())
# After collection:
dept = preferences.department.as_one_dept # "Engineering"
mentor = preferences.mentor.as_maybe_person # "Alice" or None
langs = preferences.languages.as_multi_langs # {"Python", "JavaScript"}
interests = preferences.interests.as_any_topics # {"ML", "Security"} or set()
TypeScript:
const preferences = chatfield()
// Exactly one choice required
.field('department')
.desc('Your department')
.as_one('dept', 'Engineering', 'Sales', 'Marketing', 'Support')
// Zero or one choice (optional)
.field('mentor')
.desc('Would you like a mentor?')
.as_maybe('person', 'Alice', 'Bob', 'Charlie')
// One or more choices (at least one required)
.field('languages')
.desc('Programming languages you know')
.as_multi('langs', 'Python', 'JavaScript', 'Go', 'Rust', 'Java')
// Zero or more choices (completely optional)
.field('interests')
.desc('Technical interests')
.as_any('topics', 'ML', 'Web', 'Mobile', 'DevOps', 'Security')
.build()
// After collection:
const dept = preferences.department.as_one_dept // "Engineering"
const mentor = preferences.mentor.as_maybe_person // "Alice" or null
const langs = preferences.languages.as_multi_langs // Set {"Python", "JavaScript"}
const interests = preferences.interests.as_any_topics // Set {"ML", "Security"} or Set {}
Define conversational roles and traits to shape the interaction:
Python:
tech_interview = (chatfield()
.alice() # Configure interviewer
.type("Senior Technical Interviewer")
.trait("Direct and technical")
.trait("Focuses on architecture")
.bob() # Configure interviewee
.type("Software Architect")
.trait("10+ years experience")
.trait("Prefers detailed discussions")
.field("stack")
.desc("Proposed technology stack")
.must("specific technology choices")
.field("scale")
.desc("Expected scale requirements")
.must("quantifiable metrics")
.build())
TypeScript:
const techInterview = chatfield()
.alice() // Configure interviewer
.type('Senior Technical Interviewer')
.trait('Direct and technical')
.trait('Focuses on architecture')
.bob() // Configure interviewee
.type('Software Architect')
.trait('10+ years experience')
.trait('Prefers detailed discussions')
.field('stack')
.desc('Proposed technology stack')
.must('specific technology choices')
.field('scale')
.desc('Expected scale requirements')
.must('quantifiable metrics')
.build()
Traits that activate based on conversation content:
Python:
adaptive_interview = (chatfield()
.alice()
.type("Career Counselor")
.bob()
.type("Professional")
.trait.possible("junior", "less than 3 years experience")
.trait.possible("senior", "10+ years or leadership")
.trait.possible("career_changer", "switching industries")
.field("background")
.desc("Tell me about your background")
.field("goals")
.desc("What are your career goals?")
.build())
# Traits activate automatically based on responses
TypeScript:
const adaptiveInterview = chatfield()
.alice()
.type('Career Counselor')
.bob()
.type('Professional')
.trait.possible('junior', 'less than 3 years experience')
.trait.possible('senior', '10+ years or leadership')
.trait.possible('careerChanger', 'switching industries')
.field('background')
.desc('Tell me about your background')
.field('goals')
.desc('What are your career goals?')
.build()
// Traits activate automatically based on responses
Track information silently without directly asking:
Python:
interview = (chatfield()
.field("experience")
.desc("Years of experience")
.as_int()
.field("shows_leadership")
.desc("Demonstrates leadership qualities")
.confidential() # Never mentioned, only tracked
.as_bool()
.field("mentions_mentoring")
.desc("Mentions mentoring experience")
.confidential()
.as_bool()
.build())
# After conversation:
print(f"Experience: {interview.experience.as_int} years")
print(f"Shows leadership: {interview.shows_leadership.as_bool}")
print(f"Mentions mentoring: {interview.mentions_mentoring.as_bool}")
TypeScript:
const interview = chatfield()
.field('experience')
.desc('Years of experience')
.as_int()
.field('showsLeadership')
.desc('Demonstrates leadership qualities')
.confidential() // Never mentioned, only tracked
.as_bool()
.field('mentionsMentoring')
.desc('Mentions mentoring experience')
.confidential()
.as_bool()
.build()
// After conversation:
console.log(`Experience: ${interview.experience.as_int} years`)
console.log(`Shows leadership: ${interview.showsLeadership.as_bool}`)
console.log(`Mentions mentoring: ${interview.mentionsMentoring.as_bool}`)
Evaluated only after all other fields are collected:
Python:
assessment = (chatfield()
.field("technical_skills")
.desc("Technical competencies")
.field("project_experience")
.desc("Project background")
.field("communication_quality")
.desc("Communication effectiveness throughout conversation")
.conclude() # Evaluated at end
.as_percent()
.field("overall_fit")
.desc("Overall fit for the role")
.conclude()
.as_one('rating', 'poor', 'fair', 'good', 'excellent')
.build())
# Conclude fields are filled after conversation ends
TypeScript:
const assessment = chatfield()
.field('technicalSkills')
.desc('Technical competencies')
.field('projectExperience')
.desc('Project background')
.field('communicationQuality')
.desc('Communication effectiveness throughout conversation')
.conclude() // Evaluated at end
.as_percent()
.field('overallFit')
.desc('Overall fit for the role')
.conclude()
.as_one('rating', 'poor', 'fair', 'good', 'excellent')
.build()
// Conclude fields are filled after conversation ends
Preserve conversational context and exact user quotes:
Python:
support_ticket = (chatfield()
.field("issue")
.desc("What problem are you experiencing?")
.as_quote() # Capture exact words
.as_context() # Capture conversation context
.field("impact")
.desc("How is this affecting your work?")
.as_quote()
.build())
# After collection:
issue = support_ticket.issue # "Cannot access dashboard"
issue_quote = support_ticket.issue.as_quote # "I can't get into my dashboard, it spins forever"
issue_context = support_ticket.issue.as_context # "User mentioned recent password change"
impact = support_ticket.impact # "Critical - blocking all work"
impact_quote = support_ticket.impact.as_quote # "We literally can't do anything!"
TypeScript:
const supportTicket = chatfield()
.field('issue')
.desc('What problem are you experiencing?')
.as_quote() // Capture exact words
.as_context() // Capture conversation context
.field('impact')
.desc('How is this affecting your work?')
.as_quote()
.build()
// After collection:
const issue = supportTicket.issue // "Cannot access dashboard"
const issueQuote = supportTicket.issue.as_quote // "I can't get into my dashboard, it spins forever"
const issueContext = supportTicket.issue.as_context // "User mentioned recent password change"
const impact = supportTicket.impact // "Critical - blocking all work"
const impactQuote = supportTicket.impact.as_quote // "We literally can't do anything!"
Python:
from chatfield import chatfield, Interviewer
job_application = (chatfield()
.type("SoftwareEngineerApplication")
.desc("Senior Software Engineer Position")
# Configure personas
.alice()
.type("Hiring Manager")
.trait("Professional and encouraging")
.trait("Values specific examples")
.bob()
.type("Candidate")
.trait.possible("startup_experience", "mentions startups")
.trait.possible("enterprise_experience", "mentions large companies")
# Basic information
.field("name")
.desc("Your full name")
.must("include first and last name")
.field("email")
.desc("Email address")
.must("be a valid email")
# Experience with transformations
.field("years_experience")
.desc("Years of professional experience")
.as_int()
.must("be realistic (0-50 years)")
.as_bool('senior', 'True if >= 5 years')
.as_bool('lead', 'True if >= 8 years')
# Skills selection
.field("primary_languages")
.desc("Primary programming languages")
.as_multi('langs', 'Python', 'JavaScript', 'Go', 'Java', 'C++', 'Rust')
.must("select at least one")
.field("frameworks")
.desc("Frameworks you're proficient with")
.as_any('tools', 'React', 'Django', 'Flask', 'FastAPI', 'Node.js', 'Spring')
# Salary with formatting
.field("salary_expectation")
.desc("Salary expectations")
.hint("You can give a range")
.as_int()
.as_str('formatted', 'Formatted as $XXX,XXX')
# Work preferences
.field("work_location")
.desc("Preferred work arrangement")
.as_one('preference', 'Remote', 'Hybrid', 'Office')
# Confidential assessments
.field("values_concern")
.desc("Candidate mentions something raising a values concern")
.confidential()
.as_bool()
# Evaluate the entire conversation to conclude the communication style
.field('communication_score')
.desc('Clarity, focus, active listening during the conversation')
.conclude()
.as_percent()
.build())
# Run the interview
interviewer = Interviewer(job_application)
user_input = None
while not job_application._done:
message = interviewer.go(user_input)
if message:
print(f"Interviewer: {message}")
if not job_application._done:
user_input = input("You: ")
# Access results
print(f"Name: {job_application.name}")
print(f"Years: {job_application.years_experience.as_int}")
print(f"Is Senior: {job_application.years_experience.as_bool_senior}")
print(f"Languages: {job_application.primary_languages.as_multi_langs}")
print(f"Salary: {job_application.salary_expectation.as_str_formatted}")
print(f"Communication: {job_application.communication_quality.as_one_level}")
print(f"Values concern: {job_application.values_concern.as_bool}")
TypeScript:
import { chatfield, Interviewer } from '@chatfield/core'
const jobApplication = chatfield()
.type('SoftwareEngineerApplication')
.desc('Senior Software Engineer Position')
// Configure personas
.alice()
.type('Hiring Manager')
.trait('Professional and encouraging')
.trait('Values specific examples')
.bob()
.type('Candidate')
.trait.possible('startupExperience', 'mentions startups')
.trait.possible('enterpriseExperience', 'mentions large companies')
// Basic information
.field('name')
.desc('Your full name')
.must('include first and last name')
.field('email')
.desc('Email address')
.must('be a valid email')
// Experience with transformations
.field('yearsExperience')
.desc('Years of professional experience')
.as_int()
.must('be realistic (0-50 years)')
.as_bool('senior', 'True if >= 5 years')
.as_bool('lead', 'True if >= 8 years')
// Skills selection
.field('primaryLanguages')
.desc('Primary programming languages')
.as_multi('langs', 'Python', 'JavaScript', 'Go', 'Java', 'C++', 'Rust')
.must('select at least one')
.field('frameworks')
.desc("Frameworks you're proficient with")
.as_any('tools', 'React', 'Django', 'Flask', 'FastAPI', 'Node.js', 'Spring')
// Salary with formatting
.field('salaryExpectation')
.desc('Salary expectations')
.hint('You can give a range')
.as_int()
.as_str('formatted', 'Formatted as $XXX,XXX')
// Work preferences
.field('workLocation')
.desc('Preferred work arrangement')
.as_one('preference', 'Remote', 'Hybrid', 'Office')
// Confidential assessments
.field('communicationQuality')
.desc('Communication clarity during interview')
.confidential()
.as_one('level', 'poor', 'adequate', 'good', 'excellent')
// Confidential assessments
.field("valuesConcern")
.desc("Candidate mentions something raising a values concern")
.confidential()
.as_bool()
// Evaluate the entire conversation to conclude the communication style
.field('communicationScore')
.desc('Clarity, focus, active listening during the conversation')
.conclude()
.as_percent()
.build()
// Run the interview
const interviewer = new Interviewer(jobApplication)
let userInput: string | null = null
while (!jobApplication._done) {
const message = await interviewer.go(userInput)
if (message) {
console.log(`Interviewer: ${message}`)
}
if (!jobApplication._done) {
userInput = await getUserInput()
}
}
// Access results
console.log(`Name: ${jobApplication.name}`)
console.log(`Years: ${jobApplication.yearsExperience.as_int}`)
console.log(`Is Senior: ${jobApplication.yearsExperience.as_bool_senior}`)
console.log(`Languages: ${jobApplication.primaryLanguages.as_multi_langs}`)
console.log(`Salary: ${jobApplication.salaryExpectation.as_str_formatted}`)
console.log(`Communication: ${jobApplication.communicationQuality.as_one_level}`)
console.log(`Values concern: ${jobApplication.valuesConcern.as_bool}`)
Python:
product_launch = (chatfield()
.type("Product Launch")
.desc("Product launch planning")
.alice()
.type("Product Manager")
.trait("Focuses on user value")
.bob()
.type("Startup Founder")
.trait("First-time founder")
.field("product")
.desc("Product description and target market")
.must("clear value proposition")
.must("target audience defined")
.reject("everything for everyone")
.hint("Who specifically will pay?")
.field("first_year_users")
.desc("First year user target")
.as_int()
.as_lang('ja')
.must("realistic for MVP")
.must("between 1000 and 100000")
.field("market_share")
.desc("Expected market share")
.as_percent()
.hint("Most startups capture <5% initially")
.field("competitors")
.desc("Direct competitors")
.as_list()
.must("at least 3 competitors")
.field("unique_value")
.desc("Unique value proposition")
.as_dict()
.hint("Format: feature, value, differentiator")
.field("model")
.desc("Business model")
.as_one('selection', 'B2B', 'B2C', 'B2B2C', 'marketplace')
.field("channels")
.desc("Marketing channels")
.as_multi('selection', 'organic', 'paid_ads', 'content', 'partnerships', 'social')
.must("at least 2 channels")
.must("no more than 3 to start")
.field("ready")
.desc("Ready to launch?")
.as_bool()
.as_bool('funded', 'True if you have funding')
.as_bool('technical', 'True if you have technical co-founder')
.field("enthusiasm")
.desc("Level of founder enthusiasm")
.confidential()
.as_percent()
.field("viability")
.desc("Overall business viability")
.conclude()
.as_percent()
.build())
TypeScript:
const productLaunch = chatfield()
.type('Product Launch')
.desc('Product launch planning')
.alice()
.type('Product Manager')
.trait('Focuses on user value')
.bob()
.type('Startup Founder')
.trait('First-time founder')
.field('product')
.desc('Product description and target market')
.must('clear value proposition')
.must('target audience defined')
.reject('everything for everyone')
.hint('Who specifically will pay?')
.field('firstYearUsers')
.desc('First year user target')
.as_int()
.as_lang('ja')
.must('realistic for MVP')
.must('between 1000 and 100000')
.field('marketShare')
.desc('Expected market share')
.as_percent()
.hint('Most startups capture <5% initially')
.field('competitors')
.desc('Direct competitors')
.as_list()
.must('at least 3 competitors')
.field('uniqueValue')
.desc('Unique value proposition')
.as_dict()
.hint('Format: feature, value, differentiator')
.field('model')
.desc('Business model')
.as_one('selection', 'B2B', 'B2C', 'B2B2C', 'marketplace')
.field('channels')
.desc('Marketing channels')
.as_multi('selection', 'organic', 'paid_ads', 'content', 'partnerships', 'social')
.must('at least 2 channels')
.must('no more than 3 to start')
.field('ready')
.desc('Ready to launch?')
.as_bool()
.as_bool('funded', 'True if you have funding')
.as_bool('technical', 'True if you have technical co-founder')
.field('enthusiasm')
.desc('Level of founder enthusiasm')
.confidential()
.as_percent()
.field('viability')
.desc('Overall business viability')
.conclude()
.as_percent()
.build()
chatfield()
- Start building a new interview.type(name)
- Set the interview type name.desc(description)
- Set the interview description.build()
- Build the final Interview object
.alice()
- Configure the interviewer role.bob()
- Configure the interviewee role.type(role_type)
- Set role type (after .alice() or .bob()).trait(trait)
- Add a trait.trait.possible(name, trigger)
- Add a conditional trait
.field(name)
- Start defining a field.desc(description)
- Set field description.must(rule)
- Add a requirement.reject(rule)
- Add a rejection rule.hint(tip)
- Add helpful guidance.confidential()
- Mark as silently tracked.conclude()
- Evaluate only at end
.as_int()
- Parse as integer.as_float()
- Parse as float.as_bool()
- Parse as boolean.as_str()
- String format.as_percent()
- Parse as 0.0-1.0.as_list()
- Parse as list.as_set()
- Parse as unique set.as_dict()
/.as_obj()
- Parse as dictionary/object.as_lang(code)
- Translate to language.as_quote()
- Capture exact user quote.as_context()
- Capture conversation context
.as_bool(predicate, description)
- Custom boolean check.as_int(transform, description)
- Custom integer transform.as_str(format, description)
- Custom string format.as_set(operation, description)
- Set operation
.as_one(name, ...choices)
- Exactly one choice required.as_maybe(name, ...choices)
- Zero or one choice.as_multi(name, ...choices)
- One or more choices required.as_any(name, ...choices)
- Zero or more choices
Base class for conversational data collection:
._done
- Check if all fields are collected.field_name
- Access field value (returns FieldProxy)._chatfield
- Internal structure containing all metadata
Manages conversation flow:
constructor(interview, options)
- Create interviewer instance.go(user_input)
- Process one conversation turn- Returns AI message as string (Python) or Promise (TypeScript)
String-like class providing transformation access:
- Base value when accessed directly
.as_*
attributes for transformations- All string methods available
Set your OpenAI API key:
# Environment variable
export OPENAI_API_KEY=your-api-key
# Or .env file
echo "OPENAI_API_KEY=your-api-key" > .env
Pass API key directly:
Python:
interviewer = Interviewer(interview, api_key="your-api-key")
TypeScript:
const interviewer = new Interviewer(interview, { apiKey: "your-api-key" })
Chatfield/
├── Python/ # Python implementation
│ ├── chatfield/ # Core package
│ ├── tests/ # Test suite
│ └── examples/ # Example scripts
├── TypeScript/ # TypeScript implementation
│ ├── src/ # Source code
│ ├── tests/ # Test suite
│ └── examples/ # Example scripts
└── Documentation/ # Additional documentation
See language-specific READMEs for development setup:
Apache License 2.0 - See LICENSE for details.