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 chatfieldconsttrip=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:
fromchatfieldimportchatfield, 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 conversationinterviewer=Interviewer(trip)
user_input=Nonewhilenottrip._done:
message=interviewer.go(user_input)
print(message)
user_input=input("> ")
# Access collected dataprint(f"Destination: {trip.destination}")
print(f"Budget: {trip.budget}")TypeScript:
import{chatfield,Interviewer}from'chatfield'// Define the travel planner (from above)consttrip=chatfield().field('destination').desc('Where would you like to go?').field('budget').desc('What is your budget?').build()// Run the conversationconstinterviewer=newInterviewer(trip)letuserInput: string|null=nullwhile(!trip._done){constmessage=awaitinterviewer.go(userInput)console.log(message)userInput=awaitgetUserInput()// Your input method}// Access collected dataconsole.log(`Destination: ${trip.destination}`)console.log(`Budget: ${trip.budget}`)The Chatfield Claude Code Marketplace includes a plugin to complete PDF forms through conversation.
Add the Chatfield Marketplace
/plugin marketplace add jhs/ChatfieldAdd the Chatfield PDF plugin
/plugin install filling-pdf-forms@jhs/ChatfieldTo use, /pdf path/to/form.pdf or just ask "Help me complete this PDF form"
The plugin understands form fields, interviews you conversationally, and produces a completed .done.pdf file.
Chatfield supports both fillable and non-fillable PDFs, with text content or without text content.
If the form is not the language you speak, Chatfield will speak your language and translate for you.
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:
consttrip=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:
constvacationPlanner=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:
consttrip=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:
constadvancedTrip=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:
consttrip=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 valuestravelers=booking.group_size.as_int# integerbudget=booking.trip_budget.as_float# floatinclusive=booking.all_inclusive.as_bool# booleancountries=booking.destinations.as_list# listflex=booking.flexibility.as_percent# floatTypeScript:
constbooking=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 valuesconsttravelers=booking.groupSize.as_int// numberconstbudget=booking.tripBudget.as_float// numberconstinclusive=booking.allInclusive.as_bool// booleanconstcountries=booking.destinations.as_list// arrayconstflex=booking.flexibility.as_percent// numberMultiple 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.0budget_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# Falseformatted=international_trip.budget.as_str_formatted# "$3,000"of_annual=international_trip.budget.as_percent_of_annual# 0.06TypeScript:
constinternationalTrip=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":constbudget=internationalTrip.budget// "3000"constbudgetFloat=internationalTrip.budget.as_float// 3000.0constbudgetEs=internationalTrip.budget.as_lang_es// "tres mil dólares"constbudgetJp=internationalTrip.budget.as_lang_jp// "三千ドル"constisLuxury=internationalTrip.budget.as_bool_luxury// falseconstformatted=internationalTrip.budget.as_str_formatted// "$3,000"constofAnnual=internationalTrip.budget.as_percent_ofAnnual// 0.06Python:
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:
constmultilingualBooking=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"constrequestEs=multilingualBooking.specialRequest.as_lang_es// "Necesito una habitación tranquila lejos del ascensor"constrequestFr=multilingualBooking.specialRequest.as_lang_fr// "J'ai besoin d'une chambre calme loin de l'ascenseur"constrequestDe=multilingualBooking.specialRequest.as_lang_de// "Ich brauche ein ruhiges Zimmer weit vom Aufzug"constrequestJa=multilingualBooking.specialRequest.as_lang_ja// "エレベーターから離れた静かな部屋が必要です"constrequestZh=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:
constapiForm=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"constconfig=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 extractionentities=analysis.tags.as_set_entities# Named entities from textTypeScript:
constanalysis=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"consttags=analysis.tags.as_set// Set {'machine learning', 'AI', 'neural networks'}constkeywords=analysis.tags.as_set_keywords// Custom set extractionconstentities=analysis.tags.as_set_entities// Named entities from textControl 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 Nonelangs=preferences.languages.as_multi_langs# {"Python", "JavaScript"}interests=preferences.interests.as_any_topics# {"ML", "Security"} or set()TypeScript:
constpreferences=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:constdept=preferences.department.as_one_dept// "Engineering"constmentor=preferences.mentor.as_maybe_person// "Alice" or nullconstlangs=preferences.languages.as_multi_langs// Set {"Python", "JavaScript"}constinterests=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:
consttechInterview=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")
.field("background")
.desc("Tell me about your background")
.field("goals")
.desc("What are your career goals?")
.build())
# Traits activate automatically based on responsesTypeScript:
constadaptiveInterview=chatfield().alice().type('Career Counselor').bob().type('Professional').field('background').desc('Tell me about your background').field('goals').desc('What are your career goals?').build()// Traits activate automatically based on responsesTrack 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:
constinterview=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 endsTypeScript:
constassessment=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 endsPreserve 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:
constsupportTicket=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:constissue=supportTicket.issue// "Cannot access dashboard"constissueQuote=supportTicket.issue.as_quote// "I can't get into my dashboard, it spins forever"constissueContext=supportTicket.issue.as_context// "User mentioned recent password change"constimpact=supportTicket.impact// "Critical - blocking all work"constimpactQuote=supportTicket.impact.as_quote// "We literally can't do anything!"Python:
fromchatfieldimportchatfield, Interviewerjob_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")
# 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 interviewinterviewer=Interviewer(job_application)
user_input=Nonewhilenotjob_application._done:
message=interviewer.go(user_input)
ifmessage:
print(f"Interviewer: {message}")
ifnotjob_application._done:
user_input=input("You: ")
# Access resultsprint(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'constjobApplication=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')// 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 interviewconstinterviewer=newInterviewer(jobApplication)letuserInput: string|null=nullwhile(!jobApplication._done){constmessage=awaitinterviewer.go(userInput)if(message){console.log(`Interviewer: ${message}`)}if(!jobApplication._done){userInput=awaitgetUserInput()}}// Access resultsconsole.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:
constproductLaunch=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
.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 variableexport OPENAI_API_KEY=your-api-key
# Or .env fileecho"OPENAI_API_KEY=your-api-key"> .envPass API key directly:
Python:
interviewer=Interviewer(interview, api_key="your-api-key")TypeScript:
constinterviewer=newInterviewer(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.